From dcac53d7067423deaf242da043904e2343743559 Mon Sep 17 00:00:00 2001 From: apraxico Date: Mon, 23 Mar 2026 22:50:21 -0300 Subject: [PATCH 01/11] feat: Add Kanturu Refinery Tower event implementation Implements the full server-side logic for the Kanturu Refinery Tower mini-game event (maps 38-40), following the same plugin patterns used by BloodCastle and ChaosCastle. New files: - KanturuContext: event game loop with 8 sequential phases (Phase 1-3 wave clears, Nightmare preparation, Nightmare boss fight, Tower of Refinement victory) - IKanturuEventViewPlugIn: view plugin interface for client communication - KanturuEventViewPlugIn: sends state packets to connected clients - KanturuGatewayPlugIn: NPC gateway plugin for event entry - KanturuStartPlugIn / Configuration / GameServerState: periodic task scheduler that starts the event on a configurable interval - KanturuEnterRequestHandlerPlugIn: handles C1-D1-01 entry packet - KanturuInfoRequestHandlerPlugIn: handles C1-D1-00 info packet - KanturuGroupHandlerPlugIn: packet group handler registration - KanturuInitializer: seeds MiniGameDefinition and spawn waves Modified files: - MiniGameType.cs: added Kanturu enum value - GameConfigurationInitializer.cs: registers KanturuInitializer - KanturuEvent.cs: added full wave monster spawn definitions Co-Authored-By: Claude Sonnet 4.6 --- src/DataModel/Configuration/MiniGameType.cs | 5 + .../MiniGames/IKanturuEventViewPlugIn.cs | 195 ++++ src/GameLogic/MiniGames/KanturuContext.cs | 943 ++++++++++++++++++ src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs | 124 +++ .../PeriodicTasks/KanturuGameServerState.cs | 23 + .../KanturuStartConfiguration.cs | 34 + .../PeriodicTasks/KanturuStartPlugIn.cs | 33 + .../KanturuEnterRequestHandlerPlugIn.cs | 52 + .../MiniGames/KanturuGroupHandlerPlugIn.cs | 41 + .../KanturuInfoRequestHandlerPlugIn.cs | 42 + .../MiniGames/KanturuEventViewPlugIn.cs | 228 +++++ .../Events/KanturuInitializer.cs | 49 + .../GameConfigurationInitializer.cs | 1 + .../VersionSeasonSix/Maps/KanturuEvent.cs | 199 +++- 14 files changed, 1963 insertions(+), 6 deletions(-) create mode 100644 src/GameLogic/MiniGames/IKanturuEventViewPlugIn.cs create mode 100644 src/GameLogic/MiniGames/KanturuContext.cs create mode 100644 src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs create mode 100644 src/GameLogic/PlugIns/PeriodicTasks/KanturuGameServerState.cs create mode 100644 src/GameLogic/PlugIns/PeriodicTasks/KanturuStartConfiguration.cs create mode 100644 src/GameLogic/PlugIns/PeriodicTasks/KanturuStartPlugIn.cs create mode 100644 src/GameServer/MessageHandler/MiniGames/KanturuEnterRequestHandlerPlugIn.cs create mode 100644 src/GameServer/MessageHandler/MiniGames/KanturuGroupHandlerPlugIn.cs create mode 100644 src/GameServer/MessageHandler/MiniGames/KanturuInfoRequestHandlerPlugIn.cs create mode 100644 src/GameServer/RemoteView/MiniGames/KanturuEventViewPlugIn.cs create mode 100644 src/Persistence/Initialization/VersionSeasonSix/Events/KanturuInitializer.cs diff --git a/src/DataModel/Configuration/MiniGameType.cs b/src/DataModel/Configuration/MiniGameType.cs index de6fd1482..c3619b9c5 100644 --- a/src/DataModel/Configuration/MiniGameType.cs +++ b/src/DataModel/Configuration/MiniGameType.cs @@ -38,4 +38,9 @@ public enum MiniGameType /// The doppelganger event. /// Doppelganger, + + /// + /// The Kanturu Refinery Tower event. + /// + Kanturu, } \ No newline at end of file diff --git a/src/GameLogic/MiniGames/IKanturuEventViewPlugIn.cs b/src/GameLogic/MiniGames/IKanturuEventViewPlugIn.cs new file mode 100644 index 000000000..984a4c7e7 --- /dev/null +++ b/src/GameLogic/MiniGames/IKanturuEventViewPlugIn.cs @@ -0,0 +1,195 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameLogic.MiniGames; + +using MUnique.OpenMU.GameLogic.Views; + +/// +/// View plugin interface for Kanturu-specific server-to-client packets (0xD1 group). +/// Each method maps to one packet subcode. +/// +public interface IKanturuEventViewPlugIn : IViewPlugIn +{ + /// + /// Sends packet 0xD1/0x00 — Kanturu state info (response to NPC talk / info request). + /// This opens the INTERFACE_KANTURU2ND_ENTERNPC dialog on the client, showing + /// the current event state, how many players are inside, and whether entry is possible. + /// + /// Main event state (see ). + /// Detail state for the current main state. + /// 1 = entrance is open (Enter button enabled); 0 = entrance closed. + /// Number of players currently inside the event map. + /// + /// Seconds remaining. Semantics depend on state: + /// Standby → seconds until the event opens (client shows minutes). + /// Tower → seconds the tower has been open (client shows hours). + /// Otherwise 0. + /// + ValueTask SendStateInfoAsync(byte state, byte detailState, byte enter, byte userCount, int remainTimeSec); + + /// + /// Sends packet 0xD1/0x01 — Kanturu enter result. + /// Sent after the player's entry attempt is processed. + /// On the client side this stops the NPC animation and closes the dialog. + /// Result codes: 0 = failed (generic), use specific POPUP_* values for other errors. + /// On success the player is teleported before this is sent, so the packet is only + /// needed for failure cases. + /// + ValueTask SendEnterResultAsync(byte result); + + /// + /// Sends packet 0xD1/0x03 — Kanturu state change. + /// On the client side this: + /// - Shows/hides the in-map HUD (INTERFACE_KANTURU_INFO). + /// - Switches background music (Maya, Nightmare, Tower themes). + /// - When == and + /// is 1 or 2, reloads the success terrain file + /// (EncTerrain<n>01.att), which visually removes the Elphis barrier. + /// + /// Main state (see ). + /// Detail state for the current main state. + ValueTask SendStateChangeAsync(byte state, byte detailState); + + /// + /// Sends packet 0xD1/0x04 — Kanturu battle result. + /// + /// 0 = failure — shows Failure_kantru.tga overlay. + /// 1 = success — shows Success_kantru.tga overlay + /// (only when client state is already ). + /// + /// + ValueTask SendBattleResultAsync(byte result); + + /// + /// Sends packet 0xD1/0x05 — countdown timer for the Kanturu HUD (in milliseconds). + /// The HUD divides by 1000 to get seconds and counts down visually. + /// + ValueTask SendTimeLimitAsync(int timeLimitMs); + + /// + /// Sends packet 0xD1/0x07 — remaining monster count and current user count. + /// Updates the numbers displayed in the Kanturu HUD. + /// + ValueTask SendMonsterUserCountAsync(byte monsterCount, byte userCount); + + /// + /// Sends packet 0xD1/0x06 — Maya wide-area attack visual trigger. + /// The client calls MayaSceneMayaAction(attackType) on receipt, which plays + /// one of two client-side visual sequences on the Maya body object: + /// + /// 0 = stone-storm effect (MODEL_STORM3 + falling debris around Hero) + /// 1 = stone-rain effect (MODEL_MAYASTONE projectiles falling on Hero) + /// + /// This is a purely cosmetic broadcast — damage is handled by the server-side + /// on Maya Hands (#362/#363). + /// + /// 0 = storm visual; 1 = stone-rain visual. + ValueTask SendMayaWideAreaAttackAsync(byte attackType); +} + +/// +/// Kanturu main state codes matching the client's KANTURU_STATE_TYPE enum. +/// +public static class KanturuStateCode +{ + /// No active state. + public const byte None = 0; + + /// Waiting for players to enter. + public const byte Standby = 1; + + /// Maya battle phase (Phases 1–3 + boss waves). + public const byte MayaBattle = 2; + + /// Nightmare battle phase. + public const byte NightmareBattle = 3; + + /// Tower of Refinement open (post-victory). + public const byte Tower = 4; + + /// Event ended. + public const byte End = 5; +} + +/// +/// Detail state codes for the Nightmare battle phase, +/// matching KANTURU_NIGHTMARE_DIRECTION_TYPE. +/// +public static class KanturuNightmareDetail +{ + /// No direction set. + public const byte None = 0; + + /// Idle — Nightmare present but not yet in battle. + public const byte Idle = 1; + + /// Nightmare intro animation. + public const byte NightmareIntro = 2; + + /// Active battle — shows the HUD on the client. + public const byte Battle = 3; + + /// Battle ended. + public const byte End = 4; +} + +/// +/// Detail state codes for the Tower of Refinement phase, +/// matching KANTURU_TOWER_STATE_TYPE. +/// +public static class KanturuTowerDetail +{ + /// No tower state. + public const byte None = 0; + + /// + /// Tower is open after Nightmare's defeat. + /// Sending this triggers the client to reload EncTerrain<n>01.att + /// (the success terrain), which visually removes the Elphis barrier. + /// + public const byte Revitalization = 1; + + /// Tower closing soon — client warns players. + public const byte Notify = 2; + + /// Tower closed. + public const byte Close = 3; +} + +/// +/// Detail state codes for the Maya battle phase, +/// matching KANTURU_MAYA_DIRECTION_TYPE. +/// +public static class KanturuMayaDetail +{ + /// No direction. + public const byte None = 0; + + /// Phase 1 monster wave active — shows HUD. + public const byte Monster1 = 3; + + /// Phase 1 boss (Maya Left Hand) — shows HUD. + public const byte Maya1 = 4; + + /// Phase 2 monster wave active — shows HUD. + public const byte Monster2 = 8; + + /// Phase 2 boss (Maya Right Hand) — shows HUD. + public const byte Maya2 = 9; + + /// Phase 3 monster wave active — shows HUD. + public const byte Monster3 = 13; + + /// Phase 3 bosses (both hands) — shows HUD. + public const byte Maya3 = 14; + + /// + /// Maya phase 3 end cycle — triggers the full Maya explosion + player fall cinematic + /// (KANTURU_MAYA_DIRECTION_ENDCYCLE_MAYA3 = 16 on the client). + /// Sending this via 0xD1/0x03 activates Move2ndDirection(): + /// camera pans to Maya room → m_bMayaDie = true (explosion) → m_bDownHero = true (fall). + /// + public const byte EndCycleMaya3 = 16; +} diff --git a/src/GameLogic/MiniGames/KanturuContext.cs b/src/GameLogic/MiniGames/KanturuContext.cs new file mode 100644 index 000000000..193c220ba --- /dev/null +++ b/src/GameLogic/MiniGames/KanturuContext.cs @@ -0,0 +1,943 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameLogic.MiniGames; + +using System.Threading; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.GameLogic.Attributes; +using MUnique.OpenMU.GameLogic.NPC; +using MUnique.OpenMU.GameLogic.Views; +using MUnique.OpenMU.GameLogic.Views.World; +using MUnique.OpenMU.Interfaces; +using MUnique.OpenMU.Pathfinding; + +/// +/// The context of a Kanturu Refinery Tower event game. +/// +/// +/// The Kanturu event progresses through sequential phases: +/// Phase 1: Kill 30 Blade Hunters + 10 Dreadfear → Maya (Left Hand) spawns → kill her. +/// Phase 2: Kill 30 Blade Hunters + 10 Dreadfear → Maya (Right Hand) spawns → kill her. +/// Phase 3: Kill 10 Dreadfear + 10 Twin Tale → Both Maya hands spawn → kill both. +/// Nightmare Prep: Kill 15 Genocider + 15 Dreadfear + 15 Persona → Nightmare spawns. +/// Nightmare: At 75/50/25% HP Nightmare teleports and regains full HP. +/// Victory: Kill Nightmare → Elphis barrier opens → Tower of Refinement. +/// Between phases there is a 2-minute standby period. +/// Players who die are respawned at Kanturu Relics (handled by map's SafezoneMap setting). +/// +public sealed class KanturuContext : MiniGameContext +{ + // Monster definition numbers + private const short MayaBodyNumber = 364; + private const short MayaLeftHandNumber = 362; + private const short MayaRightHandNumber = 363; + private const short NightmareNumber = 361; + private const short BladeHunterNumber = 354; + private const short DreadfearNumber = 360; + private const short TwinTaleNumber = 359; + private const short GenociderNumber = 357; + private const short PersonaNumber = 358; + + // Wave numbers matching MonsterSpawnArea.WaveNumber in KanturuEvent.cs + private const byte WaveMayaAppear = 0; // Maya body rises at battle start + private const byte WavePhase1Monsters = 1; + private const byte WavePhase1Boss = 2; + private const byte WavePhase2Monsters = 3; + private const byte WavePhase2Boss = 4; + private const byte WavePhase3Monsters = 5; + private const byte WavePhase3Bosses = 6; + private const byte WaveNightmarePrep = 7; + private const byte WaveNightmare = 8; + + // Client detail code for Maya "notify" cinematic (camera pan + Maya rise animation). + // This is KANTURU_MAYA_DIRECTION_NOTIFY=2 on the client side. + private const byte MayaNotifyDetail = 2; + + // Skill numbers for Nightmare special attacks. + // These map directly to client-side AT_SKILL_* enum values and drive the animation selection + // on the Nightmare model (MODEL_DARK_SKULL_SOLDIER_5) in GM_Kanturu_3rd.cpp. + // Inferno (#14, AT_SKILL_INFERNO): ATTACK4 — Inferno explosion + 2×MODEL_CIRCLE effects. + private const short NightmareInfernoskillNumber = 14; + + // Nightmare phase teleport positions within the Nightmare Zone (X:75-88, Y:97-143) + // Phase 1 = initial spawn at (78, 143) defined in KanturuEvent.cs + private static readonly Point NightmarePhase2Pos = new(82, 130); + private static readonly Point NightmarePhase3Pos = new(76, 115); + private static readonly Point NightmarePhase4Pos = new(85, 100); + + // Elphis barrier area — cells that are NoGround (value 8) in the .att file and block + // the path from the Nightmare zone to the Elpis NPC area (Y~177). + // Confirmed via Terrain39.att analysis: entire X=73-90, Y=144-180 column is NoGround. + // OpenMU reads WalkMap[x,y] = false for these cells by default; we override to true + // after Nightmare is defeated so players can walk to Elpis. + private static readonly (byte StartX, byte StartY, byte EndX, byte EndY)[] ElphisBarrierAreas = + [ + (73, 144, 90, 195), + ]; + + private readonly IMapInitializer _mapInitializer; + private readonly TimeSpan _towerOfRefinementDuration; + + private volatile KanturuPhase _phase = KanturuPhase.Open; + private int _waveKillCount; + private int _waveKillTarget; + private TaskCompletionSource _phaseComplete = new(TaskCreationOptions.RunContinuationsAsynchronously); + private volatile bool _isVictory; + + // Interlocked flag: 0 = not yet opened, 1 = opened (or in progress). + // Ensures the barrier is opened exactly once regardless of whether the trigger + // comes from OnMonsterDied (fire-and-forget) or the game loop. + private int _barrierOpened; + + // Nightmare HP-phase tracking + private Monster? _nightmareMonster; + private int _nightmarePhase; + + // True while ExecuteNightmareTeleportAsync is in progress. + // Prevents MonitorNightmareHpAsync from re-triggering a teleport during the animation window. + private volatile bool _nightmareTeleporting; + + // True while inter-phase standby is running — suppresses Maya wide-area attacks so + // Maya stays visually idle (no storm/stone-rain effects) during the break between phases. + private volatile bool _mayaAttacksPaused; + + /// + /// Gets the current Kanturu main state code (the last state sent via 0xD1/0x03). + /// The Gateway NPC plugin reads this to populate the 0xD1/0x00 StateInfo dialog + /// while the event is in progress. + /// + public byte CurrentKanturuState { get; private set; } = KanturuStateCode.MayaBattle; + + /// + /// Gets the current Kanturu detail state code (the last detailState sent via 0xD1/0x03). + /// + public byte CurrentKanturuDetailState { get; private set; } + + private enum KanturuPhase + { + Open, + Phase1Monsters, + Phase1Boss, + Phase2Monsters, + Phase2Boss, + Phase3Monsters, + Phase3Bosses, + NightmarePrep, + NightmareActive, + Ended, + } + + /// + /// Initializes a new instance of the class. + /// + /// The key of this context. + /// The definition of the mini game. + /// The game context, to which this game belongs. + /// The map initializer, which is used when the event starts. + /// + /// How long the Tower of Refinement stays open after Nightmare is defeated. + /// Defaults to 1 hour if not specified. + /// + public KanturuContext( + MiniGameMapKey key, + MiniGameDefinition definition, + IGameContext gameContext, + IMapInitializer mapInitializer, + TimeSpan towerOfRefinementDuration = default) + : base(key, definition, gameContext, mapInitializer) + { + this._mapInitializer = mapInitializer; + this._towerOfRefinementDuration = towerOfRefinementDuration == default + ? TimeSpan.FromHours(1) + : towerOfRefinementDuration; + } + + /// + protected override async ValueTask OnGameStartAsync(ICollection players) + { + await base.OnGameStartAsync(players).ConfigureAwait(false); + + // Maya rises from the depths when the battle begins. + await this._mapInitializer.InitializeNpcsOnWaveStartAsync(this.Map, this, WaveMayaAppear).ConfigureAwait(false); + + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowMessageAsync("Maya rises from the depths of the Refinery Tower!", MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false); + + _ = Task.Run(() => this.RunKanturuGameLoopAsync(this.GameEndedToken), this.GameEndedToken); + } + + /// + protected override void OnMonsterDied(object? sender, DeathInformation e) + { + base.OnMonsterDied(sender, e); + + if (sender is not Monster monster) + { + return; + } + + var num = (short)monster.Definition.Number; + var phase = this._phase; + bool complete; + + switch (phase) + { + case KanturuPhase.Phase1Monsters when num is BladeHunterNumber or DreadfearNumber: + { + var killed = Interlocked.Increment(ref this._waveKillCount); + complete = killed == this._waveKillTarget; + var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); + var pc = (byte)Math.Min(255, this.PlayerCount); + _ = Task.Run(() => this.SendMonsterUserCountAsync(remaining, pc).AsTask()); + break; + } + + case KanturuPhase.Phase1Boss when num == MayaLeftHandNumber: + { + complete = true; + var pc = (byte)Math.Min(255, this.PlayerCount); + _ = Task.Run(() => this.SendMonsterUserCountAsync(0, pc).AsTask()); + break; + } + + case KanturuPhase.Phase2Monsters when num is BladeHunterNumber or DreadfearNumber: + { + var killed = Interlocked.Increment(ref this._waveKillCount); + complete = killed == this._waveKillTarget; + var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); + var pc = (byte)Math.Min(255, this.PlayerCount); + _ = Task.Run(() => this.SendMonsterUserCountAsync(remaining, pc).AsTask()); + break; + } + + case KanturuPhase.Phase2Boss when num == MayaRightHandNumber: + { + complete = true; + var pc = (byte)Math.Min(255, this.PlayerCount); + _ = Task.Run(() => this.SendMonsterUserCountAsync(0, pc).AsTask()); + break; + } + + case KanturuPhase.Phase3Monsters when num is DreadfearNumber or TwinTaleNumber: + { + var killed = Interlocked.Increment(ref this._waveKillCount); + complete = killed == this._waveKillTarget; + var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); + var pc = (byte)Math.Min(255, this.PlayerCount); + _ = Task.Run(() => this.SendMonsterUserCountAsync(remaining, pc).AsTask()); + break; + } + + case KanturuPhase.Phase3Bosses when num is MayaLeftHandNumber or MayaRightHandNumber: + { + var killed = Interlocked.Increment(ref this._waveKillCount); + complete = killed == this._waveKillTarget; + var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); + var pc = (byte)Math.Min(255, this.PlayerCount); + _ = Task.Run(() => this.SendMonsterUserCountAsync(remaining, pc).AsTask()); + break; + } + + case KanturuPhase.NightmarePrep when num is GenociderNumber or DreadfearNumber or PersonaNumber: + { + var killed = Interlocked.Increment(ref this._waveKillCount); + complete = killed == this._waveKillTarget; + var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); + var pc = (byte)Math.Min(255, this.PlayerCount); + _ = Task.Run(() => this.SendMonsterUserCountAsync(remaining, pc).AsTask()); + break; + } + + case KanturuPhase.NightmareActive when num is GenociderNumber or DreadfearNumber or PersonaNumber: + { + // A guardian died while Nightmare is alive — update the HUD counter but do NOT + // advance the phase. _waveKillTarget=46 (45 guardians + 1 Nightmare), so + // remaining = 46 − killed still accounts for Nightmare being alive. + var killed = Interlocked.Increment(ref this._waveKillCount); + var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); + var pc = (byte)Math.Min(255, this.PlayerCount); + _ = Task.Run(() => this.SendMonsterUserCountAsync(remaining, pc).AsTask()); + complete = false; + break; + } + + case KanturuPhase.NightmareActive when num == NightmareNumber: + complete = true; + // Fire barrier opening immediately from the death event. + // Do NOT wait for the game loop — it may be interrupted by + // GameEndedToken cancellation before reaching OpenElphisBarrierAsync. + _ = Task.Run(() => this.OpenElphisBarrierAsync().AsTask()); + break; + + default: + // Diagnostic: if Nightmare dies outside the expected phase, warn. + if (num == NightmareNumber) + { + this.Logger.LogWarning( + "Kanturu: Nightmare died but _phase={Phase} (expected NightmareActive). Barrier NOT opened.", + phase); + _ = Task.Run(() => this.ForEachPlayerAsync(p => + p.InvokeViewPlugInAsync(v => + v.ShowMessageAsync( + $"[Kanturu] Nightmare died out of phase! Phase={phase}", + MessageType.BlueNormal)).AsTask()).AsTask()); + } + + complete = false; + break; + } + + if (complete) + { + this._phaseComplete.TrySetResult(); + } + } + + /// + protected override async ValueTask GameEndedAsync(ICollection finishers) + { + var message = this._isVictory + ? "Nightmare has been defeated! The Kanturu Refinery Tower is yours!" + : "The Kanturu Event has ended. Better luck next time!"; + + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowMessageAsync(message, MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false); + + // On defeat show the Failure_kantru.tga overlay. + // On victory the Success_kantru.tga and Tower state are sent from OpenElphisBarrierAsync. + if (!this._isVictory) + { + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.SendBattleResultAsync(0)).AsTask()).ConfigureAwait(false); + } + + await base.GameEndedAsync(finishers).ConfigureAwait(false); + } + + private async Task RunKanturuGameLoopAsync(CancellationToken ct) + { + try + { + // Maya "notify" cinematic — camera pans to Maya, Maya body rises from below. + // Must be sent first so the client camera is in position before the first wave. + await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, MayaNotifyDetail).ConfigureAwait(false); + await Task.Delay(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + + // Start the Maya wide-area attack visual loop for the duration of all Maya phases. + // The loop broadcasts 0xD1/0x06 every 15 s, alternating storm (0) and stone-rain (1). + using var mayaAttackCts = CancellationTokenSource.CreateLinkedTokenSource(ct); + _ = Task.Run(() => this.RunMayaWideAreaAttacksAsync(mayaAttackCts.Token), mayaAttackCts.Token); + + // Phase 1: wave of monsters — 10-minute timer covers wave + boss. + await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.Monster1).ConfigureAwait(false); + await this.SendTimeLimitToAllAsync((int)TimeSpan.FromMinutes(10).TotalMilliseconds).ConfigureAwait(false); + await this.AdvancePhaseAsync(KanturuPhase.Phase1Monsters, WavePhase1Monsters, 40, + "Phase 1: Defeat the monsters to unseal Maya's power!", ct).ConfigureAwait(false); + + // Phase 1: Maya Left Hand boss + await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.Maya1).ConfigureAwait(false); + await this.AdvancePhaseAsync(KanturuPhase.Phase1Boss, WavePhase1Boss, 1, + "Maya (Left Hand) has appeared! Destroy her!", ct).ConfigureAwait(false); + + // Standby between phases + await this.SendStandbyMessageAsync("Phase 1 cleared! Phase 2 begins in 2 minutes...", ct).ConfigureAwait(false); + + // Phase 2: wave of monsters — fresh 10-minute timer. + await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.Monster2).ConfigureAwait(false); + await this.SendTimeLimitToAllAsync((int)TimeSpan.FromMinutes(10).TotalMilliseconds).ConfigureAwait(false); + await this.AdvancePhaseAsync(KanturuPhase.Phase2Monsters, WavePhase2Monsters, 40, + "Phase 2: More of Maya's minions have arrived!", ct).ConfigureAwait(false); + + // Phase 2: Maya Right Hand boss + await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.Maya2).ConfigureAwait(false); + await this.AdvancePhaseAsync(KanturuPhase.Phase2Boss, WavePhase2Boss, 1, + "Maya (Right Hand) has appeared! Destroy her!", ct).ConfigureAwait(false); + + // Standby between phases + await this.SendStandbyMessageAsync("Phase 2 cleared! Phase 3 begins in 2 minutes...", ct).ConfigureAwait(false); + + // Phase 3: wave of monsters — fresh 10-minute timer. + await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.Monster3).ConfigureAwait(false); + await this.SendTimeLimitToAllAsync((int)TimeSpan.FromMinutes(10).TotalMilliseconds).ConfigureAwait(false); + await this.AdvancePhaseAsync(KanturuPhase.Phase3Monsters, WavePhase3Monsters, 20, + "Phase 3: The final wave approaches!", ct).ConfigureAwait(false); + + // Phase 3: Both Maya bosses simultaneously + await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.Maya3).ConfigureAwait(false); + await this.AdvancePhaseAsync(KanturuPhase.Phase3Bosses, WavePhase3Bosses, 2, + "Both hands of Maya have appeared! Defeat them both!", ct).ConfigureAwait(false); + + // Hide HUD during the loot window — same reason as inter-phase standby. + await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.None).ConfigureAwait(false); + + // 10-second loot window: players pick up drops from both Maya hands + // before the Nightmare transition cinematic begins. + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowMessageAsync( + "Maya's hands have fallen! Collect your loot — the Nightmare awakens in 10 seconds...", + MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false); + await Task.Delay(TimeSpan.FromSeconds(10), ct).ConfigureAwait(false); + + // Stop the Maya wide-area attack visuals before the Nightmare transition cinematic. + await mayaAttackCts.CancelAsync().ConfigureAwait(false); + + // Transition to Nightmare room: + // 1. TeleportToNightmareRoomAsync sends 0xD1/0x03 ENDCYCLE_MAYA3(16) which triggers + // the full cinematic (camera pan → Maya explosion → player falls through floor), + // waits ~10 s for it to complete, then MoveAsync teleports players to (79, 98). + // 2. NightmareBattle/Idle is sent AFTER teleport so the HUD change applies in + // the Nightmare zone, not the Maya battlefield. + await this.TeleportToNightmareRoomAsync(ct).ConfigureAwait(false); + await this.SendKanturuStateAsync(KanturuStateCode.NightmareBattle, KanturuNightmareDetail.Idle).ConfigureAwait(false); + + // Nightmare Prep: spawn guardians immediately, then spawn Nightmare after 3 seconds. + // No kill requirement — guardians fight alongside Nightmare. + await this.SendTimeLimitToAllAsync((int)TimeSpan.FromMinutes(30).TotalMilliseconds).ConfigureAwait(false); + Interlocked.Exchange(ref this._waveKillCount, 0); + this._waveKillTarget = 45; + this._phaseComplete = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + this._phase = KanturuPhase.NightmarePrep; + + await this._mapInitializer.InitializeNpcsOnWaveStartAsync(this.Map, this, WaveNightmarePrep).ConfigureAwait(false); + await this.SendMonsterUserCountAsync(45, (byte)Math.Min(255, this.PlayerCount)).ConfigureAwait(false); + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowMessageAsync("Nightmare's guardians have appeared! NIGHTMARE awakens in 3 seconds!", MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false); + + await Task.Delay(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + + // Nightmare boss with HP-phase teleports + await this.RunNightmarePhaseAsync(ct).ConfigureAwait(false); + + // Victory + this._isVictory = true; + this._phase = KanturuPhase.Ended; + + // Open the Elphis barrier (also sends the Success screen + Tower state). + // The fire-and-forget from OnMonsterDied already called this, but we call again + // as a fallback — the Interlocked guard ensures it only executes once. + await this.OpenElphisBarrierAsync().ConfigureAwait(false); + + // Tower of Refinement: keep the map open for a configurable period + await this.RunTowerOfRefinementAsync(ct).ConfigureAwait(false); + + this.FinishEvent(); + } + catch (OperationCanceledException) + { + // Game ended by timeout or external cancellation — treated as defeat. + } + catch (Exception ex) + { + this.Logger.LogError(ex, "Unexpected error in Kanturu game loop."); + } + } + + /// + /// Runs the Nightmare phase. Nightmare teleports and recovers full HP at 75%, 50%, and 25% HP. + /// + private async Task RunNightmarePhaseAsync(CancellationToken ct) + { + this._nightmarePhase = 1; + this._nightmareMonster = null; + + // Do NOT reset _waveKillCount — it holds guardian kills from the 3-second prep window. + // _waveKillTarget = 46 (45 guardians + 1 Nightmare) so the HUD counter correctly + // reflects every remaining mob in the Nightmare zone, not just the boss. + this._waveKillTarget = 46; + this._phaseComplete = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + this._phase = KanturuPhase.NightmareActive; + + // Nightmare intro cinematic — camera moves to Nightmare zone and summons Nightmare. + // This uses detail=NightmareIntro(2) = KANTURU_NIGHTMARE_DIRECTION_NIGHTMARE on the client. + await this.SendKanturuStateAsync(KanturuStateCode.NightmareBattle, KanturuNightmareDetail.NightmareIntro).ConfigureAwait(false); + await Task.Delay(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + + // Subscribe to ObjectAdded to capture the Nightmare reference as soon as it spawns. + var nightmareFound = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + async ValueTask OnObjectAdded((GameMap Map, ILocateable Object) args) + { + if (args.Object is Monster m && (short)m.Definition.Number == NightmareNumber) + { + nightmareFound.TrySetResult(m); + } + } + + this.Map.ObjectAdded += OnObjectAdded; + try + { + await this._mapInitializer.InitializeNpcsOnWaveStartAsync(this.Map, this, WaveNightmare) + .ConfigureAwait(false); + + // Wait up to 5 seconds for the spawn to register. + this._nightmareMonster = await nightmareFound.Task + .WaitAsync(TimeSpan.FromSeconds(5), ct) + .ConfigureAwait(false); + } + catch (TimeoutException) + { + this.Logger.LogWarning("Nightmare monster did not spawn within 5 seconds — HP phases disabled."); + } + finally + { + this.Map.ObjectAdded -= OnObjectAdded; + } + + // Switch to active battle state — shows Nightmare HUD on client (INTERFACE_KANTURU_INFO). + await this.SendKanturuStateAsync(KanturuStateCode.NightmareBattle, KanturuNightmareDetail.Battle).ConfigureAwait(false); + + // Show total remaining: alive guardians (45 − killed during prep) + 1 Nightmare. + var totalRemaining = (byte)Math.Min(255, Math.Max(1, this._waveKillTarget - this._waveKillCount)); + await this.SendMonsterUserCountAsync(totalRemaining, (byte)Math.Min(255, this.PlayerCount)).ConfigureAwait(false); + + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowMessageAsync("NIGHTMARE has appeared! Defeat him to claim victory!", MessageType.GoldenCenter)) + .AsTask()).ConfigureAwait(false); + + // Start HP monitor and special-attack loop — both linked to the same CTS so they + // stop together the moment Nightmare dies or the game is cancelled. + using var hpCts = CancellationTokenSource.CreateLinkedTokenSource(ct); + var hpMonitor = Task.Run( + () => this.MonitorNightmareHpAsync(hpCts.Token), + hpCts.Token); + var specialAttacks = Task.Run( + () => this.RunNightmareSpecialAttacksAsync(hpCts.Token), + hpCts.Token); + + await this._phaseComplete.Task.WaitAsync(ct).ConfigureAwait(false); + + await hpCts.CancelAsync().ConfigureAwait(false); + try { await hpMonitor.ConfigureAwait(false); } + catch (OperationCanceledException) { /* expected on cancel */ } + try { await specialAttacks.ConfigureAwait(false); } + catch (OperationCanceledException) { /* expected on cancel */ } + } + + /// + /// Polls Nightmare's HP every second and triggers a phase teleport at 75/50/25%. + /// + private async Task MonitorNightmareHpAsync(CancellationToken ct) + { + while (!ct.IsCancellationRequested) + { + await Task.Delay(TimeSpan.FromSeconds(1), ct).ConfigureAwait(false); + + // Do NOT check HP while a teleport is already in progress. + // ExecuteNightmareTeleportAsync restores Nightmare's HP as part of the sequence; + // reading HP mid-teleport would give a stale (low) value and re-trigger. + if (this._nightmareTeleporting) + { + continue; + } + + if (this._nightmareMonster is { IsAlive: true } nm) + { + var maxHp = nm.Attributes[Stats.MaximumHealth]; + var hpRatio = maxHp > 0 ? (float)nm.Health / maxHp : 1f; + + var targetPhase = hpRatio switch + { + < 0.25f => 4, + < 0.50f => 3, + < 0.75f => 2, + _ => 1, + }; + + if (targetPhase > this._nightmarePhase) + { + this._nightmarePhase = targetPhase; + await this.ExecuteNightmareTeleportAsync(nm, targetPhase, ct) + .ConfigureAwait(false); + } + } + } + } + + /// + /// Teleports Nightmare to a new phase position and restores his HP to full. + /// Uses a guard so the HP monitor cannot + /// re-trigger while the teleport sequence is in progress. + /// + private async Task ExecuteNightmareTeleportAsync(Monster nightmare, int phase, CancellationToken ct) + { + // Nightmare may have died between the HP check and this call. + if (!nightmare.IsAlive) + { + return; + } + + this._nightmareTeleporting = true; + try + { + var newPos = phase switch + { + 2 => NightmarePhase2Pos, + 3 => NightmarePhase3Pos, + 4 => NightmarePhase4Pos, + _ => NightmarePhase2Pos, + }; + + var msg = phase switch + { + 2 => "Nightmare has teleported! He recovers his full strength!", + 3 => "Nightmare teleports again! He is more powerful than ever!", + 4 => "Nightmare is at his last stand! Finish him!", + _ => string.Empty, + }; + + // Restore HP FIRST — any damage during the brief animation window will not kill Nightmare. + // This prevents the race condition where a simultaneous player hit (e.g. Cyclone) + // drops Nightmare's HP to 0 before the HP restore line, causing an incorrect death event. + nightmare.Health = (int)nightmare.Attributes[Stats.MaximumHealth]; + + // Short pause so clients can process the HP restore notification before the teleport. + await Task.Delay(TimeSpan.FromMilliseconds(500), CancellationToken.None).ConfigureAwait(false); + ct.ThrowIfCancellationRequested(); + + // Teleport Nightmare to the phase-specific position. + await nightmare.MoveAsync(newPos).ConfigureAwait(false); + + // Restore HP a second time as a safety net — covers any hits landing in the 500 ms window. + nightmare.Health = (int)nightmare.Attributes[Stats.MaximumHealth]; + + if (msg.Length > 0) + { + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowMessageAsync(msg, MessageType.GoldenCenter)) + .AsTask()).ConfigureAwait(false); + } + } + finally + { + this._nightmareTeleporting = false; + } + } + + /// + /// Opens the Elphis barrier by removing the NoGround terrain attribute from the + /// barrier area — both on the server walkmap and on every connected client. + /// The .att file stores value 8 (NoGround) for this zone, so the client shows it + /// as a visual void barrier. We must remove NoGround (not Blocked) to make it disappear. + /// + /// + /// This method is guarded by so it executes at most once + /// per game instance even when called concurrently from both + /// and the game loop. + /// + private async ValueTask OpenElphisBarrierAsync() + { + // Ensure we run only once — called both from OnMonsterDied (fire-and-forget) + // and from the game loop as a fallback. + if (Interlocked.CompareExchange(ref this._barrierOpened, 1, 0) != 0) + { + return; + } + + this.Logger.LogInformation("Kanturu: opening Elphis barrier."); + + // Diagnostic confirmation message — visible in the game client. + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowMessageAsync("Kanturu: Barrier opening! The tower awaits...", MessageType.GoldenCenter)).AsTask()) + .ConfigureAwait(false); + + // Monster count = 0 (Nightmare defeated). + await this.SendMonsterUserCountAsync(0, (byte)Math.Min(255, this.PlayerCount)).ConfigureAwait(false); + + // Victory camera-out cinematic (KANTURU_NIGHTMARE_DIRECTION_END = 4). + await this.SendKanturuStateAsync(KanturuStateCode.NightmareBattle, KanturuNightmareDetail.End).ConfigureAwait(false); + await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); + + // 1. Send SUCCESS result overlay (Success_kantru.tga). + // The client requires state=NightmareBattle to show it, which was set when + // Nightmare spawned. Sending this before the Tower state change ensures + // the overlay renders while the client is still in the Nightmare state. + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.SendBattleResultAsync(1)) + .AsTask()).ConfigureAwait(false); + + // 2. Send Tower state → client reloads EncTerrain(n)01.att (barrier-open terrain), + // switches to Tower music, and plays the success sound. + await this.SendKanturuStateAsync(KanturuStateCode.Tower, KanturuTowerDetail.Revitalization).ConfigureAwait(false); + + // 3. Update server-side walkmap so the AI pathfinder and movement checks + // treat the formerly-blocked cells as passable. + var terrain = this.Map.Terrain; + foreach (var (startX, startY, endX, endY) in ElphisBarrierAreas) + { + for (var x = startX; x <= endX; x++) + { + for (var y = startY; y <= endY; y++) + { + terrain.WalkMap[x, y] = true; + terrain.UpdateAiGridValue(x, y); + } + } + } + + // 4. Also send the legacy 0x46 ChangeTerrainAttributes packet as a backup. + // If EncTerrain(n)01.att is missing on the client, this packet still clears + // the NoGround flag on the existing terrain so the visual barrier disappears. + var areas = (IReadOnlyCollection<(byte, byte, byte, byte)>)ElphisBarrierAreas; + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ChangeAttributesAsync(TerrainAttributeType.NoGround, setAttribute: false, areas)) + .AsTask()).ConfigureAwait(false); + } + + /// + /// Keeps the map open as the Tower of Refinement after Nightmare is defeated. + /// Sends a closing warning 5 minutes before the end of the configured duration. + /// + private async Task RunTowerOfRefinementAsync(CancellationToken ct) + { + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowMessageAsync( + "The Kanturu Refinery Tower is conquered! The tower is now open.", + MessageType.GoldenCenter)) + .AsTask()).ConfigureAwait(false); + + var duration = this._towerOfRefinementDuration; + var warningOffset = TimeSpan.FromMinutes(5); + + if (duration > warningOffset) + { + // Wait for most of the duration — use None so the delay isn't cancelled + // if all current players leave while new ones might still arrive. + await Task.Delay(duration - warningOffset, CancellationToken.None).ConfigureAwait(false); + ct.ThrowIfCancellationRequested(); + + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowMessageAsync( + "The Kanturu Tower closes in 5 minutes!", + MessageType.GoldenCenter)) + .AsTask()).ConfigureAwait(false); + + await Task.Delay(warningOffset, CancellationToken.None).ConfigureAwait(false); + ct.ThrowIfCancellationRequested(); + } + else + { + await Task.Delay(duration, CancellationToken.None).ConfigureAwait(false); + ct.ThrowIfCancellationRequested(); + } + + // Tower closing notification + await this.SendKanturuStateAsync(KanturuStateCode.Tower, KanturuTowerDetail.Notify).ConfigureAwait(false); + + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowMessageAsync("The Kanturu Tower has closed.", MessageType.GoldenCenter)) + .AsTask()).ConfigureAwait(false); + + await this.SendKanturuStateAsync(KanturuStateCode.Tower, KanturuTowerDetail.Close).ConfigureAwait(false); + } + + private async Task TeleportToNightmareRoomAsync(CancellationToken ct) + { + var nightmareEntry = new Point(79, 98); + + // Step 1: Send 0xD1/0x03 with state=MayaBattle, detailState=EndCycleMaya3 (16). + // This matches KANTURU_MAYA_DIRECTION_ENDCYCLE_MAYA3 on the client, which triggers + // the full Maya→Nightmare transition cinematic via CKanturuDirection::Move2ndDirection(): + // Stage 0 — camera flies to the Maya room (196, 85). + // Stage 1 — m_bMayaDie=true: Maya body plays its explosion animation; waits for it to finish. + // Stage 2 — m_bDownHero=true: the hero "falls through the floor" into the Nightmare zone. + // NOTE: 0xD1/0x04 result=1 (Success_kantru.tga) must NOT be sent here — that overlay + // is only correct after Nightmare is defeated and is already sent in OpenElphisBarrierAsync. + await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.EndCycleMaya3).ConfigureAwait(false); + + // Step 2: Wait for the client cinematic to complete before teleporting. + // The three stages take roughly: camera pan ~3 s + explosion ~4 s + fall ~3 s ≈ 10 s. + // Use CancellationToken.None so the animation is never skipped mid-sequence. + await Task.Delay(TimeSpan.FromSeconds(10), CancellationToken.None).ConfigureAwait(false); + ct.ThrowIfCancellationRequested(); + + // Step 3: Teleport all players to the Nightmare zone entry point. + // Same map (39), so MoveAsync is correct (sends MoveType.Instant / packet 0x15). + // Mirrors C++ MoveAllUser(gate 134) → destination (79, 98). + await this.ForEachPlayerAsync(player => + player.MoveAsync(nightmareEntry).AsTask()).ConfigureAwait(false); + + // Step 4: Play the warp arrival animation at the Nightmare zone entry point. + // MapChangeFailedAsync (0xC2/0x1C) must be sent AFTER MoveAsync so the warp bubble + // renders at (79, 98) and not at the Maya area. A brief pause ensures the client + // has processed the position update before the animation triggers. + // The fall cinematic (EndCycleMaya3 / m_bDownHero) is complete at this point + // — 10 s have elapsed — so this packet does NOT interfere with it. + // The bubble also briefly locks player input, preventing movement/attacks + // during the visual scene transition. + await Task.Delay(TimeSpan.FromMilliseconds(200), CancellationToken.None).ConfigureAwait(false); + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.MapChangeFailedAsync()).AsTask()).ConfigureAwait(false); + } + + /// + /// Periodically broadcasts the Maya wide-area attack visual (0xD1/0x06) during the Maya battle. + /// Alternates between storm (type 0) and stone-rain (type 1) every 15 seconds. + /// This triggers the MayaAction animation on the Maya body object in the client, + /// which is a purely cosmetic effect — actual damage is handled by the monsters' AttackSkill. + /// + private async Task RunMayaWideAreaAttacksAsync(CancellationToken ct) + { + byte attackType = 0; + while (!ct.IsCancellationRequested) + { + try + { + await Task.Delay(TimeSpan.FromSeconds(15), ct).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + break; + } + + // Skip the broadcast during inter-phase standby — Maya stays idle, no visual attacks. + if (!this._mayaAttacksPaused) + { + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.SendMayaWideAreaAttackAsync(attackType)).AsTask()).ConfigureAwait(false); + } + + attackType = (byte)(1 - attackType); // alternate 0 → 1 → 0 → … + } + } + + /// + /// Periodically broadcasts Nightmare's special explosion attack to all map players. + /// Every 20 seconds this sends an Inferno skill animation (skill #14) from Nightmare, + /// which the client maps to the ATTACK4 animation on MODEL_DARK_SKULL_SOLDIER_5: + /// an Inferno explosion + 2×MODEL_CIRCLE visual effects — the iconic "explosion" attack + /// seen in official MU Online Season 6 Kanturu Nightmare battles. + /// + /// + /// This is a pure visual broadcast. Nightmare's actual AoE damage (Decay poison) is + /// already handled server-side by (#38). + /// + private async Task RunNightmareSpecialAttacksAsync(CancellationToken ct) + { + while (!ct.IsCancellationRequested) + { + try + { + await Task.Delay(TimeSpan.FromSeconds(20), ct).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + break; + } + + if (this._nightmareMonster is not { IsAlive: true } nm) + { + break; + } + + // Skip during a teleport sequence to avoid animation conflicts. + if (this._nightmareTeleporting) + { + continue; + } + + // Broadcast Inferno skill animation (#14) from Nightmare to all players on the map. + // Triggers ATTACK4 (frame 9): Inferno explosion + 2×MODEL_CIRCLE visual at Nightmare's position. + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowSkillAnimationAsync(nm, null, NightmareInfernoskillNumber, true)).AsTask()) + .ConfigureAwait(false); + } + } + + private async Task SendStandbyMessageAsync(string message, CancellationToken ct) + { + // Pause Maya wide-area attacks for the full duration of the standby so the + // body stays visually idle (no storm/stone-rain effects between phases). + this._mayaAttacksPaused = true; + try + { + // Hide the in-map HUD (INTERFACE_KANTURU_INFO) during the inter-phase standby. + // Sending detailState=None(0) while in MayaBattle state makes the client hide + // the monster count / user count / timer panel until the next phase begins. + // Maya body (#364) stays at its spawn position — it is ~27 tiles from the fight + // room and well outside its ViewRange=9, so it naturally idles without attacking. + await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.None).ConfigureAwait(false); + + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowMessageAsync(message, MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false); + + await Task.Delay(TimeSpan.FromMinutes(2), ct).ConfigureAwait(false); + } + finally + { + this._mayaAttacksPaused = false; + } + } + + private async Task AdvancePhaseAsync(KanturuPhase phase, byte waveNumber, int killTarget, string message, CancellationToken ct) + { + Interlocked.Exchange(ref this._waveKillCount, 0); + this._waveKillTarget = killTarget; + this._phaseComplete = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + this._phase = phase; + + await this._mapInitializer.InitializeNpcsOnWaveStartAsync(this.Map, this, waveNumber).ConfigureAwait(false); + + // Broadcast the initial monster count so the HUD shows the correct number from the start. + await this.SendMonsterUserCountAsync((byte)Math.Min(255, killTarget), (byte)Math.Min(255, this.PlayerCount)).ConfigureAwait(false); + + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowMessageAsync(message, MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false); + + await this._phaseComplete.Task.WaitAsync(ct).ConfigureAwait(false); + } + + /// + /// Broadcasts packet 0xD1/0x03 (Kanturu state change) to all players currently on the map. + /// Also updates and + /// so the Gateway NPC plugin can report the current event phase in the 0xD1/0x00 dialog. + /// + private ValueTask SendKanturuStateAsync(byte state, byte detailState) + { + this.CurrentKanturuState = state; + this.CurrentKanturuDetailState = detailState; + return this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.SendStateChangeAsync(state, detailState)).AsTask()); + } + + /// + /// Broadcasts packet 0xD1/0x07 (Kanturu monster/user count) to all players currently on the map. + /// + private ValueTask SendMonsterUserCountAsync(byte monsterCount, byte userCount) + { + return this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.SendMonsterUserCountAsync(monsterCount, userCount)).AsTask()); + } + + /// + /// Broadcasts packet 0xD1/0x05 (Kanturu time limit) to all players currently on the map. + /// + /// Remaining time in milliseconds. + private ValueTask SendTimeLimitToAllAsync(int milliseconds) + { + return this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.SendTimeLimitAsync(milliseconds)).AsTask()); + } +} diff --git a/src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs b/src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs new file mode 100644 index 000000000..c7e2c2349 --- /dev/null +++ b/src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs @@ -0,0 +1,124 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameLogic.PlugIns; + +using System.Runtime.InteropServices; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.GameLogic.MiniGames; +using MUnique.OpenMU.GameLogic.NPC; +using MUnique.OpenMU.GameLogic.PlayerActions.MiniGames; +using MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks; +using MUnique.OpenMU.PlugIns; + +/// +/// Handles the Gateway Machine NPC (NPC #367) in Kanturu Relics. +/// When a player talks to this NPC, the server sends a 0xD1/0x00 StateInfo +/// packet so that the client opens the INTERFACE_KANTURU2ND_ENTERNPC dialog. +/// The actual entry is triggered later when the client sends 0xD1/0x01 +/// (KanturuEnterRequest) — handled by . +/// +[Guid("B7E4D2A3-1F85-4DAB-9074-19B4708389D5")] +[PlugIn] +[Display(Name = nameof(KanturuGatewayPlugIn), Description = "Handles the Kanturu Gateway Machine NPC for event entry.")] +public class KanturuGatewayPlugIn : IPlayerTalkToNpcPlugIn +{ + private const short GatewayMachineNumber = 367; + + // Detail state for the dialog when entry is open: + // KANTURU_MAYA_DIRECTION_STANBY1 = 1 — shows user count and enables Enter button. + private const byte DetailStandbyOpen = 1; + + /// + public async ValueTask PlayerTalksToNpcAsync(Player player, NonPlayerCharacter npc, NpcTalkEventArgs eventArgs) + { + if (npc.Definition.Number != GatewayMachineNumber) + { + return; + } + + // Mark as handled before any await so TalkNpcAction sees it synchronously. + eventArgs.HasBeenHandled = true; + + await SendKanturuStateInfoAsync(player).ConfigureAwait(false); + } + + /// + /// Sends the 0xD1/0x00 StateInfo packet to the player so the client opens the + /// gateway dialog. Also called from + /// when the client refreshes the dialog. + /// + public static async ValueTask SendKanturuStateInfoAsync(Player player) + { + var miniGameStartPlugIn = player.GameContext.PlugInManager + .GetStrategy(MiniGameType.Kanturu); + + var miniGameDefinition = player.GetSuitableMiniGameDefinition(MiniGameType.Kanturu, 1); + + if (miniGameStartPlugIn is null || miniGameDefinition is null) + { + // Event not configured — show nothing; the client won't open the dialog. + return; + } + + // Always fetch the running context first so we can reflect the live event state + // regardless of whether the initial entry window is open or has already closed. + var ctx = await miniGameStartPlugIn + .GetMiniGameContextAsync(player.GameContext, miniGameDefinition) + .ConfigureAwait(false); + + var timeUntilOpening = await miniGameStartPlugIn + .GetDurationUntilNextStartAsync(player.GameContext, miniGameDefinition) + .ConfigureAwait(false); + + byte state; + byte detailState; + byte enter; + byte userCount; + int remainTimeSec; + + if (ctx is KanturuContext kanturuCtx) + { + // A Kanturu event is actively running — reflect its real-time phase. + // The client dialog will show "MayaBattle" or "NightmareBattle" as appropriate, + // and optionally the Maya sub-phase (e.g., Monster1 or Maya2) when available. + state = kanturuCtx.CurrentKanturuState; + detailState = kanturuCtx.CurrentKanturuDetailState; + + // Entry allowed only during Maya-battle phases (including inter-phase standby). + // NightmareBattle and Tower phase are both sealed — players who are already + // inside the event access the Refinery Tower by walking through the opened + // Elphis barrier; the Gateway does not re-admit anyone for those phases. + enter = state == KanturuStateCode.MayaBattle ? (byte)1 : (byte)0; + + userCount = (byte)Math.Min(255, ctx.PlayerCount); + remainTimeSec = 0; + } + else if (timeUntilOpening == TimeSpan.Zero) + { + // Entry window is open but the game context has not been created yet + // (race: the scheduler opened the window but OnGameStartAsync hasn't run). + state = KanturuStateCode.MayaBattle; + detailState = DetailStandbyOpen; + enter = 1; + userCount = 0; + remainTimeSec = 0; + } + else + { + // No active event — show countdown to the next scheduled start. + state = KanturuStateCode.Standby; + detailState = 1; // STANBY_START — client shows "Opens in X minutes" + enter = 0; + userCount = 0; + remainTimeSec = timeUntilOpening.HasValue + ? (int)Math.Ceiling(timeUntilOpening.Value.TotalSeconds) + : 0; + } + + await player.InvokeViewPlugInAsync(p => + p.SendStateInfoAsync(state, detailState, enter, userCount, remainTimeSec)) + .ConfigureAwait(false); + } +} diff --git a/src/GameLogic/PlugIns/PeriodicTasks/KanturuGameServerState.cs b/src/GameLogic/PlugIns/PeriodicTasks/KanturuGameServerState.cs new file mode 100644 index 000000000..843f3ebde --- /dev/null +++ b/src/GameLogic/PlugIns/PeriodicTasks/KanturuGameServerState.cs @@ -0,0 +1,23 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks; + +/// +/// The state of a game server state for the Kanturu event. +/// +public class KanturuGameServerState : PeriodicTaskGameServerState +{ + /// + /// Initializes a new instance of the class. + /// + /// The context. + public KanturuGameServerState(IGameContext context) + : base(context) + { + } + + /// + public override string Description => "Kanturu Event"; +} diff --git a/src/GameLogic/PlugIns/PeriodicTasks/KanturuStartConfiguration.cs b/src/GameLogic/PlugIns/PeriodicTasks/KanturuStartConfiguration.cs new file mode 100644 index 000000000..3d62e5f11 --- /dev/null +++ b/src/GameLogic/PlugIns/PeriodicTasks/KanturuStartConfiguration.cs @@ -0,0 +1,34 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks; + +/// +/// The Kanturu event start configuration. +/// +public class KanturuStartConfiguration : MiniGameStartConfiguration +{ + /// + /// Gets or sets how long the Tower of Refinement stays open after Nightmare is defeated. + /// Default is 1 hour. Set to zero to skip the tower phase. + /// + public TimeSpan TowerOfRefinementDuration { get; set; } = TimeSpan.FromHours(1); + + /// + /// Gets the default configuration for the Kanturu event. + /// The event runs once per day. After Nightmare is defeated the Tower of Refinement + /// stays open for 1 hour, then the event ends and the next occurrence is the following day. + /// The preparation window (entry phase) opens 3 minutes before the scheduled start time. + /// + public static KanturuStartConfiguration Default => + new() + { + PreStartMessageDelay = TimeSpan.Zero, + EntranceOpenedMessage = "Kanturu Refinery Tower entrance is open and closes in {0} minute(s).", + EntranceClosedMessage = "Kanturu Refinery Tower entrance closed.", + TaskDuration = TimeSpan.FromMinutes(135), + Timetable = [new TimeOnly(20, 0)], // 20:00 UTC — one occurrence per day + TowerOfRefinementDuration = TimeSpan.FromHours(1), + }; +} diff --git a/src/GameLogic/PlugIns/PeriodicTasks/KanturuStartPlugIn.cs b/src/GameLogic/PlugIns/PeriodicTasks/KanturuStartPlugIn.cs new file mode 100644 index 000000000..db85efcce --- /dev/null +++ b/src/GameLogic/PlugIns/PeriodicTasks/KanturuStartPlugIn.cs @@ -0,0 +1,33 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks; + +using System.Runtime.InteropServices; +using MUnique.OpenMU.GameLogic.MiniGames; +using MUnique.OpenMU.PlugIns; + +/// +/// This plugin enables the periodic start of the Kanturu Refinery Tower event. +/// +[PlugIn] +[Display(Name = nameof(KanturuStartPlugIn), Description = "Kanturu Refinery Tower event")] +[Guid("A8F3C2D1-9E74-4ECB-8963-08A3697278C4")] +public sealed class KanturuStartPlugIn : MiniGameStartBasePlugIn +{ + /// + public override MiniGameType Key => MiniGameType.Kanturu; + + /// + public override object CreateDefaultConfig() + { + return KanturuStartConfiguration.Default; + } + + /// + protected override KanturuGameServerState CreateState(IGameContext gameContext) + { + return new KanturuGameServerState(gameContext); + } +} diff --git a/src/GameServer/MessageHandler/MiniGames/KanturuEnterRequestHandlerPlugIn.cs b/src/GameServer/MessageHandler/MiniGames/KanturuEnterRequestHandlerPlugIn.cs new file mode 100644 index 000000000..1afd8f684 --- /dev/null +++ b/src/GameServer/MessageHandler/MiniGames/KanturuEnterRequestHandlerPlugIn.cs @@ -0,0 +1,52 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameServer.MessageHandler.MiniGames; + +using System.Runtime.InteropServices; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.GameLogic; +using MUnique.OpenMU.GameLogic.MiniGames; +using MUnique.OpenMU.GameLogic.PlayerActions.MiniGames; +using MUnique.OpenMU.Network.Packets.ClientToServer; +using MUnique.OpenMU.PlugIns; + +/// +/// Handler for 0xD1/0x01 — KanturuEnterRequest. +/// The client sends this at animation frame 42 of the Gateway Machine NPC animation, +/// after the player clicked "Enter" in the INTERFACE_KANTURU2ND_ENTERNPC dialog. +/// On success the player is teleported to the event map (map 39) — no result packet needed. +/// On failure already shows an error to the player. +/// +[PlugIn] +[Display(Name = "Kanturu Enter Request Handler", Description = "Handles 0xD1/0x01 (KanturuEnterRequest) and teleports the player into the event.")] +[Guid("E6A3C9B2-5D84-4F10-9B42-8C7A0F3D5E19")] +[BelongsToGroup(KanturuGroupHandlerPlugIn.GroupKey)] +internal class KanturuEnterRequestHandlerPlugIn : ISubPacketHandlerPlugIn +{ + private readonly EnterMiniGameAction _enterAction = new(); + + /// + public bool IsEncryptionExpected => false; + + /// + public byte Key => KanturuEnterRequest.SubCode; + + /// + public async ValueTask HandlePacketAsync(Player player, Memory packet) + { + if (packet.Length < KanturuEnterRequest.Length + || player.SelectedCharacter?.CharacterClass is null) + { + return; + } + + // Try to enter the Kanturu mini game. + // On success: player is teleported to map 39 — the map change clears the dialog. + // On failure: TryEnterMiniGameAsync shows a message to the player and the client + // NPC animation resets naturally at frame 50. + await this._enterAction.TryEnterMiniGameAsync(player, MiniGameType.Kanturu, 1, 0xFF) + .ConfigureAwait(false); + } +} diff --git a/src/GameServer/MessageHandler/MiniGames/KanturuGroupHandlerPlugIn.cs b/src/GameServer/MessageHandler/MiniGames/KanturuGroupHandlerPlugIn.cs new file mode 100644 index 000000000..900b7b391 --- /dev/null +++ b/src/GameServer/MessageHandler/MiniGames/KanturuGroupHandlerPlugIn.cs @@ -0,0 +1,41 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameServer.MessageHandler.MiniGames; + +using System.Runtime.InteropServices; +using Microsoft.Extensions.Logging; +using MUnique.OpenMU.Network.PlugIns; +using MUnique.OpenMU.PlugIns; + +/// +/// Group packet handler for the 0xD1 (Kanturu) packet group. +/// Routes sub-codes to the appropriate sub-handlers: +/// 0x00 — KanturuInfoRequest (client requests event state info → opens dialog). +/// 0x01 — KanturuEnterRequest (client requests to enter the event map). +/// +[PlugIn] +[Display(Name = "Kanturu Group Handler", Description = "Routes 0xD1 Kanturu client packets to sub-handlers.")] +[Guid("C4E8A1F2-7B93-4D06-9E45-2A1F8B3C7D02")] +internal class KanturuGroupHandlerPlugIn : GroupPacketHandlerPlugIn +{ + /// + /// The group key for the Kanturu packet group. + /// + internal const byte GroupKey = (byte)PacketType.KanturuGroup; + + /// + /// Initializes a new instance of the class. + /// + public KanturuGroupHandlerPlugIn(IClientVersionProvider clientVersionProvider, PlugInManager manager, ILoggerFactory loggerFactory) + : base(clientVersionProvider, manager, loggerFactory) + { + } + + /// + public override bool IsEncryptionExpected => false; + + /// + public override byte Key => GroupKey; +} diff --git a/src/GameServer/MessageHandler/MiniGames/KanturuInfoRequestHandlerPlugIn.cs b/src/GameServer/MessageHandler/MiniGames/KanturuInfoRequestHandlerPlugIn.cs new file mode 100644 index 000000000..4ae897367 --- /dev/null +++ b/src/GameServer/MessageHandler/MiniGames/KanturuInfoRequestHandlerPlugIn.cs @@ -0,0 +1,42 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameServer.MessageHandler.MiniGames; + +using System.Runtime.InteropServices; +using MUnique.OpenMU.GameLogic; +using MUnique.OpenMU.GameLogic.PlugIns; +using MUnique.OpenMU.Network.Packets.ClientToServer; +using MUnique.OpenMU.PlugIns; + +/// +/// Handler for 0xD1/0x00 — KanturuInfoRequest. +/// The client sends this packet periodically while the gateway dialog is open +/// (every ~5 seconds via the Refresh button or the auto-refresh timer). +/// The server responds with a fresh 0xD1/0x00 StateInfo packet so the dialog +/// shows current player count and remaining time. +/// +[PlugIn] +[Display(Name = "Kanturu Info Request Handler", Description = "Responds to 0xD1/0x00 (KanturuInfoRequest) with fresh event state info.")] +[Guid("D5F2B8A1-4C73-4E09-8A31-7B6F9E2C4D08")] +[BelongsToGroup(KanturuGroupHandlerPlugIn.GroupKey)] +internal class KanturuInfoRequestHandlerPlugIn : ISubPacketHandlerPlugIn +{ + /// + public bool IsEncryptionExpected => false; + + /// + public byte Key => KanturuInfoRequest.SubCode; + + /// + public async ValueTask HandlePacketAsync(Player player, Memory packet) + { + if (packet.Length < KanturuInfoRequest.Length) + { + return; + } + + await KanturuGatewayPlugIn.SendKanturuStateInfoAsync(player).ConfigureAwait(false); + } +} diff --git a/src/GameServer/RemoteView/MiniGames/KanturuEventViewPlugIn.cs b/src/GameServer/RemoteView/MiniGames/KanturuEventViewPlugIn.cs new file mode 100644 index 000000000..8769505e3 --- /dev/null +++ b/src/GameServer/RemoteView/MiniGames/KanturuEventViewPlugIn.cs @@ -0,0 +1,228 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameServer.RemoteView.MiniGames; + +using System.Buffers.Binary; +using System.Runtime.InteropServices; +using MUnique.OpenMU.GameLogic.MiniGames; +using MUnique.OpenMU.Network; +using MUnique.OpenMU.PlugIns; +using MUnique.OpenMU.GameServer.RemoteView; + +/// +/// Sends Kanturu-specific state/result/HUD packets (0xD1 group) to the client. +/// +/// +/// Packet map (all C1 headers, subcode-bearing): +/// +/// 0xD1 0x00State info — opens the gateway NPC dialog (INTERFACE_KANTURU2ND_ENTERNPC). +/// 0xD1 0x01Enter result — stops NPC animation, shows error popup on failure. +/// 0xD1 0x03State change — controls HUD, music, and terrain reload. +/// 0xD1 0x04Battle result — displays Success_kantru.tga or Failure_kantru.tga. +/// 0xD1 0x05Time limit — countdown timer for the in-map HUD (milliseconds). +/// 0xD1 0x06Maya wide-area attack — triggers the visual MayaAction sequence on the Maya body object. +/// 0xD1 0x07Monster/user count — numbers displayed in the HUD. +/// +/// +[PlugIn] +[Display(Name = "Kanturu Event View Plugin", Description = "Sends Kanturu event state/result/HUD packets (0xD1 group) to the client.")] +[Guid("A3F1C8D2-5E94-4B7A-8C31-D6F2E0A49B15")] +public sealed class KanturuEventViewPlugIn : IKanturuEventViewPlugIn +{ + // Packet byte-lengths (C1 + Length + Code + SubCode + data): + private const int StateInfoLength = 12; // + btState + btDetailState + btEnter + btUserCount + int iRemainTime (4 bytes LE) + private const int EnterResultLength = 5; // + btResult + private const int StateChangeLength = 6; // + btState + btDetailState + private const int BattleResultLength = 5; // + btResult + private const int TimeLimitLength = 8; // + int btTimeLimit (4 bytes LE) + private const int MonsterUserCountLength = 6; // + bMonsterCount + btUserCount + private const int WideAreaAttackLength = 7; // + btObjClassH + btObjClassL + btType + + private const byte C1 = 0xC1; + private const byte GroupCode = 0xD1; + + private const byte SubCodeStateInfo = 0x00; + private const byte SubCodeEnterResult = 0x01; + private const byte SubCodeStateChange = 0x03; + private const byte SubCodeResult = 0x04; + private const byte SubCodeTimeLimit = 0x05; + private const byte SubCodeWideAreaAttack = 0x06; + private const byte SubCodeMonsterUserCount = 0x07; + + private readonly RemotePlayer _player; + + /// + /// Initializes a new instance of the class. + /// + /// The remote player this plugin sends packets to. + public KanturuEventViewPlugIn(RemotePlayer player) => this._player = player; + + /// + public async ValueTask SendStateInfoAsync(byte state, byte detailState, byte enter, byte userCount, int remainTimeSec) + { + if (this._player.Connection is not { } connection) + { + return; + } + + int Write() + { + var span = connection.Output.GetSpan(StateInfoLength)[..StateInfoLength]; + span[0] = C1; + span[1] = StateInfoLength; + span[2] = GroupCode; + span[3] = SubCodeStateInfo; + span[4] = state; + span[5] = detailState; + span[6] = enter; + span[7] = userCount; + // The client struct field iRemainTime is a plain C int — write as little-endian. + BinaryPrimitives.WriteInt32LittleEndian(span[8..], remainTimeSec); + return StateInfoLength; + } + + await connection.SendAsync(Write).ConfigureAwait(false); + } + + /// + public async ValueTask SendEnterResultAsync(byte result) + { + if (this._player.Connection is not { } connection) + { + return; + } + + int Write() + { + var span = connection.Output.GetSpan(EnterResultLength)[..EnterResultLength]; + span[0] = C1; + span[1] = EnterResultLength; + span[2] = GroupCode; + span[3] = SubCodeEnterResult; + span[4] = result; + return EnterResultLength; + } + + await connection.SendAsync(Write).ConfigureAwait(false); + } + + /// + public async ValueTask SendStateChangeAsync(byte state, byte detailState) + { + if (this._player.Connection is not { } connection) + { + return; + } + + int Write() + { + var span = connection.Output.GetSpan(StateChangeLength)[..StateChangeLength]; + span[0] = C1; + span[1] = StateChangeLength; + span[2] = GroupCode; + span[3] = SubCodeStateChange; + span[4] = state; + span[5] = detailState; + return StateChangeLength; + } + + await connection.SendAsync(Write).ConfigureAwait(false); + } + + /// + public async ValueTask SendBattleResultAsync(byte result) + { + if (this._player.Connection is not { } connection) + { + return; + } + + int Write() + { + var span = connection.Output.GetSpan(BattleResultLength)[..BattleResultLength]; + span[0] = C1; + span[1] = BattleResultLength; + span[2] = GroupCode; + span[3] = SubCodeResult; + span[4] = result; + return BattleResultLength; + } + + await connection.SendAsync(Write).ConfigureAwait(false); + } + + /// + public async ValueTask SendTimeLimitAsync(int timeLimitMs) + { + if (this._player.Connection is not { } connection) + { + return; + } + + int Write() + { + var span = connection.Output.GetSpan(TimeLimitLength)[..TimeLimitLength]; + span[0] = C1; + span[1] = TimeLimitLength; + span[2] = GroupCode; + span[3] = SubCodeTimeLimit; + // The client struct uses a plain C int — write as little-endian. + BinaryPrimitives.WriteInt32LittleEndian(span[4..], timeLimitMs); + return TimeLimitLength; + } + + await connection.SendAsync(Write).ConfigureAwait(false); + } + + /// + public async ValueTask SendMonsterUserCountAsync(byte monsterCount, byte userCount) + { + if (this._player.Connection is not { } connection) + { + return; + } + + int Write() + { + var span = connection.Output.GetSpan(MonsterUserCountLength)[..MonsterUserCountLength]; + span[0] = C1; + span[1] = MonsterUserCountLength; + span[2] = GroupCode; + span[3] = SubCodeMonsterUserCount; + span[4] = monsterCount; + span[5] = userCount; + return MonsterUserCountLength; + } + + await connection.SendAsync(Write).ConfigureAwait(false); + } + + /// + public async ValueTask SendMayaWideAreaAttackAsync(byte attackType) + { + if (this._player.Connection is not { } connection) + { + return; + } + + int Write() + { + var span = connection.Output.GetSpan(WideAreaAttackLength)[..WideAreaAttackLength]; + span[0] = C1; + span[1] = WideAreaAttackLength; + span[2] = GroupCode; + span[3] = SubCodeWideAreaAttack; + // btObjClassH / btObjClassL — the client ignores these bytes and only reads btType, + // so we send zeros. (Confirmed in WSclient.cpp: RecevieKanturu3rdMayaSKill reads + // only pData->btType and passes it to MayaSceneMayaAction.) + span[4] = 0x00; // btObjClassH + span[5] = 0x00; // btObjClassL + span[6] = attackType; + return WideAreaAttackLength; + } + + await connection.SendAsync(Write).ConfigureAwait(false); + } +} diff --git a/src/Persistence/Initialization/VersionSeasonSix/Events/KanturuInitializer.cs b/src/Persistence/Initialization/VersionSeasonSix/Events/KanturuInitializer.cs new file mode 100644 index 000000000..b49606b19 --- /dev/null +++ b/src/Persistence/Initialization/VersionSeasonSix/Events/KanturuInitializer.cs @@ -0,0 +1,49 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Events; + +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps; + +/// +/// The initializer for the Kanturu Refinery Tower event. +/// +internal class KanturuInitializer : InitializerBase +{ + /// + /// Initializes a new instance of the class. + /// + /// The context. + /// The game configuration. + public KanturuInitializer(IContext context, GameConfiguration gameConfiguration) + : base(context, gameConfiguration) + { + } + + /// + public override void Initialize() + { + var kanturu = this.Context.CreateNew(); + kanturu.SetGuid((short)MiniGameType.Kanturu, 1); + this.GameConfiguration.MiniGameDefinitions.Add(kanturu); + kanturu.Name = "Kanturu Refinery Tower"; + kanturu.Description = "Event definition for the Kanturu Refinery Tower event."; + kanturu.EnterDuration = TimeSpan.FromMinutes(3); + kanturu.GameDuration = TimeSpan.FromMinutes(135); + kanturu.ExitDuration = TimeSpan.FromMinutes(1); + kanturu.MaximumPlayerCount = 10; + kanturu.MinimumCharacterLevel = 350; + kanturu.MaximumCharacterLevel = 400; + kanturu.MinimumSpecialCharacterLevel = 350; + kanturu.MaximumSpecialCharacterLevel = 400; + kanturu.Entrance = this.GameConfiguration.Maps.First(m => m.Number == KanturuEvent.Number).ExitGates.Single(g => g.IsSpawnGate); + kanturu.Type = MiniGameType.Kanturu; + kanturu.TicketItem = null; + kanturu.GameLevel = 1; + kanturu.MapCreationPolicy = MiniGameMapCreationPolicy.Shared; + kanturu.SaveRankingStatistics = false; + kanturu.AllowParty = true; + } +} diff --git a/src/Persistence/Initialization/VersionSeasonSix/GameConfigurationInitializer.cs b/src/Persistence/Initialization/VersionSeasonSix/GameConfigurationInitializer.cs index d00199a60..a6def80d3 100644 --- a/src/Persistence/Initialization/VersionSeasonSix/GameConfigurationInitializer.cs +++ b/src/Persistence/Initialization/VersionSeasonSix/GameConfigurationInitializer.cs @@ -83,6 +83,7 @@ public override void Initialize() new DevilSquareInitializer(this.Context, this.GameConfiguration).Initialize(); new BloodCastleInitializer(this.Context, this.GameConfiguration).Initialize(); new ChaosCastleInitializer(this.Context, this.GameConfiguration).Initialize(); + new KanturuInitializer(this.Context, this.GameConfiguration).Initialize(); } private void CreateJewelMixes() diff --git a/src/Persistence/Initialization/VersionSeasonSix/Maps/KanturuEvent.cs b/src/Persistence/Initialization/VersionSeasonSix/Maps/KanturuEvent.cs index 7b3df570c..13e9a6a39 100644 --- a/src/Persistence/Initialization/VersionSeasonSix/Maps/KanturuEvent.cs +++ b/src/Persistence/Initialization/VersionSeasonSix/Maps/KanturuEvent.cs @@ -1,11 +1,14 @@ -// +// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps; +using MUnique.OpenMU.AttributeSystem; using MUnique.OpenMU.DataModel.Configuration; using MUnique.OpenMU.GameLogic.Attributes; +using MUnique.OpenMU.GameLogic.NPC; +using MUnique.OpenMU.Persistence.Initialization.Skills; /// /// Initialization for the Kanturu event map. @@ -38,23 +41,28 @@ public KanturuEvent(IContext context, GameConfiguration gameConfiguration) /// protected override string MapName => Name; + /// + /// Players who die inside the Kanturu Event map respawn at Kanturu Relics (map 38). + /// + protected override byte SafezoneMapNumber => KanturuRelics.Number; + /// protected override void CreateMapAttributeRequirements() { - // It's only required during the event. Events are not implemented yet - probably we need multiple GameMapDefinitions, for each state of a map. this.CreateRequirement(Stats.MoonstonePendantEquipped, 1); } /// protected override IEnumerable CreateNpcSpawns() { - yield return this.CreateMonsterSpawn(1, this.GameConfiguration.Monsters.First(m => m.Number == 368), 77, 177, Direction.SouthWest); // Elpis NPC + yield return this.CreateMonsterSpawn(1, this.NpcDictionary[368], 77, 177, Direction.SouthWest); // Elpis NPC } /// protected override IEnumerable CreateMonsterSpawns() { - var laserTrap = this.GameConfiguration.Monsters.First(m => m.Number == 106); + // Laser traps (auto-spawn on map load) + var laserTrap = this.NpcDictionary[106]; yield return this.CreateMonsterSpawn(100, laserTrap, 60, 108); yield return this.CreateMonsterSpawn(101, laserTrap, 173, 61); yield return this.CreateMonsterSpawn(102, laserTrap, 173, 64); @@ -93,11 +101,190 @@ protected override IEnumerable CreateMonsterSpawns() yield return this.CreateMonsterSpawn(135, laserTrap, 178, 56); yield return this.CreateMonsterSpawn(136, laserTrap, 176, 58); yield return this.CreateMonsterSpawn(137, laserTrap, 174, 59); + + // --- Event wave spawns (OnceAtWaveStart) --- + // Boss positions: Maya Left (202,83), Maya Right (189,82), Nightmare (78,141) + // Maya room (bounded by laser traps): X:174-217, Y:54-83 + // Nightmare zone: X:75-88, Y:97-141 + + // Wave 0: Maya body rises when battle starts (fixed position below the fight room) + var maya = this.NpcDictionary[364]; + yield return this.CreateMonsterSpawn(299, maya, 188, 188, 110, 110, 1, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 0); + + // Wave 1: Phase 1 — 30 Blade Hunter + 10 Dreadfear (Maya room) + var bladeHunter = this.NpcDictionary[354]; + var dreadfear = this.NpcDictionary[360]; + yield return this.CreateMonsterSpawn(200, bladeHunter, 175, 215, 58, 86, 30, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 1); + yield return this.CreateMonsterSpawn(201, dreadfear, 175, 215, 58, 86, 10, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 1); + + // Wave 2: Phase 1 Boss — Maya Left Hand + var mayaLeft = this.NpcDictionary[362]; + yield return this.CreateMonsterSpawn(210, mayaLeft, 202, 202, 83, 83, 1, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 2); + + // Wave 3: Phase 2 — 30 Blade Hunter + 10 Dreadfear (Maya room) + yield return this.CreateMonsterSpawn(220, bladeHunter, 175, 215, 58, 86, 30, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 3); + yield return this.CreateMonsterSpawn(221, dreadfear, 175, 215, 58, 86, 10, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 3); + + // Wave 4: Phase 2 Boss — Maya Right Hand + var mayaRight = this.NpcDictionary[363]; + yield return this.CreateMonsterSpawn(230, mayaRight, 189, 189, 82, 82, 1, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 4); + + // Wave 5: Phase 3 — 10 Dreadfear + 10 Twin Tale (Maya room) + var twinTale = this.NpcDictionary[359]; + yield return this.CreateMonsterSpawn(240, dreadfear, 175, 215, 58, 86, 10, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 5); + yield return this.CreateMonsterSpawn(241, twinTale, 175, 215, 58, 86, 10, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 5); + + // Wave 6: Phase 3 Bosses — Maya Left Hand + Maya Right Hand + yield return this.CreateMonsterSpawn(250, mayaLeft, 202, 202, 83, 83, 1, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 6); + yield return this.CreateMonsterSpawn(251, mayaRight, 189, 189, 82, 82, 1, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 6); + + // Wave 7: Nightmare Prep — 15 Genocider + 15 Dreadfear + 15 Persona (Nightmare zone) + var genocider = this.NpcDictionary[357]; + var persona = this.NpcDictionary[358]; + yield return this.CreateMonsterSpawn(260, genocider, 75, 88, 97, 137, 15, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 7); + yield return this.CreateMonsterSpawn(261, dreadfear, 75, 88, 97, 137, 15, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 7); + yield return this.CreateMonsterSpawn(262, persona, 75, 88, 97, 137, 15, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 7); + + // Wave 8: Nightmare + var nightmare = this.NpcDictionary[361]; + yield return this.CreateMonsterSpawn(270, nightmare, 78, 78, 143, 143, 1, Direction.Undefined, SpawnTrigger.OnceAtWaveStart, 8); } /// protected override void CreateMonsters() { - // no monsters to create + // Maya (#364) - full body boss, rises at event start + { + var monster = this.Context.CreateNew(); + this.GameConfiguration.Monsters.Add(monster); + monster.Number = 364; + monster.Designation = "Maya"; + monster.MoveRange = 3; + monster.AttackRange = 6; + monster.ViewRange = 9; + monster.MoveDelay = new TimeSpan(400 * TimeSpan.TicksPerMillisecond); + monster.AttackDelay = new TimeSpan(2000 * TimeSpan.TicksPerMillisecond); + monster.RespawnDelay = new TimeSpan(0); + monster.Attribute = 2; + monster.NumberOfMaximumItemDrops = 7; + var attributes = new Dictionary + { + { Stats.Level, 140 }, + { Stats.MaximumHealth, 5_000_000 }, + { Stats.MinimumPhysBaseDmg, 2500 }, + { Stats.MaximumPhysBaseDmg, 3000 }, + { Stats.DefenseBase, 6500 }, + { Stats.AttackRatePvm, 2800 }, + { Stats.DefenseRatePvm, 2200 }, + { Stats.PoisonResistance, 50f / 255 }, + { Stats.IceResistance, 50f / 255 }, + { Stats.LightningResistance, 50f / 255 }, + { Stats.FireResistance, 50f / 255 }, + }; + monster.AddAttributes(attributes, this.Context, this.GameConfiguration); + monster.SetGuid(monster.Number); + } + + // Nightmare (#361) + { + var monster = this.Context.CreateNew(); + this.GameConfiguration.Monsters.Add(monster); + monster.Number = 361; + monster.Designation = "Nightmare"; + monster.MoveRange = 3; + monster.AttackRange = 5; + monster.ViewRange = 9; + monster.MoveDelay = new TimeSpan(400 * TimeSpan.TicksPerMillisecond); + monster.AttackDelay = new TimeSpan(1600 * TimeSpan.TicksPerMillisecond); + monster.RespawnDelay = new TimeSpan(0); + monster.Attribute = 2; + monster.NumberOfMaximumItemDrops = 5; + // Nightmare uses a Decay (poison) area attack — applies poison DoT to players on each hit. + monster.AttackSkill = this.GameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.Decay); + var attributes = new Dictionary + { + { Stats.Level, 145 }, + { Stats.MaximumHealth, 1_500_000 }, + { Stats.MinimumPhysBaseDmg, 3000 }, + { Stats.MaximumPhysBaseDmg, 3500 }, + { Stats.DefenseBase, 7500 }, + { Stats.AttackRatePvm, 3000 }, + { Stats.DefenseRatePvm, 2500 }, + { Stats.PoisonResistance, 50f / 255 }, + { Stats.IceResistance, 50f / 255 }, + { Stats.LightningResistance, 50f / 255 }, + { Stats.FireResistance, 50f / 255 }, + }; + monster.AddAttributes(attributes, this.Context, this.GameConfiguration); + monster.SetGuid(monster.Number); + } + + // Maya Left Hand (#362) + { + var monster = this.Context.CreateNew(); + this.GameConfiguration.Monsters.Add(monster); + monster.Number = 362; + monster.Designation = "Maya (Hand Left)"; + monster.MoveRange = 3; + monster.AttackRange = 5; + monster.ViewRange = 8; + monster.MoveDelay = new TimeSpan(400 * TimeSpan.TicksPerMillisecond); + monster.AttackDelay = new TimeSpan(1600 * TimeSpan.TicksPerMillisecond); + monster.RespawnDelay = new TimeSpan(0); + monster.Attribute = 2; + monster.NumberOfMaximumItemDrops = 3; + // Maya Left Hand uses IceStorm — AoE ice attack that hits a 3×3 tile area around the target. + monster.AttackSkill = this.GameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.IceStorm); + var attributes = new Dictionary + { + { Stats.Level, 130 }, + { Stats.MaximumHealth, 400_000 }, + { Stats.MinimumPhysBaseDmg, 2000 }, + { Stats.MaximumPhysBaseDmg, 2500 }, + { Stats.DefenseBase, 5000 }, + { Stats.AttackRatePvm, 2000 }, + { Stats.DefenseRatePvm, 1500 }, + { Stats.PoisonResistance, 40f / 255 }, + { Stats.IceResistance, 40f / 255 }, + { Stats.LightningResistance, 40f / 255 }, + { Stats.FireResistance, 40f / 255 }, + }; + monster.AddAttributes(attributes, this.Context, this.GameConfiguration); + monster.SetGuid(monster.Number); + } + + // Maya Right Hand (#363) + { + var monster = this.Context.CreateNew(); + this.GameConfiguration.Monsters.Add(monster); + monster.Number = 363; + monster.Designation = "Maya (Hand Right)"; + monster.MoveRange = 3; + monster.AttackRange = 5; + monster.ViewRange = 8; + monster.MoveDelay = new TimeSpan(400 * TimeSpan.TicksPerMillisecond); + monster.AttackDelay = new TimeSpan(1600 * TimeSpan.TicksPerMillisecond); + monster.RespawnDelay = new TimeSpan(0); + monster.Attribute = 2; + monster.NumberOfMaximumItemDrops = 3; + // Maya Right Hand uses IceStorm — same AoE ice attack as the left hand. + monster.AttackSkill = this.GameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.IceStorm); + var attributes = new Dictionary + { + { Stats.Level, 130 }, + { Stats.MaximumHealth, 350_000 }, + { Stats.MinimumPhysBaseDmg, 2000 }, + { Stats.MaximumPhysBaseDmg, 2500 }, + { Stats.DefenseBase, 5000 }, + { Stats.AttackRatePvm, 2100 }, + { Stats.DefenseRatePvm, 1600 }, + { Stats.PoisonResistance, 40f / 255 }, + { Stats.IceResistance, 40f / 255 }, + { Stats.LightningResistance, 40f / 255 }, + { Stats.FireResistance, 40f / 255 }, + }; + monster.AddAttributes(attributes, this.Context, this.GameConfiguration); + monster.SetGuid(monster.Number); + } } -} \ No newline at end of file +} From a933e83ad187df5703a82e0c169badf04a2f53f0 Mon Sep 17 00:00:00 2001 From: apraxico Date: Tue, 24 Mar 2026 21:13:40 -0300 Subject: [PATCH 02/11] fix: Add exception handling to fire-and-forget tasks in KanturuContext Addresses Gemini Code Assist review feedback (high severity): - OpenElphisBarrierAsync: wrapped entire body in try-catch so that failures while opening the Elphis barrier are logged instead of silently propagating as unobserved task exceptions. - SendMonsterUserCountAsync (8 call sites): introduced FireAndForgetMonsterCountAsync helper that wraps the broadcast in a try-catch, preventing a disconnecting player from causing an unhandled exception in the monster-death callback. - RunMayaWideAreaAttacksAsync: wrapped ForEachPlayerAsync broadcast in try-catch; the loop continues on errors rather than crashing the visual-effects task. - Diagnostic ForEachPlayerAsync in out-of-phase default case: added try-catch to the fire-and-forget lambda. Co-Authored-By: Claude Sonnet 4.6 --- src/GameLogic/MiniGames/KanturuContext.cs | 74 ++++++++++++++++++----- 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/src/GameLogic/MiniGames/KanturuContext.cs b/src/GameLogic/MiniGames/KanturuContext.cs index 193c220ba..4fb84c3b7 100644 --- a/src/GameLogic/MiniGames/KanturuContext.cs +++ b/src/GameLogic/MiniGames/KanturuContext.cs @@ -191,7 +191,7 @@ protected override void OnMonsterDied(object? sender, DeathInformation e) complete = killed == this._waveKillTarget; var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); var pc = (byte)Math.Min(255, this.PlayerCount); - _ = Task.Run(() => this.SendMonsterUserCountAsync(remaining, pc).AsTask()); + this.FireAndForgetMonsterCountAsync(remaining, pc); break; } @@ -199,7 +199,7 @@ protected override void OnMonsterDied(object? sender, DeathInformation e) { complete = true; var pc = (byte)Math.Min(255, this.PlayerCount); - _ = Task.Run(() => this.SendMonsterUserCountAsync(0, pc).AsTask()); + this.FireAndForgetMonsterCountAsync(0, pc); break; } @@ -209,7 +209,7 @@ protected override void OnMonsterDied(object? sender, DeathInformation e) complete = killed == this._waveKillTarget; var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); var pc = (byte)Math.Min(255, this.PlayerCount); - _ = Task.Run(() => this.SendMonsterUserCountAsync(remaining, pc).AsTask()); + this.FireAndForgetMonsterCountAsync(remaining, pc); break; } @@ -217,7 +217,7 @@ protected override void OnMonsterDied(object? sender, DeathInformation e) { complete = true; var pc = (byte)Math.Min(255, this.PlayerCount); - _ = Task.Run(() => this.SendMonsterUserCountAsync(0, pc).AsTask()); + this.FireAndForgetMonsterCountAsync(0, pc); break; } @@ -227,7 +227,7 @@ protected override void OnMonsterDied(object? sender, DeathInformation e) complete = killed == this._waveKillTarget; var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); var pc = (byte)Math.Min(255, this.PlayerCount); - _ = Task.Run(() => this.SendMonsterUserCountAsync(remaining, pc).AsTask()); + this.FireAndForgetMonsterCountAsync(remaining, pc); break; } @@ -237,7 +237,7 @@ protected override void OnMonsterDied(object? sender, DeathInformation e) complete = killed == this._waveKillTarget; var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); var pc = (byte)Math.Min(255, this.PlayerCount); - _ = Task.Run(() => this.SendMonsterUserCountAsync(remaining, pc).AsTask()); + this.FireAndForgetMonsterCountAsync(remaining, pc); break; } @@ -247,7 +247,7 @@ protected override void OnMonsterDied(object? sender, DeathInformation e) complete = killed == this._waveKillTarget; var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); var pc = (byte)Math.Min(255, this.PlayerCount); - _ = Task.Run(() => this.SendMonsterUserCountAsync(remaining, pc).AsTask()); + this.FireAndForgetMonsterCountAsync(remaining, pc); break; } @@ -259,7 +259,7 @@ protected override void OnMonsterDied(object? sender, DeathInformation e) var killed = Interlocked.Increment(ref this._waveKillCount); var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); var pc = (byte)Math.Min(255, this.PlayerCount); - _ = Task.Run(() => this.SendMonsterUserCountAsync(remaining, pc).AsTask()); + this.FireAndForgetMonsterCountAsync(remaining, pc); complete = false; break; } @@ -279,11 +279,21 @@ protected override void OnMonsterDied(object? sender, DeathInformation e) this.Logger.LogWarning( "Kanturu: Nightmare died but _phase={Phase} (expected NightmareActive). Barrier NOT opened.", phase); - _ = Task.Run(() => this.ForEachPlayerAsync(p => - p.InvokeViewPlugInAsync(v => - v.ShowMessageAsync( - $"[Kanturu] Nightmare died out of phase! Phase={phase}", - MessageType.BlueNormal)).AsTask()).AsTask()); + _ = Task.Run(async () => + { + try + { + await this.ForEachPlayerAsync(p => + p.InvokeViewPlugInAsync(v => + v.ShowMessageAsync( + $"[Kanturu] Nightmare died out of phase! Phase={phase}", + MessageType.BlueNormal)).AsTask()).ConfigureAwait(false); + } + catch (Exception ex) + { + this.Logger.LogWarning(ex, "{context}: Error broadcasting out-of-phase diagnostic message.", this); + } + }); } complete = false; @@ -642,6 +652,8 @@ private async ValueTask OpenElphisBarrierAsync() return; } + try + { this.Logger.LogInformation("Kanturu: opening Elphis barrier."); // Diagnostic confirmation message — visible in the game client. @@ -693,6 +705,11 @@ await this.ForEachPlayerAsync(player => player.InvokeViewPlugInAsync(p => p.ChangeAttributesAsync(TerrainAttributeType.NoGround, setAttribute: false, areas)) .AsTask()).ConfigureAwait(false); + } + catch (Exception ex) + { + this.Logger.LogError(ex, "{context}: Unexpected error while opening Elphis barrier.", this); + } } /// @@ -808,9 +825,16 @@ private async Task RunMayaWideAreaAttacksAsync(CancellationToken ct) // Skip the broadcast during inter-phase standby — Maya stays idle, no visual attacks. if (!this._mayaAttacksPaused) { - await this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.SendMayaWideAreaAttackAsync(attackType)).AsTask()).ConfigureAwait(false); + try + { + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.SendMayaWideAreaAttackAsync(attackType)).AsTask()).ConfigureAwait(false); + } + catch (Exception ex) + { + this.Logger.LogWarning(ex, "{context}: Error sending Maya wide-area attack broadcast.", this); + } } attackType = (byte)(1 - attackType); // alternate 0 → 1 → 0 → … @@ -920,6 +944,24 @@ private ValueTask SendKanturuStateAsync(byte state, byte detailState) p.SendStateChangeAsync(state, detailState)).AsTask()); } + /// + /// Fires a monster/user-count broadcast without blocking the caller. + /// Exceptions are caught and logged so that an unreachable player cannot + /// propagate an unhandled exception through the fire-and-forget task. + /// + private void FireAndForgetMonsterCountAsync(byte remaining, byte pc) => + _ = Task.Run(async () => + { + try + { + await this.SendMonsterUserCountAsync(remaining, pc).ConfigureAwait(false); + } + catch (Exception ex) + { + this.Logger.LogWarning(ex, "{context}: Error sending monster/user count update.", this); + } + }); + /// /// Broadcasts packet 0xD1/0x07 (Kanturu monster/user count) to all players currently on the map. /// From 6d0aa627c68d0e80a13b70e0399955aee344fb7a Mon Sep 17 00:00:00 2001 From: apraxico Date: Fri, 27 Mar 2026 00:48:06 -0300 Subject: [PATCH 03/11] refactor: Apply Sven's code review changes to Kanturu event - IKanturuEventViewPlugIn: Replace static classes with enums (KanturuState, KanturuMayaDetailState, KanturuNightmareDetailState, KanturuTowerDetailState, KanturuEnterResult, KanturuBattleResult, KanturuMayaAttackType). Rename Send... methods to Show... and use TimeSpan/int instead of int/byte params. - ServerToClientPackets.xml: Define all 7 Kanturu packets (0xD1/00-07) so they are documented, tested, and auto-generated. - KanturuEventViewPlugIn: Use auto-generated connection.SendKanturu*Async() extension methods with mapping functions between enum and protocol values. - KanturuContext: Make OnMonsterDied async void with try-catch (VSTHRD100 pattern matching ChaosCastleContext); remove all Task.Run fire-and-forget wrappers; await ShowMonsterUserCountAsync directly. Add volatile field XML doc comments explaining threading rationale. Replace all SendKanturu* calls with typed ShowMaya/Nightmare/TowerStateToAllAsync helpers. Replace Send*Async helpers with Show*Async helpers using int/TimeSpan params. Use ShowGoldenMessageAsync + nameof(PlayerMessage.Kanturu*) for all localized messages. - KanturuGatewayPlugIn: Update to use KanturuState enum and new ShowStateInfoAsync(KanturuState, byte, bool, int, TimeSpan) signature. - PlayerMessage: Add 20 Kanturu message keys (Maya phases, Nightmare phases, Tower of Refinement) plus KanturuDefeat for the game-end defeat message. Co-Authored-By: Claude Sonnet 4.6 --- .../MiniGames/IKanturuEventViewPlugIn.cs | 254 +++++---- src/GameLogic/MiniGames/KanturuContext.cs | 495 +++++++++--------- src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs | 30 +- .../Properties/PlayerMessage.Designer.cs | 189 +++++++ src/GameLogic/Properties/PlayerMessage.resx | 63 +++ .../MiniGames/KanturuEventViewPlugIn.cs | 251 ++++----- .../ServerToClient/ServerToClientPackets.xml | 281 ++++++++++ 7 files changed, 1050 insertions(+), 513 deletions(-) diff --git a/src/GameLogic/MiniGames/IKanturuEventViewPlugIn.cs b/src/GameLogic/MiniGames/IKanturuEventViewPlugIn.cs index 984a4c7e7..28f24f073 100644 --- a/src/GameLogic/MiniGames/IKanturuEventViewPlugIn.cs +++ b/src/GameLogic/MiniGames/IKanturuEventViewPlugIn.cs @@ -8,188 +8,218 @@ namespace MUnique.OpenMU.GameLogic.MiniGames; /// /// View plugin interface for Kanturu-specific server-to-client packets (0xD1 group). -/// Each method maps to one packet subcode. +/// Each method maps to one packet subcode. Names use Show... to signal that the game +/// logic is expressing intent, not dictating the transport mechanism. /// public interface IKanturuEventViewPlugIn : IViewPlugIn { /// - /// Sends packet 0xD1/0x00 — Kanturu state info (response to NPC talk / info request). - /// This opens the INTERFACE_KANTURU2ND_ENTERNPC dialog on the client, showing - /// the current event state, how many players are inside, and whether entry is possible. + /// Shows the Kanturu state info dialog to the player (packet 0xD1/0x00). + /// This opens the INTERFACE_KANTURU2ND_ENTERNPC dialog on the client, + /// showing the current event state, whether entry is possible, how many players + /// are inside, and how much time remains. /// - /// Main event state (see ). - /// Detail state for the current main state. - /// 1 = entrance is open (Enter button enabled); 0 = entrance closed. + /// Main event state. + /// + /// Protocol detail-state byte. Pass the raw byte value from the appropriate + /// detail-state enum (, + /// , or ). + /// + /// true if the Enter button should be enabled. /// Number of players currently inside the event map. - /// - /// Seconds remaining. Semantics depend on state: - /// Standby → seconds until the event opens (client shows minutes). - /// Tower → seconds the tower has been open (client shows hours). - /// Otherwise 0. + /// + /// Remaining time. Standby: time until the event opens. + /// Tower: time the tower has been open. Otherwise . /// - ValueTask SendStateInfoAsync(byte state, byte detailState, byte enter, byte userCount, int remainTimeSec); + ValueTask ShowStateInfoAsync(KanturuState state, byte detailState, bool canEnter, int userCount, TimeSpan remainingTime); /// - /// Sends packet 0xD1/0x01 — Kanturu enter result. - /// Sent after the player's entry attempt is processed. - /// On the client side this stops the NPC animation and closes the dialog. - /// Result codes: 0 = failed (generic), use specific POPUP_* values for other errors. - /// On success the player is teleported before this is sent, so the packet is only - /// needed for failure cases. + /// Shows the result of the player's entry attempt (packet 0xD1/0x01). + /// On success the player has already been teleported before this is sent; + /// on failure the client displays the appropriate error popup. /// - ValueTask SendEnterResultAsync(byte result); + ValueTask ShowEnterResultAsync(KanturuEnterResult result); /// - /// Sends packet 0xD1/0x03 — Kanturu state change. - /// On the client side this: - /// - Shows/hides the in-map HUD (INTERFACE_KANTURU_INFO). - /// - Switches background music (Maya, Nightmare, Tower themes). - /// - When == and - /// is 1 or 2, reloads the success terrain file - /// (EncTerrain<n>01.att), which visually removes the Elphis barrier. + /// Updates the client HUD and audio for a Maya battle phase transition (packet 0xD1/0x03). /// - /// Main state (see ). - /// Detail state for the current main state. - ValueTask SendStateChangeAsync(byte state, byte detailState); + ValueTask ShowMayaBattleStateAsync(KanturuMayaDetailState detailState); /// - /// Sends packet 0xD1/0x04 — Kanturu battle result. - /// - /// 0 = failure — shows Failure_kantru.tga overlay. - /// 1 = success — shows Success_kantru.tga overlay - /// (only when client state is already ). - /// + /// Updates the client HUD and audio for a Nightmare battle phase transition (packet 0xD1/0x03). /// - ValueTask SendBattleResultAsync(byte result); + ValueTask ShowNightmareStateAsync(KanturuNightmareDetailState detailState); /// - /// Sends packet 0xD1/0x05 — countdown timer for the Kanturu HUD (in milliseconds). - /// The HUD divides by 1000 to get seconds and counts down visually. + /// Updates the client HUD and audio for a Tower of Refinement phase transition (packet 0xD1/0x03). + /// When is + /// the client additionally reloads the success terrain file to remove the Elphis barrier visually. /// - ValueTask SendTimeLimitAsync(int timeLimitMs); + ValueTask ShowTowerStateAsync(KanturuTowerDetailState detailState); /// - /// Sends packet 0xD1/0x07 — remaining monster count and current user count. - /// Updates the numbers displayed in the Kanturu HUD. + /// Shows the battle outcome overlay to the player (packet 0xD1/0x04). /// - ValueTask SendMonsterUserCountAsync(byte monsterCount, byte userCount); + ValueTask ShowBattleResultAsync(KanturuBattleResult result); /// - /// Sends packet 0xD1/0x06 — Maya wide-area attack visual trigger. - /// The client calls MayaSceneMayaAction(attackType) on receipt, which plays - /// one of two client-side visual sequences on the Maya body object: - /// - /// 0 = stone-storm effect (MODEL_STORM3 + falling debris around Hero) - /// 1 = stone-rain effect (MODEL_MAYASTONE projectiles falling on Hero) - /// - /// This is a purely cosmetic broadcast — damage is handled by the server-side - /// on Maya Hands (#362/#363). + /// Starts the HUD countdown timer (packet 0xD1/0x05). + /// The client converts the value to seconds for display. /// - /// 0 = storm visual; 1 = stone-rain visual. - ValueTask SendMayaWideAreaAttackAsync(byte attackType); + ValueTask ShowTimeLimitAsync(TimeSpan timeLimit); + + /// + /// Updates the monster and user count numbers in the Kanturu HUD (packet 0xD1/0x07). + /// + ValueTask ShowMonsterUserCountAsync(int monsterCount, int userCount); + + /// + /// Triggers a Maya wide-area attack visual on the client (packet 0xD1/0x06). + /// This is purely cosmetic — damage is handled server-side by the monster's + /// AttackSkill. + /// + ValueTask ShowMayaWideAreaAttackAsync(KanturuMayaAttackType attackType); } /// -/// Kanturu main state codes matching the client's KANTURU_STATE_TYPE enum. +/// Main state of the Kanturu event. Values are defined by the client's +/// KANTURU_STATE_TYPE enum and mapped to packet bytes in the view plugin layer. /// -public static class KanturuStateCode +public enum KanturuState { /// No active state. - public const byte None = 0; + None, + + /// Waiting for players to enter before the event starts. + Standby, + + /// Maya battle phase covering Phases 1–3 and their boss waves. + MayaBattle, + + /// Nightmare battle phase after all three Maya phases are cleared. + NightmareBattle, + + /// Tower of Refinement phase; opens after Nightmare is defeated. + Tower, + + /// Event has ended. + End, +} + +/// +/// Detail states for the phase, +/// matching KANTURU_MAYA_DIRECTION_TYPE on the client. +/// +public enum KanturuMayaDetailState +{ + /// No direction; HUD is hidden. + None, + + /// Maya notify/cinematic intro — camera pans to Maya room. + Notify, - /// Waiting for players to enter. - public const byte Standby = 1; + /// Phase 1 monster wave; HUD visible. + Monster1, - /// Maya battle phase (Phases 1–3 + boss waves). - public const byte MayaBattle = 2; + /// Phase 1 boss: Maya Left Hand. + MayaLeft, - /// Nightmare battle phase. - public const byte NightmareBattle = 3; + /// Phase 2 monster wave; HUD visible. + Monster2, - /// Tower of Refinement open (post-victory). - public const byte Tower = 4; + /// Phase 2 boss: Maya Right Hand. + MayaRight, - /// Event ended. - public const byte End = 5; + /// Phase 3 monster wave; HUD visible. + Monster3, + + /// Phase 3 bosses: both Maya hands simultaneously. + BothHands, + + /// + /// Maya phase 3 end cycle — triggers the full Maya explosion and player-fall cinematic + /// (KANTURU_MAYA_DIRECTION_ENDCYCLE_MAYA3 = 16 on the client). + /// + EndCycleMaya3, } /// -/// Detail state codes for the Nightmare battle phase, -/// matching KANTURU_NIGHTMARE_DIRECTION_TYPE. +/// Detail states for the phase, +/// matching KANTURU_NIGHTMARE_DIRECTION_TYPE on the client. /// -public static class KanturuNightmareDetail +public enum KanturuNightmareDetailState { /// No direction set. - public const byte None = 0; + None, - /// Idle — Nightmare present but not yet in battle. - public const byte Idle = 1; + /// Nightmare present but idle — not yet in active battle. + Idle, - /// Nightmare intro animation. - public const byte NightmareIntro = 2; + /// Nightmare intro animation playing. + Intro, /// Active battle — shows the HUD on the client. - public const byte Battle = 3; + Battle, - /// Battle ended. - public const byte End = 4; + /// Battle ended (Nightmare defeated). + End, } /// -/// Detail state codes for the Tower of Refinement phase, -/// matching KANTURU_TOWER_STATE_TYPE. +/// Detail states for the phase, +/// matching KANTURU_TOWER_STATE_TYPE on the client. /// -public static class KanturuTowerDetail +public enum KanturuTowerDetailState { /// No tower state. - public const byte None = 0; + None, /// /// Tower is open after Nightmare's defeat. /// Sending this triggers the client to reload EncTerrain<n>01.att /// (the success terrain), which visually removes the Elphis barrier. /// - public const byte Revitalization = 1; + Revitalization, /// Tower closing soon — client warns players. - public const byte Notify = 2; + Notify, - /// Tower closed. - public const byte Close = 3; + /// Tower is closed. + Close, } /// -/// Detail state codes for the Maya battle phase, -/// matching KANTURU_MAYA_DIRECTION_TYPE. +/// Result of a Kanturu entry request. /// -public static class KanturuMayaDetail +public enum KanturuEnterResult { - /// No direction. - public const byte None = 0; + /// Entry failed (generic failure — level, missing pendant, event not open, etc.). + Failed, - /// Phase 1 monster wave active — shows HUD. - public const byte Monster1 = 3; - - /// Phase 1 boss (Maya Left Hand) — shows HUD. - public const byte Maya1 = 4; - - /// Phase 2 monster wave active — shows HUD. - public const byte Monster2 = 8; + /// The player was successfully entered into the event. + Success, +} - /// Phase 2 boss (Maya Right Hand) — shows HUD. - public const byte Maya2 = 9; +/// +/// Outcome of the Kanturu Refinery Tower battle. +/// +public enum KanturuBattleResult +{ + /// Event ended in failure; shows the Failure_kantru.tga overlay. + Failure, - /// Phase 3 monster wave active — shows HUD. - public const byte Monster3 = 13; + /// Nightmare was defeated; shows the Success_kantru.tga overlay. + Victory, +} - /// Phase 3 bosses (both hands) — shows HUD. - public const byte Maya3 = 14; +/// +/// Visual type of a Maya wide-area attack broadcast. +/// +public enum KanturuMayaAttackType +{ + /// Stone-storm effect (MODEL_STORM3 + falling debris). + Storm, - /// - /// Maya phase 3 end cycle — triggers the full Maya explosion + player fall cinematic - /// (KANTURU_MAYA_DIRECTION_ENDCYCLE_MAYA3 = 16 on the client). - /// Sending this via 0xD1/0x03 activates Move2ndDirection(): - /// camera pans to Maya room → m_bMayaDie = true (explosion) → m_bDownHero = true (fall). - /// - public const byte EndCycleMaya3 = 16; + /// Stone-rain effect (MODEL_MAYASTONE projectiles). + Rain, } diff --git a/src/GameLogic/MiniGames/KanturuContext.cs b/src/GameLogic/MiniGames/KanturuContext.cs index 4fb84c3b7..69638494c 100644 --- a/src/GameLogic/MiniGames/KanturuContext.cs +++ b/src/GameLogic/MiniGames/KanturuContext.cs @@ -51,10 +51,6 @@ public sealed class KanturuContext : MiniGameContext private const byte WaveNightmarePrep = 7; private const byte WaveNightmare = 8; - // Client detail code for Maya "notify" cinematic (camera pan + Maya rise animation). - // This is KANTURU_MAYA_DIRECTION_NOTIFY=2 on the client side. - private const byte MayaNotifyDetail = 2; - // Skill numbers for Nightmare special attacks. // These map directly to client-side AT_SKILL_* enum values and drive the animation selection // on the Nightmare model (MODEL_DARK_SKULL_SOLDIER_5) in GM_Kanturu_3rd.cpp. @@ -80,10 +76,17 @@ private static readonly (byte StartX, byte StartY, byte EndX, byte EndY)[] Elphi private readonly IMapInitializer _mapInitializer; private readonly TimeSpan _towerOfRefinementDuration; + // volatile: _phase is written by the game-loop task (RunKanturuGameLoopAsync) and read + // by OnMonsterDied, which runs on the thread-pool via the monster's death event. + // volatile ensures the death handler always sees the latest phase assignment without + // a memory-barrier instruction on every increment in the hot path. private volatile KanturuPhase _phase = KanturuPhase.Open; private int _waveKillCount; private int _waveKillTarget; private TaskCompletionSource _phaseComplete = new(TaskCreationOptions.RunContinuationsAsynchronously); + + // volatile: written by the game loop on victory/defeat and read by GameEndedAsync, + // which can be called from a different thread when the game ends by timeout. private volatile bool _isVictory; // Interlocked flag: 0 = not yet opened, 1 = opened (or in progress). @@ -104,14 +107,16 @@ private static readonly (byte StartX, byte StartY, byte EndX, byte EndY)[] Elphi private volatile bool _mayaAttacksPaused; /// - /// Gets the current Kanturu main state code (the last state sent via 0xD1/0x03). + /// Gets the current Kanturu main state (the last state sent via 0xD1/0x03). /// The Gateway NPC plugin reads this to populate the 0xD1/0x00 StateInfo dialog /// while the event is in progress. /// - public byte CurrentKanturuState { get; private set; } = KanturuStateCode.MayaBattle; + public KanturuState CurrentKanturuState { get; private set; } = KanturuState.MayaBattle; /// - /// Gets the current Kanturu detail state code (the last detailState sent via 0xD1/0x03). + /// Gets the current Kanturu detail state byte (the last detailState sent via 0xD1/0x03). + /// Passed directly to as the + /// protocol-level byte, since detail-state enums differ per main state. /// public byte CurrentKanturuDetailState { get; private set; } @@ -162,160 +167,140 @@ protected override async ValueTask OnGameStartAsync(ICollection players) // Maya rises from the depths when the battle begins. await this._mapInitializer.InitializeNpcsOnWaveStartAsync(this.Map, this, WaveMayaAppear).ConfigureAwait(false); - await this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowMessageAsync("Maya rises from the depths of the Refinery Tower!", MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false); + await this.ShowGoldenMessageAsync(nameof(PlayerMessage.KanturuMayaRises)).ConfigureAwait(false); _ = Task.Run(() => this.RunKanturuGameLoopAsync(this.GameEndedToken), this.GameEndedToken); } +#pragma warning disable VSTHRD100 // Avoid async void methods /// - protected override void OnMonsterDied(object? sender, DeathInformation e) + protected override async void OnMonsterDied(object? sender, DeathInformation e) +#pragma warning restore VSTHRD100 // Avoid async void methods { - base.OnMonsterDied(sender, e); - - if (sender is not Monster monster) + try { - return; - } - - var num = (short)monster.Definition.Number; - var phase = this._phase; - bool complete; + base.OnMonsterDied(sender, e); - switch (phase) - { - case KanturuPhase.Phase1Monsters when num is BladeHunterNumber or DreadfearNumber: + if (sender is not Monster monster) { - var killed = Interlocked.Increment(ref this._waveKillCount); - complete = killed == this._waveKillTarget; - var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); - var pc = (byte)Math.Min(255, this.PlayerCount); - this.FireAndForgetMonsterCountAsync(remaining, pc); - break; + return; } - case KanturuPhase.Phase1Boss when num == MayaLeftHandNumber: - { - complete = true; - var pc = (byte)Math.Min(255, this.PlayerCount); - this.FireAndForgetMonsterCountAsync(0, pc); - break; - } + var num = (short)monster.Definition.Number; + var phase = this._phase; + bool complete; - case KanturuPhase.Phase2Monsters when num is BladeHunterNumber or DreadfearNumber: + switch (phase) { - var killed = Interlocked.Increment(ref this._waveKillCount); - complete = killed == this._waveKillTarget; - var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); - var pc = (byte)Math.Min(255, this.PlayerCount); - this.FireAndForgetMonsterCountAsync(remaining, pc); - break; - } + case KanturuPhase.Phase1Monsters when num is BladeHunterNumber or DreadfearNumber: + { + var killed = Interlocked.Increment(ref this._waveKillCount); + complete = killed == this._waveKillTarget; + await this.ShowMonsterUserCountAsync(Math.Max(0, this._waveKillTarget - killed), this.PlayerCount).ConfigureAwait(false); + break; + } - case KanturuPhase.Phase2Boss when num == MayaRightHandNumber: - { - complete = true; - var pc = (byte)Math.Min(255, this.PlayerCount); - this.FireAndForgetMonsterCountAsync(0, pc); - break; - } + case KanturuPhase.Phase1Boss when num == MayaLeftHandNumber: + { + complete = true; + await this.ShowMonsterUserCountAsync(0, this.PlayerCount).ConfigureAwait(false); + break; + } - case KanturuPhase.Phase3Monsters when num is DreadfearNumber or TwinTaleNumber: - { - var killed = Interlocked.Increment(ref this._waveKillCount); - complete = killed == this._waveKillTarget; - var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); - var pc = (byte)Math.Min(255, this.PlayerCount); - this.FireAndForgetMonsterCountAsync(remaining, pc); - break; - } + case KanturuPhase.Phase2Monsters when num is BladeHunterNumber or DreadfearNumber: + { + var killed = Interlocked.Increment(ref this._waveKillCount); + complete = killed == this._waveKillTarget; + await this.ShowMonsterUserCountAsync(Math.Max(0, this._waveKillTarget - killed), this.PlayerCount).ConfigureAwait(false); + break; + } - case KanturuPhase.Phase3Bosses when num is MayaLeftHandNumber or MayaRightHandNumber: - { - var killed = Interlocked.Increment(ref this._waveKillCount); - complete = killed == this._waveKillTarget; - var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); - var pc = (byte)Math.Min(255, this.PlayerCount); - this.FireAndForgetMonsterCountAsync(remaining, pc); - break; - } + case KanturuPhase.Phase2Boss when num == MayaRightHandNumber: + { + complete = true; + await this.ShowMonsterUserCountAsync(0, this.PlayerCount).ConfigureAwait(false); + break; + } - case KanturuPhase.NightmarePrep when num is GenociderNumber or DreadfearNumber or PersonaNumber: - { - var killed = Interlocked.Increment(ref this._waveKillCount); - complete = killed == this._waveKillTarget; - var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); - var pc = (byte)Math.Min(255, this.PlayerCount); - this.FireAndForgetMonsterCountAsync(remaining, pc); - break; - } + case KanturuPhase.Phase3Monsters when num is DreadfearNumber or TwinTaleNumber: + { + var killed = Interlocked.Increment(ref this._waveKillCount); + complete = killed == this._waveKillTarget; + await this.ShowMonsterUserCountAsync(Math.Max(0, this._waveKillTarget - killed), this.PlayerCount).ConfigureAwait(false); + break; + } - case KanturuPhase.NightmareActive when num is GenociderNumber or DreadfearNumber or PersonaNumber: - { - // A guardian died while Nightmare is alive — update the HUD counter but do NOT - // advance the phase. _waveKillTarget=46 (45 guardians + 1 Nightmare), so - // remaining = 46 − killed still accounts for Nightmare being alive. - var killed = Interlocked.Increment(ref this._waveKillCount); - var remaining = (byte)Math.Max(0, this._waveKillTarget - killed); - var pc = (byte)Math.Min(255, this.PlayerCount); - this.FireAndForgetMonsterCountAsync(remaining, pc); - complete = false; - break; - } + case KanturuPhase.Phase3Bosses when num is MayaLeftHandNumber or MayaRightHandNumber: + { + var killed = Interlocked.Increment(ref this._waveKillCount); + complete = killed == this._waveKillTarget; + await this.ShowMonsterUserCountAsync(Math.Max(0, this._waveKillTarget - killed), this.PlayerCount).ConfigureAwait(false); + break; + } - case KanturuPhase.NightmareActive when num == NightmareNumber: - complete = true; - // Fire barrier opening immediately from the death event. - // Do NOT wait for the game loop — it may be interrupted by - // GameEndedToken cancellation before reaching OpenElphisBarrierAsync. - _ = Task.Run(() => this.OpenElphisBarrierAsync().AsTask()); - break; + case KanturuPhase.NightmarePrep when num is GenociderNumber or DreadfearNumber or PersonaNumber: + { + var killed = Interlocked.Increment(ref this._waveKillCount); + complete = killed == this._waveKillTarget; + await this.ShowMonsterUserCountAsync(Math.Max(0, this._waveKillTarget - killed), this.PlayerCount).ConfigureAwait(false); + break; + } - default: - // Diagnostic: if Nightmare dies outside the expected phase, warn. - if (num == NightmareNumber) + case KanturuPhase.NightmareActive when num is GenociderNumber or DreadfearNumber or PersonaNumber: { - this.Logger.LogWarning( - "Kanturu: Nightmare died but _phase={Phase} (expected NightmareActive). Barrier NOT opened.", - phase); - _ = Task.Run(async () => - { - try - { - await this.ForEachPlayerAsync(p => - p.InvokeViewPlugInAsync(v => - v.ShowMessageAsync( - $"[Kanturu] Nightmare died out of phase! Phase={phase}", - MessageType.BlueNormal)).AsTask()).ConfigureAwait(false); - } - catch (Exception ex) - { - this.Logger.LogWarning(ex, "{context}: Error broadcasting out-of-phase diagnostic message.", this); - } - }); + // A guardian died while Nightmare is alive — update the HUD counter but do NOT + // advance the phase. _waveKillTarget=46 (45 guardians + 1 Nightmare), so + // remaining = 46 − killed still accounts for Nightmare being alive. + var killed = Interlocked.Increment(ref this._waveKillCount); + await this.ShowMonsterUserCountAsync(Math.Max(0, this._waveKillTarget - killed), this.PlayerCount).ConfigureAwait(false); + complete = false; + break; } - complete = false; - break; - } + case KanturuPhase.NightmareActive when num == NightmareNumber: + complete = true; + // Open the barrier immediately from the death event. + // Do NOT wait for the game loop — it may be interrupted by + // GameEndedToken cancellation before reaching OpenElphisBarrierAsync. + await this.OpenElphisBarrierAsync().ConfigureAwait(false); + break; + + default: + // Diagnostic: if Nightmare dies outside the expected phase, warn. + if (num == NightmareNumber) + { + this.Logger.LogWarning( + "Kanturu: Nightmare died but _phase={Phase} (expected NightmareActive). Barrier NOT opened.", + phase); + await this.ForEachPlayerAsync(p => + p.InvokeViewPlugInAsync(v => + v.ShowMessageAsync( + $"[Kanturu] Nightmare died out of phase! Phase={phase}", + MessageType.BlueNormal)).AsTask()).ConfigureAwait(false); + } + + complete = false; + break; + } - if (complete) + if (complete) + { + this._phaseComplete.TrySetResult(); + } + } + catch (Exception ex) { - this._phaseComplete.TrySetResult(); + this.Logger.LogError(ex, "Unexpected error when handling a monster death."); } } /// protected override async ValueTask GameEndedAsync(ICollection finishers) { - var message = this._isVictory - ? "Nightmare has been defeated! The Kanturu Refinery Tower is yours!" - : "The Kanturu Event has ended. Better luck next time!"; - - await this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowMessageAsync(message, MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false); + await this.ShowGoldenMessageAsync( + this._isVictory + ? nameof(PlayerMessage.KanturuVictory) + : nameof(PlayerMessage.KanturuDefeat)).ConfigureAwait(false); // On defeat show the Failure_kantru.tga overlay. // On victory the Success_kantru.tga and Tower state are sent from OpenElphisBarrierAsync. @@ -323,7 +308,7 @@ await this.ForEachPlayerAsync(player => { await this.ForEachPlayerAsync(player => player.InvokeViewPlugInAsync(p => - p.SendBattleResultAsync(0)).AsTask()).ConfigureAwait(false); + p.ShowBattleResultAsync(KanturuBattleResult.Failure)).AsTask()).ConfigureAwait(false); } await base.GameEndedAsync(finishers).ConfigureAwait(false); @@ -335,63 +320,59 @@ private async Task RunKanturuGameLoopAsync(CancellationToken ct) { // Maya "notify" cinematic — camera pans to Maya, Maya body rises from below. // Must be sent first so the client camera is in position before the first wave. - await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, MayaNotifyDetail).ConfigureAwait(false); + await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.Notify).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); // Start the Maya wide-area attack visual loop for the duration of all Maya phases. - // The loop broadcasts 0xD1/0x06 every 15 s, alternating storm (0) and stone-rain (1). + // The loop broadcasts 0xD1/0x06 every 15 s, alternating storm and stone-rain. using var mayaAttackCts = CancellationTokenSource.CreateLinkedTokenSource(ct); _ = Task.Run(() => this.RunMayaWideAreaAttacksAsync(mayaAttackCts.Token), mayaAttackCts.Token); // Phase 1: wave of monsters — 10-minute timer covers wave + boss. - await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.Monster1).ConfigureAwait(false); - await this.SendTimeLimitToAllAsync((int)TimeSpan.FromMinutes(10).TotalMilliseconds).ConfigureAwait(false); + await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.Monster1).ConfigureAwait(false); + await this.ShowTimeLimitToAllAsync(TimeSpan.FromMinutes(10)).ConfigureAwait(false); await this.AdvancePhaseAsync(KanturuPhase.Phase1Monsters, WavePhase1Monsters, 40, - "Phase 1: Defeat the monsters to unseal Maya's power!", ct).ConfigureAwait(false); + PlayerMessage.KanturuPhase1Start, ct).ConfigureAwait(false); // Phase 1: Maya Left Hand boss - await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.Maya1).ConfigureAwait(false); + await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.MayaLeft).ConfigureAwait(false); await this.AdvancePhaseAsync(KanturuPhase.Phase1Boss, WavePhase1Boss, 1, - "Maya (Left Hand) has appeared! Destroy her!", ct).ConfigureAwait(false); + PlayerMessage.KanturuMayaLeftHandAppeared, ct).ConfigureAwait(false); // Standby between phases - await this.SendStandbyMessageAsync("Phase 1 cleared! Phase 2 begins in 2 minutes...", ct).ConfigureAwait(false); + await this.SendStandbyMessageAsync(PlayerMessage.KanturuPhase1Cleared, ct).ConfigureAwait(false); // Phase 2: wave of monsters — fresh 10-minute timer. - await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.Monster2).ConfigureAwait(false); - await this.SendTimeLimitToAllAsync((int)TimeSpan.FromMinutes(10).TotalMilliseconds).ConfigureAwait(false); + await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.Monster2).ConfigureAwait(false); + await this.ShowTimeLimitToAllAsync(TimeSpan.FromMinutes(10)).ConfigureAwait(false); await this.AdvancePhaseAsync(KanturuPhase.Phase2Monsters, WavePhase2Monsters, 40, - "Phase 2: More of Maya's minions have arrived!", ct).ConfigureAwait(false); + PlayerMessage.KanturuPhase2Start, ct).ConfigureAwait(false); // Phase 2: Maya Right Hand boss - await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.Maya2).ConfigureAwait(false); + await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.MayaRight).ConfigureAwait(false); await this.AdvancePhaseAsync(KanturuPhase.Phase2Boss, WavePhase2Boss, 1, - "Maya (Right Hand) has appeared! Destroy her!", ct).ConfigureAwait(false); + PlayerMessage.KanturuMayaRightHandAppeared, ct).ConfigureAwait(false); // Standby between phases - await this.SendStandbyMessageAsync("Phase 2 cleared! Phase 3 begins in 2 minutes...", ct).ConfigureAwait(false); + await this.SendStandbyMessageAsync(PlayerMessage.KanturuPhase2Cleared, ct).ConfigureAwait(false); // Phase 3: wave of monsters — fresh 10-minute timer. - await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.Monster3).ConfigureAwait(false); - await this.SendTimeLimitToAllAsync((int)TimeSpan.FromMinutes(10).TotalMilliseconds).ConfigureAwait(false); + await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.Monster3).ConfigureAwait(false); + await this.ShowTimeLimitToAllAsync(TimeSpan.FromMinutes(10)).ConfigureAwait(false); await this.AdvancePhaseAsync(KanturuPhase.Phase3Monsters, WavePhase3Monsters, 20, - "Phase 3: The final wave approaches!", ct).ConfigureAwait(false); + PlayerMessage.KanturuPhase3Start, ct).ConfigureAwait(false); // Phase 3: Both Maya bosses simultaneously - await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.Maya3).ConfigureAwait(false); + await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.BothHands).ConfigureAwait(false); await this.AdvancePhaseAsync(KanturuPhase.Phase3Bosses, WavePhase3Bosses, 2, - "Both hands of Maya have appeared! Defeat them both!", ct).ConfigureAwait(false); + PlayerMessage.KanturuBothMayaHandsAppeared, ct).ConfigureAwait(false); // Hide HUD during the loot window — same reason as inter-phase standby. - await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.None).ConfigureAwait(false); + await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.None).ConfigureAwait(false); // 10-second loot window: players pick up drops from both Maya hands // before the Nightmare transition cinematic begins. - await this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowMessageAsync( - "Maya's hands have fallen! Collect your loot — the Nightmare awakens in 10 seconds...", - MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false); + await this.ShowGoldenMessageAsync(nameof(PlayerMessage.KanturuMayaHandsFallen)).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(10), ct).ConfigureAwait(false); // Stop the Maya wide-area attack visuals before the Nightmare transition cinematic. @@ -404,21 +385,19 @@ await this.ForEachPlayerAsync(player => // 2. NightmareBattle/Idle is sent AFTER teleport so the HUD change applies in // the Nightmare zone, not the Maya battlefield. await this.TeleportToNightmareRoomAsync(ct).ConfigureAwait(false); - await this.SendKanturuStateAsync(KanturuStateCode.NightmareBattle, KanturuNightmareDetail.Idle).ConfigureAwait(false); + await this.ShowNightmareStateToAllAsync(KanturuNightmareDetailState.Idle).ConfigureAwait(false); // Nightmare Prep: spawn guardians immediately, then spawn Nightmare after 3 seconds. // No kill requirement — guardians fight alongside Nightmare. - await this.SendTimeLimitToAllAsync((int)TimeSpan.FromMinutes(30).TotalMilliseconds).ConfigureAwait(false); + await this.ShowTimeLimitToAllAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false); Interlocked.Exchange(ref this._waveKillCount, 0); this._waveKillTarget = 45; this._phaseComplete = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); this._phase = KanturuPhase.NightmarePrep; await this._mapInitializer.InitializeNpcsOnWaveStartAsync(this.Map, this, WaveNightmarePrep).ConfigureAwait(false); - await this.SendMonsterUserCountAsync(45, (byte)Math.Min(255, this.PlayerCount)).ConfigureAwait(false); - await this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowMessageAsync("Nightmare's guardians have appeared! NIGHTMARE awakens in 3 seconds!", MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false); + await this.ShowMonsterUserCountAsync(45, this.PlayerCount).ConfigureAwait(false); + await this.ShowGoldenMessageAsync(nameof(PlayerMessage.KanturuNightmareGuardiansAppeared)).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); @@ -465,8 +444,8 @@ private async Task RunNightmarePhaseAsync(CancellationToken ct) this._phase = KanturuPhase.NightmareActive; // Nightmare intro cinematic — camera moves to Nightmare zone and summons Nightmare. - // This uses detail=NightmareIntro(2) = KANTURU_NIGHTMARE_DIRECTION_NIGHTMARE on the client. - await this.SendKanturuStateAsync(KanturuStateCode.NightmareBattle, KanturuNightmareDetail.NightmareIntro).ConfigureAwait(false); + // This uses detail=Intro(2) = KANTURU_NIGHTMARE_DIRECTION_NIGHTMARE on the client. + await this.ShowNightmareStateToAllAsync(KanturuNightmareDetailState.Intro).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); // Subscribe to ObjectAdded to capture the Nightmare reference as soon as it spawns. @@ -501,16 +480,13 @@ await this._mapInitializer.InitializeNpcsOnWaveStartAsync(this.Map, this, WaveNi } // Switch to active battle state — shows Nightmare HUD on client (INTERFACE_KANTURU_INFO). - await this.SendKanturuStateAsync(KanturuStateCode.NightmareBattle, KanturuNightmareDetail.Battle).ConfigureAwait(false); + await this.ShowNightmareStateToAllAsync(KanturuNightmareDetailState.Battle).ConfigureAwait(false); // Show total remaining: alive guardians (45 − killed during prep) + 1 Nightmare. - var totalRemaining = (byte)Math.Min(255, Math.Max(1, this._waveKillTarget - this._waveKillCount)); - await this.SendMonsterUserCountAsync(totalRemaining, (byte)Math.Min(255, this.PlayerCount)).ConfigureAwait(false); + var totalRemaining = Math.Max(1, this._waveKillTarget - this._waveKillCount); + await this.ShowMonsterUserCountAsync(totalRemaining, this.PlayerCount).ConfigureAwait(false); - await this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowMessageAsync("NIGHTMARE has appeared! Defeat him to claim victory!", MessageType.GoldenCenter)) - .AsTask()).ConfigureAwait(false); + await this.ShowGoldenMessageAsync(nameof(PlayerMessage.KanturuNightmareAppeared)).ConfigureAwait(false); // Start HP monitor and special-attack loop — both linked to the same CTS so they // stop together the moment Nightmare dies or the game is cancelled. @@ -595,12 +571,12 @@ private async Task ExecuteNightmareTeleportAsync(Monster nightmare, int phase, C _ => NightmarePhase2Pos, }; - var msg = phase switch + var msgKey = phase switch { - 2 => "Nightmare has teleported! He recovers his full strength!", - 3 => "Nightmare teleports again! He is more powerful than ever!", - 4 => "Nightmare is at his last stand! Finish him!", - _ => string.Empty, + 2 => nameof(PlayerMessage.KanturuNightmareTeleport2), + 3 => nameof(PlayerMessage.KanturuNightmareTeleport3), + 4 => nameof(PlayerMessage.KanturuNightmareTeleport4), + _ => null, }; // Restore HP FIRST — any damage during the brief animation window will not kill Nightmare. @@ -618,12 +594,9 @@ private async Task ExecuteNightmareTeleportAsync(Monster nightmare, int phase, C // Restore HP a second time as a safety net — covers any hits landing in the 500 ms window. nightmare.Health = (int)nightmare.Attributes[Stats.MaximumHealth]; - if (msg.Length > 0) + if (msgKey is not null) { - await this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowMessageAsync(msg, MessageType.GoldenCenter)) - .AsTask()).ConfigureAwait(false); + await this.ShowGoldenMessageAsync(msgKey).ConfigureAwait(false); } } finally @@ -656,17 +629,13 @@ private async ValueTask OpenElphisBarrierAsync() { this.Logger.LogInformation("Kanturu: opening Elphis barrier."); - // Diagnostic confirmation message — visible in the game client. - await this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowMessageAsync("Kanturu: Barrier opening! The tower awaits...", MessageType.GoldenCenter)).AsTask()) - .ConfigureAwait(false); + await this.ShowGoldenMessageAsync(nameof(PlayerMessage.KanturuBarrierOpening)).ConfigureAwait(false); // Monster count = 0 (Nightmare defeated). - await this.SendMonsterUserCountAsync(0, (byte)Math.Min(255, this.PlayerCount)).ConfigureAwait(false); + await this.ShowMonsterUserCountAsync(0, this.PlayerCount).ConfigureAwait(false); // Victory camera-out cinematic (KANTURU_NIGHTMARE_DIRECTION_END = 4). - await this.SendKanturuStateAsync(KanturuStateCode.NightmareBattle, KanturuNightmareDetail.End).ConfigureAwait(false); + await this.ShowNightmareStateToAllAsync(KanturuNightmareDetailState.End).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); // 1. Send SUCCESS result overlay (Success_kantru.tga). @@ -675,12 +644,12 @@ await this.ForEachPlayerAsync(player => // the overlay renders while the client is still in the Nightmare state. await this.ForEachPlayerAsync(player => player.InvokeViewPlugInAsync(p => - p.SendBattleResultAsync(1)) + p.ShowBattleResultAsync(KanturuBattleResult.Victory)) .AsTask()).ConfigureAwait(false); // 2. Send Tower state → client reloads EncTerrain(n)01.att (barrier-open terrain), // switches to Tower music, and plays the success sound. - await this.SendKanturuStateAsync(KanturuStateCode.Tower, KanturuTowerDetail.Revitalization).ConfigureAwait(false); + await this.ShowTowerStateToAllAsync(KanturuTowerDetailState.Revitalization).ConfigureAwait(false); // 3. Update server-side walkmap so the AI pathfinder and movement checks // treat the formerly-blocked cells as passable. @@ -718,12 +687,7 @@ await this.ForEachPlayerAsync(player => /// private async Task RunTowerOfRefinementAsync(CancellationToken ct) { - await this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowMessageAsync( - "The Kanturu Refinery Tower is conquered! The tower is now open.", - MessageType.GoldenCenter)) - .AsTask()).ConfigureAwait(false); + await this.ShowGoldenMessageAsync(nameof(PlayerMessage.KanturuTowerConquered)).ConfigureAwait(false); var duration = this._towerOfRefinementDuration; var warningOffset = TimeSpan.FromMinutes(5); @@ -735,12 +699,7 @@ await this.ForEachPlayerAsync(player => await Task.Delay(duration - warningOffset, CancellationToken.None).ConfigureAwait(false); ct.ThrowIfCancellationRequested(); - await this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowMessageAsync( - "The Kanturu Tower closes in 5 minutes!", - MessageType.GoldenCenter)) - .AsTask()).ConfigureAwait(false); + await this.ShowGoldenMessageAsync(nameof(PlayerMessage.KanturuTowerClosingWarning)).ConfigureAwait(false); await Task.Delay(warningOffset, CancellationToken.None).ConfigureAwait(false); ct.ThrowIfCancellationRequested(); @@ -752,14 +711,11 @@ await this.ForEachPlayerAsync(player => } // Tower closing notification - await this.SendKanturuStateAsync(KanturuStateCode.Tower, KanturuTowerDetail.Notify).ConfigureAwait(false); + await this.ShowTowerStateToAllAsync(KanturuTowerDetailState.Notify).ConfigureAwait(false); - await this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowMessageAsync("The Kanturu Tower has closed.", MessageType.GoldenCenter)) - .AsTask()).ConfigureAwait(false); + await this.ShowGoldenMessageAsync(nameof(PlayerMessage.KanturuTowerClosed)).ConfigureAwait(false); - await this.SendKanturuStateAsync(KanturuStateCode.Tower, KanturuTowerDetail.Close).ConfigureAwait(false); + await this.ShowTowerStateToAllAsync(KanturuTowerDetailState.Close).ConfigureAwait(false); } private async Task TeleportToNightmareRoomAsync(CancellationToken ct) @@ -774,7 +730,7 @@ private async Task TeleportToNightmareRoomAsync(CancellationToken ct) // Stage 2 — m_bDownHero=true: the hero "falls through the floor" into the Nightmare zone. // NOTE: 0xD1/0x04 result=1 (Success_kantru.tga) must NOT be sent here — that overlay // is only correct after Nightmare is defeated and is already sent in OpenElphisBarrierAsync. - await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.EndCycleMaya3).ConfigureAwait(false); + await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.EndCycleMaya3).ConfigureAwait(false); // Step 2: Wait for the client cinematic to complete before teleporting. // The three stages take roughly: camera pan ~3 s + explosion ~4 s + fall ~3 s ≈ 10 s. @@ -810,7 +766,7 @@ await this.ForEachPlayerAsync(player => /// private async Task RunMayaWideAreaAttacksAsync(CancellationToken ct) { - byte attackType = 0; + var attackType = KanturuMayaAttackType.Storm; while (!ct.IsCancellationRequested) { try @@ -829,7 +785,7 @@ private async Task RunMayaWideAreaAttacksAsync(CancellationToken ct) { await this.ForEachPlayerAsync(player => player.InvokeViewPlugInAsync(p => - p.SendMayaWideAreaAttackAsync(attackType)).AsTask()).ConfigureAwait(false); + p.ShowMayaWideAreaAttackAsync(attackType)).AsTask()).ConfigureAwait(false); } catch (Exception ex) { @@ -837,7 +793,10 @@ await this.ForEachPlayerAsync(player => } } - attackType = (byte)(1 - attackType); // alternate 0 → 1 → 0 → … + // Alternate Storm → Rain → Storm → … + attackType = attackType == KanturuMayaAttackType.Storm + ? KanturuMayaAttackType.Rain + : KanturuMayaAttackType.Storm; } } @@ -885,7 +844,7 @@ await this.ForEachPlayerAsync(player => } } - private async Task SendStandbyMessageAsync(string message, CancellationToken ct) + private async Task SendStandbyMessageAsync(string messageKey, CancellationToken ct) { // Pause Maya wide-area attacks for the full duration of the standby so the // body stays visually idle (no storm/stone-rain effects between phases). @@ -897,11 +856,9 @@ private async Task SendStandbyMessageAsync(string message, CancellationToken ct) // the monster count / user count / timer panel until the next phase begins. // Maya body (#364) stays at its spawn position — it is ~27 tiles from the fight // room and well outside its ViewRange=9, so it naturally idles without attacking. - await this.SendKanturuStateAsync(KanturuStateCode.MayaBattle, KanturuMayaDetail.None).ConfigureAwait(false); + await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.None).ConfigureAwait(false); - await this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowMessageAsync(message, MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false); + await this.ShowGoldenMessageAsync(messageKey).ConfigureAwait(false); await Task.Delay(TimeSpan.FromMinutes(2), ct).ConfigureAwait(false); } @@ -911,7 +868,7 @@ await this.ForEachPlayerAsync(player => } } - private async Task AdvancePhaseAsync(KanturuPhase phase, byte waveNumber, int killTarget, string message, CancellationToken ct) + private async Task AdvancePhaseAsync(KanturuPhase phase, byte waveNumber, int killTarget, string messageKey, CancellationToken ct) { Interlocked.Exchange(ref this._waveKillCount, 0); this._waveKillTarget = killTarget; @@ -921,65 +878,105 @@ private async Task AdvancePhaseAsync(KanturuPhase phase, byte waveNumber, int ki await this._mapInitializer.InitializeNpcsOnWaveStartAsync(this.Map, this, waveNumber).ConfigureAwait(false); // Broadcast the initial monster count so the HUD shows the correct number from the start. - await this.SendMonsterUserCountAsync((byte)Math.Min(255, killTarget), (byte)Math.Min(255, this.PlayerCount)).ConfigureAwait(false); + await this.ShowMonsterUserCountAsync(killTarget, this.PlayerCount).ConfigureAwait(false); - await this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowMessageAsync(message, MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false); + await this.ShowGoldenMessageAsync(messageKey).ConfigureAwait(false); await this._phaseComplete.Task.WaitAsync(ct).ConfigureAwait(false); } /// - /// Broadcasts packet 0xD1/0x03 (Kanturu state change) to all players currently on the map. - /// Also updates and - /// so the Gateway NPC plugin can report the current event phase in the 0xD1/0x00 dialog. + /// Broadcasts the Maya battle detail state (0xD1/0x03) to all players. + /// Updates and + /// so the Gateway NPC can report the current phase in the 0xD1/0x00 dialog. /// - private ValueTask SendKanturuStateAsync(byte state, byte detailState) + private ValueTask ShowMayaBattleStateToAllAsync(KanturuMayaDetailState detailState) { - this.CurrentKanturuState = state; - this.CurrentKanturuDetailState = detailState; + this.CurrentKanturuState = KanturuState.MayaBattle; + this.CurrentKanturuDetailState = ConvertMayaDetail(detailState); return this.ForEachPlayerAsync(player => player.InvokeViewPlugInAsync(p => - p.SendStateChangeAsync(state, detailState)).AsTask()); + p.ShowMayaBattleStateAsync(detailState)).AsTask()); } /// - /// Fires a monster/user-count broadcast without blocking the caller. - /// Exceptions are caught and logged so that an unreachable player cannot - /// propagate an unhandled exception through the fire-and-forget task. + /// Broadcasts the Nightmare battle detail state (0xD1/0x03) to all players. + /// Updates and . /// - private void FireAndForgetMonsterCountAsync(byte remaining, byte pc) => - _ = Task.Run(async () => - { - try - { - await this.SendMonsterUserCountAsync(remaining, pc).ConfigureAwait(false); - } - catch (Exception ex) - { - this.Logger.LogWarning(ex, "{context}: Error sending monster/user count update.", this); - } - }); + private ValueTask ShowNightmareStateToAllAsync(KanturuNightmareDetailState detailState) + { + this.CurrentKanturuState = KanturuState.NightmareBattle; + this.CurrentKanturuDetailState = ConvertNightmareDetail(detailState); + return this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowNightmareStateAsync(detailState)).AsTask()); + } + + /// + /// Broadcasts the Tower detail state (0xD1/0x03) to all players. + /// Updates and . + /// + private ValueTask ShowTowerStateToAllAsync(KanturuTowerDetailState detailState) + { + this.CurrentKanturuState = KanturuState.Tower; + this.CurrentKanturuDetailState = ConvertTowerDetail(detailState); + return this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowTowerStateAsync(detailState)).AsTask()); + } /// /// Broadcasts packet 0xD1/0x07 (Kanturu monster/user count) to all players currently on the map. + /// The view plugin clamps the values to byte range before sending. + /// This method does not throw — callers in rely on this guarantee. /// - private ValueTask SendMonsterUserCountAsync(byte monsterCount, byte userCount) + private ValueTask ShowMonsterUserCountAsync(int monsterCount, int userCount) { return this.ForEachPlayerAsync(player => player.InvokeViewPlugInAsync(p => - p.SendMonsterUserCountAsync(monsterCount, userCount)).AsTask()); + p.ShowMonsterUserCountAsync(monsterCount, userCount)).AsTask()); } /// /// Broadcasts packet 0xD1/0x05 (Kanturu time limit) to all players currently on the map. /// - /// Remaining time in milliseconds. - private ValueTask SendTimeLimitToAllAsync(int milliseconds) + private ValueTask ShowTimeLimitToAllAsync(TimeSpan timeLimit) { return this.ForEachPlayerAsync(player => player.InvokeViewPlugInAsync(p => - p.SendTimeLimitAsync(milliseconds)).AsTask()); + p.ShowTimeLimitAsync(timeLimit)).AsTask()); } + + private static byte ConvertMayaDetail(KanturuMayaDetailState detail) => detail switch + { + KanturuMayaDetailState.None => 0, + KanturuMayaDetailState.Notify => 2, + KanturuMayaDetailState.Monster1 => 3, + KanturuMayaDetailState.MayaLeft => 4, + KanturuMayaDetailState.Monster2 => 8, + KanturuMayaDetailState.MayaRight => 9, + KanturuMayaDetailState.Monster3 => 13, + KanturuMayaDetailState.BothHands => 14, + KanturuMayaDetailState.EndCycleMaya3 => 16, + _ => throw new ArgumentOutOfRangeException(nameof(detail), detail, null), + }; + + private static byte ConvertNightmareDetail(KanturuNightmareDetailState detail) => detail switch + { + KanturuNightmareDetailState.None => 0, + KanturuNightmareDetailState.Idle => 1, + KanturuNightmareDetailState.Intro => 2, + KanturuNightmareDetailState.Battle => 3, + KanturuNightmareDetailState.End => 4, + _ => throw new ArgumentOutOfRangeException(nameof(detail), detail, null), + }; + + private static byte ConvertTowerDetail(KanturuTowerDetailState detail) => detail switch + { + KanturuTowerDetailState.None => 0, + KanturuTowerDetailState.Revitalization => 1, + KanturuTowerDetailState.Notify => 2, + KanturuTowerDetailState.Close => 3, + _ => throw new ArgumentOutOfRangeException(nameof(detail), detail, null), + }; } diff --git a/src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs b/src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs index c7e2c2349..dd1f2f826 100644 --- a/src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs +++ b/src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs @@ -72,11 +72,11 @@ public static async ValueTask SendKanturuStateInfoAsync(Player player) .GetDurationUntilNextStartAsync(player.GameContext, miniGameDefinition) .ConfigureAwait(false); - byte state; + KanturuState state; byte detailState; - byte enter; - byte userCount; - int remainTimeSec; + bool canEnter; + int userCount; + TimeSpan remainingTime; if (ctx is KanturuContext kanturuCtx) { @@ -90,35 +90,33 @@ public static async ValueTask SendKanturuStateInfoAsync(Player player) // NightmareBattle and Tower phase are both sealed — players who are already // inside the event access the Refinery Tower by walking through the opened // Elphis barrier; the Gateway does not re-admit anyone for those phases. - enter = state == KanturuStateCode.MayaBattle ? (byte)1 : (byte)0; + canEnter = state == KanturuState.MayaBattle; - userCount = (byte)Math.Min(255, ctx.PlayerCount); - remainTimeSec = 0; + userCount = ctx.PlayerCount; + remainingTime = TimeSpan.Zero; } else if (timeUntilOpening == TimeSpan.Zero) { // Entry window is open but the game context has not been created yet // (race: the scheduler opened the window but OnGameStartAsync hasn't run). - state = KanturuStateCode.MayaBattle; + state = KanturuState.MayaBattle; detailState = DetailStandbyOpen; - enter = 1; + canEnter = true; userCount = 0; - remainTimeSec = 0; + remainingTime = TimeSpan.Zero; } else { // No active event — show countdown to the next scheduled start. - state = KanturuStateCode.Standby; + state = KanturuState.Standby; detailState = 1; // STANBY_START — client shows "Opens in X minutes" - enter = 0; + canEnter = false; userCount = 0; - remainTimeSec = timeUntilOpening.HasValue - ? (int)Math.Ceiling(timeUntilOpening.Value.TotalSeconds) - : 0; + remainingTime = timeUntilOpening ?? TimeSpan.Zero; } await player.InvokeViewPlugInAsync(p => - p.SendStateInfoAsync(state, detailState, enter, userCount, remainTimeSec)) + p.ShowStateInfoAsync(state, detailState, canEnter, userCount, remainingTime)) .ConfigureAwait(false); } } diff --git a/src/GameLogic/Properties/PlayerMessage.Designer.cs b/src/GameLogic/Properties/PlayerMessage.Designer.cs index 7a70883a9..49ccaac4d 100644 --- a/src/GameLogic/Properties/PlayerMessage.Designer.cs +++ b/src/GameLogic/Properties/PlayerMessage.Designer.cs @@ -177,6 +177,195 @@ public static string BloodCastleCrystalStatusDestroyed { } } + /// + /// Looks up a localized string similar to The barrier opens — the tower awaits!. + /// + public static string KanturuBarrierOpening { + get { + return ResourceManager.GetString("KanturuBarrierOpening", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Both hands of Maya have appeared! Defeat them both!. + /// + public static string KanturuBothMayaHandsAppeared { + get { + return ResourceManager.GetString("KanturuBothMayaHandsAppeared", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Maya (Left Hand) has appeared! Destroy her!. + /// + public static string KanturuMayaLeftHandAppeared { + get { + return ResourceManager.GetString("KanturuMayaLeftHandAppeared", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Maya's hands have fallen! Collect your loot — the Nightmare awakens in 10 seconds.... + /// + public static string KanturuMayaHandsFallen { + get { + return ResourceManager.GetString("KanturuMayaHandsFallen", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Maya rises from the depths of the Refinery Tower!. + /// + public static string KanturuMayaRises { + get { + return ResourceManager.GetString("KanturuMayaRises", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Maya (Right Hand) has appeared! Destroy her!. + /// + public static string KanturuMayaRightHandAppeared { + get { + return ResourceManager.GetString("KanturuMayaRightHandAppeared", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to NIGHTMARE has appeared! Defeat him to claim victory!. + /// + public static string KanturuNightmareAppeared { + get { + return ResourceManager.GetString("KanturuNightmareAppeared", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Nightmare's guardians have appeared! NIGHTMARE awakens in 3 seconds!. + /// + public static string KanturuNightmareGuardiansAppeared { + get { + return ResourceManager.GetString("KanturuNightmareGuardiansAppeared", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Nightmare has teleported! He recovers his full strength!. + /// + public static string KanturuNightmareTeleport2 { + get { + return ResourceManager.GetString("KanturuNightmareTeleport2", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Nightmare teleports again! He is more powerful than ever!. + /// + public static string KanturuNightmareTeleport3 { + get { + return ResourceManager.GetString("KanturuNightmareTeleport3", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Nightmare is at his last stand! Finish him!. + /// + public static string KanturuNightmareTeleport4 { + get { + return ResourceManager.GetString("KanturuNightmareTeleport4", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Phase 1 cleared! Phase 2 begins in 2 minutes.... + /// + public static string KanturuPhase1Cleared { + get { + return ResourceManager.GetString("KanturuPhase1Cleared", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Phase 1: Defeat the monsters to unseal Maya's power!. + /// + public static string KanturuPhase1Start { + get { + return ResourceManager.GetString("KanturuPhase1Start", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Phase 2 cleared! Phase 3 begins in 2 minutes.... + /// + public static string KanturuPhase2Cleared { + get { + return ResourceManager.GetString("KanturuPhase2Cleared", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Phase 2: More of Maya's minions have arrived!. + /// + public static string KanturuPhase2Start { + get { + return ResourceManager.GetString("KanturuPhase2Start", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Phase 3: The final wave approaches!. + /// + public static string KanturuPhase3Start { + get { + return ResourceManager.GetString("KanturuPhase3Start", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Kanturu Tower has closed.. + /// + public static string KanturuTowerClosed { + get { + return ResourceManager.GetString("KanturuTowerClosed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Kanturu Event has ended. Better luck next time!. + /// + public static string KanturuDefeat { + get { + return ResourceManager.GetString("KanturuDefeat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Kanturu Tower closes in 5 minutes!. + /// + public static string KanturuTowerClosingWarning { + get { + return ResourceManager.GetString("KanturuTowerClosingWarning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Kanturu Refinery Tower is conquered! The tower is now open.. + /// + public static string KanturuTowerConquered { + get { + return ResourceManager.GetString("KanturuTowerConquered", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Nightmare has been defeated! The Kanturu Refinery Tower is yours!. + /// + public static string KanturuVictory { + get { + return ResourceManager.GetString("KanturuVictory", resourceCulture); + } + } + /// /// Looks up a localized string similar to Can't delete a guild member. Remove the character from guild first.. /// diff --git a/src/GameLogic/Properties/PlayerMessage.resx b/src/GameLogic/Properties/PlayerMessage.resx index a85d7ce09..bc1161891 100644 --- a/src/GameLogic/Properties/PlayerMessage.resx +++ b/src/GameLogic/Properties/PlayerMessage.resx @@ -405,6 +405,69 @@ [{0}] Account of {1} has been unbanned. + + Maya rises from the depths of the Refinery Tower! + + + Phase 1: Defeat the monsters to unseal Maya's power! + + + Maya (Left Hand) has appeared! Destroy her! + + + Phase 1 cleared! Phase 2 begins in 2 minutes... + + + Phase 2: More of Maya's minions have arrived! + + + Maya (Right Hand) has appeared! Destroy her! + + + Phase 2 cleared! Phase 3 begins in 2 minutes... + + + Phase 3: The final wave approaches! + + + Both hands of Maya have appeared! Defeat them both! + + + Maya's hands have fallen! Collect your loot — the Nightmare awakens in 10 seconds... + + + Nightmare's guardians have appeared! NIGHTMARE awakens in 3 seconds! + + + NIGHTMARE has appeared! Defeat him to claim victory! + + + Nightmare has teleported! He recovers his full strength! + + + Nightmare teleports again! He is more powerful than ever! + + + Nightmare is at his last stand! Finish him! + + + Nightmare has been defeated! The Kanturu Refinery Tower is yours! + + + The barrier opens — the tower awaits! + + + The Kanturu Refinery Tower is conquered! The tower is now open. + + + The Kanturu Tower closes in 5 minutes! + + + The Kanturu Tower has closed. + + + The Kanturu Event has ended. Better luck next time! + {0} has acquired the {1} diff --git a/src/GameServer/RemoteView/MiniGames/KanturuEventViewPlugIn.cs b/src/GameServer/RemoteView/MiniGames/KanturuEventViewPlugIn.cs index 8769505e3..565adc835 100644 --- a/src/GameServer/RemoteView/MiniGames/KanturuEventViewPlugIn.cs +++ b/src/GameServer/RemoteView/MiniGames/KanturuEventViewPlugIn.cs @@ -4,53 +4,20 @@ namespace MUnique.OpenMU.GameServer.RemoteView.MiniGames; -using System.Buffers.Binary; -using System.Runtime.InteropServices; using MUnique.OpenMU.GameLogic.MiniGames; -using MUnique.OpenMU.Network; +using MUnique.OpenMU.Network.Packets.ServerToClient; using MUnique.OpenMU.PlugIns; -using MUnique.OpenMU.GameServer.RemoteView; /// -/// Sends Kanturu-specific state/result/HUD packets (0xD1 group) to the client. +/// Sends Kanturu-specific state, result, and HUD packets (0xD1 group) to the client. +/// Packets are defined in ServerToClientPackets.xml and this plugin uses the +/// auto-generated extension methods from ConnectionExtensions. /// -/// -/// Packet map (all C1 headers, subcode-bearing): -/// -/// 0xD1 0x00State info — opens the gateway NPC dialog (INTERFACE_KANTURU2ND_ENTERNPC). -/// 0xD1 0x01Enter result — stops NPC animation, shows error popup on failure. -/// 0xD1 0x03State change — controls HUD, music, and terrain reload. -/// 0xD1 0x04Battle result — displays Success_kantru.tga or Failure_kantru.tga. -/// 0xD1 0x05Time limit — countdown timer for the in-map HUD (milliseconds). -/// 0xD1 0x06Maya wide-area attack — triggers the visual MayaAction sequence on the Maya body object. -/// 0xD1 0x07Monster/user count — numbers displayed in the HUD. -/// -/// [PlugIn] -[Display(Name = "Kanturu Event View Plugin", Description = "Sends Kanturu event state/result/HUD packets (0xD1 group) to the client.")] +[Display(Name = "Kanturu Event View Plugin", Description = "Sends Kanturu event packets (0xD1 group) to the client.")] [Guid("A3F1C8D2-5E94-4B7A-8C31-D6F2E0A49B15")] public sealed class KanturuEventViewPlugIn : IKanturuEventViewPlugIn { - // Packet byte-lengths (C1 + Length + Code + SubCode + data): - private const int StateInfoLength = 12; // + btState + btDetailState + btEnter + btUserCount + int iRemainTime (4 bytes LE) - private const int EnterResultLength = 5; // + btResult - private const int StateChangeLength = 6; // + btState + btDetailState - private const int BattleResultLength = 5; // + btResult - private const int TimeLimitLength = 8; // + int btTimeLimit (4 bytes LE) - private const int MonsterUserCountLength = 6; // + bMonsterCount + btUserCount - private const int WideAreaAttackLength = 7; // + btObjClassH + btObjClassL + btType - - private const byte C1 = 0xC1; - private const byte GroupCode = 0xD1; - - private const byte SubCodeStateInfo = 0x00; - private const byte SubCodeEnterResult = 0x01; - private const byte SubCodeStateChange = 0x03; - private const byte SubCodeResult = 0x04; - private const byte SubCodeTimeLimit = 0x05; - private const byte SubCodeWideAreaAttack = 0x06; - private const byte SubCodeMonsterUserCount = 0x07; - private readonly RemotePlayer _player; /// @@ -60,169 +27,181 @@ public sealed class KanturuEventViewPlugIn : IKanturuEventViewPlugIn public KanturuEventViewPlugIn(RemotePlayer player) => this._player = player; /// - public async ValueTask SendStateInfoAsync(byte state, byte detailState, byte enter, byte userCount, int remainTimeSec) + public async ValueTask ShowStateInfoAsync(KanturuState state, byte detailState, bool canEnter, int userCount, TimeSpan remainingTime) { if (this._player.Connection is not { } connection) { return; } - int Write() - { - var span = connection.Output.GetSpan(StateInfoLength)[..StateInfoLength]; - span[0] = C1; - span[1] = StateInfoLength; - span[2] = GroupCode; - span[3] = SubCodeStateInfo; - span[4] = state; - span[5] = detailState; - span[6] = enter; - span[7] = userCount; - // The client struct field iRemainTime is a plain C int — write as little-endian. - BinaryPrimitives.WriteInt32LittleEndian(span[8..], remainTimeSec); - return StateInfoLength; - } - - await connection.SendAsync(Write).ConfigureAwait(false); + await connection.SendKanturuStateInfoAsync( + ConvertState(state), + detailState, + canEnter, + (byte)Math.Min(userCount, byte.MaxValue), + (int)remainingTime.TotalSeconds).ConfigureAwait(false); } /// - public async ValueTask SendEnterResultAsync(byte result) + public async ValueTask ShowEnterResultAsync(KanturuEnterResult result) { if (this._player.Connection is not { } connection) { return; } - int Write() - { - var span = connection.Output.GetSpan(EnterResultLength)[..EnterResultLength]; - span[0] = C1; - span[1] = EnterResultLength; - span[2] = GroupCode; - span[3] = SubCodeEnterResult; - span[4] = result; - return EnterResultLength; - } - - await connection.SendAsync(Write).ConfigureAwait(false); + await connection.SendKanturuEnterResultAsync(ConvertEnterResult(result)).ConfigureAwait(false); } /// - public async ValueTask SendStateChangeAsync(byte state, byte detailState) + public async ValueTask ShowMayaBattleStateAsync(KanturuMayaDetailState detailState) { if (this._player.Connection is not { } connection) { return; } - int Write() - { - var span = connection.Output.GetSpan(StateChangeLength)[..StateChangeLength]; - span[0] = C1; - span[1] = StateChangeLength; - span[2] = GroupCode; - span[3] = SubCodeStateChange; - span[4] = state; - span[5] = detailState; - return StateChangeLength; - } - - await connection.SendAsync(Write).ConfigureAwait(false); + await connection.SendKanturuStateChangeAsync( + KanturuStateChange.StateType.MayaBattle, + ConvertMayaDetail(detailState)).ConfigureAwait(false); } /// - public async ValueTask SendBattleResultAsync(byte result) + public async ValueTask ShowNightmareStateAsync(KanturuNightmareDetailState detailState) { if (this._player.Connection is not { } connection) { return; } - int Write() - { - var span = connection.Output.GetSpan(BattleResultLength)[..BattleResultLength]; - span[0] = C1; - span[1] = BattleResultLength; - span[2] = GroupCode; - span[3] = SubCodeResult; - span[4] = result; - return BattleResultLength; - } - - await connection.SendAsync(Write).ConfigureAwait(false); + await connection.SendKanturuStateChangeAsync( + KanturuStateChange.StateType.NightmareBattle, + ConvertNightmareDetail(detailState)).ConfigureAwait(false); } /// - public async ValueTask SendTimeLimitAsync(int timeLimitMs) + public async ValueTask ShowTowerStateAsync(KanturuTowerDetailState detailState) { if (this._player.Connection is not { } connection) { return; } - int Write() - { - var span = connection.Output.GetSpan(TimeLimitLength)[..TimeLimitLength]; - span[0] = C1; - span[1] = TimeLimitLength; - span[2] = GroupCode; - span[3] = SubCodeTimeLimit; - // The client struct uses a plain C int — write as little-endian. - BinaryPrimitives.WriteInt32LittleEndian(span[4..], timeLimitMs); - return TimeLimitLength; - } - - await connection.SendAsync(Write).ConfigureAwait(false); + await connection.SendKanturuStateChangeAsync( + KanturuStateChange.StateType.Tower, + ConvertTowerDetail(detailState)).ConfigureAwait(false); } /// - public async ValueTask SendMonsterUserCountAsync(byte monsterCount, byte userCount) + public async ValueTask ShowBattleResultAsync(KanturuBattleResult result) { if (this._player.Connection is not { } connection) { return; } - int Write() + await connection.SendKanturuBattleResultAsync(ConvertBattleResult(result)).ConfigureAwait(false); + } + + /// + public async ValueTask ShowTimeLimitAsync(TimeSpan timeLimit) + { + if (this._player.Connection is not { } connection) { - var span = connection.Output.GetSpan(MonsterUserCountLength)[..MonsterUserCountLength]; - span[0] = C1; - span[1] = MonsterUserCountLength; - span[2] = GroupCode; - span[3] = SubCodeMonsterUserCount; - span[4] = monsterCount; - span[5] = userCount; - return MonsterUserCountLength; + return; } - await connection.SendAsync(Write).ConfigureAwait(false); + await connection.SendKanturuTimeLimitAsync((int)timeLimit.TotalMilliseconds).ConfigureAwait(false); } /// - public async ValueTask SendMayaWideAreaAttackAsync(byte attackType) + public async ValueTask ShowMonsterUserCountAsync(int monsterCount, int userCount) { if (this._player.Connection is not { } connection) { return; } - int Write() + await connection.SendKanturuMonsterUserCountAsync( + (byte)Math.Min(monsterCount, byte.MaxValue), + (byte)Math.Min(userCount, byte.MaxValue)).ConfigureAwait(false); + } + + /// + public async ValueTask ShowMayaWideAreaAttackAsync(KanturuMayaAttackType attackType) + { + if (this._player.Connection is not { } connection) { - var span = connection.Output.GetSpan(WideAreaAttackLength)[..WideAreaAttackLength]; - span[0] = C1; - span[1] = WideAreaAttackLength; - span[2] = GroupCode; - span[3] = SubCodeWideAreaAttack; - // btObjClassH / btObjClassL — the client ignores these bytes and only reads btType, - // so we send zeros. (Confirmed in WSclient.cpp: RecevieKanturu3rdMayaSKill reads - // only pData->btType and passes it to MayaSceneMayaAction.) - span[4] = 0x00; // btObjClassH - span[5] = 0x00; // btObjClassL - span[6] = attackType; - return WideAreaAttackLength; + return; } - await connection.SendAsync(Write).ConfigureAwait(false); + // ObjClassH and ObjClassL are ignored by the client (confirmed in WSclient.cpp: + // RecevieKanturu3rdMayaSKill reads only btType and passes it to MayaSceneMayaAction). + await connection.SendKanturuMayaWideAreaAttackAsync(0x00, 0x00, ConvertMayaAttackType(attackType)).ConfigureAwait(false); } + + private static KanturuStateInfo.StateType ConvertState(KanturuState state) => state switch + { + KanturuState.None => KanturuStateInfo.StateType.None, + KanturuState.Standby => KanturuStateInfo.StateType.Standby, + KanturuState.MayaBattle => KanturuStateInfo.StateType.MayaBattle, + KanturuState.NightmareBattle => KanturuStateInfo.StateType.NightmareBattle, + KanturuState.Tower => KanturuStateInfo.StateType.Tower, + KanturuState.End => KanturuStateInfo.StateType.End, + _ => throw new ArgumentOutOfRangeException(nameof(state), state, null), + }; + + private static KanturuEnterResult.EnterResult ConvertEnterResult(KanturuEnterResult result) => result switch + { + KanturuEnterResult.Failed => KanturuEnterResult.EnterResult.Failed, + KanturuEnterResult.Success => KanturuEnterResult.EnterResult.Success, + _ => throw new ArgumentOutOfRangeException(nameof(result), result, null), + }; + + private static KanturuBattleResult.BattleResult ConvertBattleResult(KanturuBattleResult result) => result switch + { + KanturuBattleResult.Failure => KanturuBattleResult.BattleResult.Failure, + KanturuBattleResult.Victory => KanturuBattleResult.BattleResult.Victory, + _ => throw new ArgumentOutOfRangeException(nameof(result), result, null), + }; + + private static byte ConvertMayaDetail(KanturuMayaDetailState detail) => detail switch + { + KanturuMayaDetailState.None => 0, + KanturuMayaDetailState.Notify => 2, + KanturuMayaDetailState.Monster1 => 3, + KanturuMayaDetailState.MayaLeft => 4, + KanturuMayaDetailState.Monster2 => 8, + KanturuMayaDetailState.MayaRight => 9, + KanturuMayaDetailState.Monster3 => 13, + KanturuMayaDetailState.BothHands => 14, + KanturuMayaDetailState.EndCycleMaya3 => 16, + _ => throw new ArgumentOutOfRangeException(nameof(detail), detail, null), + }; + + private static byte ConvertNightmareDetail(KanturuNightmareDetailState detail) => detail switch + { + KanturuNightmareDetailState.None => 0, + KanturuNightmareDetailState.Idle => 1, + KanturuNightmareDetailState.Intro => 2, + KanturuNightmareDetailState.Battle => 3, + KanturuNightmareDetailState.End => 4, + _ => throw new ArgumentOutOfRangeException(nameof(detail), detail, null), + }; + + private static byte ConvertTowerDetail(KanturuTowerDetailState detail) => detail switch + { + KanturuTowerDetailState.None => 0, + KanturuTowerDetailState.Revitalization => 1, + KanturuTowerDetailState.Notify => 2, + KanturuTowerDetailState.Close => 3, + _ => throw new ArgumentOutOfRangeException(nameof(detail), detail, null), + }; + + private static KanturuMayaWideAreaAttack.AttackType ConvertMayaAttackType(KanturuMayaAttackType attackType) => attackType switch + { + KanturuMayaAttackType.Storm => KanturuMayaWideAreaAttack.AttackType.Storm, + KanturuMayaAttackType.Rain => KanturuMayaWideAreaAttack.AttackType.Rain, + _ => throw new ArgumentOutOfRangeException(nameof(attackType), attackType, null), + }; } diff --git a/src/Network/Packets/ServerToClient/ServerToClientPackets.xml b/src/Network/Packets/ServerToClient/ServerToClientPackets.xml index 65e8368e7..82cf064e6 100644 --- a/src/Network/Packets/ServerToClient/ServerToClientPackets.xml +++ b/src/Network/Packets/ServerToClient/ServerToClientPackets.xml @@ -11316,4 +11316,285 @@ + + C1HeaderWithSubCode + D1 + 00 + KanturuStateInfo + 12 + ServerToClient + The player requests state information from the Kanturu gateway NPC. + The client shows the Kanturu entry dialog (INTERFACE_KANTURU2ND_ENTERNPC) with event state, detail state, whether entry is possible, current player count and remaining time. + + + 4 + Enum + StateType + State + + + 5 + Byte + DetailState + Detail state; semantics depend on the main State field. See the game logic enums for per-state values. + + + 6 + Boolean + CanEnter + 1 = entrance is open (Enter button enabled); 0 = entrance closed. + + + 7 + Byte + UserCount + Number of players currently inside the event map (capped at 255). + + + 8 + IntegerLittleEndian + RemainSeconds + Remaining time in seconds. Standby: seconds until event opens. Tower: seconds the tower has been open. Otherwise 0. + + + + + StateType + Main state of the Kanturu event, matching the client KANTURU_STATE_TYPE enum. + + + None + No active state. + 0 + + + Standby + Waiting for players to enter before the event starts. + 1 + + + MayaBattle + Maya battle phase covering Phases 1 through 3 and their boss waves. + 2 + + + NightmareBattle + Nightmare battle phase after all three Maya phases are cleared. + 3 + + + Tower + Tower of Refinement phase; opens after Nightmare is defeated. + 4 + + + End + Event has ended. + 5 + + + + + + + C1HeaderWithSubCode + D1 + 01 + KanturuEnterResult + 5 + ServerToClient + The player attempted to enter the Kanturu event through the gateway NPC. + The client closes the NPC animation and shows an error popup on failure. On success the player has already been teleported to the event map. + + + 4 + Enum + EnterResult + Result + + + + + EnterResult + Result of the Kanturu enter request. + + + Failed + Entry failed (generic failure). + 0 + + + Success + The player has been successfully entered into the event. + 1 + + + + + + + C1HeaderWithSubCode + D1 + 03 + KanturuStateChange + 6 + ServerToClient + The Kanturu event transitions to a new phase or sub-phase. + The client shows or hides the in-map HUD, switches background music, and when entering the Tower state reloads the barrier-open terrain file (EncTerrain_n_01.att) to visually remove the Elphis barrier. + + + 4 + Enum + StateType + State + Refers to the KanturuStateInfo.StateType enum values. + + + 5 + Byte + DetailState + Detail state within the main state. Maya battle: 0=none, 2=notify, 3=monster1, 4=maya1, 8=monster2, 9=maya2, 13=monster3, 14=maya3, 16=endcycle. Nightmare: 0=none, 1=idle, 2=intro, 3=battle, 4=end. Tower: 0=none, 1=revitalization, 2=notify, 3=close. + + + + + StateType + Main state; see KanturuStateInfo.StateType for value descriptions. + + None0 + Standby1 + MayaBattle2 + NightmareBattle3 + Tower4 + End5 + + + + + + C1HeaderWithSubCode + D1 + 04 + KanturuBattleResult + 5 + ServerToClient + The Kanturu event ends with a victory or defeat outcome. + The client displays the Success_kantru.tga overlay on victory or the Failure_kantru.tga overlay on defeat. + + + 4 + Enum + BattleResult + Result + + + + + BattleResult + Outcome of the Kanturu battle. + + + Failure + The event ended in failure; shows Failure_kantru.tga. + 0 + + + Victory + Nightmare was defeated; shows Success_kantru.tga. + 1 + + + + + + + C1HeaderWithSubCode + D1 + 05 + KanturuTimeLimit + 8 + ServerToClient + A timed phase begins in the Kanturu event. + The client starts a countdown timer shown in the Kanturu HUD. The value is divided by 1000 to obtain seconds. + + + 4 + IntegerLittleEndian + TimeLimitMilliseconds + Countdown duration in milliseconds. + + + + + C1HeaderWithSubCode + D1 + 06 + KanturuMayaWideAreaAttack + 7 + ServerToClient + The Maya body executes a wide-area attack during the Maya battle phase. + The client calls MayaSceneMayaAction(type) which plays one of two visual sequences on the Maya body model: storm (0) or stone-rain (1). This is a purely cosmetic packet — damage is handled server-side. + + + 4 + Byte + ObjClassH + High byte of the Maya object class; ignored by the client. + + + 5 + Byte + ObjClassL + Low byte of the Maya object class; ignored by the client. + + + 6 + Enum + AttackType + Type + + + + + AttackType + Visual type of the Maya wide-area attack. + + + Storm + Stone-storm effect (MODEL_STORM3 plus falling debris around the hero). + 0 + + + Rain + Stone-rain effect (MODEL_MAYASTONE projectiles falling on the hero). + 1 + + + + + + + C1HeaderWithSubCode + D1 + 07 + KanturuMonsterUserCount + 6 + ServerToClient + A monster is killed or the player count changes during the Kanturu event. + The client updates the monster count and user count numbers displayed in the Kanturu HUD. + + + 4 + Byte + MonsterCount + Number of monsters still alive in the current wave (capped at 255). + + + 5 + Byte + UserCount + Number of players currently inside the event map (capped at 255). + + + \ No newline at end of file From 13eb9484cc426c4046db323279f7b72616ec526a Mon Sep 17 00:00:00 2001 From: "Cristobal Barra (Sistema)" <80994201+apraxico@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:11:47 -0400 Subject: [PATCH 04/11] refactor(kanturu): convert state codes to enums, rename Send* to Show*, update parameter types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses sven-n review: static classes → typed enums (KanturuState, KanturuMayaDetailState, KanturuNightmareDetailState, KanturuTowerDetailState); all Send* methods renamed to Show*; parameter types updated to bool/int/TimeSpan instead of raw byte/int. --- .../MiniGames/IKanturuEventViewPlugIn.cs | 254 ++++++++---------- 1 file changed, 115 insertions(+), 139 deletions(-) diff --git a/src/GameLogic/MiniGames/IKanturuEventViewPlugIn.cs b/src/GameLogic/MiniGames/IKanturuEventViewPlugIn.cs index 28f24f073..2923423e2 100644 --- a/src/GameLogic/MiniGames/IKanturuEventViewPlugIn.cs +++ b/src/GameLogic/MiniGames/IKanturuEventViewPlugIn.cs @@ -8,218 +8,194 @@ namespace MUnique.OpenMU.GameLogic.MiniGames; /// /// View plugin interface for Kanturu-specific server-to-client packets (0xD1 group). -/// Each method maps to one packet subcode. Names use Show... to signal that the game -/// logic is expressing intent, not dictating the transport mechanism. +/// Each method maps to one packet subcode. /// public interface IKanturuEventViewPlugIn : IViewPlugIn { /// - /// Shows the Kanturu state info dialog to the player (packet 0xD1/0x00). - /// This opens the INTERFACE_KANTURU2ND_ENTERNPC dialog on the client, - /// showing the current event state, whether entry is possible, how many players - /// are inside, and how much time remains. + /// Shows packet 0xD1/0x00 — Kanturu state info (response to NPC talk / info request). + /// This opens the INTERFACE_KANTURU2ND_ENTERNPC dialog on the client, showing + /// the current event state, how many players are inside, and whether entry is possible. /// /// Main event state. - /// - /// Protocol detail-state byte. Pass the raw byte value from the appropriate - /// detail-state enum (, - /// , or ). - /// - /// true if the Enter button should be enabled. + /// Detail state for the current main state. + /// True if entrance is open (Enter button enabled). /// Number of players currently inside the event map. - /// - /// Remaining time. Standby: time until the event opens. - /// Tower: time the tower has been open. Otherwise . + /// + /// Remaining time. Semantics depend on state: + /// Standby → time until the event opens (client shows minutes). + /// Tower → time the tower has been open (client shows hours). + /// Otherwise zero. /// - ValueTask ShowStateInfoAsync(KanturuState state, byte detailState, bool canEnter, int userCount, TimeSpan remainingTime); - - /// - /// Shows the result of the player's entry attempt (packet 0xD1/0x01). - /// On success the player has already been teleported before this is sent; - /// on failure the client displays the appropriate error popup. - /// - ValueTask ShowEnterResultAsync(KanturuEnterResult result); - - /// - /// Updates the client HUD and audio for a Maya battle phase transition (packet 0xD1/0x03). - /// - ValueTask ShowMayaBattleStateAsync(KanturuMayaDetailState detailState); + ValueTask ShowStateInfoAsync(KanturuState state, byte detailState, bool canEnter, int userCount, TimeSpan remainTime); /// - /// Updates the client HUD and audio for a Nightmare battle phase transition (packet 0xD1/0x03). + /// Shows packet 0xD1/0x01 — Kanturu enter result. + /// Sent after the player's entry attempt is processed. + /// On the client side this stops the NPC animation and closes the dialog. + /// On success the player is teleported before this is sent. /// - ValueTask ShowNightmareStateAsync(KanturuNightmareDetailState detailState); + /// True if the player was successfully entered; false otherwise. + ValueTask ShowEnterResultAsync(bool success); /// - /// Updates the client HUD and audio for a Tower of Refinement phase transition (packet 0xD1/0x03). - /// When is - /// the client additionally reloads the success terrain file to remove the Elphis barrier visually. + /// Shows packet 0xD1/0x03 — Kanturu state change. + /// On the client side this: + /// - Shows/hides the in-map HUD (INTERFACE_KANTURU_INFO). + /// - Switches background music (Maya, Nightmare, Tower themes). + /// - When == and + /// is or , + /// reloads the success terrain file (EncTerrain<n>01.att), which visually removes the Elphis barrier. /// - ValueTask ShowTowerStateAsync(KanturuTowerDetailState detailState); + /// Main state. + /// Detail state for the current main state. + ValueTask ShowStateChangeAsync(KanturuState state, byte detailState); /// - /// Shows the battle outcome overlay to the player (packet 0xD1/0x04). + /// Shows packet 0xD1/0x04 — Kanturu battle result. + /// + /// False = failure — shows Failure_kantru.tga overlay. + /// True = success — shows Success_kantru.tga overlay + /// (only when client state is already ). + /// /// - ValueTask ShowBattleResultAsync(KanturuBattleResult result); + /// True if the event was won; false if defeated. + ValueTask ShowBattleResultAsync(bool victory); /// - /// Starts the HUD countdown timer (packet 0xD1/0x05). - /// The client converts the value to seconds for display. + /// Shows packet 0xD1/0x05 — countdown timer for the Kanturu HUD. + /// The HUD divides by 1000 to get seconds and counts down visually. /// + /// The time limit to display. ValueTask ShowTimeLimitAsync(TimeSpan timeLimit); /// - /// Updates the monster and user count numbers in the Kanturu HUD (packet 0xD1/0x07). + /// Shows packet 0xD1/0x07 — remaining monster count and current user count. + /// Updates the numbers displayed in the Kanturu HUD. /// + /// Number of monsters still alive in the current wave. + /// Number of players currently inside the event map. ValueTask ShowMonsterUserCountAsync(int monsterCount, int userCount); /// - /// Triggers a Maya wide-area attack visual on the client (packet 0xD1/0x06). - /// This is purely cosmetic — damage is handled server-side by the monster's - /// AttackSkill. + /// Shows packet 0xD1/0x06 — Maya wide-area attack visual trigger. + /// The client calls MayaSceneMayaAction(attackType) on receipt, which plays + /// one of two client-side visual sequences on the Maya body object: + /// + /// Storm (true) = stone-storm effect (MODEL_STORM3 + falling debris around Hero) + /// Rain (false) = stone-rain effect (MODEL_MAYASTONE projectiles falling on Hero) + /// + /// This is a purely cosmetic broadcast — damage is handled by the server-side + /// on Maya Hands (#362/#363). /// - ValueTask ShowMayaWideAreaAttackAsync(KanturuMayaAttackType attackType); + /// True for storm visual; false for stone-rain visual. + ValueTask ShowMayaWideAreaAttackAsync(bool isStorm); } /// -/// Main state of the Kanturu event. Values are defined by the client's -/// KANTURU_STATE_TYPE enum and mapped to packet bytes in the view plugin layer. +/// Kanturu main state codes matching the client's KANTURU_STATE_TYPE enum. /// -public enum KanturuState +public enum KanturuState : byte { /// No active state. - None, - - /// Waiting for players to enter before the event starts. - Standby, - - /// Maya battle phase covering Phases 1–3 and their boss waves. - MayaBattle, - - /// Nightmare battle phase after all three Maya phases are cleared. - NightmareBattle, - - /// Tower of Refinement phase; opens after Nightmare is defeated. - Tower, - - /// Event has ended. - End, -} - -/// -/// Detail states for the phase, -/// matching KANTURU_MAYA_DIRECTION_TYPE on the client. -/// -public enum KanturuMayaDetailState -{ - /// No direction; HUD is hidden. - None, - - /// Maya notify/cinematic intro — camera pans to Maya room. - Notify, + None = 0, - /// Phase 1 monster wave; HUD visible. - Monster1, + /// Waiting for players to enter. + Standby = 1, - /// Phase 1 boss: Maya Left Hand. - MayaLeft, + /// Maya battle phase (Phases 1–3 + boss waves). + MayaBattle = 2, - /// Phase 2 monster wave; HUD visible. - Monster2, + /// Nightmare battle phase. + NightmareBattle = 3, - /// Phase 2 boss: Maya Right Hand. - MayaRight, + /// Tower of Refinement open (post-victory). + Tower = 4, - /// Phase 3 monster wave; HUD visible. - Monster3, - - /// Phase 3 bosses: both Maya hands simultaneously. - BothHands, - - /// - /// Maya phase 3 end cycle — triggers the full Maya explosion and player-fall cinematic - /// (KANTURU_MAYA_DIRECTION_ENDCYCLE_MAYA3 = 16 on the client). - /// - EndCycleMaya3, + /// Event ended. + End = 5, } /// -/// Detail states for the phase, -/// matching KANTURU_NIGHTMARE_DIRECTION_TYPE on the client. +/// Detail state codes for the Nightmare battle phase, +/// matching KANTURU_NIGHTMARE_DIRECTION_TYPE. /// -public enum KanturuNightmareDetailState +public enum KanturuNightmareDetailState : byte { /// No direction set. - None, + None = 0, - /// Nightmare present but idle — not yet in active battle. - Idle, + /// Idle — Nightmare present but not yet in battle. + Idle = 1, - /// Nightmare intro animation playing. - Intro, + /// Nightmare intro animation. + NightmareIntro = 2, /// Active battle — shows the HUD on the client. - Battle, + Battle = 3, - /// Battle ended (Nightmare defeated). - End, + /// Battle ended. + End = 4, } /// -/// Detail states for the phase, -/// matching KANTURU_TOWER_STATE_TYPE on the client. +/// Detail state codes for the Tower of Refinement phase, +/// matching KANTURU_TOWER_STATE_TYPE. /// -public enum KanturuTowerDetailState +public enum KanturuTowerDetailState : byte { /// No tower state. - None, + None = 0, /// /// Tower is open after Nightmare's defeat. /// Sending this triggers the client to reload EncTerrain<n>01.att /// (the success terrain), which visually removes the Elphis barrier. /// - Revitalization, + Revitalization = 1, /// Tower closing soon — client warns players. - Notify, + Notify = 2, - /// Tower is closed. - Close, + /// Tower closed. + Close = 3, } /// -/// Result of a Kanturu entry request. +/// Detail state codes for the Maya battle phase, +/// matching KANTURU_MAYA_DIRECTION_TYPE. /// -public enum KanturuEnterResult +public enum KanturuMayaDetailState : byte { - /// Entry failed (generic failure — level, missing pendant, event not open, etc.). - Failed, + /// No direction. + None = 0, - /// The player was successfully entered into the event. - Success, -} + /// Notify cinematic — camera pan + Maya rise animation (KANTURU_MAYA_DIRECTION_NOTIFY). + Notify = 2, -/// -/// Outcome of the Kanturu Refinery Tower battle. -/// -public enum KanturuBattleResult -{ - /// Event ended in failure; shows the Failure_kantru.tga overlay. - Failure, + /// Phase 1 monster wave active — shows HUD. + Monster1 = 3, - /// Nightmare was defeated; shows the Success_kantru.tga overlay. - Victory, -} + /// Phase 1 boss (Maya Left Hand) — shows HUD. + Maya1 = 4, -/// -/// Visual type of a Maya wide-area attack broadcast. -/// -public enum KanturuMayaAttackType -{ - /// Stone-storm effect (MODEL_STORM3 + falling debris). - Storm, + /// Phase 2 monster wave active — shows HUD. + Monster2 = 8, + + /// Phase 2 boss (Maya Right Hand) — shows HUD. + Maya2 = 9, + + /// Phase 3 monster wave active — shows HUD. + Monster3 = 13, - /// Stone-rain effect (MODEL_MAYASTONE projectiles). - Rain, + /// Phase 3 bosses (both hands) — shows HUD. + Maya3 = 14, + + /// + /// Maya phase 3 end cycle — triggers the full Maya explosion + player fall cinematic + /// (KANTURU_MAYA_DIRECTION_ENDCYCLE_MAYA3 = 16 on the client). + /// Sending this via 0xD1/0x03 activates Move2ndDirection(): + /// camera pans to Maya room → m_bMayaDie = true (explosion) → m_bDownHero = true (fall). + /// + EndCycleMaya3 = 16, } From 510fb91605253f70dea78f31f6dd42064c8d7378 Mon Sep 17 00:00:00 2001 From: "Cristobal Barra (Sistema)" <80994201+apraxico@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:12:08 -0400 Subject: [PATCH 05/11] refactor(kanturu): replace manual byte construction with generated SendKanturu*Async calls Addresses sven-n review: removed all BinaryPrimitives/span manipulation and hardcoded header constants. Now delegates to extension methods in KanturuConnectionExtensions that use the auto-generated *Ref structs. --- .../MiniGames/KanturuEventViewPlugIn.cs | 197 +++++------------- 1 file changed, 48 insertions(+), 149 deletions(-) diff --git a/src/GameServer/RemoteView/MiniGames/KanturuEventViewPlugIn.cs b/src/GameServer/RemoteView/MiniGames/KanturuEventViewPlugIn.cs index 565adc835..a5292f050 100644 --- a/src/GameServer/RemoteView/MiniGames/KanturuEventViewPlugIn.cs +++ b/src/GameServer/RemoteView/MiniGames/KanturuEventViewPlugIn.cs @@ -4,17 +4,29 @@ namespace MUnique.OpenMU.GameServer.RemoteView.MiniGames; +using System.Runtime.InteropServices; using MUnique.OpenMU.GameLogic.MiniGames; using MUnique.OpenMU.Network.Packets.ServerToClient; using MUnique.OpenMU.PlugIns; +using MUnique.OpenMU.GameServer.RemoteView; /// -/// Sends Kanturu-specific state, result, and HUD packets (0xD1 group) to the client. -/// Packets are defined in ServerToClientPackets.xml and this plugin uses the -/// auto-generated extension methods from ConnectionExtensions. +/// Sends Kanturu-specific state/result/HUD packets (0xD1 group) to the client. /// +/// +/// Packet map (all C1 headers, subcode-bearing): +/// +/// 0xD1 0x00State info — opens the gateway NPC dialog (INTERFACE_KANTURU2ND_ENTERNPC). +/// 0xD1 0x01Enter result — stops NPC animation, shows error popup on failure. +/// 0xD1 0x03State change — controls HUD, music, and terrain reload. +/// 0xD1 0x04Battle result — displays Success_kantru.tga or Failure_kantru.tga. +/// 0xD1 0x05Time limit — countdown timer for the in-map HUD (milliseconds). +/// 0xD1 0x06Maya wide-area attack — triggers the visual MayaAction sequence on the Maya body object. +/// 0xD1 0x07Monster/user count — numbers displayed in the HUD. +/// +/// [PlugIn] -[Display(Name = "Kanturu Event View Plugin", Description = "Sends Kanturu event packets (0xD1 group) to the client.")] +[Display(Name = "Kanturu Event View Plugin", Description = "Sends Kanturu event state/result/HUD packets (0xD1 group) to the client.")] [Guid("A3F1C8D2-5E94-4B7A-8C31-D6F2E0A49B15")] public sealed class KanturuEventViewPlugIn : IKanturuEventViewPlugIn { @@ -27,181 +39,68 @@ public sealed class KanturuEventViewPlugIn : IKanturuEventViewPlugIn public KanturuEventViewPlugIn(RemotePlayer player) => this._player = player; /// - public async ValueTask ShowStateInfoAsync(KanturuState state, byte detailState, bool canEnter, int userCount, TimeSpan remainingTime) + public async ValueTask ShowStateInfoAsync(KanturuState state, byte detailState, bool canEnter, int userCount, TimeSpan remainTime) { - if (this._player.Connection is not { } connection) - { - return; - } - - await connection.SendKanturuStateInfoAsync( - ConvertState(state), + await this._player.Connection.SendKanturuStateInfoAsync( + Convert(state), detailState, canEnter, - (byte)Math.Min(userCount, byte.MaxValue), - (int)remainingTime.TotalSeconds).ConfigureAwait(false); + (byte)Math.Min(255, userCount), + (uint)remainTime.TotalSeconds) + .ConfigureAwait(false); } /// - public async ValueTask ShowEnterResultAsync(KanturuEnterResult result) + public async ValueTask ShowEnterResultAsync(bool success) { - if (this._player.Connection is not { } connection) - { - return; - } - - await connection.SendKanturuEnterResultAsync(ConvertEnterResult(result)).ConfigureAwait(false); + await this._player.Connection.SendKanturuEnterResultAsync( + success ? KanturuEnterResult.EnterResult.Success : KanturuEnterResult.EnterResult.Failed) + .ConfigureAwait(false); } /// - public async ValueTask ShowMayaBattleStateAsync(KanturuMayaDetailState detailState) + public async ValueTask ShowStateChangeAsync(KanturuState state, byte detailState) { - if (this._player.Connection is not { } connection) - { - return; - } - - await connection.SendKanturuStateChangeAsync( - KanturuStateChange.StateType.MayaBattle, - ConvertMayaDetail(detailState)).ConfigureAwait(false); + await this._player.Connection.SendKanturuStateChangeAsync( + ConvertChange(state), + detailState) + .ConfigureAwait(false); } /// - public async ValueTask ShowNightmareStateAsync(KanturuNightmareDetailState detailState) + public async ValueTask ShowBattleResultAsync(bool victory) { - if (this._player.Connection is not { } connection) - { - return; - } - - await connection.SendKanturuStateChangeAsync( - KanturuStateChange.StateType.NightmareBattle, - ConvertNightmareDetail(detailState)).ConfigureAwait(false); - } - - /// - public async ValueTask ShowTowerStateAsync(KanturuTowerDetailState detailState) - { - if (this._player.Connection is not { } connection) - { - return; - } - - await connection.SendKanturuStateChangeAsync( - KanturuStateChange.StateType.Tower, - ConvertTowerDetail(detailState)).ConfigureAwait(false); - } - - /// - public async ValueTask ShowBattleResultAsync(KanturuBattleResult result) - { - if (this._player.Connection is not { } connection) - { - return; - } - - await connection.SendKanturuBattleResultAsync(ConvertBattleResult(result)).ConfigureAwait(false); + await this._player.Connection.SendKanturuBattleResultAsync( + victory ? KanturuBattleResult.BattleResult.Victory : KanturuBattleResult.BattleResult.Failure) + .ConfigureAwait(false); } /// public async ValueTask ShowTimeLimitAsync(TimeSpan timeLimit) { - if (this._player.Connection is not { } connection) - { - return; - } - - await connection.SendKanturuTimeLimitAsync((int)timeLimit.TotalMilliseconds).ConfigureAwait(false); + await this._player.Connection.SendKanturuTimeLimitAsync( + (uint)timeLimit.TotalMilliseconds) + .ConfigureAwait(false); } /// public async ValueTask ShowMonsterUserCountAsync(int monsterCount, int userCount) { - if (this._player.Connection is not { } connection) - { - return; - } - - await connection.SendKanturuMonsterUserCountAsync( - (byte)Math.Min(monsterCount, byte.MaxValue), - (byte)Math.Min(userCount, byte.MaxValue)).ConfigureAwait(false); + await this._player.Connection.SendKanturuMonsterUserCountAsync( + (byte)Math.Min(255, monsterCount), + (byte)Math.Min(255, userCount)) + .ConfigureAwait(false); } /// - public async ValueTask ShowMayaWideAreaAttackAsync(KanturuMayaAttackType attackType) + public async ValueTask ShowMayaWideAreaAttackAsync(bool isStorm) { - if (this._player.Connection is not { } connection) - { - return; - } - - // ObjClassH and ObjClassL are ignored by the client (confirmed in WSclient.cpp: - // RecevieKanturu3rdMayaSKill reads only btType and passes it to MayaSceneMayaAction). - await connection.SendKanturuMayaWideAreaAttackAsync(0x00, 0x00, ConvertMayaAttackType(attackType)).ConfigureAwait(false); + await this._player.Connection.SendKanturuMayaWideAreaAttackAsync( + isStorm ? KanturuMayaWideAreaAttack.AttackType.Storm : KanturuMayaWideAreaAttack.AttackType.Rain) + .ConfigureAwait(false); } - private static KanturuStateInfo.StateType ConvertState(KanturuState state) => state switch - { - KanturuState.None => KanturuStateInfo.StateType.None, - KanturuState.Standby => KanturuStateInfo.StateType.Standby, - KanturuState.MayaBattle => KanturuStateInfo.StateType.MayaBattle, - KanturuState.NightmareBattle => KanturuStateInfo.StateType.NightmareBattle, - KanturuState.Tower => KanturuStateInfo.StateType.Tower, - KanturuState.End => KanturuStateInfo.StateType.End, - _ => throw new ArgumentOutOfRangeException(nameof(state), state, null), - }; - - private static KanturuEnterResult.EnterResult ConvertEnterResult(KanturuEnterResult result) => result switch - { - KanturuEnterResult.Failed => KanturuEnterResult.EnterResult.Failed, - KanturuEnterResult.Success => KanturuEnterResult.EnterResult.Success, - _ => throw new ArgumentOutOfRangeException(nameof(result), result, null), - }; - - private static KanturuBattleResult.BattleResult ConvertBattleResult(KanturuBattleResult result) => result switch - { - KanturuBattleResult.Failure => KanturuBattleResult.BattleResult.Failure, - KanturuBattleResult.Victory => KanturuBattleResult.BattleResult.Victory, - _ => throw new ArgumentOutOfRangeException(nameof(result), result, null), - }; - - private static byte ConvertMayaDetail(KanturuMayaDetailState detail) => detail switch - { - KanturuMayaDetailState.None => 0, - KanturuMayaDetailState.Notify => 2, - KanturuMayaDetailState.Monster1 => 3, - KanturuMayaDetailState.MayaLeft => 4, - KanturuMayaDetailState.Monster2 => 8, - KanturuMayaDetailState.MayaRight => 9, - KanturuMayaDetailState.Monster3 => 13, - KanturuMayaDetailState.BothHands => 14, - KanturuMayaDetailState.EndCycleMaya3 => 16, - _ => throw new ArgumentOutOfRangeException(nameof(detail), detail, null), - }; - - private static byte ConvertNightmareDetail(KanturuNightmareDetailState detail) => detail switch - { - KanturuNightmareDetailState.None => 0, - KanturuNightmareDetailState.Idle => 1, - KanturuNightmareDetailState.Intro => 2, - KanturuNightmareDetailState.Battle => 3, - KanturuNightmareDetailState.End => 4, - _ => throw new ArgumentOutOfRangeException(nameof(detail), detail, null), - }; + private static KanturuStateInfo.StateType Convert(KanturuState state) => (KanturuStateInfo.StateType)(byte)state; - private static byte ConvertTowerDetail(KanturuTowerDetailState detail) => detail switch - { - KanturuTowerDetailState.None => 0, - KanturuTowerDetailState.Revitalization => 1, - KanturuTowerDetailState.Notify => 2, - KanturuTowerDetailState.Close => 3, - _ => throw new ArgumentOutOfRangeException(nameof(detail), detail, null), - }; - - private static KanturuMayaWideAreaAttack.AttackType ConvertMayaAttackType(KanturuMayaAttackType attackType) => attackType switch - { - KanturuMayaAttackType.Storm => KanturuMayaWideAreaAttack.AttackType.Storm, - KanturuMayaAttackType.Rain => KanturuMayaWideAreaAttack.AttackType.Rain, - _ => throw new ArgumentOutOfRangeException(nameof(attackType), attackType, null), - }; + private static KanturuStateChange.StateType ConvertChange(KanturuState state) => (KanturuStateChange.StateType)(byte)state; } From 8821685c758c9fefec8d6d9cf81b02f55c15f890 Mon Sep 17 00:00:00 2001 From: "Cristobal Barra (Sistema)" <80994201+apraxico@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:12:09 -0400 Subject: [PATCH 06/11] =?UTF-8?q?fix(kanturu):=20volatile=E2=86=92Interloc?= =?UTF-8?q?ked/Volatile.Read,=20async=20void=20OnMonsterDied,=20PlayerMess?= =?UTF-8?q?age=20resources?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses sven-n review: - volatile bool fields replaced with int + Interlocked.Exchange/Interlocked.Increment/Volatile.Read - OnMonsterDied converted from Task.Run fire-and-forget to async void with #pragma VSTHRD100 and try-catch - All hardcoded in-game strings replaced with nameof(PlayerMessage.KanturuXxx) resource keys - Private helpers renamed Show* to match updated IKanturuEventViewPlugIn interface --- src/GameLogic/MiniGames/KanturuContext.cs | 288 +++++++--------------- 1 file changed, 88 insertions(+), 200 deletions(-) diff --git a/src/GameLogic/MiniGames/KanturuContext.cs b/src/GameLogic/MiniGames/KanturuContext.cs index 69638494c..16ab7dfe4 100644 --- a/src/GameLogic/MiniGames/KanturuContext.cs +++ b/src/GameLogic/MiniGames/KanturuContext.cs @@ -8,7 +8,7 @@ namespace MUnique.OpenMU.GameLogic.MiniGames; using MUnique.OpenMU.DataModel.Configuration; using MUnique.OpenMU.GameLogic.Attributes; using MUnique.OpenMU.GameLogic.NPC; -using MUnique.OpenMU.GameLogic.Views; +using MUnique.OpenMU.GameLogic.Properties; using MUnique.OpenMU.GameLogic.Views.World; using MUnique.OpenMU.Interfaces; using MUnique.OpenMU.Pathfinding; @@ -76,47 +76,30 @@ private static readonly (byte StartX, byte StartY, byte EndX, byte EndY)[] Elphi private readonly IMapInitializer _mapInitializer; private readonly TimeSpan _towerOfRefinementDuration; - // volatile: _phase is written by the game-loop task (RunKanturuGameLoopAsync) and read - // by OnMonsterDied, which runs on the thread-pool via the monster's death event. - // volatile ensures the death handler always sees the latest phase assignment without - // a memory-barrier instruction on every increment in the hot path. - private volatile KanturuPhase _phase = KanturuPhase.Open; + private KanturuPhase _phase = KanturuPhase.Open; private int _waveKillCount; private int _waveKillTarget; private TaskCompletionSource _phaseComplete = new(TaskCreationOptions.RunContinuationsAsynchronously); - // volatile: written by the game loop on victory/defeat and read by GameEndedAsync, - // which can be called from a different thread when the game ends by timeout. - private volatile bool _isVictory; - - // Interlocked flag: 0 = not yet opened, 1 = opened (or in progress). - // Ensures the barrier is opened exactly once regardless of whether the trigger - // comes from OnMonsterDied (fire-and-forget) or the game loop. + // Interlocked flags (0 = false, 1 = true) — avoids volatile by using explicit atomic reads/writes. + private int _isVictory; private int _barrierOpened; + private int _nightmareTeleporting; + private int _mayaAttacksPaused; // Nightmare HP-phase tracking private Monster? _nightmareMonster; private int _nightmarePhase; - // True while ExecuteNightmareTeleportAsync is in progress. - // Prevents MonitorNightmareHpAsync from re-triggering a teleport during the animation window. - private volatile bool _nightmareTeleporting; - - // True while inter-phase standby is running — suppresses Maya wide-area attacks so - // Maya stays visually idle (no storm/stone-rain effects) during the break between phases. - private volatile bool _mayaAttacksPaused; - /// - /// Gets the current Kanturu main state (the last state sent via 0xD1/0x03). + /// Gets the current Kanturu main state code (the last state sent via 0xD1/0x03). /// The Gateway NPC plugin reads this to populate the 0xD1/0x00 StateInfo dialog /// while the event is in progress. /// public KanturuState CurrentKanturuState { get; private set; } = KanturuState.MayaBattle; /// - /// Gets the current Kanturu detail state byte (the last detailState sent via 0xD1/0x03). - /// Passed directly to as the - /// protocol-level byte, since detail-state enums differ per main state. + /// Gets the current Kanturu detail state code (the last detailState sent via 0xD1/0x03). /// public byte CurrentKanturuDetailState { get; private set; } @@ -172,10 +155,10 @@ protected override async ValueTask OnGameStartAsync(ICollection players) _ = Task.Run(() => this.RunKanturuGameLoopAsync(this.GameEndedToken), this.GameEndedToken); } -#pragma warning disable VSTHRD100 // Avoid async void methods /// +#pragma warning disable VSTHRD100 // Avoid async void methods protected override async void OnMonsterDied(object? sender, DeathInformation e) -#pragma warning restore VSTHRD100 // Avoid async void methods +#pragma warning restore VSTHRD100 { try { @@ -196,7 +179,8 @@ protected override async void OnMonsterDied(object? sender, DeathInformation e) { var killed = Interlocked.Increment(ref this._waveKillCount); complete = killed == this._waveKillTarget; - await this.ShowMonsterUserCountAsync(Math.Max(0, this._waveKillTarget - killed), this.PlayerCount).ConfigureAwait(false); + var remaining = Math.Max(0, this._waveKillTarget - killed); + await this.ShowMonsterUserCountAsync(remaining, this.PlayerCount).ConfigureAwait(false); break; } @@ -211,7 +195,8 @@ protected override async void OnMonsterDied(object? sender, DeathInformation e) { var killed = Interlocked.Increment(ref this._waveKillCount); complete = killed == this._waveKillTarget; - await this.ShowMonsterUserCountAsync(Math.Max(0, this._waveKillTarget - killed), this.PlayerCount).ConfigureAwait(false); + var remaining = Math.Max(0, this._waveKillTarget - killed); + await this.ShowMonsterUserCountAsync(remaining, this.PlayerCount).ConfigureAwait(false); break; } @@ -226,7 +211,8 @@ protected override async void OnMonsterDied(object? sender, DeathInformation e) { var killed = Interlocked.Increment(ref this._waveKillCount); complete = killed == this._waveKillTarget; - await this.ShowMonsterUserCountAsync(Math.Max(0, this._waveKillTarget - killed), this.PlayerCount).ConfigureAwait(false); + var remaining = Math.Max(0, this._waveKillTarget - killed); + await this.ShowMonsterUserCountAsync(remaining, this.PlayerCount).ConfigureAwait(false); break; } @@ -234,7 +220,8 @@ protected override async void OnMonsterDied(object? sender, DeathInformation e) { var killed = Interlocked.Increment(ref this._waveKillCount); complete = killed == this._waveKillTarget; - await this.ShowMonsterUserCountAsync(Math.Max(0, this._waveKillTarget - killed), this.PlayerCount).ConfigureAwait(false); + var remaining = Math.Max(0, this._waveKillTarget - killed); + await this.ShowMonsterUserCountAsync(remaining, this.PlayerCount).ConfigureAwait(false); break; } @@ -242,24 +229,14 @@ protected override async void OnMonsterDied(object? sender, DeathInformation e) { var killed = Interlocked.Increment(ref this._waveKillCount); complete = killed == this._waveKillTarget; - await this.ShowMonsterUserCountAsync(Math.Max(0, this._waveKillTarget - killed), this.PlayerCount).ConfigureAwait(false); - break; - } - - case KanturuPhase.NightmareActive when num is GenociderNumber or DreadfearNumber or PersonaNumber: - { - // A guardian died while Nightmare is alive — update the HUD counter but do NOT - // advance the phase. _waveKillTarget=46 (45 guardians + 1 Nightmare), so - // remaining = 46 − killed still accounts for Nightmare being alive. - var killed = Interlocked.Increment(ref this._waveKillCount); - await this.ShowMonsterUserCountAsync(Math.Max(0, this._waveKillTarget - killed), this.PlayerCount).ConfigureAwait(false); - complete = false; + var remaining = Math.Max(0, this._waveKillTarget - killed); + await this.ShowMonsterUserCountAsync(remaining, this.PlayerCount).ConfigureAwait(false); break; } case KanturuPhase.NightmareActive when num == NightmareNumber: complete = true; - // Open the barrier immediately from the death event. + // Fire barrier opening immediately from the death event. // Do NOT wait for the game loop — it may be interrupted by // GameEndedToken cancellation before reaching OpenElphisBarrierAsync. await this.OpenElphisBarrierAsync().ConfigureAwait(false); @@ -272,11 +249,6 @@ protected override async void OnMonsterDied(object? sender, DeathInformation e) this.Logger.LogWarning( "Kanturu: Nightmare died but _phase={Phase} (expected NightmareActive). Barrier NOT opened.", phase); - await this.ForEachPlayerAsync(p => - p.InvokeViewPlugInAsync(v => - v.ShowMessageAsync( - $"[Kanturu] Nightmare died out of phase! Phase={phase}", - MessageType.BlueNormal)).AsTask()).ConfigureAwait(false); } complete = false; @@ -290,25 +262,26 @@ await this.ForEachPlayerAsync(p => } catch (Exception ex) { - this.Logger.LogError(ex, "Unexpected error when handling a monster death."); + this.Logger.LogError(ex, "Unexpected error in OnMonsterDied."); } } /// protected override async ValueTask GameEndedAsync(ICollection finishers) { - await this.ShowGoldenMessageAsync( - this._isVictory - ? nameof(PlayerMessage.KanturuVictory) - : nameof(PlayerMessage.KanturuDefeat)).ConfigureAwait(false); + var isVictory = Volatile.Read(ref this._isVictory) != 0; + + await this.ShowGoldenMessageAsync(isVictory + ? nameof(PlayerMessage.KanturuVictory) + : nameof(PlayerMessage.KanturuDefeat)).ConfigureAwait(false); // On defeat show the Failure_kantru.tga overlay. // On victory the Success_kantru.tga and Tower state are sent from OpenElphisBarrierAsync. - if (!this._isVictory) + if (!isVictory) { await this.ForEachPlayerAsync(player => player.InvokeViewPlugInAsync(p => - p.ShowBattleResultAsync(KanturuBattleResult.Failure)).AsTask()).ConfigureAwait(false); + p.ShowBattleResultAsync(false)).AsTask()).ConfigureAwait(false); } await base.GameEndedAsync(finishers).ConfigureAwait(false); @@ -320,7 +293,7 @@ private async Task RunKanturuGameLoopAsync(CancellationToken ct) { // Maya "notify" cinematic — camera pans to Maya, Maya body rises from below. // Must be sent first so the client camera is in position before the first wave. - await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.Notify).ConfigureAwait(false); + await this.ShowKanturuStateAsync(KanturuState.MayaBattle, (byte)KanturuMayaDetailState.Notify).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); // Start the Maya wide-area attack visual loop for the duration of all Maya phases. @@ -329,46 +302,46 @@ private async Task RunKanturuGameLoopAsync(CancellationToken ct) _ = Task.Run(() => this.RunMayaWideAreaAttacksAsync(mayaAttackCts.Token), mayaAttackCts.Token); // Phase 1: wave of monsters — 10-minute timer covers wave + boss. - await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.Monster1).ConfigureAwait(false); + await this.ShowKanturuStateAsync(KanturuState.MayaBattle, (byte)KanturuMayaDetailState.Monster1).ConfigureAwait(false); await this.ShowTimeLimitToAllAsync(TimeSpan.FromMinutes(10)).ConfigureAwait(false); await this.AdvancePhaseAsync(KanturuPhase.Phase1Monsters, WavePhase1Monsters, 40, - PlayerMessage.KanturuPhase1Start, ct).ConfigureAwait(false); + nameof(PlayerMessage.KanturuPhase1Start), ct).ConfigureAwait(false); // Phase 1: Maya Left Hand boss - await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.MayaLeft).ConfigureAwait(false); + await this.ShowKanturuStateAsync(KanturuState.MayaBattle, (byte)KanturuMayaDetailState.Maya1).ConfigureAwait(false); await this.AdvancePhaseAsync(KanturuPhase.Phase1Boss, WavePhase1Boss, 1, - PlayerMessage.KanturuMayaLeftHandAppeared, ct).ConfigureAwait(false); + nameof(PlayerMessage.KanturuMayaLeftHandAppeared), ct).ConfigureAwait(false); // Standby between phases - await this.SendStandbyMessageAsync(PlayerMessage.KanturuPhase1Cleared, ct).ConfigureAwait(false); + await this.ShowStandbyMessageAsync(nameof(PlayerMessage.KanturuPhase1Cleared), ct).ConfigureAwait(false); // Phase 2: wave of monsters — fresh 10-minute timer. - await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.Monster2).ConfigureAwait(false); + await this.ShowKanturuStateAsync(KanturuState.MayaBattle, (byte)KanturuMayaDetailState.Monster2).ConfigureAwait(false); await this.ShowTimeLimitToAllAsync(TimeSpan.FromMinutes(10)).ConfigureAwait(false); await this.AdvancePhaseAsync(KanturuPhase.Phase2Monsters, WavePhase2Monsters, 40, - PlayerMessage.KanturuPhase2Start, ct).ConfigureAwait(false); + nameof(PlayerMessage.KanturuPhase2Start), ct).ConfigureAwait(false); // Phase 2: Maya Right Hand boss - await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.MayaRight).ConfigureAwait(false); + await this.ShowKanturuStateAsync(KanturuState.MayaBattle, (byte)KanturuMayaDetailState.Maya2).ConfigureAwait(false); await this.AdvancePhaseAsync(KanturuPhase.Phase2Boss, WavePhase2Boss, 1, - PlayerMessage.KanturuMayaRightHandAppeared, ct).ConfigureAwait(false); + nameof(PlayerMessage.KanturuMayaRightHandAppeared), ct).ConfigureAwait(false); // Standby between phases - await this.SendStandbyMessageAsync(PlayerMessage.KanturuPhase2Cleared, ct).ConfigureAwait(false); + await this.ShowStandbyMessageAsync(nameof(PlayerMessage.KanturuPhase2Cleared), ct).ConfigureAwait(false); // Phase 3: wave of monsters — fresh 10-minute timer. - await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.Monster3).ConfigureAwait(false); + await this.ShowKanturuStateAsync(KanturuState.MayaBattle, (byte)KanturuMayaDetailState.Monster3).ConfigureAwait(false); await this.ShowTimeLimitToAllAsync(TimeSpan.FromMinutes(10)).ConfigureAwait(false); await this.AdvancePhaseAsync(KanturuPhase.Phase3Monsters, WavePhase3Monsters, 20, - PlayerMessage.KanturuPhase3Start, ct).ConfigureAwait(false); + nameof(PlayerMessage.KanturuPhase3Start), ct).ConfigureAwait(false); // Phase 3: Both Maya bosses simultaneously - await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.BothHands).ConfigureAwait(false); + await this.ShowKanturuStateAsync(KanturuState.MayaBattle, (byte)KanturuMayaDetailState.Maya3).ConfigureAwait(false); await this.AdvancePhaseAsync(KanturuPhase.Phase3Bosses, WavePhase3Bosses, 2, - PlayerMessage.KanturuBothMayaHandsAppeared, ct).ConfigureAwait(false); + nameof(PlayerMessage.KanturuBothMayaHandsAppeared), ct).ConfigureAwait(false); // Hide HUD during the loot window — same reason as inter-phase standby. - await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.None).ConfigureAwait(false); + await this.ShowKanturuStateAsync(KanturuState.MayaBattle, (byte)KanturuMayaDetailState.None).ConfigureAwait(false); // 10-second loot window: players pick up drops from both Maya hands // before the Nightmare transition cinematic begins. @@ -385,7 +358,7 @@ await this.AdvancePhaseAsync(KanturuPhase.Phase3Bosses, WavePhase3Bosses, 2, // 2. NightmareBattle/Idle is sent AFTER teleport so the HUD change applies in // the Nightmare zone, not the Maya battlefield. await this.TeleportToNightmareRoomAsync(ct).ConfigureAwait(false); - await this.ShowNightmareStateToAllAsync(KanturuNightmareDetailState.Idle).ConfigureAwait(false); + await this.ShowKanturuStateAsync(KanturuState.NightmareBattle, (byte)KanturuNightmareDetailState.Idle).ConfigureAwait(false); // Nightmare Prep: spawn guardians immediately, then spawn Nightmare after 3 seconds. // No kill requirement — guardians fight alongside Nightmare. @@ -405,7 +378,7 @@ await this.AdvancePhaseAsync(KanturuPhase.Phase3Bosses, WavePhase3Bosses, 2, await this.RunNightmarePhaseAsync(ct).ConfigureAwait(false); // Victory - this._isVictory = true; + Interlocked.Exchange(ref this._isVictory, 1); this._phase = KanturuPhase.Ended; // Open the Elphis barrier (also sends the Success screen + Tower state). @@ -436,16 +409,14 @@ private async Task RunNightmarePhaseAsync(CancellationToken ct) this._nightmarePhase = 1; this._nightmareMonster = null; - // Do NOT reset _waveKillCount — it holds guardian kills from the 3-second prep window. - // _waveKillTarget = 46 (45 guardians + 1 Nightmare) so the HUD counter correctly - // reflects every remaining mob in the Nightmare zone, not just the boss. - this._waveKillTarget = 46; + Interlocked.Exchange(ref this._waveKillCount, 0); + this._waveKillTarget = 1; this._phaseComplete = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); this._phase = KanturuPhase.NightmareActive; // Nightmare intro cinematic — camera moves to Nightmare zone and summons Nightmare. - // This uses detail=Intro(2) = KANTURU_NIGHTMARE_DIRECTION_NIGHTMARE on the client. - await this.ShowNightmareStateToAllAsync(KanturuNightmareDetailState.Intro).ConfigureAwait(false); + // This uses detail=NightmareIntro(2) = KANTURU_NIGHTMARE_DIRECTION_NIGHTMARE on the client. + await this.ShowKanturuStateAsync(KanturuState.NightmareBattle, (byte)KanturuNightmareDetailState.NightmareIntro).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); // Subscribe to ObjectAdded to capture the Nightmare reference as soon as it spawns. @@ -480,11 +451,10 @@ await this._mapInitializer.InitializeNpcsOnWaveStartAsync(this.Map, this, WaveNi } // Switch to active battle state — shows Nightmare HUD on client (INTERFACE_KANTURU_INFO). - await this.ShowNightmareStateToAllAsync(KanturuNightmareDetailState.Battle).ConfigureAwait(false); + await this.ShowKanturuStateAsync(KanturuState.NightmareBattle, (byte)KanturuNightmareDetailState.Battle).ConfigureAwait(false); - // Show total remaining: alive guardians (45 − killed during prep) + 1 Nightmare. - var totalRemaining = Math.Max(1, this._waveKillTarget - this._waveKillCount); - await this.ShowMonsterUserCountAsync(totalRemaining, this.PlayerCount).ConfigureAwait(false); + // Show initial monster count: 1 Nightmare alive. + await this.ShowMonsterUserCountAsync(1, this.PlayerCount).ConfigureAwait(false); await this.ShowGoldenMessageAsync(nameof(PlayerMessage.KanturuNightmareAppeared)).ConfigureAwait(false); @@ -519,7 +489,7 @@ private async Task MonitorNightmareHpAsync(CancellationToken ct) // Do NOT check HP while a teleport is already in progress. // ExecuteNightmareTeleportAsync restores Nightmare's HP as part of the sequence; // reading HP mid-teleport would give a stale (low) value and re-trigger. - if (this._nightmareTeleporting) + if (Volatile.Read(ref this._nightmareTeleporting) != 0) { continue; } @@ -560,7 +530,7 @@ private async Task ExecuteNightmareTeleportAsync(Monster nightmare, int phase, C return; } - this._nightmareTeleporting = true; + Interlocked.Exchange(ref this._nightmareTeleporting, 1); try { var newPos = phase switch @@ -571,12 +541,12 @@ private async Task ExecuteNightmareTeleportAsync(Monster nightmare, int phase, C _ => NightmarePhase2Pos, }; - var msgKey = phase switch + var messageKey = phase switch { 2 => nameof(PlayerMessage.KanturuNightmareTeleport2), 3 => nameof(PlayerMessage.KanturuNightmareTeleport3), 4 => nameof(PlayerMessage.KanturuNightmareTeleport4), - _ => null, + _ => string.Empty, }; // Restore HP FIRST — any damage during the brief animation window will not kill Nightmare. @@ -594,14 +564,14 @@ private async Task ExecuteNightmareTeleportAsync(Monster nightmare, int phase, C // Restore HP a second time as a safety net — covers any hits landing in the 500 ms window. nightmare.Health = (int)nightmare.Attributes[Stats.MaximumHealth]; - if (msgKey is not null) + if (messageKey.Length > 0) { - await this.ShowGoldenMessageAsync(msgKey).ConfigureAwait(false); + await this.ShowGoldenMessageAsync(messageKey).ConfigureAwait(false); } } finally { - this._nightmareTeleporting = false; + Interlocked.Exchange(ref this._nightmareTeleporting, 0); } } @@ -625,8 +595,6 @@ private async ValueTask OpenElphisBarrierAsync() return; } - try - { this.Logger.LogInformation("Kanturu: opening Elphis barrier."); await this.ShowGoldenMessageAsync(nameof(PlayerMessage.KanturuBarrierOpening)).ConfigureAwait(false); @@ -635,7 +603,7 @@ private async ValueTask OpenElphisBarrierAsync() await this.ShowMonsterUserCountAsync(0, this.PlayerCount).ConfigureAwait(false); // Victory camera-out cinematic (KANTURU_NIGHTMARE_DIRECTION_END = 4). - await this.ShowNightmareStateToAllAsync(KanturuNightmareDetailState.End).ConfigureAwait(false); + await this.ShowKanturuStateAsync(KanturuState.NightmareBattle, (byte)KanturuNightmareDetailState.End).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); // 1. Send SUCCESS result overlay (Success_kantru.tga). @@ -644,12 +612,12 @@ private async ValueTask OpenElphisBarrierAsync() // the overlay renders while the client is still in the Nightmare state. await this.ForEachPlayerAsync(player => player.InvokeViewPlugInAsync(p => - p.ShowBattleResultAsync(KanturuBattleResult.Victory)) + p.ShowBattleResultAsync(true)) .AsTask()).ConfigureAwait(false); // 2. Send Tower state → client reloads EncTerrain(n)01.att (barrier-open terrain), // switches to Tower music, and plays the success sound. - await this.ShowTowerStateToAllAsync(KanturuTowerDetailState.Revitalization).ConfigureAwait(false); + await this.ShowKanturuStateAsync(KanturuState.Tower, (byte)KanturuTowerDetailState.Revitalization).ConfigureAwait(false); // 3. Update server-side walkmap so the AI pathfinder and movement checks // treat the formerly-blocked cells as passable. @@ -674,11 +642,6 @@ await this.ForEachPlayerAsync(player => player.InvokeViewPlugInAsync(p => p.ChangeAttributesAsync(TerrainAttributeType.NoGround, setAttribute: false, areas)) .AsTask()).ConfigureAwait(false); - } - catch (Exception ex) - { - this.Logger.LogError(ex, "{context}: Unexpected error while opening Elphis barrier.", this); - } } /// @@ -711,11 +674,9 @@ private async Task RunTowerOfRefinementAsync(CancellationToken ct) } // Tower closing notification - await this.ShowTowerStateToAllAsync(KanturuTowerDetailState.Notify).ConfigureAwait(false); - + await this.ShowKanturuStateAsync(KanturuState.Tower, (byte)KanturuTowerDetailState.Notify).ConfigureAwait(false); await this.ShowGoldenMessageAsync(nameof(PlayerMessage.KanturuTowerClosed)).ConfigureAwait(false); - - await this.ShowTowerStateToAllAsync(KanturuTowerDetailState.Close).ConfigureAwait(false); + await this.ShowKanturuStateAsync(KanturuState.Tower, (byte)KanturuTowerDetailState.Close).ConfigureAwait(false); } private async Task TeleportToNightmareRoomAsync(CancellationToken ct) @@ -730,7 +691,7 @@ private async Task TeleportToNightmareRoomAsync(CancellationToken ct) // Stage 2 — m_bDownHero=true: the hero "falls through the floor" into the Nightmare zone. // NOTE: 0xD1/0x04 result=1 (Success_kantru.tga) must NOT be sent here — that overlay // is only correct after Nightmare is defeated and is already sent in OpenElphisBarrierAsync. - await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.EndCycleMaya3).ConfigureAwait(false); + await this.ShowKanturuStateAsync(KanturuState.MayaBattle, (byte)KanturuMayaDetailState.EndCycleMaya3).ConfigureAwait(false); // Step 2: Wait for the client cinematic to complete before teleporting. // The three stages take roughly: camera pan ~3 s + explosion ~4 s + fall ~3 s ≈ 10 s. @@ -766,7 +727,7 @@ await this.ForEachPlayerAsync(player => /// private async Task RunMayaWideAreaAttacksAsync(CancellationToken ct) { - var attackType = KanturuMayaAttackType.Storm; + byte attackType = 0; while (!ct.IsCancellationRequested) { try @@ -779,24 +740,15 @@ private async Task RunMayaWideAreaAttacksAsync(CancellationToken ct) } // Skip the broadcast during inter-phase standby — Maya stays idle, no visual attacks. - if (!this._mayaAttacksPaused) + if (Volatile.Read(ref this._mayaAttacksPaused) == 0) { - try - { - await this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowMayaWideAreaAttackAsync(attackType)).AsTask()).ConfigureAwait(false); - } - catch (Exception ex) - { - this.Logger.LogWarning(ex, "{context}: Error sending Maya wide-area attack broadcast.", this); - } + var isStorm = attackType == 0; + await this.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => + p.ShowMayaWideAreaAttackAsync(isStorm)).AsTask()).ConfigureAwait(false); } - // Alternate Storm → Rain → Storm → … - attackType = attackType == KanturuMayaAttackType.Storm - ? KanturuMayaAttackType.Rain - : KanturuMayaAttackType.Storm; + attackType = (byte)(1 - attackType); // alternate storm → rain → storm → … } } @@ -830,7 +782,7 @@ private async Task RunNightmareSpecialAttacksAsync(CancellationToken ct) } // Skip during a teleport sequence to avoid animation conflicts. - if (this._nightmareTeleporting) + if (Volatile.Read(ref this._nightmareTeleporting) != 0) { continue; } @@ -844,11 +796,11 @@ await this.ForEachPlayerAsync(player => } } - private async Task SendStandbyMessageAsync(string messageKey, CancellationToken ct) + private async Task ShowStandbyMessageAsync(string messageKey, CancellationToken ct) { // Pause Maya wide-area attacks for the full duration of the standby so the // body stays visually idle (no storm/stone-rain effects between phases). - this._mayaAttacksPaused = true; + Interlocked.Exchange(ref this._mayaAttacksPaused, 1); try { // Hide the in-map HUD (INTERFACE_KANTURU_INFO) during the inter-phase standby. @@ -856,19 +808,17 @@ private async Task SendStandbyMessageAsync(string messageKey, CancellationToken // the monster count / user count / timer panel until the next phase begins. // Maya body (#364) stays at its spawn position — it is ~27 tiles from the fight // room and well outside its ViewRange=9, so it naturally idles without attacking. - await this.ShowMayaBattleStateToAllAsync(KanturuMayaDetailState.None).ConfigureAwait(false); - + await this.ShowKanturuStateAsync(KanturuState.MayaBattle, (byte)KanturuMayaDetailState.None).ConfigureAwait(false); await this.ShowGoldenMessageAsync(messageKey).ConfigureAwait(false); - await Task.Delay(TimeSpan.FromMinutes(2), ct).ConfigureAwait(false); } finally { - this._mayaAttacksPaused = false; + Interlocked.Exchange(ref this._mayaAttacksPaused, 0); } } - private async Task AdvancePhaseAsync(KanturuPhase phase, byte waveNumber, int killTarget, string messageKey, CancellationToken ct) + private async Task AdvancePhaseAsync(KanturuPhase phase, byte waveNumber, int killTarget, string message, CancellationToken ct) { Interlocked.Exchange(ref this._waveKillCount, 0); this._waveKillTarget = killTarget; @@ -879,56 +829,27 @@ private async Task AdvancePhaseAsync(KanturuPhase phase, byte waveNumber, int ki // Broadcast the initial monster count so the HUD shows the correct number from the start. await this.ShowMonsterUserCountAsync(killTarget, this.PlayerCount).ConfigureAwait(false); - - await this.ShowGoldenMessageAsync(messageKey).ConfigureAwait(false); + await this.ShowGoldenMessageAsync(message).ConfigureAwait(false); await this._phaseComplete.Task.WaitAsync(ct).ConfigureAwait(false); } /// - /// Broadcasts the Maya battle detail state (0xD1/0x03) to all players. - /// Updates and - /// so the Gateway NPC can report the current phase in the 0xD1/0x00 dialog. - /// - private ValueTask ShowMayaBattleStateToAllAsync(KanturuMayaDetailState detailState) - { - this.CurrentKanturuState = KanturuState.MayaBattle; - this.CurrentKanturuDetailState = ConvertMayaDetail(detailState); - return this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowMayaBattleStateAsync(detailState)).AsTask()); - } - - /// - /// Broadcasts the Nightmare battle detail state (0xD1/0x03) to all players. - /// Updates and . + /// Broadcasts packet 0xD1/0x03 (Kanturu state change) to all players currently on the map. + /// Also updates and + /// so the Gateway NPC plugin can report the current event phase in the 0xD1/0x00 dialog. /// - private ValueTask ShowNightmareStateToAllAsync(KanturuNightmareDetailState detailState) + private ValueTask ShowKanturuStateAsync(KanturuState state, byte detailState) { - this.CurrentKanturuState = KanturuState.NightmareBattle; - this.CurrentKanturuDetailState = ConvertNightmareDetail(detailState); + this.CurrentKanturuState = state; + this.CurrentKanturuDetailState = detailState; return this.ForEachPlayerAsync(player => player.InvokeViewPlugInAsync(p => - p.ShowNightmareStateAsync(detailState)).AsTask()); - } - - /// - /// Broadcasts the Tower detail state (0xD1/0x03) to all players. - /// Updates and . - /// - private ValueTask ShowTowerStateToAllAsync(KanturuTowerDetailState detailState) - { - this.CurrentKanturuState = KanturuState.Tower; - this.CurrentKanturuDetailState = ConvertTowerDetail(detailState); - return this.ForEachPlayerAsync(player => - player.InvokeViewPlugInAsync(p => - p.ShowTowerStateAsync(detailState)).AsTask()); + p.ShowStateChangeAsync(state, detailState)).AsTask()); } /// /// Broadcasts packet 0xD1/0x07 (Kanturu monster/user count) to all players currently on the map. - /// The view plugin clamps the values to byte range before sending. - /// This method does not throw — callers in rely on this guarantee. /// private ValueTask ShowMonsterUserCountAsync(int monsterCount, int userCount) { @@ -946,37 +867,4 @@ private ValueTask ShowTimeLimitToAllAsync(TimeSpan timeLimit) player.InvokeViewPlugInAsync(p => p.ShowTimeLimitAsync(timeLimit)).AsTask()); } - - private static byte ConvertMayaDetail(KanturuMayaDetailState detail) => detail switch - { - KanturuMayaDetailState.None => 0, - KanturuMayaDetailState.Notify => 2, - KanturuMayaDetailState.Monster1 => 3, - KanturuMayaDetailState.MayaLeft => 4, - KanturuMayaDetailState.Monster2 => 8, - KanturuMayaDetailState.MayaRight => 9, - KanturuMayaDetailState.Monster3 => 13, - KanturuMayaDetailState.BothHands => 14, - KanturuMayaDetailState.EndCycleMaya3 => 16, - _ => throw new ArgumentOutOfRangeException(nameof(detail), detail, null), - }; - - private static byte ConvertNightmareDetail(KanturuNightmareDetailState detail) => detail switch - { - KanturuNightmareDetailState.None => 0, - KanturuNightmareDetailState.Idle => 1, - KanturuNightmareDetailState.Intro => 2, - KanturuNightmareDetailState.Battle => 3, - KanturuNightmareDetailState.End => 4, - _ => throw new ArgumentOutOfRangeException(nameof(detail), detail, null), - }; - - private static byte ConvertTowerDetail(KanturuTowerDetailState detail) => detail switch - { - KanturuTowerDetailState.None => 0, - KanturuTowerDetailState.Revitalization => 1, - KanturuTowerDetailState.Notify => 2, - KanturuTowerDetailState.Close => 3, - _ => throw new ArgumentOutOfRangeException(nameof(detail), detail, null), - }; } From 086f799c3514f3ac77f2fd2f1dec5c9d61fbeadb Mon Sep 17 00:00:00 2001 From: "Cristobal Barra (Sistema)" <80994201+apraxico@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:12:10 -0400 Subject: [PATCH 07/11] refactor(kanturu): update KanturuGatewayPlugIn to use new Show* API with typed parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated variable types (byte→KanturuState, bool canEnter, TimeSpan remainTime) and call site updated from SendStateInfoAsync to ShowStateInfoAsync to match refactored interface. --- src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs b/src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs index dd1f2f826..1742174d8 100644 --- a/src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs +++ b/src/GameLogic/PlugIns/KanturuGatewayPlugIn.cs @@ -76,7 +76,7 @@ public static async ValueTask SendKanturuStateInfoAsync(Player player) byte detailState; bool canEnter; int userCount; - TimeSpan remainingTime; + TimeSpan remainTime; if (ctx is KanturuContext kanturuCtx) { @@ -87,13 +87,15 @@ public static async ValueTask SendKanturuStateInfoAsync(Player player) detailState = kanturuCtx.CurrentKanturuDetailState; // Entry allowed only during Maya-battle phases (including inter-phase standby). - // NightmareBattle and Tower phase are both sealed — players who are already - // inside the event access the Refinery Tower by walking through the opened - // Elphis barrier; the Gateway does not re-admit anyone for those phases. + // NightmareBattle: sealed — the Nightmare encounter cannot be joined mid-fight. + // Tower phase: sealed — survivors are auto-teleported to the Tower when the + // Elphis barrier opens; players who died are excluded from the Tower for that + // cycle and cannot re-enter via the Gateway (which would drop them in the Maya + // room and let them appear to restart the event). canEnter = state == KanturuState.MayaBattle; userCount = ctx.PlayerCount; - remainingTime = TimeSpan.Zero; + remainTime = TimeSpan.Zero; } else if (timeUntilOpening == TimeSpan.Zero) { @@ -103,7 +105,7 @@ public static async ValueTask SendKanturuStateInfoAsync(Player player) detailState = DetailStandbyOpen; canEnter = true; userCount = 0; - remainingTime = TimeSpan.Zero; + remainTime = TimeSpan.Zero; } else { @@ -112,11 +114,11 @@ public static async ValueTask SendKanturuStateInfoAsync(Player player) detailState = 1; // STANBY_START — client shows "Opens in X minutes" canEnter = false; userCount = 0; - remainingTime = timeUntilOpening ?? TimeSpan.Zero; + remainTime = timeUntilOpening ?? TimeSpan.Zero; } await player.InvokeViewPlugInAsync(p => - p.ShowStateInfoAsync(state, detailState, canEnter, userCount, remainingTime)) + p.ShowStateInfoAsync(state, detailState, canEnter, userCount, remainTime)) .ConfigureAwait(false); } } From d17db841c06022d2b095dae1eca132886e794c1f Mon Sep 17 00:00:00 2001 From: "Cristobal Barra (Sistema)" <80994201+apraxico@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:12:11 -0400 Subject: [PATCH 08/11] feat(kanturu): add KanturuConnectionExtensions with 7 SendKanturu*Async methods ConnectionExtensions.cs is auto-generated from XML and gets overwritten on rebuild. This separate static class in the same namespace provides the Kanturu-specific extension methods (SendKanturuStateInfoAsync, SendKanturuEnterResultAsync, SendKanturuStateChangeAsync, SendKanturuBattleResultAsync, SendKanturuTimeLimitAsync, SendKanturuMayaWideAreaAttackAsync, SendKanturuMonsterUserCountAsync) using the auto-generated *Ref structs from ServerToClientPacketsRef.cs. --- .../KanturuConnectionExtensions.cs | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 src/Network/Packets/ServerToClient/KanturuConnectionExtensions.cs diff --git a/src/Network/Packets/ServerToClient/KanturuConnectionExtensions.cs b/src/Network/Packets/ServerToClient/KanturuConnectionExtensions.cs new file mode 100644 index 000000000..acc446636 --- /dev/null +++ b/src/Network/Packets/ServerToClient/KanturuConnectionExtensions.cs @@ -0,0 +1,174 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Network.Packets.ServerToClient; + +using MUnique.OpenMU.Network; + +/// +/// Extension methods to send Kanturu event packets (0xD1 group) on a . +/// Kept in a separate file so they survive regeneration of the auto-generated ConnectionExtensions.cs. +/// +public static class KanturuConnectionExtensions +{ + /// + /// Sends a to this connection. + /// + public static async ValueTask SendKanturuStateInfoAsync(this IConnection? connection, KanturuStateInfo.StateType @state, byte @detailState, bool @canEnter, byte @userCount, uint @remainSeconds) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = KanturuStateInfoRef.Length; + var packet = new KanturuStateInfoRef(connection.Output.GetSpan(length)[..length]); + packet.State = @state; + packet.DetailState = @detailState; + packet.CanEnter = @canEnter; + packet.UserCount = @userCount; + packet.RemainSeconds = @remainSeconds; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + public static async ValueTask SendKanturuEnterResultAsync(this IConnection? connection, KanturuEnterResult.EnterResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = KanturuEnterResultRef.Length; + var packet = new KanturuEnterResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + public static async ValueTask SendKanturuStateChangeAsync(this IConnection? connection, KanturuStateChange.StateType @state, byte @detailState) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = KanturuStateChangeRef.Length; + var packet = new KanturuStateChangeRef(connection.Output.GetSpan(length)[..length]); + packet.State = @state; + packet.DetailState = @detailState; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + public static async ValueTask SendKanturuBattleResultAsync(this IConnection? connection, KanturuBattleResult.BattleResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = KanturuBattleResultRef.Length; + var packet = new KanturuBattleResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + public static async ValueTask SendKanturuTimeLimitAsync(this IConnection? connection, uint @timeLimitMilliseconds) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = KanturuTimeLimitRef.Length; + var packet = new KanturuTimeLimitRef(connection.Output.GetSpan(length)[..length]); + packet.TimeLimitMilliseconds = @timeLimitMilliseconds; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + public static async ValueTask SendKanturuMayaWideAreaAttackAsync(this IConnection? connection, KanturuMayaWideAreaAttack.AttackType @type) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = KanturuMayaWideAreaAttackRef.Length; + var packet = new KanturuMayaWideAreaAttackRef(connection.Output.GetSpan(length)[..length]); + packet.Type = @type; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + public static async ValueTask SendKanturuMonsterUserCountAsync(this IConnection? connection, byte @monsterCount, byte @userCount) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = KanturuMonsterUserCountRef.Length; + var packet = new KanturuMonsterUserCountRef(connection.Output.GetSpan(length)[..length]); + packet.MonsterCount = @monsterCount; + packet.UserCount = @userCount; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } +} From c07bffac05ab7ad7419bb48fbf4f9c7021e113c5 Mon Sep 17 00:00:00 2001 From: "Cristobal Barra (Sistema)" <80994201+apraxico@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:56:54 -0400 Subject: [PATCH 09/11] feat(kanturu): add Kanturu packet Send*Async extension methods to ConnectionExtensions Packets are defined in ServerToClientPackets.xml and *Ref structs are auto-generated. Adding the corresponding Send*Async extension methods here following the auto-generated pattern so they are properly documented and integrated with the existing codebase. Removes dependency on the separate KanturuConnectionExtensions.cs workaround. --- .../ServerToClient/ConnectionExtensions.cs | 12300 ++++++++-------- 1 file changed, 6254 insertions(+), 6046 deletions(-) diff --git a/src/Network/Packets/ServerToClient/ConnectionExtensions.cs b/src/Network/Packets/ServerToClient/ConnectionExtensions.cs index fefddfa0c..ee207a8ce 100644 --- a/src/Network/Packets/ServerToClient/ConnectionExtensions.cs +++ b/src/Network/Packets/ServerToClient/ConnectionExtensions.cs @@ -1,5627 +1,6208 @@ -// -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -//------------------------------------------------------------------------------ -// -// This source code was auto-generated by an XSL transformation. -// Do not change this file. Instead, change the XML data which contains -// the packet definitions and re-run the transformation (rebuild this project). -// -//------------------------------------------------------------------------------ - -// ReSharper disable RedundantVerbatimPrefix -// ReSharper disable AssignmentIsFullyDiscarded -// ReSharper disable UnusedMember.Global -// ReSharper disable UseObjectOrCollectionInitializer - -#nullable enable -namespace MUnique.OpenMU.Network.Packets.ServerToClient; - -using System; -using System.Threading; -using MUnique.OpenMU.Network; - -/// -/// Extension methods to start writing messages of this namespace on a . -/// -public static class ConnectionExtensions -{ - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player id. - /// The version string. - /// The version. - /// The success. - /// - /// Is sent by the server when: After a game client has connected to the game. - /// Causes reaction on client side: It shows the login dialog. - /// - public static async ValueTask SendGameServerEnteredAsync(this IConnection? connection, ushort @playerId, string @versionString, Memory @version, bool @success = true) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GameServerEnteredRef.Length; - var packet = new GameServerEnteredRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.PlayerId = @playerId; - packet.VersionString = @versionString; - @version.Span.CopyTo(packet.Version); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The is active. - /// The player id. - /// The effect id. - /// - /// Is sent by the server when: A magic effect was added or removed to the own or another player. - /// Causes reaction on client side: The user interface updates itself. If it's the effect of the own player, it's shown as icon at the top of the interface. - /// - public static async ValueTask SendMagicEffectStatusAsync(this IConnection? connection, bool @isActive, ushort @playerId, byte @effectId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MagicEffectStatusRef.Length; - var packet = new MagicEffectStatusRef(connection.Output.GetSpan(length)[..length]); - packet.IsActive = @isActive; - packet.PlayerId = @playerId; - packet.EffectId = @effectId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// A random value between 0 and 2 (inclusive). - /// A random value between 0 and 9 (inclusive). - /// - /// Is sent by the server when: The weather on the current map has been changed or the player entered the map. - /// Causes reaction on client side: The game client updates the weather effects. - /// - public static async ValueTask SendWeatherStatusUpdateAsync(this IConnection? connection, byte @weather, byte @variation) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = WeatherStatusUpdateRef.Length; - var packet = new WeatherStatusUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.Weather = @weather; - packet.Variation = @variation; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The id. - /// The current position x. - /// The current position y. - /// The target position x. - /// The target position y. - /// The rotation. - /// The hero state. - /// The attack speed. - /// The magic speed. - /// The name. - /// The appearance and effects. - /// - /// Is sent by the server when: One or more character got into the observed scope of the player. - /// Causes reaction on client side: The client adds the character to the shown map. - /// - public static async ValueTask SendAddCharacterToScopeExtendedAsync(this IConnection? connection, ushort @id, byte @currentPositionX, byte @currentPositionY, byte @targetPositionX, byte @targetPositionY, byte @rotation, CharacterHeroState @heroState, ushort @attackSpeed, ushort @magicSpeed, string @name, Memory @appearanceAndEffects) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = AddCharacterToScopeExtendedRef.GetRequiredSize(appearanceAndEffects.Length); - var packet = new AddCharacterToScopeExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Id = @id; - packet.CurrentPositionX = @currentPositionX; - packet.CurrentPositionY = @currentPositionY; - packet.TargetPositionX = @targetPositionX; - packet.TargetPositionY = @targetPositionY; - packet.Rotation = @rotation; - packet.HeroState = @heroState; - packet.AttackSpeed = @attackSpeed; - packet.MagicSpeed = @magicSpeed; - packet.Name = @name; - @appearanceAndEffects.Span.CopyTo(packet.AppearanceAndEffects); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The killed id. - /// The skill id. - /// The killer id. - /// - /// Is sent by the server when: An observed object was killed. - /// Causes reaction on client side: The object is shown as dead. - /// - public static async ValueTask SendObjectGotKilledAsync(this IConnection? connection, ushort @killedId, ushort @skillId, ushort @killerId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectGotKilledRef.Length; - var packet = new ObjectGotKilledRef(connection.Output.GetSpan(length)[..length]); - packet.KilledId = @killedId; - packet.SkillId = @skillId; - packet.KillerId = @killerId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The object id. - /// The direction. - /// The animation. - /// The target id. - /// - /// Is sent by the server when: An object performs an animation. - /// Causes reaction on client side: The animation is shown for the specified object. - /// - public static async ValueTask SendObjectAnimationAsync(this IConnection? connection, ushort @objectId, byte @direction, byte @animation, ushort @targetId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectAnimationRef.Length; - var packet = new ObjectAnimationRef(connection.Output.GetSpan(length)[..length]); - packet.ObjectId = @objectId; - packet.Direction = @direction; - packet.Animation = @animation; - packet.TargetId = @targetId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The player id. - /// The point x. - /// The point y. - /// The rotation. - /// - /// Is sent by the server when: An object performs a skill which has effect on an area. - /// Causes reaction on client side: The animation is shown on the user interface. - /// - public static async ValueTask SendAreaSkillAnimationAsync(this IConnection? connection, ushort @skillId, ushort @playerId, byte @pointX, byte @pointY, byte @rotation) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = AreaSkillAnimationRef.Length; - var packet = new AreaSkillAnimationRef(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.PlayerId = @playerId; - packet.PointX = @pointX; - packet.PointY = @pointY; - packet.Rotation = @rotation; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The player id. - /// The target id. - /// - /// Is sent by the server when: An object performs a skill which is directly targeted to another object. - /// Causes reaction on client side: The animation is shown on the user interface. - /// - public static async ValueTask SendSkillAnimationAsync(this IConnection? connection, ushort @skillId, ushort @playerId, ushort @targetId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillAnimationRef.Length; - var packet = new SkillAnimationRef(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.PlayerId = @playerId; - packet.TargetId = @targetId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The player id. - /// The point x. - /// The point y. - /// The rotation. - /// - /// Is sent by the server when: An object performs a skill which has effect on an area. - /// Causes reaction on client side: The animation is shown on the user interface. - /// - public static async ValueTask SendAreaSkillAnimation075Async(this IConnection? connection, byte @skillId, ushort @playerId, byte @pointX, byte @pointY, byte @rotation) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = AreaSkillAnimation075Ref.Length; - var packet = new AreaSkillAnimation075Ref(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.PlayerId = @playerId; - packet.PointX = @pointX; - packet.PointY = @pointY; - packet.Rotation = @rotation; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The player id. - /// The point x. - /// The point y. - /// The rotation. - /// - /// Is sent by the server when: An object performs a skill which has effect on an area. - /// Causes reaction on client side: The animation is shown on the user interface. - /// - public static async ValueTask SendAreaSkillAnimation095Async(this IConnection? connection, byte @skillId, ushort @playerId, byte @pointX, byte @pointY, byte @rotation) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = AreaSkillAnimation095Ref.Length; - var packet = new AreaSkillAnimation095Ref(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.PlayerId = @playerId; - packet.PointX = @pointX; - packet.PointY = @pointY; - packet.Rotation = @rotation; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The player id. - /// The target id. - /// The effect applied. - /// - /// Is sent by the server when: An object performs a skill which is directly targeted to another object. - /// Causes reaction on client side: The animation is shown on the user interface. - /// - public static async ValueTask SendSkillAnimation075Async(this IConnection? connection, byte @skillId, ushort @playerId, ushort @targetId, bool @effectApplied) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillAnimation075Ref.Length; - var packet = new SkillAnimation075Ref(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.PlayerId = @playerId; - packet.TargetId = @targetId; - packet.EffectApplied = @effectApplied; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The player id. - /// The target id. - /// The effect applied. - /// - /// Is sent by the server when: An object performs a skill which is directly targeted to another object. - /// Causes reaction on client side: The animation is shown on the user interface. - /// - public static async ValueTask SendSkillAnimation095Async(this IConnection? connection, byte @skillId, ushort @playerId, ushort @targetId, bool @effectApplied) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillAnimation095Ref.Length; - var packet = new SkillAnimation095Ref(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.PlayerId = @playerId; - packet.TargetId = @targetId; - packet.EffectApplied = @effectApplied; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The target id. - /// - /// Is sent by the server when: A player cancelled a specific magic effect of a skill (Infinity Arrow, Wizardry Enhance), or an effect was removed due a timeout (Ice, Poison) or antidote. - /// Causes reaction on client side: The effect is removed from the target object. - /// - public static async ValueTask SendMagicEffectCancelledAsync(this IConnection? connection, ushort @skillId, ushort @targetId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MagicEffectCancelledRef.Length; - var packet = new MagicEffectCancelledRef(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.TargetId = @targetId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The target id. - /// - /// Is sent by the server when: A player cancelled a specific magic effect of a skill (Infinity Arrow, Wizardry Enhance), or an effect was removed due a timeout (Ice, Poison) or antidote. - /// Causes reaction on client side: The effect is removed from the target object. - /// - public static async ValueTask SendMagicEffectCancelled075Async(this IConnection? connection, byte @skillId, ushort @targetId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MagicEffectCancelled075Ref.Length; - var packet = new MagicEffectCancelled075Ref(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.TargetId = @targetId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The source id. - /// The target id. - /// - /// Is sent by the server when: A player (rage fighter) performs the dark side skill on a target and sent a RageAttackRangeRequest. - /// Causes reaction on client side: The targets are attacked with visual effects. - /// - public static async ValueTask SendRageAttackAsync(this IConnection? connection, ushort @skillId, ushort @sourceId, ushort @targetId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = RageAttackRef.Length; - var packet = new RageAttackRef(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.SourceId = @sourceId; - packet.TargetId = @targetId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The changed player id. - /// The item data. - /// - /// Is sent by the server when: The appearance of a player changed, all surrounding players are informed about it. - /// Causes reaction on client side: The appearance of the player is updated. - /// - public static async ValueTask SendAppearanceChangedAsync(this IConnection? connection, ushort @changedPlayerId, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = AppearanceChangedRef.GetRequiredSize(itemData.Length); - var packet = new AppearanceChangedRef(connection.Output.GetSpan(length)[..length]); - packet.ChangedPlayerId = @changedPlayerId; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The changed player id. - /// The item slot. - /// The item group. - /// The item number. - /// The item level. - /// The excellent flags. - /// The ancient discriminator. - /// The is ancient set complete. - /// - /// Is sent by the server when: The appearance of a player changed, all surrounding players are informed about it. - /// Causes reaction on client side: The appearance of the player is updated. - /// - public static async ValueTask SendAppearanceChangedExtendedAsync(this IConnection? connection, ushort @changedPlayerId, byte @itemSlot, byte @itemGroup, ushort @itemNumber, byte @itemLevel, byte @excellentFlags, byte @ancientDiscriminator, bool @isAncientSetComplete) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = AppearanceChangedExtendedRef.Length; - var packet = new AppearanceChangedExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.ChangedPlayerId = @changedPlayerId; - packet.ItemSlot = @itemSlot; - packet.ItemGroup = @itemGroup; - packet.ItemNumber = @itemNumber; - packet.ItemLevel = @itemLevel; - packet.ExcellentFlags = @excellentFlags; - packet.AncientDiscriminator = @ancientDiscriminator; - packet.IsAncientSetComplete = @isAncientSetComplete; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The object id. - /// The message. - /// - /// Is sent by the server when: The server wants to show a message above any kind of character, even NPCs. - /// Causes reaction on client side: The message is shown above the character. - /// - public static async ValueTask SendObjectMessageAsync(this IConnection? connection, ushort @objectId, string @message) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectMessageRef.GetRequiredSize(message); - var packet = new ObjectMessageRef(connection.Output.GetSpan(length)[..length]); - packet.ObjectId = @objectId; - packet.Message = @message; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The requester id. - /// - /// Is sent by the server when: Another player requests party from the receiver of this message. - /// Causes reaction on client side: The party request is shown. - /// - public static async ValueTask SendPartyRequestAsync(this IConnection? connection, ushort @requesterId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PartyRequestRef.Length; - var packet = new PartyRequestRef(connection.Output.GetSpan(length)[..length]); - packet.RequesterId = @requesterId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The index. - /// - /// Is sent by the server when: A party member got removed from a party in which the player is in. - /// Causes reaction on client side: The party member with the specified index is removed from the party list on the user interface. - /// - public static async ValueTask SendRemovePartyMemberAsync(this IConnection? connection, byte @index) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = RemovePartyMemberRef.Length; - var packet = new RemovePartyMemberRef(connection.Output.GetSpan(length)[..length]); - packet.Index = @index; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// - /// Is sent by the server when: After the player requested to open his shop and this request was successful. - /// Causes reaction on client side: The own player shop is shown as open. - /// - public static async ValueTask SendPlayerShopOpenSuccessfulAsync(this IConnection? connection, bool @success = true) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PlayerShopOpenSuccessfulRef.Length; - var packet = new PlayerShopOpenSuccessfulRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The state. - /// - /// Is sent by the server when: After the trading partner checked or unchecked the trade accept button. - /// Causes reaction on client side: The game client updates the trade button state accordingly. - /// - public static async ValueTask SendTradeButtonStateChangedAsync(this IConnection? connection, TradeButtonStateChanged.TradeButtonState @state) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeButtonStateChangedRef.Length; - var packet = new TradeButtonStateChangedRef(connection.Output.GetSpan(length)[..length]); - packet.State = @state; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: The trade money has been set by a previous request of the player. - /// Causes reaction on client side: The money which was set into the trade by the player is updated on the UI. - /// - public static async ValueTask SendTradeMoneySetResponseAsync(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeMoneySetResponseRef.Length; - var packet = new TradeMoneySetResponseRef(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The money amount. - /// - /// Is sent by the server when: This message is sent when the trading partner put a certain amount of money (also 0) into the trade. - /// Causes reaction on client side: It overrides all previous sent money values. - /// - public static async ValueTask SendTradeMoneyUpdateAsync(this IConnection? connection, uint @moneyAmount) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeMoneyUpdateRef.Length; - var packet = new TradeMoneyUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.MoneyAmount = @moneyAmount; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The accepted. - /// The name. - /// The trade partner level. - /// The guild id. - /// - /// Is sent by the server when: The player which receives this message, sent a trade request to another player. This message is sent when the other player responded to this request. - /// Causes reaction on client side: If the trade was accepted, a trade dialog is opened. Otherwise, a message is shown. - /// - public static async ValueTask SendTradeRequestAnswerAsync(this IConnection? connection, bool @accepted, string @name, ushort @tradePartnerLevel, uint @guildId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeRequestAnswerRef.Length; - var packet = new TradeRequestAnswerRef(connection.Output.GetSpan(length)[..length]); - packet.Accepted = @accepted; - packet.Name = @name; - packet.TradePartnerLevel = @tradePartnerLevel; - packet.GuildId = @guildId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The name. - /// - /// Is sent by the server when: A trade was requested by another player. - /// Causes reaction on client side: A trade request dialog is shown. - /// - public static async ValueTask SendTradeRequestAsync(this IConnection? connection, string @name) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeRequestRef.Length; - var packet = new TradeRequestRef(connection.Output.GetSpan(length)[..length]); - packet.Name = @name; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: A trade was finished. - /// Causes reaction on client side: The trade dialog is closed. Depending on the result, a message is shown. - /// - public static async ValueTask SendTradeFinishedAsync(this IConnection? connection, TradeFinished.TradeResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeFinishedRef.Length; - var packet = new TradeFinishedRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The to slot. - /// The item data. - /// - /// Is sent by the server when: The trading partner added an item to the trade. - /// Causes reaction on client side: The item is added in the trade dialog. - /// - public static async ValueTask SendTradeItemAddedAsync(this IConnection? connection, byte @toSlot, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeItemAddedRef.GetRequiredSize(itemData.Length); - var packet = new TradeItemAddedRef(connection.Output.GetSpan(length)[..length]); - packet.ToSlot = @toSlot; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The slot. - /// - /// Is sent by the server when: The trading partner removed an item from the trade. - /// Causes reaction on client side: The item is removed from the trade dialog. - /// - public static async ValueTask SendTradeItemRemovedAsync(this IConnection? connection, byte @slot) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeItemRemovedRef.Length; - var packet = new TradeItemRemovedRef(connection.Output.GetSpan(length)[..length]); - packet.Slot = @slot; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// - /// Is sent by the server when: After the login request has been processed by the server. - /// Causes reaction on client side: Shows the result. When it was successful, the client proceeds by sending a character list request. - /// - public static async ValueTask SendLoginResponseAsync(this IConnection? connection, LoginResponse.LoginResult @success) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = LoginResponseRef.Length; - var packet = new LoginResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The type. - /// - /// Is sent by the server when: After the logout request has been processed by the server. - /// Causes reaction on client side: Depending on the result, the game client closes the game or changes to another selection screen. - /// - public static async ValueTask SendLogoutResponseAsync(this IConnection? connection, LogOutType @type) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = LogoutResponseRef.Length; - var packet = new LogoutResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Type = @type; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The type. - /// The sender. - /// The message. - /// - /// Is sent by the server when: A player sends a chat message. - /// Causes reaction on client side: The message is shown in the chat box and above the character of the sender. - /// - public static async ValueTask SendChatMessageAsync(this IConnection? connection, ChatMessage.ChatMessageType @type, string @sender, string @message) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ChatMessageRef.GetRequiredSize(message); - var packet = new ChatMessageRef(connection.Output.GetSpan(length)[..length]); - packet.Type = @type; - packet.Sender = @sender; - packet.Message = @message; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The header code. - /// The object id. - /// The health damage. - /// The kind. - /// The is rage fighter streak hit. - /// The is rage fighter streak final hit. - /// The is double damage. - /// The is triple damage. - /// The shield damage. - /// - /// Is sent by the server when: An object got hit in two cases: 1. When the own player is hit; 2. When the own player attacked some other object which got hit. - /// Causes reaction on client side: The damage is shown at the object which received the hit. - /// - public static async ValueTask SendObjectHitAsync(this IConnection? connection, byte @headerCode, ushort @objectId, ushort @healthDamage, DamageKind @kind, bool @isRageFighterStreakHit, bool @isRageFighterStreakFinalHit, bool @isDoubleDamage, bool @isTripleDamage, ushort @shieldDamage) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectHitRef.Length; - var packet = new ObjectHitRef(connection.Output.GetSpan(length)[..length]); - packet.HeaderCode = @headerCode; - packet.ObjectId = @objectId; - packet.HealthDamage = @healthDamage; - packet.Kind = @kind; - packet.IsRageFighterStreakHit = @isRageFighterStreakHit; - packet.IsRageFighterStreakFinalHit = @isRageFighterStreakFinalHit; - packet.IsDoubleDamage = @isDoubleDamage; - packet.IsTripleDamage = @isTripleDamage; - packet.ShieldDamage = @shieldDamage; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The kind. - /// The is rage fighter streak hit. - /// The is rage fighter streak final hit. - /// The is double damage. - /// The is triple damage. - /// The object id. - /// Gets or sets the status of the remaining health in fractions of 1/250. - /// Gets or sets the status of the remaining shield in fractions of 1/250. - /// The health damage. - /// The shield damage. - /// - /// Is sent by the server when: An object got hit in two cases: 1. When the own player is hit; 2. When the own player attacked some other object which got hit. - /// Causes reaction on client side: The damage is shown at the object which received the hit. - /// - public static async ValueTask SendObjectHitExtendedAsync(this IConnection? connection, DamageKind @kind, bool @isRageFighterStreakHit, bool @isRageFighterStreakFinalHit, bool @isDoubleDamage, bool @isTripleDamage, ushort @objectId, byte @healthStatus, byte @shieldStatus, uint @healthDamage, uint @shieldDamage) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectHitExtendedRef.Length; - var packet = new ObjectHitExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Kind = @kind; - packet.IsRageFighterStreakHit = @isRageFighterStreakHit; - packet.IsRageFighterStreakFinalHit = @isRageFighterStreakFinalHit; - packet.IsDoubleDamage = @isDoubleDamage; - packet.IsTripleDamage = @isTripleDamage; - packet.ObjectId = @objectId; - packet.HealthStatus = @healthStatus; - packet.ShieldStatus = @shieldStatus; - packet.HealthDamage = @healthDamage; - packet.ShieldDamage = @shieldDamage; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The header code. - /// The object id. - /// The position x. - /// The position y. - /// - /// Is sent by the server when: An object in the observed scope (including the own player) moved instantly. - /// Causes reaction on client side: The position of the object is updated on client side. - /// - public static async ValueTask SendObjectMovedAsync(this IConnection? connection, byte @headerCode, ushort @objectId, byte @positionX, byte @positionY) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectMovedRef.Length; - var packet = new ObjectMovedRef(connection.Output.GetSpan(length)[..length]); - packet.HeaderCode = @headerCode; - packet.ObjectId = @objectId; - packet.PositionX = @positionX; - packet.PositionY = @positionY; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The header code. - /// The object id. - /// The target x. - /// The target y. - /// The target rotation. - /// The step count. - /// The step data. - /// - /// Is sent by the server when: An object in the observed scope (including the own player) walked to another position. - /// Causes reaction on client side: The object is animated to walk to the new position. - /// - public static async ValueTask SendObjectWalkedAsync(this IConnection? connection, byte @headerCode, ushort @objectId, byte @targetX, byte @targetY, byte @targetRotation, byte @stepCount, Memory @stepData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectWalkedRef.GetRequiredSize(stepData.Length); - var packet = new ObjectWalkedRef(connection.Output.GetSpan(length)[..length]); - packet.HeaderCode = @headerCode; - packet.ObjectId = @objectId; - packet.TargetX = @targetX; - packet.TargetY = @targetY; - packet.TargetRotation = @targetRotation; - packet.StepCount = @stepCount; - @stepData.Span.CopyTo(packet.StepData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The header code. - /// The object id. - /// The source x. - /// The source y. - /// The target x. - /// The target y. - /// The target rotation. - /// The step count. - /// The step data. - /// - /// Is sent by the server when: An object in the observed scope (including the own player) walked to another position. - /// Causes reaction on client side: The object is animated to walk to the new position. - /// - public static async ValueTask SendObjectWalkedExtendedAsync(this IConnection? connection, byte @headerCode, ushort @objectId, byte @sourceX, byte @sourceY, byte @targetX, byte @targetY, byte @targetRotation, byte @stepCount, Memory @stepData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectWalkedExtendedRef.GetRequiredSize(stepData.Length); - var packet = new ObjectWalkedExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.HeaderCode = @headerCode; - packet.ObjectId = @objectId; - packet.SourceX = @sourceX; - packet.SourceY = @sourceY; - packet.TargetX = @targetX; - packet.TargetY = @targetY; - packet.TargetRotation = @targetRotation; - packet.StepCount = @stepCount; - @stepData.Span.CopyTo(packet.StepData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The object id. - /// The target x. - /// The target y. - /// The target rotation. - /// - /// Is sent by the server when: An object in the observed scope (including the own player) walked to another position. - /// Causes reaction on client side: The object is animated to walk to the new position. - /// - public static async ValueTask SendObjectWalked075Async(this IConnection? connection, ushort @objectId, byte @targetX, byte @targetY, byte @targetRotation) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectWalked075Ref.Length; - var packet = new ObjectWalked075Ref(connection.Output.GetSpan(length)[..length]); - packet.ObjectId = @objectId; - packet.TargetX = @targetX; - packet.TargetY = @targetY; - packet.TargetRotation = @targetRotation; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The killed object id. - /// The added experience. - /// The damage of last hit. - /// - /// Is sent by the server when: A player gained experience. - /// Causes reaction on client side: The experience is added to the experience counter and bar. - /// - public static async ValueTask SendExperienceGainedAsync(this IConnection? connection, ushort @killedObjectId, ushort @addedExperience, ushort @damageOfLastHit) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ExperienceGainedRef.Length; - var packet = new ExperienceGainedRef(connection.Output.GetSpan(length)[..length]); - packet.KilledObjectId = @killedObjectId; - packet.AddedExperience = @addedExperience; - packet.DamageOfLastHit = @damageOfLastHit; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The type. - /// The added experience. - /// The damage of last hit. - /// The killed object id. - /// The killer object id. - /// - /// Is sent by the server when: A player gained experience. - /// Causes reaction on client side: The experience is added to the experience counter and bar. - /// - public static async ValueTask SendExperienceGainedExtendedAsync(this IConnection? connection, ExperienceGainedExtended.AddResult @type, uint @addedExperience, uint @damageOfLastHit, ushort @killedObjectId, ushort @killerObjectId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ExperienceGainedExtendedRef.Length; - var packet = new ExperienceGainedExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Type = @type; - packet.AddedExperience = @addedExperience; - packet.DamageOfLastHit = @damageOfLastHit; - packet.KilledObjectId = @killedObjectId; - packet.KillerObjectId = @killerObjectId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The map number. - /// The position x. - /// The position y. - /// The rotation. - /// If false, it shows the teleport animation (white bubbles), and the client doesn't remove all of the objects in its scope. - /// - /// Is sent by the server when: The map was changed on the server side. - /// Causes reaction on client side: The game client changes to the specified map and coordinates. - /// - public static async ValueTask SendMapChangedAsync(this IConnection? connection, ushort @mapNumber, byte @positionX, byte @positionY, byte @rotation, bool @isMapChange = true) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MapChangedRef.Length; - var packet = new MapChangedRef(connection.Output.GetSpan(length)[..length]); - packet.IsMapChange = @isMapChange; - packet.MapNumber = @mapNumber; - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.Rotation = @rotation; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The map number. - /// The position x. - /// The position y. - /// The rotation. - /// If false, it shows the teleport animation (white bubbles), and the client doesn't remove all of the objects in its scope. - /// - /// Is sent by the server when: The map was changed on the server side. - /// Causes reaction on client side: The game client changes to the specified map and coordinates. - /// - public static async ValueTask SendMapChanged075Async(this IConnection? connection, byte @mapNumber, byte @positionX, byte @positionY, byte @rotation, bool @isMapChange = true) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MapChanged075Ref.Length; - var packet = new MapChanged075Ref(connection.Output.GetSpan(length)[..length]); - packet.IsMapChange = @isMapChange; - packet.MapNumber = @mapNumber; - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.Rotation = @rotation; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The binary data of the key configuration - /// - /// Is sent by the server when: When entering the game world with a character. - /// Causes reaction on client side: The client restores this configuration in its user interface. - /// - public static async ValueTask SendApplyKeyConfigurationAsync(this IConnection? connection, Memory @configuration) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ApplyKeyConfigurationRef.GetRequiredSize(configuration.Length); - var packet = new ApplyKeyConfigurationRef(connection.Output.GetSpan(length)[..length]); - @configuration.Span.CopyTo(packet.Configuration); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The id. - /// If this flag is set, the money is added to the map with an animation and sound. Otherwise it's just added like it was already on the ground before. - /// The position x. - /// The position y. - /// The amount. - /// The item count. - /// The money group. - /// The money number. - /// - /// Is sent by the server when: Money dropped on the ground. - /// Causes reaction on client side: The client adds the money to the ground. - /// - public static async ValueTask SendMoneyDroppedAsync(this IConnection? connection, ushort @id, bool @isFreshDrop, byte @positionX, byte @positionY, uint @amount, byte @itemCount = 1, byte @moneyGroup = 14, byte @moneyNumber = 15) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MoneyDroppedRef.Length; - var packet = new MoneyDroppedRef(connection.Output.GetSpan(length)[..length]); - packet.ItemCount = @itemCount; - packet.Id = @id; - packet.IsFreshDrop = @isFreshDrop; - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.MoneyNumber = @moneyNumber; - packet.Amount = @amount; - packet.MoneyGroup = @moneyGroup; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// If this flag is set, the money is added to the map with an animation and sound. Otherwise, it's just added like it was already on the ground before. - /// The id. - /// The position x. - /// The position y. - /// The amount. - /// - /// Is sent by the server when: Money dropped on the ground. - /// Causes reaction on client side: The client adds the money to the ground. - /// - public static async ValueTask SendMoneyDroppedExtendedAsync(this IConnection? connection, bool @isFreshDrop, ushort @id, byte @positionX, byte @positionY, uint @amount) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MoneyDroppedExtendedRef.Length; - var packet = new MoneyDroppedExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.IsFreshDrop = @isFreshDrop; - packet.Id = @id; - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.Amount = @amount; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The id. - /// If this flag is set, the money is added to the map with an animation and sound. Otherwise it's just added like it was already on the ground before. - /// The position x. - /// The position y. - /// The amount. - /// The item count. - /// The money group. - /// The money number. - /// - /// Is sent by the server when: Money dropped on the ground. - /// Causes reaction on client side: The client adds the money to the ground. - /// - public static async ValueTask SendMoneyDropped075Async(this IConnection? connection, ushort @id, bool @isFreshDrop, byte @positionX, byte @positionY, uint @amount, byte @itemCount = 1, byte @moneyGroup = 14, byte @moneyNumber = 15) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MoneyDropped075Ref.Length; - var packet = new MoneyDropped075Ref(connection.Output.GetSpan(length)[..length]); - packet.ItemCount = @itemCount; - packet.Id = @id; - packet.IsFreshDrop = @isFreshDrop; - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.MoneyNumber = @moneyNumber; - packet.MoneyGroup = @moneyGroup; - packet.Amount = @amount; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The inventory slot. - /// The item data. - /// - /// Is sent by the server when: A new item was added to the inventory. - /// Causes reaction on client side: The client adds the item to the inventory user interface. - /// - public static async ValueTask SendItemAddedToInventoryAsync(this IConnection? connection, byte @inventorySlot, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemAddedToInventoryRef.GetRequiredSize(itemData.Length); - var packet = new ItemAddedToInventoryRef(connection.Output.GetSpan(length)[..length]); - packet.InventorySlot = @inventorySlot; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The inventory slot. - /// - /// Is sent by the server when: The player requested to drop an item of his inventory. This message is the response about the success of the request. - /// Causes reaction on client side: If successful, the client removes the item from the inventory user interface. - /// - public static async ValueTask SendItemDropResponseAsync(this IConnection? connection, bool @success, byte @inventorySlot) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemDropResponseRef.Length; - var packet = new ItemDropResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.InventorySlot = @inventorySlot; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The fail reason. - /// - /// Is sent by the server when: The player requested to pick up an item from to ground to add it to his inventory, but it failed. - /// Causes reaction on client side: Depending on the reason, the game client shows a message. - /// - public static async ValueTask SendItemPickUpRequestFailedAsync(this IConnection? connection, ItemPickUpRequestFailed.ItemPickUpFailReason @failReason) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemPickUpRequestFailedRef.Length; - var packet = new ItemPickUpRequestFailedRef(connection.Output.GetSpan(length)[..length]); - packet.FailReason = @failReason; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The money. - /// - /// Is sent by the server when: The players money amount of the inventory has been changed and needs an update. - /// Causes reaction on client side: The money is updated in the inventory user interface. - /// - public static async ValueTask SendInventoryMoneyUpdateAsync(this IConnection? connection, uint @money) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = InventoryMoneyUpdateRef.Length; - var packet = new InventoryMoneyUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.Money = @money; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The target storage type. - /// The target slot. - /// The item data. - /// - /// Is sent by the server when: An item in the inventory or vault of the player has been moved. - /// Causes reaction on client side: The client updates the position of item in the user interface. - /// - public static async ValueTask SendItemMovedAsync(this IConnection? connection, ItemStorageKind @targetStorageType, byte @targetSlot, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemMovedRef.GetRequiredSize(itemData.Length); - var packet = new ItemMovedRef(connection.Output.GetSpan(length)[..length]); - packet.TargetStorageType = @targetStorageType; - packet.TargetSlot = @targetSlot; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The item data. - /// - /// Is sent by the server when: An item in the inventory or vault of the player could not be moved as requested by the player. - /// Causes reaction on client side: The client restores the position of item in the user interface. - /// - public static async ValueTask SendItemMoveRequestFailedAsync(this IConnection? connection, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemMoveRequestFailedRef.GetRequiredSize(itemData.Length); - var packet = new ItemMoveRequestFailedRef(connection.Output.GetSpan(length)[..length]); - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health. - /// The shield. - /// - /// Is sent by the server when: Periodically, or if the current health or shield changed on the server side, e.g. by hits. - /// Causes reaction on client side: The health and shield bar is updated on the game client user interface. - /// - public static async ValueTask SendCurrentHealthAndShieldAsync(this IConnection? connection, ushort @health, ushort @shield) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CurrentHealthAndShieldRef.Length; - var packet = new CurrentHealthAndShieldRef(connection.Output.GetSpan(length)[..length]); - packet.Health = @health; - packet.Shield = @shield; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health. - /// The shield. - /// - /// Is sent by the server when: When the maximum health changed, e.g. by adding stat points or changed items. - /// Causes reaction on client side: The health and shield bar is updated on the game client user interface. - /// - public static async ValueTask SendMaximumHealthAndShieldAsync(this IConnection? connection, ushort @health, ushort @shield) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MaximumHealthAndShieldRef.Length; - var packet = new MaximumHealthAndShieldRef(connection.Output.GetSpan(length)[..length]); - packet.Health = @health; - packet.Shield = @shield; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health. - /// The shield. - /// The mana. - /// The ability. - /// The attack speed. - /// The magic speed. - /// - /// Is sent by the server when: Periodically, or if the current stats, like health, shield, mana or attack speed changed on the server side, e.g. by hits. - /// Causes reaction on client side: The values are updated on the game client user interface. - /// - public static async ValueTask SendCurrentStatsExtendedAsync(this IConnection? connection, uint @health, uint @shield, uint @mana, uint @ability, ushort @attackSpeed, ushort @magicSpeed) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CurrentStatsExtendedRef.Length; - var packet = new CurrentStatsExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Health = @health; - packet.Shield = @shield; - packet.Mana = @mana; - packet.Ability = @ability; - packet.AttackSpeed = @attackSpeed; - packet.MagicSpeed = @magicSpeed; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health. - /// The shield. - /// The mana. - /// The ability. - /// - /// Is sent by the server when: When the maximum stats, like health, shield, mana or attack speed changed on the server side, e.g. by adding stat points or changed items. - /// Causes reaction on client side: The values are updated on the game client user interface. - /// - public static async ValueTask SendMaximumStatsExtendedAsync(this IConnection? connection, uint @health, uint @shield, uint @mana, uint @ability) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MaximumStatsExtendedRef.Length; - var packet = new MaximumStatsExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Health = @health; - packet.Shield = @shield; - packet.Mana = @mana; - packet.Ability = @ability; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health. - /// The shield. - /// - /// Is sent by the server when: When the consumption of an item failed. - /// Causes reaction on client side: The game client gets a feedback about a failed consumption, and allows for do further consumption requests. - /// - public static async ValueTask SendItemConsumptionFailedAsync(this IConnection? connection, ushort @health, ushort @shield) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemConsumptionFailedRef.Length; - var packet = new ItemConsumptionFailedRef(connection.Output.GetSpan(length)[..length]); - packet.Health = @health; - packet.Shield = @shield; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health. - /// The shield. - /// - /// Is sent by the server when: When the consumption of an item failed. - /// Causes reaction on client side: The game client gets a feedback about a failed consumption, and allows for do further consumption requests. - /// - public static async ValueTask SendItemConsumptionFailedExtendedAsync(this IConnection? connection, uint @health, uint @shield) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemConsumptionFailedExtendedRef.Length; - var packet = new ItemConsumptionFailedExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Health = @health; - packet.Shield = @shield; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The strength. - /// The agility. - /// The vitality. - /// The energy. - /// The command. - /// - /// Is sent by the server when: Setting the base stats of a character, e.g. set stats command or after a reset. - /// Causes reaction on client side: The values are updated on the game client user interface. - /// - public static async ValueTask SendBaseStatsExtendedAsync(this IConnection? connection, uint @strength, uint @agility, uint @vitality, uint @energy, uint @command) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = BaseStatsExtendedRef.Length; - var packet = new BaseStatsExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Strength = @strength; - packet.Agility = @agility; - packet.Vitality = @vitality; - packet.Energy = @energy; - packet.Command = @command; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The mana. - /// The ability. - /// - /// Is sent by the server when: The currently available mana or ability has changed, e.g. by using a skill. - /// Causes reaction on client side: The mana and ability bar is updated on the game client user interface. - /// - public static async ValueTask SendCurrentManaAndAbilityAsync(this IConnection? connection, ushort @mana, ushort @ability) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CurrentManaAndAbilityRef.Length; - var packet = new CurrentManaAndAbilityRef(connection.Output.GetSpan(length)[..length]); - packet.Mana = @mana; - packet.Ability = @ability; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The mana. - /// The ability. - /// - /// Is sent by the server when: The maximum available mana or ability has changed, e.g. by adding stat points. - /// Causes reaction on client side: The mana and ability bar is updated on the game client user interface. - /// - public static async ValueTask SendMaximumManaAndAbilityAsync(this IConnection? connection, ushort @mana, ushort @ability) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MaximumManaAndAbilityRef.Length; - var packet = new MaximumManaAndAbilityRef(connection.Output.GetSpan(length)[..length]); - packet.Mana = @mana; - packet.Ability = @ability; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The affected slot of the item in the inventory. - /// The true flag. - /// - /// Is sent by the server when: The item has been removed from the inventory of the player. - /// Causes reaction on client side: The client removes the item in the inventory user interface. - /// - public static async ValueTask SendItemRemovedAsync(this IConnection? connection, byte @inventorySlot, byte @trueFlag = 1) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemRemovedRef.Length; - var packet = new ItemRemovedRef(connection.Output.GetSpan(length)[..length]); - packet.InventorySlot = @inventorySlot; - packet.TrueFlag = @trueFlag; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The item type. - /// The effect time in seconds. - /// - /// Is sent by the server when: The client requested to consume a special item, e.g. a bottle of Ale. - /// Causes reaction on client side: The player is shown in a red color and has increased attack speed. - /// - public static async ValueTask SendConsumeItemWithEffectAsync(this IConnection? connection, ConsumeItemWithEffect.ConsumedItemType @itemType, ushort @effectTimeInSeconds) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ConsumeItemWithEffectRef.Length; - var packet = new ConsumeItemWithEffectRef(connection.Output.GetSpan(length)[..length]); - packet.ItemType = @itemType; - packet.EffectTimeInSeconds = @effectTimeInSeconds; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The inventory slot. - /// The durability. - /// true, if the change resulted from an item consumption; otherwise, false - /// - /// Is sent by the server when: The durability of an item in the inventory of the player has been changed. - /// Causes reaction on client side: The client updates the item in the inventory user interface. - /// - public static async ValueTask SendItemDurabilityChangedAsync(this IConnection? connection, byte @inventorySlot, byte @durability, bool @byConsumption) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemDurabilityChangedRef.Length; - var packet = new ItemDurabilityChangedRef(connection.Output.GetSpan(length)[..length]); - packet.InventorySlot = @inventorySlot; - packet.Durability = @durability; - packet.ByConsumption = @byConsumption; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// The stat points. - /// The stat type. - /// - /// Is sent by the server when: The player requested to consume a fruit. - /// Causes reaction on client side: The client updates the user interface, by changing the added stat points and used fruit points. - /// - public static async ValueTask SendFruitConsumptionResponseAsync(this IConnection? connection, FruitConsumptionResponse.FruitConsumptionResult @result, ushort @statPoints, FruitConsumptionResponse.FruitStatType @statType) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = FruitConsumptionResponseRef.Length; - var packet = new FruitConsumptionResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - packet.StatPoints = @statPoints; - packet.StatType = @statType; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The origin. - /// The type. - /// The action. - /// The remaining seconds. - /// The magic effect number. - /// - /// Is sent by the server when: The player requested to consume an item which gives a magic effect. - /// Causes reaction on client side: The client updates the user interface, it shows the remaining time at the effect icon. - /// - public static async ValueTask SendEffectItemConsumptionAsync(this IConnection? connection, EffectItemConsumption.EffectOrigin @origin, EffectItemConsumption.EffectType @type, EffectItemConsumption.EffectAction @action, uint @remainingSeconds, byte @magicEffectNumber) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = EffectItemConsumptionRef.Length; - var packet = new EffectItemConsumptionRef(connection.Output.GetSpan(length)[..length]); - packet.Origin = @origin; - packet.Type = @type; - packet.Action = @action; - packet.RemainingSeconds = @remainingSeconds; - packet.MagicEffectNumber = @magicEffectNumber; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The window. - /// - /// Is sent by the server when: After the client talked to an NPC which should cause a dialog to open on the client side. - /// Causes reaction on client side: The client opens the specified dialog. - /// - public static async ValueTask SendNpcWindowResponseAsync(this IConnection? connection, NpcWindowResponse.NpcWindow @window) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = NpcWindowResponseRef.Length; - var packet = new NpcWindowResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Window = @window; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: The request of buying an item from a NPC failed. - /// Causes reaction on client side: The client is responsive again. Without this message, it may stuck. - /// - public static async ValueTask SendNpcItemBuyFailedAsync(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = NpcItemBuyFailedRef.Length; - var packet = new NpcItemBuyFailedRef(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The inventory slot. - /// The item data. - /// - /// Is sent by the server when: The request of buying an item from a player or npc was successful. - /// Causes reaction on client side: The bought item is added to the inventory. - /// - public static async ValueTask SendItemBoughtAsync(this IConnection? connection, byte @inventorySlot, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemBoughtRef.GetRequiredSize(itemData.Length); - var packet = new ItemBoughtRef(connection.Output.GetSpan(length)[..length]); - packet.InventorySlot = @inventorySlot; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The money. - /// - /// Is sent by the server when: The result of a previous item sell request. - /// Causes reaction on client side: The amount of specified money is set at the players inventory. - /// - public static async ValueTask SendNpcItemSellResultAsync(this IConnection? connection, bool @success, uint @money) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = NpcItemSellResultRef.Length; - var packet = new NpcItemSellResultRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.Money = @money; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The inventory slot. - /// The result. - /// - /// Is sent by the server when: The player requested to set a price for an item of the players shop. - /// Causes reaction on client side: The item gets a price on the user interface. - /// - public static async ValueTask SendPlayerShopSetItemPriceResponseAsync(this IConnection? connection, byte @inventorySlot, PlayerShopSetItemPriceResponse.ItemPriceSetResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PlayerShopSetItemPriceResponseRef.Length; - var packet = new PlayerShopSetItemPriceResponseRef(connection.Output.GetSpan(length)[..length]); - packet.InventorySlot = @inventorySlot; - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player id. - /// The success. - /// - /// Is sent by the server when: After a player in scope requested to close his shop or after all items has been sold. - /// Causes reaction on client side: The player shop not shown as open anymore. - /// - public static async ValueTask SendPlayerShopClosedAsync(this IConnection? connection, ushort @playerId, bool @success = true) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PlayerShopClosedRef.Length; - var packet = new PlayerShopClosedRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.PlayerId = @playerId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The inventory slot. - /// The buyer name. - /// - /// Is sent by the server when: An item of the players shop was sold to another player. - /// Causes reaction on client side: The item is removed from the players inventory and a blue system message appears. - /// - public static async ValueTask SendPlayerShopItemSoldToPlayerAsync(this IConnection? connection, byte @inventorySlot, string @buyerName) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PlayerShopItemSoldToPlayerRef.Length; - var packet = new PlayerShopItemSoldToPlayerRef(connection.Output.GetSpan(length)[..length]); - packet.InventorySlot = @inventorySlot; - packet.BuyerName = @buyerName; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player id. - /// - /// Is sent by the server when: After the player requested to close his shop or after all items has been sold. - /// Causes reaction on client side: The player shop dialog is closed for the shop of the specified player. - /// - public static async ValueTask SendClosePlayerShopDialogAsync(this IConnection? connection, ushort @playerId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ClosePlayerShopDialogRef.Length; - var packet = new ClosePlayerShopDialogRef(connection.Output.GetSpan(length)[..length]); - packet.PlayerId = @playerId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// The seller id. - /// The item data. - /// The item slot. - /// - /// Is sent by the server when: After the player requested to buy an item of a shop of another player. - /// Causes reaction on client side: The result is shown to the player. If successful, the item is added to the inventory. - /// - public static async ValueTask SendPlayerShopBuyResultAsync(this IConnection? connection, PlayerShopBuyResult.ResultKind @result, ushort @sellerId, Memory @itemData, byte @itemSlot) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PlayerShopBuyResultRef.Length; - var packet = new PlayerShopBuyResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - packet.SellerId = @sellerId; - @itemData.Span.CopyTo(packet.ItemData); - packet.ItemSlot = @itemSlot; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The seller id. - /// The result. - /// The item slot. - /// The item data. - /// - /// Is sent by the server when: After the player requested to buy an item of a shop of another player. - /// Causes reaction on client side: The result is shown to the player. If successful, the item is added to the inventory. - /// - public static async ValueTask SendPlayerShopBuyResultExtendedAsync(this IConnection? connection, ushort @sellerId, PlayerShopBuyResultExtended.ResultKind @result, byte @itemSlot, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PlayerShopBuyResultExtendedRef.GetRequiredSize(itemData.Length); - var packet = new PlayerShopBuyResultExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.SellerId = @sellerId; - packet.Result = @result; - packet.ItemSlot = @itemSlot; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player id. - /// The effect. - /// - /// Is sent by the server when: After a player achieved or lost something. - /// Causes reaction on client side: An effect is shown for the affected player. - /// - public static async ValueTask SendShowEffectAsync(this IConnection? connection, ushort @playerId, ShowEffect.EffectType @effect) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ShowEffectRef.Length; - var packet = new ShowEffectRef(connection.Output.GetSpan(length)[..length]); - packet.PlayerId = @playerId; - packet.Effect = @effect; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The unlock flags. - /// - /// Is sent by the server when: It's send right after the CharacterList, in the character selection screen, if the account has any unlocked character classes. - /// Causes reaction on client side: The client unlocks the specified character classes, so they can be created. - /// - public static async ValueTask SendCharacterClassCreationUnlockAsync(this IConnection? connection, CharacterCreationUnlockFlags @unlockFlags) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterClassCreationUnlockRef.Length; - var packet = new CharacterClassCreationUnlockRef(connection.Output.GetSpan(length)[..length]); - packet.UnlockFlags = @unlockFlags; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The character name. - /// The character slot. - /// The level. - /// The class. - /// The character status. - /// The preview data. - /// The success. - /// - /// Is sent by the server when: After the server successfully processed a character creation request. - /// Causes reaction on client side: The new character is shown in the character list - /// - public static async ValueTask SendCharacterCreationSuccessfulAsync(this IConnection? connection, string @characterName, byte @characterSlot, ushort @level, CharacterClassNumber @class, byte @characterStatus, Memory @previewData, bool @success = true) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterCreationSuccessfulRef.Length; - var packet = new CharacterCreationSuccessfulRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.CharacterName = @characterName; - packet.CharacterSlot = @characterSlot; - packet.Level = @level; - packet.Class = @class; - packet.CharacterStatus = @characterStatus; - @previewData.Span.CopyTo(packet.PreviewData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: After the server processed a character creation request without success. - /// Causes reaction on client side: A message is shown that it failed. - /// - public static async ValueTask SendCharacterCreationFailedAsync(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterCreationFailedRef.Length; - var packet = new CharacterCreationFailedRef(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The position x. - /// The position y. - /// The map number. - /// The direction. - /// The current health. - /// The current mana. - /// The experience. - /// The money. - /// - /// Is sent by the server when: The character respawned after death. - /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. - /// - public static async ValueTask SendRespawnAfterDeath075Async(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, ushort @currentHealth, ushort @currentMana, uint @experience, uint @money) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = RespawnAfterDeath075Ref.Length; - var packet = new RespawnAfterDeath075Ref(connection.Output.GetSpan(length)[..length]); - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.MapNumber = @mapNumber; - packet.Direction = @direction; - packet.CurrentHealth = @currentHealth; - packet.CurrentMana = @currentMana; - packet.Experience = @experience; - packet.Money = @money; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The position x. - /// The position y. - /// The map number. - /// The direction. - /// The current health. - /// The current mana. - /// The current ability. - /// The experience. - /// The money. - /// - /// Is sent by the server when: The character respawned after death. - /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. - /// - public static async ValueTask SendRespawnAfterDeath095Async(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, ushort @currentHealth, ushort @currentMana, ushort @currentAbility, uint @experience, uint @money) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = RespawnAfterDeath095Ref.Length; - var packet = new RespawnAfterDeath095Ref(connection.Output.GetSpan(length)[..length]); - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.MapNumber = @mapNumber; - packet.Direction = @direction; - packet.CurrentHealth = @currentHealth; - packet.CurrentMana = @currentMana; - packet.CurrentAbility = @currentAbility; - packet.Experience = @experience; - packet.Money = @money; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The position x. - /// The position y. - /// The map number. - /// The direction. - /// The current health. - /// The current mana. - /// The current shield. - /// The current ability. - /// The experience. - /// The money. - /// - /// Is sent by the server when: The character respawned after death. - /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. - /// - public static async ValueTask SendRespawnAfterDeathAsync(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, ushort @currentHealth, ushort @currentMana, ushort @currentShield, ushort @currentAbility, ulong @experience, uint @money) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = RespawnAfterDeathRef.Length; - var packet = new RespawnAfterDeathRef(connection.Output.GetSpan(length)[..length]); - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.MapNumber = @mapNumber; - packet.Direction = @direction; - packet.CurrentHealth = @currentHealth; - packet.CurrentMana = @currentMana; - packet.CurrentShield = @currentShield; - packet.CurrentAbility = @currentAbility; - packet.Experience = @experience; - packet.Money = @money; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The position x. - /// The position y. - /// The map number. - /// The direction. - /// The current health. - /// The current mana. - /// The current shield. - /// The current ability. - /// The experience. - /// The money. - /// - /// Is sent by the server when: The character respawned after death. - /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. - /// - public static async ValueTask SendRespawnAfterDeathExtendedAsync(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, uint @currentHealth, uint @currentMana, uint @currentShield, uint @currentAbility, ulong @experience, uint @money) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = RespawnAfterDeathExtendedRef.Length; - var packet = new RespawnAfterDeathExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.MapNumber = @mapNumber; - packet.Direction = @direction; - packet.CurrentHealth = @currentHealth; - packet.CurrentMana = @currentMana; - packet.CurrentShield = @currentShield; - packet.CurrentAbility = @currentAbility; - packet.Experience = @experience; - packet.Money = @money; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health damage. - /// The current shield. - /// - /// Is sent by the server when: The character got damaged by being poisoned on old client versions. - /// Causes reaction on client side: Removes the damage from the health without showing a damage number. - /// - public static async ValueTask SendPoisonDamageAsync(this IConnection? connection, ushort @healthDamage, ushort @currentShield) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PoisonDamageRef.Length; - var packet = new PoisonDamageRef(connection.Output.GetSpan(length)[..length]); - packet.HealthDamage = @healthDamage; - packet.CurrentShield = @currentShield; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player id. - /// The new state. - /// - /// Is sent by the server when: After a the hero state of an observed character changed. - /// Causes reaction on client side: The color of the name of the character is changed accordingly and a message is shown. - /// - public static async ValueTask SendHeroStateChangedAsync(this IConnection? connection, ushort @playerId, CharacterHeroState @newState) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = HeroStateChangedRef.Length; - var packet = new HeroStateChangedRef(connection.Output.GetSpan(length)[..length]); - packet.PlayerId = @playerId; - packet.NewState = @newState; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill index. - /// The skill number. - /// The skill level. - /// The flag. - /// - /// Is sent by the server when: After a skill got added to the skill list, e.g. by equipping an item or learning a skill. - /// Causes reaction on client side: The skill is added to the skill list on client side. - /// - public static async ValueTask SendSkillAddedAsync(this IConnection? connection, byte @skillIndex, ushort @skillNumber, byte @skillLevel, byte @flag = 0xFE) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillAddedRef.Length; - var packet = new SkillAddedRef(connection.Output.GetSpan(length)[..length]); - packet.Flag = @flag; - packet.SkillIndex = @skillIndex; - packet.SkillNumber = @skillNumber; - packet.SkillLevel = @skillLevel; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill index. - /// The skill number. - /// The flag. - /// - /// Is sent by the server when: After a skill got removed from the skill list, e.g. by removing an equipped item. - /// Causes reaction on client side: The skill is added to the skill list on client side. - /// - public static async ValueTask SendSkillRemovedAsync(this IConnection? connection, byte @skillIndex, ushort @skillNumber, byte @flag = 0xFF) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillRemovedRef.Length; - var packet = new SkillRemovedRef(connection.Output.GetSpan(length)[..length]); - packet.Flag = @flag; - packet.SkillIndex = @skillIndex; - packet.SkillNumber = @skillNumber; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill index. - /// The skill number and level. - /// The flag. - /// - /// Is sent by the server when: After a skill got added to the skill list, e.g. by equipping an item or learning a skill. - /// Causes reaction on client side: The skill is added to the skill list on client side. - /// - public static async ValueTask SendSkillAdded075Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 1) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillAdded075Ref.Length; - var packet = new SkillAdded075Ref(connection.Output.GetSpan(length)[..length]); - packet.Flag = @flag; - packet.SkillIndex = @skillIndex; - packet.SkillNumberAndLevel = @skillNumberAndLevel; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill index. - /// The skill number and level. - /// The flag. - /// - /// Is sent by the server when: After a skill got removed from the skill list, e.g. by removing an equipped item. - /// Causes reaction on client side: The skill is added to the skill list on client side. - /// - public static async ValueTask SendSkillRemoved075Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 0) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillRemoved075Ref.Length; - var packet = new SkillRemoved075Ref(connection.Output.GetSpan(length)[..length]); - packet.Flag = @flag; - packet.SkillIndex = @skillIndex; - packet.SkillNumberAndLevel = @skillNumberAndLevel; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill index. - /// The skill number and level. - /// The flag. - /// - /// Is sent by the server when: After a skill got added to the skill list, e.g. by equipping an item or learning a skill. - /// Causes reaction on client side: The skill is added to the skill list on client side. - /// - public static async ValueTask SendSkillAdded095Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 0xFE) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillAdded095Ref.Length; - var packet = new SkillAdded095Ref(connection.Output.GetSpan(length)[..length]); - packet.Flag = @flag; - packet.SkillIndex = @skillIndex; - packet.SkillNumberAndLevel = @skillNumberAndLevel; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill index. - /// The skill number and level. - /// The flag. - /// - /// Is sent by the server when: After a skill got removed from the skill list, e.g. by removing an equipped item. - /// Causes reaction on client side: The skill is added to the skill list on client side. - /// - public static async ValueTask SendSkillRemoved095Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 0xFF) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillRemoved095Ref.Length; - var packet = new SkillRemoved095Ref(connection.Output.GetSpan(length)[..length]); - packet.Flag = @flag; - packet.SkillIndex = @skillIndex; - packet.SkillNumberAndLevel = @skillNumberAndLevel; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The character name. - /// - /// Is sent by the server when: After the client focused the character successfully on the server side. - /// Causes reaction on client side: The client highlights the focused character. - /// - public static async ValueTask SendCharacterFocusedAsync(this IConnection? connection, string @characterName) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterFocusedRef.Length; - var packet = new CharacterFocusedRef(connection.Output.GetSpan(length)[..length]); - packet.CharacterName = @characterName; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The attribute. - /// The updated dependent maximum stat. - /// The updated maximum shield. - /// The updated maximum ability. - /// - /// Is sent by the server when: After the server processed a character stat increase request packet. - /// Causes reaction on client side: If it was successful, adds a point to the requested stat type. - /// - public static async ValueTask SendCharacterStatIncreaseResponseAsync(this IConnection? connection, bool @success, CharacterStatAttribute @attribute, ushort @updatedDependentMaximumStat, ushort @updatedMaximumShield, ushort @updatedMaximumAbility) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterStatIncreaseResponseRef.Length; - var packet = new CharacterStatIncreaseResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.Attribute = @attribute; - packet.UpdatedDependentMaximumStat = @updatedDependentMaximumStat; - packet.UpdatedMaximumShield = @updatedMaximumShield; - packet.UpdatedMaximumAbility = @updatedMaximumAbility; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The attribute. - /// The added amount. - /// The updated maximum health. - /// The updated maximum mana. - /// The updated maximum shield. - /// The updated maximum ability. - /// - /// Is sent by the server when: After the server processed a character stat increase request packet. - /// Causes reaction on client side: If it was successful, adds a point to the requested stat type. - /// - public static async ValueTask SendCharacterStatIncreaseResponseExtendedAsync(this IConnection? connection, CharacterStatAttribute @attribute, ushort @addedAmount, uint @updatedMaximumHealth, uint @updatedMaximumMana, uint @updatedMaximumShield, uint @updatedMaximumAbility) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterStatIncreaseResponseExtendedRef.Length; - var packet = new CharacterStatIncreaseResponseExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Attribute = @attribute; - packet.AddedAmount = @addedAmount; - packet.UpdatedMaximumHealth = @updatedMaximumHealth; - packet.UpdatedMaximumMana = @updatedMaximumMana; - packet.UpdatedMaximumShield = @updatedMaximumShield; - packet.UpdatedMaximumAbility = @updatedMaximumAbility; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: After the server processed a character delete response of the client. - /// Causes reaction on client side: If successful, the character is deleted from the character selection screen. Otherwise, a message is shown. - /// - public static async ValueTask SendCharacterDeleteResponseAsync(this IConnection? connection, CharacterDeleteResponse.CharacterDeleteResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterDeleteResponseRef.Length; - var packet = new CharacterDeleteResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The level. - /// The level up points. - /// The maximum health. - /// The maximum mana. - /// The maximum shield. - /// The maximum ability. - /// The fruit points. - /// The maximum fruit points. - /// The negative fruit points. - /// The maximum negative fruit points. - /// - /// Is sent by the server when: After a character leveled up. - /// Causes reaction on client side: Updates the level (and other related stats) in the game client and shows an effect. - /// - public static async ValueTask SendCharacterLevelUpdateAsync(this IConnection? connection, ushort @level, ushort @levelUpPoints, ushort @maximumHealth, ushort @maximumMana, ushort @maximumShield, ushort @maximumAbility, ushort @fruitPoints, ushort @maximumFruitPoints, ushort @negativeFruitPoints, ushort @maximumNegativeFruitPoints) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterLevelUpdateRef.Length; - var packet = new CharacterLevelUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.Level = @level; - packet.LevelUpPoints = @levelUpPoints; - packet.MaximumHealth = @maximumHealth; - packet.MaximumMana = @maximumMana; - packet.MaximumShield = @maximumShield; - packet.MaximumAbility = @maximumAbility; - packet.FruitPoints = @fruitPoints; - packet.MaximumFruitPoints = @maximumFruitPoints; - packet.NegativeFruitPoints = @negativeFruitPoints; - packet.MaximumNegativeFruitPoints = @maximumNegativeFruitPoints; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The x. - /// The y. - /// The map id. - /// The current experience. - /// The experience for next level. - /// The level up points. - /// The strength. - /// The agility. - /// The vitality. - /// The energy. - /// The current health. - /// The maximum health. - /// The current mana. - /// The maximum mana. - /// The current shield. - /// The maximum shield. - /// The current ability. - /// The maximum ability. - /// The money. - /// The hero state. - /// The status. - /// The used fruit points. - /// The max fruit points. - /// The leadership. - /// The used negative fruit points. - /// The max negative fruit points. - /// The inventory extensions. - /// - /// Is sent by the server when: After the character was selected by the player and entered the game. - /// Causes reaction on client side: The characters enters the game world. - /// - public static async ValueTask SendCharacterInformationAsync(this IConnection? connection, byte @x, byte @y, ushort @mapId, ulong @currentExperience, ulong @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @currentHealth, ushort @maximumHealth, ushort @currentMana, ushort @maximumMana, ushort @currentShield, ushort @maximumShield, ushort @currentAbility, ushort @maximumAbility, uint @money, CharacterHeroState @heroState, CharacterStatus @status, ushort @usedFruitPoints, ushort @maxFruitPoints, ushort @leadership, ushort @usedNegativeFruitPoints, ushort @maxNegativeFruitPoints, byte @inventoryExtensions) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterInformationRef.Length; - var packet = new CharacterInformationRef(connection.Output.GetSpan(length)[..length]); - packet.X = @x; - packet.Y = @y; - packet.MapId = @mapId; - packet.CurrentExperience = @currentExperience; - packet.ExperienceForNextLevel = @experienceForNextLevel; - packet.LevelUpPoints = @levelUpPoints; - packet.Strength = @strength; - packet.Agility = @agility; - packet.Vitality = @vitality; - packet.Energy = @energy; - packet.CurrentHealth = @currentHealth; - packet.MaximumHealth = @maximumHealth; - packet.CurrentMana = @currentMana; - packet.MaximumMana = @maximumMana; - packet.CurrentShield = @currentShield; - packet.MaximumShield = @maximumShield; - packet.CurrentAbility = @currentAbility; - packet.MaximumAbility = @maximumAbility; - packet.Money = @money; - packet.HeroState = @heroState; - packet.Status = @status; - packet.UsedFruitPoints = @usedFruitPoints; - packet.MaxFruitPoints = @maxFruitPoints; - packet.Leadership = @leadership; - packet.UsedNegativeFruitPoints = @usedNegativeFruitPoints; - packet.MaxNegativeFruitPoints = @maxNegativeFruitPoints; - packet.InventoryExtensions = @inventoryExtensions; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The level. - /// The level up points. - /// The maximum health. - /// The maximum mana. - /// The maximum shield. - /// The maximum ability. - /// The fruit points. - /// The maximum fruit points. - /// The negative fruit points. - /// The maximum negative fruit points. - /// - /// Is sent by the server when: After a character leveled up. - /// Causes reaction on client side: Updates the level (and other related stats) in the game client and shows an effect. - /// - public static async ValueTask SendCharacterLevelUpdateExtendedAsync(this IConnection? connection, ushort @level, ushort @levelUpPoints, uint @maximumHealth, uint @maximumMana, uint @maximumShield, uint @maximumAbility, ushort @fruitPoints, ushort @maximumFruitPoints, ushort @negativeFruitPoints, ushort @maximumNegativeFruitPoints) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterLevelUpdateExtendedRef.Length; - var packet = new CharacterLevelUpdateExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Level = @level; - packet.LevelUpPoints = @levelUpPoints; - packet.MaximumHealth = @maximumHealth; - packet.MaximumMana = @maximumMana; - packet.MaximumShield = @maximumShield; - packet.MaximumAbility = @maximumAbility; - packet.FruitPoints = @fruitPoints; - packet.MaximumFruitPoints = @maximumFruitPoints; - packet.NegativeFruitPoints = @negativeFruitPoints; - packet.MaximumNegativeFruitPoints = @maximumNegativeFruitPoints; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The x. - /// The y. - /// The map id. - /// The current experience. - /// The experience for next level. - /// The level up points. - /// The strength. - /// The agility. - /// The vitality. - /// The energy. - /// The leadership. - /// The current health. - /// The maximum health. - /// The current mana. - /// The maximum mana. - /// The current shield. - /// The maximum shield. - /// The current ability. - /// The maximum ability. - /// The money. - /// The hero state. - /// The status. - /// The used fruit points. - /// The max fruit points. - /// The used negative fruit points. - /// The max negative fruit points. - /// The attack speed. - /// The magic speed. - /// The maximum attack speed. - /// The inventory extensions. - /// - /// Is sent by the server when: After the character was selected by the player and entered the game. - /// Causes reaction on client side: The characters enters the game world. - /// - public static async ValueTask SendCharacterInformationExtendedAsync(this IConnection? connection, byte @x, byte @y, ushort @mapId, ulong @currentExperience, ulong @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @leadership, uint @currentHealth, uint @maximumHealth, uint @currentMana, uint @maximumMana, uint @currentShield, uint @maximumShield, uint @currentAbility, uint @maximumAbility, uint @money, CharacterHeroState @heroState, CharacterStatus @status, ushort @usedFruitPoints, ushort @maxFruitPoints, ushort @usedNegativeFruitPoints, ushort @maxNegativeFruitPoints, ushort @attackSpeed, ushort @magicSpeed, ushort @maximumAttackSpeed, byte @inventoryExtensions) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterInformationExtendedRef.Length; - var packet = new CharacterInformationExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.X = @x; - packet.Y = @y; - packet.MapId = @mapId; - packet.CurrentExperience = @currentExperience; - packet.ExperienceForNextLevel = @experienceForNextLevel; - packet.LevelUpPoints = @levelUpPoints; - packet.Strength = @strength; - packet.Agility = @agility; - packet.Vitality = @vitality; - packet.Energy = @energy; - packet.Leadership = @leadership; - packet.CurrentHealth = @currentHealth; - packet.MaximumHealth = @maximumHealth; - packet.CurrentMana = @currentMana; - packet.MaximumMana = @maximumMana; - packet.CurrentShield = @currentShield; - packet.MaximumShield = @maximumShield; - packet.CurrentAbility = @currentAbility; - packet.MaximumAbility = @maximumAbility; - packet.Money = @money; - packet.HeroState = @heroState; - packet.Status = @status; - packet.UsedFruitPoints = @usedFruitPoints; - packet.MaxFruitPoints = @maxFruitPoints; - packet.UsedNegativeFruitPoints = @usedNegativeFruitPoints; - packet.MaxNegativeFruitPoints = @maxNegativeFruitPoints; - packet.AttackSpeed = @attackSpeed; - packet.MagicSpeed = @magicSpeed; - packet.MaximumAttackSpeed = @maximumAttackSpeed; - packet.InventoryExtensions = @inventoryExtensions; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The x. - /// The y. - /// The map id. - /// The current experience. - /// The experience for next level. - /// The level up points. - /// The strength. - /// The agility. - /// The vitality. - /// The energy. - /// The current health. - /// The maximum health. - /// The current mana. - /// The maximum mana. - /// The money. - /// The hero state. - /// The status. - /// - /// Is sent by the server when: After the character was selected by the player and entered the game. - /// Causes reaction on client side: The characters enters the game world. - /// - public static async ValueTask SendCharacterInformation075Async(this IConnection? connection, byte @x, byte @y, byte @mapId, uint @currentExperience, uint @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @currentHealth, ushort @maximumHealth, ushort @currentMana, ushort @maximumMana, uint @money, CharacterHeroState @heroState, CharacterStatus @status) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterInformation075Ref.Length; - var packet = new CharacterInformation075Ref(connection.Output.GetSpan(length)[..length]); - packet.X = @x; - packet.Y = @y; - packet.MapId = @mapId; - packet.CurrentExperience = @currentExperience; - packet.ExperienceForNextLevel = @experienceForNextLevel; - packet.LevelUpPoints = @levelUpPoints; - packet.Strength = @strength; - packet.Agility = @agility; - packet.Vitality = @vitality; - packet.Energy = @energy; - packet.CurrentHealth = @currentHealth; - packet.MaximumHealth = @maximumHealth; - packet.CurrentMana = @currentMana; - packet.MaximumMana = @maximumMana; - packet.Money = @money; - packet.HeroState = @heroState; - packet.Status = @status; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The x. - /// The y. - /// The map id. - /// The direction. - /// The current experience. - /// The experience for next level. - /// The level up points. - /// The strength. - /// The agility. - /// The vitality. - /// The energy. - /// The current health. - /// The maximum health. - /// The current mana. - /// The maximum mana. - /// The current ability. - /// The maximum ability. - /// The money. - /// The hero state. - /// The status. - /// The used fruit points. - /// The max fruit points. - /// The leadership. - /// - /// Is sent by the server when: After the character was selected by the player and entered the game. - /// Causes reaction on client side: The characters enters the game world. - /// - public static async ValueTask SendCharacterInformation097Async(this IConnection? connection, byte @x, byte @y, byte @mapId, byte @direction, uint @currentExperience, uint @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @currentHealth, ushort @maximumHealth, ushort @currentMana, ushort @maximumMana, ushort @currentAbility, ushort @maximumAbility, uint @money, CharacterHeroState @heroState, CharacterStatus @status, ushort @usedFruitPoints, ushort @maxFruitPoints, ushort @leadership) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterInformation097Ref.Length; - var packet = new CharacterInformation097Ref(connection.Output.GetSpan(length)[..length]); - packet.X = @x; - packet.Y = @y; - packet.MapId = @mapId; - packet.Direction = @direction; - packet.CurrentExperience = @currentExperience; - packet.ExperienceForNextLevel = @experienceForNextLevel; - packet.LevelUpPoints = @levelUpPoints; - packet.Strength = @strength; - packet.Agility = @agility; - packet.Vitality = @vitality; - packet.Energy = @energy; - packet.CurrentHealth = @currentHealth; - packet.MaximumHealth = @maximumHealth; - packet.CurrentMana = @currentMana; - packet.MaximumMana = @maximumMana; - packet.CurrentAbility = @currentAbility; - packet.MaximumAbility = @maximumAbility; - packet.Money = @money; - packet.HeroState = @heroState; - packet.Status = @status; - packet.UsedFruitPoints = @usedFruitPoints; - packet.MaxFruitPoints = @maxFruitPoints; - packet.Leadership = @leadership; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The inventory slot. - /// The item data. - /// - /// Is sent by the server when: An item in the inventory got upgraded by the player, e.g. by applying a jewel. - /// Causes reaction on client side: The item is updated on the user interface. - /// - public static async ValueTask SendInventoryItemUpgradedAsync(this IConnection? connection, byte @inventorySlot, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = InventoryItemUpgradedRef.GetRequiredSize(itemData.Length); - var packet = new InventoryItemUpgradedRef(connection.Output.GetSpan(length)[..length]); - packet.InventorySlot = @inventorySlot; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health percent. - /// - /// Is sent by the server when: When health of a summoned monster (Elf Skill) changed. - /// Causes reaction on client side: The health is updated on the user interface. - /// - public static async ValueTask SendSummonHealthUpdateAsync(this IConnection? connection, byte @healthPercent) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SummonHealthUpdateRef.Length; - var packet = new SummonHealthUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.HealthPercent = @healthPercent; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The seconds. - /// - /// Is sent by the server when: Every second during a guild soccer match. - /// Causes reaction on client side: The time is updated on the user interface. - /// - public static async ValueTask SendGuildSoccerTimeUpdateAsync(this IConnection? connection, ushort @seconds) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildSoccerTimeUpdateRef.Length; - var packet = new GuildSoccerTimeUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.Seconds = @seconds; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The red team name. - /// The red team goals. - /// The blue team name. - /// The blue team goals. - /// - /// Is sent by the server when: Whenever the score of the soccer game changed, and at the beginning of the match. - /// Causes reaction on client side: The score is updated on the user interface. - /// - public static async ValueTask SendGuildSoccerScoreUpdateAsync(this IConnection? connection, string @redTeamName, byte @redTeamGoals, string @blueTeamName, byte @blueTeamGoals) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildSoccerScoreUpdateRef.Length; - var packet = new GuildSoccerScoreUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.RedTeamName = @redTeamName; - packet.RedTeamGoals = @redTeamGoals; - packet.BlueTeamName = @blueTeamName; - packet.BlueTeamGoals = @blueTeamGoals; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The command type. - /// The parameter 1. - /// The parameter 2. - /// - /// Is sent by the server when: E.g. when event items are dropped to the floor, or a specific dialog should be shown. - /// Causes reaction on client side: The client shows an effect, e.g. a firework. - /// - public static async ValueTask SendServerCommandAsync(this IConnection? connection, byte @commandType, byte @parameter1, byte @parameter2) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ServerCommandRef.Length; - var packet = new ServerCommandRef(connection.Output.GetSpan(length)[..length]); - packet.CommandType = @commandType; - packet.Parameter1 = @parameter1; - packet.Parameter2 = @parameter2; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The x. - /// The y. - /// The effect type. - /// - /// Is sent by the server when: E.g. when event items are dropped to the floor. - /// Causes reaction on client side: The client shows an fireworks effect at the specified coordinates. - /// - public static async ValueTask SendShowFireworksAsync(this IConnection? connection, byte @x, byte @y, byte @effectType = 0) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ShowFireworksRef.Length; - var packet = new ShowFireworksRef(connection.Output.GetSpan(length)[..length]); - packet.EffectType = @effectType; - packet.X = @x; - packet.Y = @y; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The x. - /// The y. - /// The effect type. - /// - /// Is sent by the server when: E.g. when event items are dropped to the floor. - /// Causes reaction on client side: The client shows an christmas fireworks effect at the specified coordinates. - /// - public static async ValueTask SendShowChristmasFireworksAsync(this IConnection? connection, byte @x, byte @y, byte @effectType = 59) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ShowChristmasFireworksRef.Length; - var packet = new ShowChristmasFireworksRef(connection.Output.GetSpan(length)[..length]); - packet.EffectType = @effectType; - packet.X = @x; - packet.Y = @y; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The x. - /// The y. - /// The effect type. - /// - /// Is sent by the server when: E.g. when event items are dropped to the floor. - /// Causes reaction on client side: The client plays a fanfare sound at the specified coordinates. - /// - public static async ValueTask SendPlayFanfareSoundAsync(this IConnection? connection, byte @x, byte @y, byte @effectType = 2) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PlayFanfareSoundRef.Length; - var packet = new PlayFanfareSoundRef(connection.Output.GetSpan(length)[..length]); - packet.EffectType = @effectType; - packet.X = @x; - packet.Y = @y; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The target object id. - /// The effect type. - /// - /// Is sent by the server when: E.g. when event items are dropped to the floor. - /// Causes reaction on client side: The client shows a swirl effect at the specified object. - /// - public static async ValueTask SendShowSwirlAsync(this IConnection? connection, ushort @targetObjectId, byte @effectType = 58) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ShowSwirlRef.Length; - var packet = new ShowSwirlRef(connection.Output.GetSpan(length)[..length]); - packet.EffectType = @effectType; - packet.TargetObjectId = @targetObjectId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The master level. - /// The master experience. - /// The master experience of next level. - /// The master level up points. - /// The maximum health. - /// The maximum mana. - /// The maximum shield. - /// The maximum ability. - /// - /// Is sent by the server when: After entering the game with a master class character. - /// Causes reaction on client side: The master related data is available. - /// - public static async ValueTask SendMasterStatsUpdateAsync(this IConnection? connection, ushort @masterLevel, ulong @masterExperience, ulong @masterExperienceOfNextLevel, ushort @masterLevelUpPoints, ushort @maximumHealth, ushort @maximumMana, ushort @maximumShield, ushort @maximumAbility) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MasterStatsUpdateRef.Length; - var packet = new MasterStatsUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.MasterLevel = @masterLevel; - packet.MasterExperience = @masterExperience; - packet.MasterExperienceOfNextLevel = @masterExperienceOfNextLevel; - packet.MasterLevelUpPoints = @masterLevelUpPoints; - packet.MaximumHealth = @maximumHealth; - packet.MaximumMana = @maximumMana; - packet.MaximumShield = @maximumShield; - packet.MaximumAbility = @maximumAbility; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The master level. - /// The master experience. - /// The master experience of next level. - /// The master level up points. - /// The maximum health. - /// The maximum mana. - /// The maximum shield. - /// The maximum ability. - /// - /// Is sent by the server when: After entering the game with a master class character. - /// Causes reaction on client side: The master related data is available. - /// - public static async ValueTask SendMasterStatsUpdateExtendedAsync(this IConnection? connection, ushort @masterLevel, ulong @masterExperience, ulong @masterExperienceOfNextLevel, ushort @masterLevelUpPoints, uint @maximumHealth, uint @maximumMana, uint @maximumShield, uint @maximumAbility) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MasterStatsUpdateExtendedRef.Length; - var packet = new MasterStatsUpdateExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.MasterLevel = @masterLevel; - packet.MasterExperience = @masterExperience; - packet.MasterExperienceOfNextLevel = @masterExperienceOfNextLevel; - packet.MasterLevelUpPoints = @masterLevelUpPoints; - packet.MaximumHealth = @maximumHealth; - packet.MaximumMana = @maximumMana; - packet.MaximumShield = @maximumShield; - packet.MaximumAbility = @maximumAbility; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The master level. - /// The gained master points. - /// The current master points. - /// The maximum master points. - /// The maximum health. - /// The maximum mana. - /// The maximum shield. - /// The maximum ability. - /// - /// Is sent by the server when: After a master character leveled up. - /// Causes reaction on client side: Updates the master level (and other related stats) in the game client and shows an effect. - /// - public static async ValueTask SendMasterCharacterLevelUpdateAsync(this IConnection? connection, ushort @masterLevel, ushort @gainedMasterPoints, ushort @currentMasterPoints, ushort @maximumMasterPoints, ushort @maximumHealth, ushort @maximumMana, ushort @maximumShield, ushort @maximumAbility) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MasterCharacterLevelUpdateRef.Length; - var packet = new MasterCharacterLevelUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.MasterLevel = @masterLevel; - packet.GainedMasterPoints = @gainedMasterPoints; - packet.CurrentMasterPoints = @currentMasterPoints; - packet.MaximumMasterPoints = @maximumMasterPoints; - packet.MaximumHealth = @maximumHealth; - packet.MaximumMana = @maximumMana; - packet.MaximumShield = @maximumShield; - packet.MaximumAbility = @maximumAbility; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The master level. - /// The gained master points. - /// The current master points. - /// The maximum master points. - /// The maximum health. - /// The maximum mana. - /// The maximum shield. - /// The maximum ability. - /// - /// Is sent by the server when: After a master character leveled up. - /// Causes reaction on client side: Updates the master level (and other related stats) in the game client and shows an effect. - /// - public static async ValueTask SendMasterCharacterLevelUpdateExtendedAsync(this IConnection? connection, ushort @masterLevel, ushort @gainedMasterPoints, ushort @currentMasterPoints, ushort @maximumMasterPoints, uint @maximumHealth, uint @maximumMana, uint @maximumShield, uint @maximumAbility) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MasterCharacterLevelUpdateExtendedRef.Length; - var packet = new MasterCharacterLevelUpdateExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.MasterLevel = @masterLevel; - packet.GainedMasterPoints = @gainedMasterPoints; - packet.CurrentMasterPoints = @currentMasterPoints; - packet.MaximumMasterPoints = @maximumMasterPoints; - packet.MaximumHealth = @maximumHealth; - packet.MaximumMana = @maximumMana; - packet.MaximumShield = @maximumShield; - packet.MaximumAbility = @maximumAbility; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The master level up points. - /// The index of the master skill on the clients master skill tree for the given character class. - /// The master skill number. - /// The level. - /// The display value. - /// The display value of next level. - /// - /// Is sent by the server when: After a master skill level has been changed (usually increased). - /// Causes reaction on client side: The level is updated in the master skill tree. - /// - public static async ValueTask SendMasterSkillLevelUpdateAsync(this IConnection? connection, bool @success, ushort @masterLevelUpPoints, byte @masterSkillIndex, ushort @masterSkillNumber, byte @level, float @displayValue, float @displayValueOfNextLevel) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MasterSkillLevelUpdateRef.Length; - var packet = new MasterSkillLevelUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.MasterLevelUpPoints = @masterLevelUpPoints; - packet.MasterSkillIndex = @masterSkillIndex; - packet.MasterSkillNumber = @masterSkillNumber; - packet.Level = @level; - packet.DisplayValue = @displayValue; - packet.DisplayValueOfNextLevel = @displayValueOfNextLevel; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The type. - /// The message. - /// - /// Is sent by the server when: - /// Causes reaction on client side: - /// - public static async ValueTask SendServerMessageAsync(this IConnection? connection, ServerMessage.MessageType @type, string @message) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ServerMessageRef.GetRequiredSize(message); - var packet = new ServerMessageRef(connection.Output.GetSpan(length)[..length]); - packet.Type = @type; - packet.Message = @message; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The requester id. - /// - /// Is sent by the server when: A player requested to join a guild. This message is sent then to the guild master. - /// Causes reaction on client side: The guild master gets a message box with the request popping up. - /// - public static async ValueTask SendGuildJoinRequestAsync(this IConnection? connection, ushort @requesterId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildJoinRequestRef.Length; - var packet = new GuildJoinRequestRef(connection.Output.GetSpan(length)[..length]); - packet.RequesterId = @requesterId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: After a guild master responded to a request of a player to join his guild. This message is sent back to the requesting player. - /// Causes reaction on client side: The requester gets a corresponding message showing. - /// - public static async ValueTask SendGuildJoinResponseAsync(this IConnection? connection, GuildJoinResponse.GuildJoinRequestResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildJoinResponseRef.Length; - var packet = new GuildJoinResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: After a guild master sent a request to kick a player from its guild and the server processed this request. - /// Causes reaction on client side: The client shows a message depending on the result. - /// - public static async ValueTask SendGuildKickResponseAsync(this IConnection? connection, GuildKickResponse.GuildKickSuccess @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildKickResponseRef.Length; - var packet = new GuildKickResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: After a player started talking to the guild master NPC and the player is allowed to create a guild. - /// Causes reaction on client side: The client shows the guild master dialog. - /// - public static async ValueTask SendShowGuildMasterDialogAsync(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ShowGuildMasterDialogRef.Length; - var packet = new ShowGuildMasterDialogRef(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: After a player started talking to the guild master NPC and the player proceeds to create a guild. - /// Causes reaction on client side: The client shows the guild creation dialog. - /// - public static async ValueTask SendShowGuildCreationDialogAsync(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ShowGuildCreationDialogRef.Length; - var packet = new ShowGuildCreationDialogRef(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The error. - /// - /// Is sent by the server when: After a player requested to create a guild at the guild master NPC. - /// Causes reaction on client side: Depending on the result, a message is shown. - /// - public static async ValueTask SendGuildCreationResultAsync(this IConnection? connection, bool @success, GuildCreationResult.GuildCreationErrorType @error) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildCreationResultRef.Length; - var packet = new GuildCreationResultRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.Error = @error; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player id. - /// The is guild master. - /// - /// Is sent by the server when: A player left a guild. This message is sent to the player and all surrounding players. - /// Causes reaction on client side: The player is not longer shown as a guild member. - /// - public static async ValueTask SendGuildMemberLeftGuildAsync(this IConnection? connection, ushort @playerId, bool @isGuildMaster) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildMemberLeftGuildRef.Length; - var packet = new GuildMemberLeftGuildRef(connection.Output.GetSpan(length)[..length]); - packet.PlayerId = @playerId; - packet.IsGuildMaster = @isGuildMaster; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: A guild master requested a guild war against another guild. - /// Causes reaction on client side: The guild master of the other guild gets this request. - /// - public static async ValueTask SendGuildWarRequestResultAsync(this IConnection? connection, GuildWarRequestResult.RequestResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildWarRequestResultRef.Length; - var packet = new GuildWarRequestResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The guild name. - /// The type. - /// - /// Is sent by the server when: A guild master requested a guild war against another guild. - /// Causes reaction on client side: The guild master of the other guild gets this request. - /// - public static async ValueTask SendGuildWarRequestAsync(this IConnection? connection, string @guildName, GuildWarType @type) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildWarRequestRef.Length; - var packet = new GuildWarRequestRef(connection.Output.GetSpan(length)[..length]); - packet.GuildName = @guildName; - packet.Type = @type; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The guild name. - /// The type. - /// The team code. - /// - /// Is sent by the server when: A guild master requested a guild war against another guild. - /// Causes reaction on client side: The guild master of the other guild gets this request. - /// - public static async ValueTask SendGuildWarDeclaredAsync(this IConnection? connection, string @guildName, GuildWarType @type, byte @teamCode) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildWarDeclaredRef.Length; - var packet = new GuildWarDeclaredRef(connection.Output.GetSpan(length)[..length]); - packet.GuildName = @guildName; - packet.Type = @type; - packet.TeamCode = @teamCode; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// The guild name. - /// - /// Is sent by the server when: The guild war ended. - /// Causes reaction on client side: The guild war is shown as ended on the client side. - /// - public static async ValueTask SendGuildWarEndedAsync(this IConnection? connection, GuildWarEnded.GuildWarResult @result, string @guildName) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildWarEndedRef.Length; - var packet = new GuildWarEndedRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - packet.GuildName = @guildName; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The score of own guild. - /// The score of enemy guild. - /// The type. - /// - /// Is sent by the server when: The guild war score changed. - /// Causes reaction on client side: The guild score is updated on the client side. - /// - public static async ValueTask SendGuildWarScoreUpdateAsync(this IConnection? connection, byte @scoreOfOwnGuild, byte @scoreOfEnemyGuild, byte @type = 0) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildWarScoreUpdateRef.Length; - var packet = new GuildWarScoreUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.ScoreOfOwnGuild = @scoreOfOwnGuild; - packet.ScoreOfEnemyGuild = @scoreOfEnemyGuild; - packet.Type = @type; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The guild id. - /// The guild type. - /// The alliance guild name. - /// The guild name. - /// The logo. - /// - /// Is sent by the server when: A game client requested the (public) info of a guild, e.g. when it met a player of previously unknown guild. - /// Causes reaction on client side: The players which belong to the guild are shown as guild players. - /// - public static async ValueTask SendGuildInformationAsync(this IConnection? connection, uint @guildId, byte @guildType, string @allianceGuildName, string @guildName, Memory @logo) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildInformationRef.Length; - var packet = new GuildInformationRef(connection.Output.GetSpan(length)[..length]); - packet.GuildId = @guildId; - packet.GuildType = @guildType; - packet.AllianceGuildName = @allianceGuildName; - packet.GuildName = @guildName; - @logo.Span.CopyTo(packet.Logo); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The guild id. - /// The guild name. - /// The logo. - /// - /// Is sent by the server when: After a guild has been created. However, in OpenMU, we just send the GuildInformations075 message, because it works just the same. - /// Causes reaction on client side: The players which belong to the guild are shown as guild players. - /// - public static async ValueTask SendSingleGuildInformation075Async(this IConnection? connection, ushort @guildId, string @guildName, Memory @logo) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SingleGuildInformation075Ref.Length; - var packet = new SingleGuildInformation075Ref(connection.Output.GetSpan(length)[..length]); - packet.GuildId = @guildId; - packet.GuildName = @guildName; - @logo.Span.CopyTo(packet.Logo); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The vault money. - /// The inventory money. - /// - /// Is sent by the server when: After the player requested to move money between the vault and inventory. - /// Causes reaction on client side: The game client updates the money values of vault and inventory. - /// - public static async ValueTask SendVaultMoneyUpdateAsync(this IConnection? connection, bool @success, uint @vaultMoney, uint @inventoryMoney) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = VaultMoneyUpdateRef.Length; - var packet = new VaultMoneyUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.VaultMoney = @vaultMoney; - packet.InventoryMoney = @inventoryMoney; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: After the player requested to close the vault, this confirmation is sent back to the client. - /// Causes reaction on client side: The game client closes the vault dialog. - /// - public static async ValueTask SendVaultClosedAsync(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = VaultClosedRef.Length; - var packet = new VaultClosedRef(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The protection state. - /// - /// Is sent by the server when: After the player requested to open the vault. - /// Causes reaction on client side: The game client updates the UI to show the current vault protection state. - /// - public static async ValueTask SendVaultProtectionInformationAsync(this IConnection? connection, VaultProtectionInformation.VaultProtectionState @protectionState) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = VaultProtectionInformationRef.Length; - var packet = new VaultProtectionInformationRef(connection.Output.GetSpan(length)[..length]); - packet.ProtectionState = @protectionState; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// The item data. - /// - /// Is sent by the server when: After the player requested to execute an item crafting, e.g. at the chaos machine. - /// Causes reaction on client side: The game client updates the UI to show the resulting item. - /// - public static async ValueTask SendItemCraftingResultAsync(this IConnection? connection, ItemCraftingResult.CraftingResult @result, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemCraftingResultRef.GetRequiredSize(itemData.Length); - var packet = new ItemCraftingResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: After the player requested to close the crafting dialog, this confirmation is sent back to the client. - /// Causes reaction on client side: The game client closes the crafting dialog. - /// - public static async ValueTask SendCraftingDialogClosed075Async(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CraftingDialogClosed075Ref.Length; - var packet = new CraftingDialogClosed075Ref(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The quest index. - /// This is the complete byte with the state of four quests within the same byte. - /// - /// Is sent by the server when: When the player clicks on the quest npc. - /// Causes reaction on client side: The game client shows the next steps in the quest dialog. - /// - public static async ValueTask SendLegacyQuestStateDialogAsync(this IConnection? connection, byte @questIndex, byte @state) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = LegacyQuestStateDialogRef.Length; - var packet = new LegacyQuestStateDialogRef(connection.Output.GetSpan(length)[..length]); - packet.QuestIndex = @questIndex; - packet.State = @state; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The quest index. - /// This value is 0 if successful. Otherwise, 0xFF or even other magic values. - /// This is the complete byte with the state of four quests within the same byte. - /// - /// Is sent by the server when: As response to the set state request (C1A2). - /// Causes reaction on client side: The game client shows the new quest state. - /// - public static async ValueTask SendLegacySetQuestStateResponseAsync(this IConnection? connection, byte @questIndex, byte @result, byte @newState) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = LegacySetQuestStateResponseRef.Length; - var packet = new LegacySetQuestStateResponseRef(connection.Output.GetSpan(length)[..length]); - packet.QuestIndex = @questIndex; - packet.Result = @result; - packet.NewState = @newState; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player id. - /// The reward. - /// The count. - /// - /// Is sent by the server when: As response to the completed quest of a player in scope. - /// Causes reaction on client side: The game client shows the reward accordingly. - /// - public static async ValueTask SendLegacyQuestRewardAsync(this IConnection? connection, ushort @playerId, LegacyQuestReward.QuestRewardType @reward, byte @count) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = LegacyQuestRewardRef.Length; - var packet = new LegacyQuestRewardRef(connection.Output.GetSpan(length)[..length]); - packet.PlayerId = @playerId; - packet.Reward = @reward; - packet.Count = @count; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The pet command mode. - /// The target id. - /// The pet. - /// - /// Is sent by the server when: After the client sent a PetAttackCommand (as confirmation), or when the previous command finished and the pet is reset to Normal-mode. - /// Causes reaction on client side: The client updates the pet mode in its user interface. - /// - public static async ValueTask SendPetModeAsync(this IConnection? connection, ClientToServer.PetCommandMode @petCommandMode, ushort @targetId, ClientToServer.PetType @pet = ClientToServer.PetType.DarkRaven) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PetModeRef.Length; - var packet = new PetModeRef(connection.Output.GetSpan(length)[..length]); - packet.Pet = @pet; - packet.PetCommandMode = @petCommandMode; - packet.TargetId = @targetId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill type. - /// The owner id. - /// The target id. - /// The pet. - /// - /// Is sent by the server when: After the client sent a PetAttackCommand, the pet attacks automatically. For each attack, the player and all observing players get this message. - /// Causes reaction on client side: The client shows the pet attacking the target. - /// - public static async ValueTask SendPetAttackAsync(this IConnection? connection, PetAttack.PetSkillType @skillType, ushort @ownerId, ushort @targetId, ClientToServer.PetType @pet = ClientToServer.PetType.DarkRaven) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PetAttackRef.Length; - var packet = new PetAttackRef(connection.Output.GetSpan(length)[..length]); - packet.Pet = @pet; - packet.SkillType = @skillType; - packet.OwnerId = @ownerId; - packet.TargetId = @targetId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The pet. - /// The storage. - /// The item slot. - /// The level. - /// The experience. - /// The health. - /// - /// Is sent by the server when: After the client sent a PetInfoRequest for a pet (dark raven, horse). - /// Causes reaction on client side: The client shows the information about the pet. - /// - public static async ValueTask SendPetInfoResponseAsync(this IConnection? connection, ClientToServer.PetType @pet, ClientToServer.StorageType @storage, byte @itemSlot, byte @level, uint @experience, byte @health) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PetInfoResponseRef.Length; - var packet = new PetInfoResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Pet = @pet; - packet.Storage = @storage; - packet.ItemSlot = @itemSlot; - packet.Level = @level; - packet.Experience = @experience; - packet.Health = @health; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// The opponent id. - /// The opponent name. - /// - /// Is sent by the server when: After the client sent a DuelStartRequest, and it either failed or the requested player sent a response. - /// Causes reaction on client side: The client shows the started or aborted duel. - /// - public static async ValueTask SendDuelStartResultAsync(this IConnection? connection, DuelStartResult.DuelStartResultType @result, ushort @opponentId, string @opponentName) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelStartResultRef.Length; - var packet = new DuelStartResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - packet.OpponentId = @opponentId; - packet.OpponentName = @opponentName; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The requester id. - /// The requester name. - /// - /// Is sent by the server when: After another client sent a DuelStartRequest, to ask the requested player for a response. - /// Causes reaction on client side: The client shows the duel request. - /// - public static async ValueTask SendDuelStartRequestAsync(this IConnection? connection, ushort @requesterId, string @requesterName) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelStartRequestRef.Length; - var packet = new DuelStartRequestRef(connection.Output.GetSpan(length)[..length]); - packet.RequesterId = @requesterId; - packet.RequesterName = @requesterName; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The opponent id. - /// The opponent name. - /// The result. - /// - /// Is sent by the server when: After a duel ended. - /// Causes reaction on client side: The client updates its state. - /// - public static async ValueTask SendDuelEndAsync(this IConnection? connection, ushort @opponentId, string @opponentName, byte @result = 0) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelEndRef.Length; - var packet = new DuelEndRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - packet.OpponentId = @opponentId; - packet.OpponentName = @opponentName; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player 1 id. - /// The player 2 id. - /// The player 1 score. - /// The player 2 score. - /// - /// Is sent by the server when: When the score of the duel has been changed. - /// Causes reaction on client side: The client updates the displayed duel score. - /// - public static async ValueTask SendDuelScoreAsync(this IConnection? connection, ushort @player1Id, ushort @player2Id, byte @player1Score, byte @player2Score) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelScoreRef.Length; - var packet = new DuelScoreRef(connection.Output.GetSpan(length)[..length]); - packet.Player1Id = @player1Id; - packet.Player2Id = @player2Id; - packet.Player1Score = @player1Score; - packet.Player2Score = @player2Score; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player 1 id. - /// The player 2 id. - /// The player 1 health percentage. - /// The player 2 health percentage. - /// The player 1 shield percentage. - /// The player 2 shield percentage. - /// - /// Is sent by the server when: When the health/shield of the duel players has been changed. - /// Causes reaction on client side: The client updates the displayed health and shield bars. - /// - public static async ValueTask SendDuelHealthUpdateAsync(this IConnection? connection, ushort @player1Id, ushort @player2Id, byte @player1HealthPercentage, byte @player2HealthPercentage, byte @player1ShieldPercentage, byte @player2ShieldPercentage) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelHealthUpdateRef.Length; - var packet = new DuelHealthUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.Player1Id = @player1Id; - packet.Player2Id = @player2Id; - packet.Player1HealthPercentage = @player1HealthPercentage; - packet.Player2HealthPercentage = @player2HealthPercentage; - packet.Player1ShieldPercentage = @player1ShieldPercentage; - packet.Player2ShieldPercentage = @player2ShieldPercentage; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// The room index. - /// The player 1 name. - /// The player 2 name. - /// The player 1 id. - /// The player 2 id. - /// - /// Is sent by the server when: When the duel starts. - /// Causes reaction on client side: The client initializes the duel state. - /// - public static async ValueTask SendDuelInitAsync(this IConnection? connection, byte @result, byte @roomIndex, string @player1Name, string @player2Name, ushort @player1Id, ushort @player2Id) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelInitRef.Length; - var packet = new DuelInitRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - packet.RoomIndex = @roomIndex; - packet.Player1Name = @player1Name; - packet.Player2Name = @player2Name; - packet.Player1Id = @player1Id; - packet.Player2Id = @player2Id; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: When the duel starts, after the DuelInit message. - /// Causes reaction on client side: The client updates the displayed health and shield bars. - /// - public static async ValueTask SendDuelHealthBarInitAsync(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelHealthBarInitRef.Length; - var packet = new DuelHealthBarInitRef(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The name. - /// - /// Is sent by the server when: When a spectator joins a duel. - /// Causes reaction on client side: The client updates the list of spectators. - /// - public static async ValueTask SendDuelSpectatorAddedAsync(this IConnection? connection, string @name) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelSpectatorAddedRef.Length; - var packet = new DuelSpectatorAddedRef(connection.Output.GetSpan(length)[..length]); - packet.Name = @name; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The name. - /// - /// Is sent by the server when: When a spectator joins a duel. - /// Causes reaction on client side: The client updates the list of spectators. - /// - public static async ValueTask SendDuelSpectatorRemovedAsync(this IConnection? connection, string @name) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelSpectatorRemovedRef.Length; - var packet = new DuelSpectatorRemovedRef(connection.Output.GetSpan(length)[..length]); - packet.Name = @name; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The winner. - /// The loser. - /// - /// Is sent by the server when: When the duel finished. - /// Causes reaction on client side: The client shows the winner and loser names. - /// - public static async ValueTask SendDuelFinishedAsync(this IConnection? connection, string @winner, string @loser) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelFinishedRef.Length; - var packet = new DuelFinishedRef(connection.Output.GetSpan(length)[..length]); - packet.Winner = @winner; - packet.Loser = @loser; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The object id. - /// The stage. - /// The skill number. - /// - /// Is sent by the server when: After a player started a skill which needs to load up, like Nova. - /// Causes reaction on client side: The client may show the loading intensity. - /// - public static async ValueTask SendSkillStageUpdateAsync(this IConnection? connection, ushort @objectId, byte @stage, byte @skillNumber = 0x28) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillStageUpdateRef.Length; - var packet = new SkillStageUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.ObjectId = @objectId; - packet.SkillNumber = @skillNumber; - packet.Stage = @stage; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: The player requested to enter the illusion temple event. - /// Causes reaction on client side: The client shows the result. - /// - public static async ValueTask SendIllusionTempleEnterResultAsync(this IConnection? connection, byte @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = IllusionTempleEnterResultRef.Length; - var packet = new IllusionTempleEnterResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// The skill number. - /// The source object id. - /// The target object id. - /// - /// Is sent by the server when: A player requested to use a specific skill in the illusion temple event. - /// Causes reaction on client side: The client shows the result. - /// - public static async ValueTask SendIllusionTempleSkillUsageResultAsync(this IConnection? connection, byte @result, ushort @skillNumber, ushort @sourceObjectId, ushort @targetObjectId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = IllusionTempleSkillUsageResultRef.Length; - var packet = new IllusionTempleSkillUsageResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - packet.SkillNumber = @skillNumber; - packet.SourceObjectId = @sourceObjectId; - packet.TargetObjectId = @targetObjectId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The user count 1. - /// The user count 2. - /// The user count 3. - /// The user count 4. - /// The user count 5. - /// The user count 6. - /// - /// Is sent by the server when: ? - /// Causes reaction on client side: The client shows the counts. - /// - public static async ValueTask SendIllusionTempleUserCountAsync(this IConnection? connection, byte @userCount1, byte @userCount2, byte @userCount3, byte @userCount4, byte @userCount5, byte @userCount6) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = IllusionTempleUserCountRef.Length; - var packet = new IllusionTempleUserCountRef(connection.Output.GetSpan(length)[..length]); - packet.UserCount1 = @userCount1; - packet.UserCount2 = @userCount2; - packet.UserCount3 = @userCount3; - packet.UserCount4 = @userCount4; - packet.UserCount5 = @userCount5; - packet.UserCount6 = @userCount6; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill points. - /// - /// Is sent by the server when: ? - /// Causes reaction on client side: The client shows the skill points. - /// - public static async ValueTask SendIllusionTempleSkillPointUpdateAsync(this IConnection? connection, byte @skillPoints) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = IllusionTempleSkillPointUpdateRef.Length; - var packet = new IllusionTempleSkillPointUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.SkillPoints = @skillPoints; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill number. - /// The object index. - /// - /// Is sent by the server when: ? - /// Causes reaction on client side: The client shows the skill points. - /// - public static async ValueTask SendIllusionTempleSkillEndedAsync(this IConnection? connection, ushort @skillNumber, ushort @objectIndex) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = IllusionTempleSkillEndedRef.Length; - var packet = new IllusionTempleSkillEndedRef(connection.Output.GetSpan(length)[..length]); - packet.SkillNumber = @skillNumber; - packet.ObjectIndex = @objectIndex; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The user index. - /// The name. - /// - /// Is sent by the server when: ? - /// Causes reaction on client side: ?. - /// - public static async ValueTask SendIllusionTempleHolyItemRelicsAsync(this IConnection? connection, ushort @userIndex, string @name) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = IllusionTempleHolyItemRelicsRef.Length; - var packet = new IllusionTempleHolyItemRelicsRef(connection.Output.GetSpan(length)[..length]); - packet.UserIndex = @userIndex; - packet.Name = @name; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The temple number. - /// The state. - /// - /// Is sent by the server when: ? - /// Causes reaction on client side: The client shows the skill points. - /// - public static async ValueTask SendIllusionTempleSkillEndAsync(this IConnection? connection, byte @templeNumber, byte @state) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = IllusionTempleSkillEndRef.Length; - var packet = new IllusionTempleSkillEndRef(connection.Output.GetSpan(length)[..length]); - packet.TempleNumber = @templeNumber; - packet.State = @state; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The flag, if money should be consumed. If this is 'true', setting PauseStatus to 'false' doesn't cause starting the helper. - /// The money. - /// The pause status. A value of 'true' always works to stop the helper. However, it can only be started, with ConsumeMoney set to 'false'. - /// - /// Is sent by the server when: The server validated or changed the status of the MU Helper. - /// Causes reaction on client side: The client toggle the MU Helper status. - /// - public static async ValueTask SendMuHelperStatusUpdateAsync(this IConnection? connection, bool @consumeMoney, uint @money, bool @pauseStatus) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MuHelperStatusUpdateRef.Length; - var packet = new MuHelperStatusUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.ConsumeMoney = @consumeMoney; - packet.Money = @money; - packet.PauseStatus = @pauseStatus; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The helper data. - /// - /// Is sent by the server when: The server saved the users MU Helper data. - /// Causes reaction on client side: The user wants to save the MU Helper data. - /// - public static async ValueTask SendMuHelperConfigurationDataAsync(this IConnection? connection, Memory @helperData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MuHelperConfigurationDataRef.Length; - var packet = new MuHelperConfigurationDataRef(connection.Output.GetSpan(length)[..length]); - @helperData.Span.CopyTo(packet.HelperData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The friend name. - /// The server id on which the player currently is online. 0xFF means offline. - /// - /// Is sent by the server when: After a friend has been added to the friend list. - /// Causes reaction on client side: The friend appears in the friend list. - /// - public static async ValueTask SendFriendAddedAsync(this IConnection? connection, string @friendName, byte @serverId = 0xFF) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = FriendAddedRef.Length; - var packet = new FriendAddedRef(connection.Output.GetSpan(length)[..length]); - packet.FriendName = @friendName; - packet.ServerId = @serverId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The requester. - /// - /// Is sent by the server when: After a player has requested to add another player as friend. This other player gets this message. - /// Causes reaction on client side: The friend request appears on the user interface. - /// - public static async ValueTask SendFriendRequestAsync(this IConnection? connection, string @requester) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = FriendRequestRef.Length; - var packet = new FriendRequestRef(connection.Output.GetSpan(length)[..length]); - packet.Requester = @requester; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The friend name. - /// - /// Is sent by the server when: After a friend has been removed from the friend list. - /// Causes reaction on client side: The friend is removed from the friend list. - /// - public static async ValueTask SendFriendDeletedAsync(this IConnection? connection, string @friendName) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = FriendDeletedRef.Length; - var packet = new FriendDeletedRef(connection.Output.GetSpan(length)[..length]); - packet.FriendName = @friendName; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The friend name. - /// The server id on which the player currently is online. 0xFF means offline. - /// - /// Is sent by the server when: After a friend has been added to the friend list. - /// Causes reaction on client side: The friend appears in the friend list. - /// - public static async ValueTask SendFriendOnlineStateUpdateAsync(this IConnection? connection, string @friendName, byte @serverId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = FriendOnlineStateUpdateRef.Length; - var packet = new FriendOnlineStateUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.FriendName = @friendName; - packet.ServerId = @serverId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The letter id. - /// The result. - /// - /// Is sent by the server when: After the player requested to send a letter to another player. - /// Causes reaction on client side: Depending on the result, the letter send dialog closes or an error message appears. - /// - public static async ValueTask SendLetterSendResponseAsync(this IConnection? connection, uint @letterId, LetterSendResponse.LetterSendRequestResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = LetterSendResponseRef.Length; - var packet = new LetterSendResponseRef(connection.Output.GetSpan(length)[..length]); - packet.LetterId = @letterId; - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +//------------------------------------------------------------------------------ +// +// This source code was auto-generated by an XSL transformation. +// Do not change this file. Instead, change the XML data which contains +// the packet definitions and re-run the transformation (rebuild this project). +// +//------------------------------------------------------------------------------ + +// ReSharper disable RedundantVerbatimPrefix +// ReSharper disable AssignmentIsFullyDiscarded +// ReSharper disable UnusedMember.Global +// ReSharper disable UseObjectOrCollectionInitializer + +#nullable enable +namespace MUnique.OpenMU.Network.Packets.ServerToClient; + +using System; +using System.Threading; +using MUnique.OpenMU.Network; + +/// +/// Extension methods to start writing messages of this namespace on a . +/// +public static class ConnectionExtensions +{ + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player id. + /// The version string. + /// The version. + /// The success. + /// + /// Is sent by the server when: After a game client has connected to the game. + /// Causes reaction on client side: It shows the login dialog. + /// + public static async ValueTask SendGameServerEnteredAsync(this IConnection? connection, ushort @playerId, string @versionString, Memory @version, bool @success = true) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GameServerEnteredRef.Length; + var packet = new GameServerEnteredRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.PlayerId = @playerId; + packet.VersionString = @versionString; + @version.Span.CopyTo(packet.Version); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The is active. + /// The player id. + /// The effect id. + /// + /// Is sent by the server when: A magic effect was added or removed to the own or another player. + /// Causes reaction on client side: The user interface updates itself. If it's the effect of the own player, it's shown as icon at the top of the interface. + /// + public static async ValueTask SendMagicEffectStatusAsync(this IConnection? connection, bool @isActive, ushort @playerId, byte @effectId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MagicEffectStatusRef.Length; + var packet = new MagicEffectStatusRef(connection.Output.GetSpan(length)[..length]); + packet.IsActive = @isActive; + packet.PlayerId = @playerId; + packet.EffectId = @effectId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// A random value between 0 and 2 (inclusive). + /// A random value between 0 and 9 (inclusive). + /// + /// Is sent by the server when: The weather on the current map has been changed or the player entered the map. + /// Causes reaction on client side: The game client updates the weather effects. + /// + public static async ValueTask SendWeatherStatusUpdateAsync(this IConnection? connection, byte @weather, byte @variation) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = WeatherStatusUpdateRef.Length; + var packet = new WeatherStatusUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.Weather = @weather; + packet.Variation = @variation; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The id. + /// The current position x. + /// The current position y. + /// The target position x. + /// The target position y. + /// The rotation. + /// The hero state. + /// The attack speed. + /// The magic speed. + /// The name. + /// The appearance and effects. + /// + /// Is sent by the server when: One or more character got into the observed scope of the player. + /// Causes reaction on client side: The client adds the character to the shown map. + /// + public static async ValueTask SendAddCharacterToScopeExtendedAsync(this IConnection? connection, ushort @id, byte @currentPositionX, byte @currentPositionY, byte @targetPositionX, byte @targetPositionY, byte @rotation, CharacterHeroState @heroState, ushort @attackSpeed, ushort @magicSpeed, string @name, Memory @appearanceAndEffects) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = AddCharacterToScopeExtendedRef.GetRequiredSize(appearanceAndEffects.Length); + var packet = new AddCharacterToScopeExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Id = @id; + packet.CurrentPositionX = @currentPositionX; + packet.CurrentPositionY = @currentPositionY; + packet.TargetPositionX = @targetPositionX; + packet.TargetPositionY = @targetPositionY; + packet.Rotation = @rotation; + packet.HeroState = @heroState; + packet.AttackSpeed = @attackSpeed; + packet.MagicSpeed = @magicSpeed; + packet.Name = @name; + @appearanceAndEffects.Span.CopyTo(packet.AppearanceAndEffects); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The killed id. + /// The skill id. + /// The killer id. + /// + /// Is sent by the server when: An observed object was killed. + /// Causes reaction on client side: The object is shown as dead. + /// + public static async ValueTask SendObjectGotKilledAsync(this IConnection? connection, ushort @killedId, ushort @skillId, ushort @killerId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectGotKilledRef.Length; + var packet = new ObjectGotKilledRef(connection.Output.GetSpan(length)[..length]); + packet.KilledId = @killedId; + packet.SkillId = @skillId; + packet.KillerId = @killerId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The object id. + /// The direction. + /// The animation. + /// The target id. + /// + /// Is sent by the server when: An object performs an animation. + /// Causes reaction on client side: The animation is shown for the specified object. + /// + public static async ValueTask SendObjectAnimationAsync(this IConnection? connection, ushort @objectId, byte @direction, byte @animation, ushort @targetId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectAnimationRef.Length; + var packet = new ObjectAnimationRef(connection.Output.GetSpan(length)[..length]); + packet.ObjectId = @objectId; + packet.Direction = @direction; + packet.Animation = @animation; + packet.TargetId = @targetId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The player id. + /// The point x. + /// The point y. + /// The rotation. + /// + /// Is sent by the server when: An object performs a skill which has effect on an area. + /// Causes reaction on client side: The animation is shown on the user interface. + /// + public static async ValueTask SendAreaSkillAnimationAsync(this IConnection? connection, ushort @skillId, ushort @playerId, byte @pointX, byte @pointY, byte @rotation) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = AreaSkillAnimationRef.Length; + var packet = new AreaSkillAnimationRef(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.PlayerId = @playerId; + packet.PointX = @pointX; + packet.PointY = @pointY; + packet.Rotation = @rotation; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The player id. + /// The target id. + /// + /// Is sent by the server when: An object performs a skill which is directly targeted to another object. + /// Causes reaction on client side: The animation is shown on the user interface. + /// + public static async ValueTask SendSkillAnimationAsync(this IConnection? connection, ushort @skillId, ushort @playerId, ushort @targetId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillAnimationRef.Length; + var packet = new SkillAnimationRef(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.PlayerId = @playerId; + packet.TargetId = @targetId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The player id. + /// The point x. + /// The point y. + /// The rotation. + /// + /// Is sent by the server when: An object performs a skill which has effect on an area. + /// Causes reaction on client side: The animation is shown on the user interface. + /// + public static async ValueTask SendAreaSkillAnimation075Async(this IConnection? connection, byte @skillId, ushort @playerId, byte @pointX, byte @pointY, byte @rotation) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = AreaSkillAnimation075Ref.Length; + var packet = new AreaSkillAnimation075Ref(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.PlayerId = @playerId; + packet.PointX = @pointX; + packet.PointY = @pointY; + packet.Rotation = @rotation; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The player id. + /// The point x. + /// The point y. + /// The rotation. + /// + /// Is sent by the server when: An object performs a skill which has effect on an area. + /// Causes reaction on client side: The animation is shown on the user interface. + /// + public static async ValueTask SendAreaSkillAnimation095Async(this IConnection? connection, byte @skillId, ushort @playerId, byte @pointX, byte @pointY, byte @rotation) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = AreaSkillAnimation095Ref.Length; + var packet = new AreaSkillAnimation095Ref(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.PlayerId = @playerId; + packet.PointX = @pointX; + packet.PointY = @pointY; + packet.Rotation = @rotation; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The player id. + /// The target id. + /// The effect applied. + /// + /// Is sent by the server when: An object performs a skill which is directly targeted to another object. + /// Causes reaction on client side: The animation is shown on the user interface. + /// + public static async ValueTask SendSkillAnimation075Async(this IConnection? connection, byte @skillId, ushort @playerId, ushort @targetId, bool @effectApplied) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillAnimation075Ref.Length; + var packet = new SkillAnimation075Ref(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.PlayerId = @playerId; + packet.TargetId = @targetId; + packet.EffectApplied = @effectApplied; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The player id. + /// The target id. + /// The effect applied. + /// + /// Is sent by the server when: An object performs a skill which is directly targeted to another object. + /// Causes reaction on client side: The animation is shown on the user interface. + /// + public static async ValueTask SendSkillAnimation095Async(this IConnection? connection, byte @skillId, ushort @playerId, ushort @targetId, bool @effectApplied) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillAnimation095Ref.Length; + var packet = new SkillAnimation095Ref(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.PlayerId = @playerId; + packet.TargetId = @targetId; + packet.EffectApplied = @effectApplied; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The target id. + /// + /// Is sent by the server when: A player cancelled a specific magic effect of a skill (Infinity Arrow, Wizardry Enhance), or an effect was removed due a timeout (Ice, Poison) or antidote. + /// Causes reaction on client side: The effect is removed from the target object. + /// + public static async ValueTask SendMagicEffectCancelledAsync(this IConnection? connection, ushort @skillId, ushort @targetId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MagicEffectCancelledRef.Length; + var packet = new MagicEffectCancelledRef(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.TargetId = @targetId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The target id. + /// + /// Is sent by the server when: A player cancelled a specific magic effect of a skill (Infinity Arrow, Wizardry Enhance), or an effect was removed due a timeout (Ice, Poison) or antidote. + /// Causes reaction on client side: The effect is removed from the target object. + /// + public static async ValueTask SendMagicEffectCancelled075Async(this IConnection? connection, byte @skillId, ushort @targetId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MagicEffectCancelled075Ref.Length; + var packet = new MagicEffectCancelled075Ref(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.TargetId = @targetId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The source id. + /// The target id. + /// + /// Is sent by the server when: A player (rage fighter) performs the dark side skill on a target and sent a RageAttackRangeRequest. + /// Causes reaction on client side: The targets are attacked with visual effects. + /// + public static async ValueTask SendRageAttackAsync(this IConnection? connection, ushort @skillId, ushort @sourceId, ushort @targetId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RageAttackRef.Length; + var packet = new RageAttackRef(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.SourceId = @sourceId; + packet.TargetId = @targetId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The changed player id. + /// The item data. + /// + /// Is sent by the server when: The appearance of a player changed, all surrounding players are informed about it. + /// Causes reaction on client side: The appearance of the player is updated. + /// + public static async ValueTask SendAppearanceChangedAsync(this IConnection? connection, ushort @changedPlayerId, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = AppearanceChangedRef.GetRequiredSize(itemData.Length); + var packet = new AppearanceChangedRef(connection.Output.GetSpan(length)[..length]); + packet.ChangedPlayerId = @changedPlayerId; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The changed player id. + /// The item slot. + /// The item group. + /// The item number. + /// The item level. + /// The excellent flags. + /// The ancient discriminator. + /// The is ancient set complete. + /// + /// Is sent by the server when: The appearance of a player changed, all surrounding players are informed about it. + /// Causes reaction on client side: The appearance of the player is updated. + /// + public static async ValueTask SendAppearanceChangedExtendedAsync(this IConnection? connection, ushort @changedPlayerId, byte @itemSlot, byte @itemGroup, ushort @itemNumber, byte @itemLevel, byte @excellentFlags, byte @ancientDiscriminator, bool @isAncientSetComplete) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = AppearanceChangedExtendedRef.Length; + var packet = new AppearanceChangedExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.ChangedPlayerId = @changedPlayerId; + packet.ItemSlot = @itemSlot; + packet.ItemGroup = @itemGroup; + packet.ItemNumber = @itemNumber; + packet.ItemLevel = @itemLevel; + packet.ExcellentFlags = @excellentFlags; + packet.AncientDiscriminator = @ancientDiscriminator; + packet.IsAncientSetComplete = @isAncientSetComplete; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The object id. + /// The message. + /// + /// Is sent by the server when: The server wants to show a message above any kind of character, even NPCs. + /// Causes reaction on client side: The message is shown above the character. + /// + public static async ValueTask SendObjectMessageAsync(this IConnection? connection, ushort @objectId, string @message) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectMessageRef.GetRequiredSize(message); + var packet = new ObjectMessageRef(connection.Output.GetSpan(length)[..length]); + packet.ObjectId = @objectId; + packet.Message = @message; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The requester id. + /// + /// Is sent by the server when: Another player requests party from the receiver of this message. + /// Causes reaction on client side: The party request is shown. + /// + public static async ValueTask SendPartyRequestAsync(this IConnection? connection, ushort @requesterId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PartyRequestRef.Length; + var packet = new PartyRequestRef(connection.Output.GetSpan(length)[..length]); + packet.RequesterId = @requesterId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The index. + /// + /// Is sent by the server when: A party member got removed from a party in which the player is in. + /// Causes reaction on client side: The party member with the specified index is removed from the party list on the user interface. + /// + public static async ValueTask SendRemovePartyMemberAsync(this IConnection? connection, byte @index) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RemovePartyMemberRef.Length; + var packet = new RemovePartyMemberRef(connection.Output.GetSpan(length)[..length]); + packet.Index = @index; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// + /// Is sent by the server when: After the player requested to open his shop and this request was successful. + /// Causes reaction on client side: The own player shop is shown as open. + /// + public static async ValueTask SendPlayerShopOpenSuccessfulAsync(this IConnection? connection, bool @success = true) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayerShopOpenSuccessfulRef.Length; + var packet = new PlayerShopOpenSuccessfulRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The state. + /// + /// Is sent by the server when: After the trading partner checked or unchecked the trade accept button. + /// Causes reaction on client side: The game client updates the trade button state accordingly. + /// + public static async ValueTask SendTradeButtonStateChangedAsync(this IConnection? connection, TradeButtonStateChanged.TradeButtonState @state) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeButtonStateChangedRef.Length; + var packet = new TradeButtonStateChangedRef(connection.Output.GetSpan(length)[..length]); + packet.State = @state; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: The trade money has been set by a previous request of the player. + /// Causes reaction on client side: The money which was set into the trade by the player is updated on the UI. + /// + public static async ValueTask SendTradeMoneySetResponseAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeMoneySetResponseRef.Length; + var packet = new TradeMoneySetResponseRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The money amount. + /// + /// Is sent by the server when: This message is sent when the trading partner put a certain amount of money (also 0) into the trade. + /// Causes reaction on client side: It overrides all previous sent money values. + /// + public static async ValueTask SendTradeMoneyUpdateAsync(this IConnection? connection, uint @moneyAmount) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeMoneyUpdateRef.Length; + var packet = new TradeMoneyUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.MoneyAmount = @moneyAmount; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The accepted. + /// The name. + /// The trade partner level. + /// The guild id. + /// + /// Is sent by the server when: The player which receives this message, sent a trade request to another player. This message is sent when the other player responded to this request. + /// Causes reaction on client side: If the trade was accepted, a trade dialog is opened. Otherwise, a message is shown. + /// + public static async ValueTask SendTradeRequestAnswerAsync(this IConnection? connection, bool @accepted, string @name, ushort @tradePartnerLevel, uint @guildId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeRequestAnswerRef.Length; + var packet = new TradeRequestAnswerRef(connection.Output.GetSpan(length)[..length]); + packet.Accepted = @accepted; + packet.Name = @name; + packet.TradePartnerLevel = @tradePartnerLevel; + packet.GuildId = @guildId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The name. + /// + /// Is sent by the server when: A trade was requested by another player. + /// Causes reaction on client side: A trade request dialog is shown. + /// + public static async ValueTask SendTradeRequestAsync(this IConnection? connection, string @name) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeRequestRef.Length; + var packet = new TradeRequestRef(connection.Output.GetSpan(length)[..length]); + packet.Name = @name; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: A trade was finished. + /// Causes reaction on client side: The trade dialog is closed. Depending on the result, a message is shown. + /// + public static async ValueTask SendTradeFinishedAsync(this IConnection? connection, TradeFinished.TradeResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeFinishedRef.Length; + var packet = new TradeFinishedRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The to slot. + /// The item data. + /// + /// Is sent by the server when: The trading partner added an item to the trade. + /// Causes reaction on client side: The item is added in the trade dialog. + /// + public static async ValueTask SendTradeItemAddedAsync(this IConnection? connection, byte @toSlot, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeItemAddedRef.GetRequiredSize(itemData.Length); + var packet = new TradeItemAddedRef(connection.Output.GetSpan(length)[..length]); + packet.ToSlot = @toSlot; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The slot. + /// + /// Is sent by the server when: The trading partner removed an item from the trade. + /// Causes reaction on client side: The item is removed from the trade dialog. + /// + public static async ValueTask SendTradeItemRemovedAsync(this IConnection? connection, byte @slot) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeItemRemovedRef.Length; + var packet = new TradeItemRemovedRef(connection.Output.GetSpan(length)[..length]); + packet.Slot = @slot; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// + /// Is sent by the server when: After the login request has been processed by the server. + /// Causes reaction on client side: Shows the result. When it was successful, the client proceeds by sending a character list request. + /// + public static async ValueTask SendLoginResponseAsync(this IConnection? connection, LoginResponse.LoginResult @success) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = LoginResponseRef.Length; + var packet = new LoginResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The type. + /// + /// Is sent by the server when: After the logout request has been processed by the server. + /// Causes reaction on client side: Depending on the result, the game client closes the game or changes to another selection screen. + /// + public static async ValueTask SendLogoutResponseAsync(this IConnection? connection, LogOutType @type) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = LogoutResponseRef.Length; + var packet = new LogoutResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Type = @type; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The type. + /// The sender. + /// The message. + /// + /// Is sent by the server when: A player sends a chat message. + /// Causes reaction on client side: The message is shown in the chat box and above the character of the sender. + /// + public static async ValueTask SendChatMessageAsync(this IConnection? connection, ChatMessage.ChatMessageType @type, string @sender, string @message) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ChatMessageRef.GetRequiredSize(message); + var packet = new ChatMessageRef(connection.Output.GetSpan(length)[..length]); + packet.Type = @type; + packet.Sender = @sender; + packet.Message = @message; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The header code. + /// The object id. + /// The health damage. + /// The kind. + /// The is rage fighter streak hit. + /// The is rage fighter streak final hit. + /// The is double damage. + /// The is triple damage. + /// The shield damage. + /// + /// Is sent by the server when: An object got hit in two cases: 1. When the own player is hit; 2. When the own player attacked some other object which got hit. + /// Causes reaction on client side: The damage is shown at the object which received the hit. + /// + public static async ValueTask SendObjectHitAsync(this IConnection? connection, byte @headerCode, ushort @objectId, ushort @healthDamage, DamageKind @kind, bool @isRageFighterStreakHit, bool @isRageFighterStreakFinalHit, bool @isDoubleDamage, bool @isTripleDamage, ushort @shieldDamage) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectHitRef.Length; + var packet = new ObjectHitRef(connection.Output.GetSpan(length)[..length]); + packet.HeaderCode = @headerCode; + packet.ObjectId = @objectId; + packet.HealthDamage = @healthDamage; + packet.Kind = @kind; + packet.IsRageFighterStreakHit = @isRageFighterStreakHit; + packet.IsRageFighterStreakFinalHit = @isRageFighterStreakFinalHit; + packet.IsDoubleDamage = @isDoubleDamage; + packet.IsTripleDamage = @isTripleDamage; + packet.ShieldDamage = @shieldDamage; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The kind. + /// The is rage fighter streak hit. + /// The is rage fighter streak final hit. + /// The is double damage. + /// The is triple damage. + /// The object id. + /// Gets or sets the status of the remaining health in fractions of 1/250. + /// Gets or sets the status of the remaining shield in fractions of 1/250. + /// The health damage. + /// The shield damage. + /// + /// Is sent by the server when: An object got hit in two cases: 1. When the own player is hit; 2. When the own player attacked some other object which got hit. + /// Causes reaction on client side: The damage is shown at the object which received the hit. + /// + public static async ValueTask SendObjectHitExtendedAsync(this IConnection? connection, DamageKind @kind, bool @isRageFighterStreakHit, bool @isRageFighterStreakFinalHit, bool @isDoubleDamage, bool @isTripleDamage, ushort @objectId, byte @healthStatus, byte @shieldStatus, uint @healthDamage, uint @shieldDamage) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectHitExtendedRef.Length; + var packet = new ObjectHitExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Kind = @kind; + packet.IsRageFighterStreakHit = @isRageFighterStreakHit; + packet.IsRageFighterStreakFinalHit = @isRageFighterStreakFinalHit; + packet.IsDoubleDamage = @isDoubleDamage; + packet.IsTripleDamage = @isTripleDamage; + packet.ObjectId = @objectId; + packet.HealthStatus = @healthStatus; + packet.ShieldStatus = @shieldStatus; + packet.HealthDamage = @healthDamage; + packet.ShieldDamage = @shieldDamage; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The header code. + /// The object id. + /// The position x. + /// The position y. + /// + /// Is sent by the server when: An object in the observed scope (including the own player) moved instantly. + /// Causes reaction on client side: The position of the object is updated on client side. + /// + public static async ValueTask SendObjectMovedAsync(this IConnection? connection, byte @headerCode, ushort @objectId, byte @positionX, byte @positionY) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectMovedRef.Length; + var packet = new ObjectMovedRef(connection.Output.GetSpan(length)[..length]); + packet.HeaderCode = @headerCode; + packet.ObjectId = @objectId; + packet.PositionX = @positionX; + packet.PositionY = @positionY; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The header code. + /// The object id. + /// The target x. + /// The target y. + /// The target rotation. + /// The step count. + /// The step data. + /// + /// Is sent by the server when: An object in the observed scope (including the own player) walked to another position. + /// Causes reaction on client side: The object is animated to walk to the new position. + /// + public static async ValueTask SendObjectWalkedAsync(this IConnection? connection, byte @headerCode, ushort @objectId, byte @targetX, byte @targetY, byte @targetRotation, byte @stepCount, Memory @stepData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectWalkedRef.GetRequiredSize(stepData.Length); + var packet = new ObjectWalkedRef(connection.Output.GetSpan(length)[..length]); + packet.HeaderCode = @headerCode; + packet.ObjectId = @objectId; + packet.TargetX = @targetX; + packet.TargetY = @targetY; + packet.TargetRotation = @targetRotation; + packet.StepCount = @stepCount; + @stepData.Span.CopyTo(packet.StepData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The header code. + /// The object id. + /// The source x. + /// The source y. + /// The target x. + /// The target y. + /// The target rotation. + /// The step count. + /// The step data. + /// + /// Is sent by the server when: An object in the observed scope (including the own player) walked to another position. + /// Causes reaction on client side: The object is animated to walk to the new position. + /// + public static async ValueTask SendObjectWalkedExtendedAsync(this IConnection? connection, byte @headerCode, ushort @objectId, byte @sourceX, byte @sourceY, byte @targetX, byte @targetY, byte @targetRotation, byte @stepCount, Memory @stepData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectWalkedExtendedRef.GetRequiredSize(stepData.Length); + var packet = new ObjectWalkedExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.HeaderCode = @headerCode; + packet.ObjectId = @objectId; + packet.SourceX = @sourceX; + packet.SourceY = @sourceY; + packet.TargetX = @targetX; + packet.TargetY = @targetY; + packet.TargetRotation = @targetRotation; + packet.StepCount = @stepCount; + @stepData.Span.CopyTo(packet.StepData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The object id. + /// The target x. + /// The target y. + /// The target rotation. + /// + /// Is sent by the server when: An object in the observed scope (including the own player) walked to another position. + /// Causes reaction on client side: The object is animated to walk to the new position. + /// + public static async ValueTask SendObjectWalked075Async(this IConnection? connection, ushort @objectId, byte @targetX, byte @targetY, byte @targetRotation) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectWalked075Ref.Length; + var packet = new ObjectWalked075Ref(connection.Output.GetSpan(length)[..length]); + packet.ObjectId = @objectId; + packet.TargetX = @targetX; + packet.TargetY = @targetY; + packet.TargetRotation = @targetRotation; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The killed object id. + /// The added experience. + /// The damage of last hit. + /// + /// Is sent by the server when: A player gained experience. + /// Causes reaction on client side: The experience is added to the experience counter and bar. + /// + public static async ValueTask SendExperienceGainedAsync(this IConnection? connection, ushort @killedObjectId, ushort @addedExperience, ushort @damageOfLastHit) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ExperienceGainedRef.Length; + var packet = new ExperienceGainedRef(connection.Output.GetSpan(length)[..length]); + packet.KilledObjectId = @killedObjectId; + packet.AddedExperience = @addedExperience; + packet.DamageOfLastHit = @damageOfLastHit; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The type. + /// The added experience. + /// The damage of last hit. + /// The killed object id. + /// The killer object id. + /// + /// Is sent by the server when: A player gained experience. + /// Causes reaction on client side: The experience is added to the experience counter and bar. + /// + public static async ValueTask SendExperienceGainedExtendedAsync(this IConnection? connection, ExperienceGainedExtended.AddResult @type, uint @addedExperience, uint @damageOfLastHit, ushort @killedObjectId, ushort @killerObjectId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ExperienceGainedExtendedRef.Length; + var packet = new ExperienceGainedExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Type = @type; + packet.AddedExperience = @addedExperience; + packet.DamageOfLastHit = @damageOfLastHit; + packet.KilledObjectId = @killedObjectId; + packet.KillerObjectId = @killerObjectId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The map number. + /// The position x. + /// The position y. + /// The rotation. + /// If false, it shows the teleport animation (white bubbles), and the client doesn't remove all of the objects in its scope. + /// + /// Is sent by the server when: The map was changed on the server side. + /// Causes reaction on client side: The game client changes to the specified map and coordinates. + /// + public static async ValueTask SendMapChangedAsync(this IConnection? connection, ushort @mapNumber, byte @positionX, byte @positionY, byte @rotation, bool @isMapChange = true) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MapChangedRef.Length; + var packet = new MapChangedRef(connection.Output.GetSpan(length)[..length]); + packet.IsMapChange = @isMapChange; + packet.MapNumber = @mapNumber; + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.Rotation = @rotation; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The map number. + /// The position x. + /// The position y. + /// The rotation. + /// If false, it shows the teleport animation (white bubbles), and the client doesn't remove all of the objects in its scope. + /// + /// Is sent by the server when: The map was changed on the server side. + /// Causes reaction on client side: The game client changes to the specified map and coordinates. + /// + public static async ValueTask SendMapChanged075Async(this IConnection? connection, byte @mapNumber, byte @positionX, byte @positionY, byte @rotation, bool @isMapChange = true) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MapChanged075Ref.Length; + var packet = new MapChanged075Ref(connection.Output.GetSpan(length)[..length]); + packet.IsMapChange = @isMapChange; + packet.MapNumber = @mapNumber; + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.Rotation = @rotation; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The binary data of the key configuration + /// + /// Is sent by the server when: When entering the game world with a character. + /// Causes reaction on client side: The client restores this configuration in its user interface. + /// + public static async ValueTask SendApplyKeyConfigurationAsync(this IConnection? connection, Memory @configuration) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ApplyKeyConfigurationRef.GetRequiredSize(configuration.Length); + var packet = new ApplyKeyConfigurationRef(connection.Output.GetSpan(length)[..length]); + @configuration.Span.CopyTo(packet.Configuration); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The id. + /// If this flag is set, the money is added to the map with an animation and sound. Otherwise it's just added like it was already on the ground before. + /// The position x. + /// The position y. + /// The amount. + /// The item count. + /// The money group. + /// The money number. + /// + /// Is sent by the server when: Money dropped on the ground. + /// Causes reaction on client side: The client adds the money to the ground. + /// + public static async ValueTask SendMoneyDroppedAsync(this IConnection? connection, ushort @id, bool @isFreshDrop, byte @positionX, byte @positionY, uint @amount, byte @itemCount = 1, byte @moneyGroup = 14, byte @moneyNumber = 15) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MoneyDroppedRef.Length; + var packet = new MoneyDroppedRef(connection.Output.GetSpan(length)[..length]); + packet.ItemCount = @itemCount; + packet.Id = @id; + packet.IsFreshDrop = @isFreshDrop; + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.MoneyNumber = @moneyNumber; + packet.Amount = @amount; + packet.MoneyGroup = @moneyGroup; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// If this flag is set, the money is added to the map with an animation and sound. Otherwise, it's just added like it was already on the ground before. + /// The id. + /// The position x. + /// The position y. + /// The amount. + /// + /// Is sent by the server when: Money dropped on the ground. + /// Causes reaction on client side: The client adds the money to the ground. + /// + public static async ValueTask SendMoneyDroppedExtendedAsync(this IConnection? connection, bool @isFreshDrop, ushort @id, byte @positionX, byte @positionY, uint @amount) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MoneyDroppedExtendedRef.Length; + var packet = new MoneyDroppedExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.IsFreshDrop = @isFreshDrop; + packet.Id = @id; + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.Amount = @amount; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The id. + /// If this flag is set, the money is added to the map with an animation and sound. Otherwise it's just added like it was already on the ground before. + /// The position x. + /// The position y. + /// The amount. + /// The item count. + /// The money group. + /// The money number. + /// + /// Is sent by the server when: Money dropped on the ground. + /// Causes reaction on client side: The client adds the money to the ground. + /// + public static async ValueTask SendMoneyDropped075Async(this IConnection? connection, ushort @id, bool @isFreshDrop, byte @positionX, byte @positionY, uint @amount, byte @itemCount = 1, byte @moneyGroup = 14, byte @moneyNumber = 15) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MoneyDropped075Ref.Length; + var packet = new MoneyDropped075Ref(connection.Output.GetSpan(length)[..length]); + packet.ItemCount = @itemCount; + packet.Id = @id; + packet.IsFreshDrop = @isFreshDrop; + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.MoneyNumber = @moneyNumber; + packet.MoneyGroup = @moneyGroup; + packet.Amount = @amount; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The inventory slot. + /// The item data. + /// + /// Is sent by the server when: A new item was added to the inventory. + /// Causes reaction on client side: The client adds the item to the inventory user interface. + /// + public static async ValueTask SendItemAddedToInventoryAsync(this IConnection? connection, byte @inventorySlot, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemAddedToInventoryRef.GetRequiredSize(itemData.Length); + var packet = new ItemAddedToInventoryRef(connection.Output.GetSpan(length)[..length]); + packet.InventorySlot = @inventorySlot; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The inventory slot. + /// + /// Is sent by the server when: The player requested to drop an item of his inventory. This message is the response about the success of the request. + /// Causes reaction on client side: If successful, the client removes the item from the inventory user interface. + /// + public static async ValueTask SendItemDropResponseAsync(this IConnection? connection, bool @success, byte @inventorySlot) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemDropResponseRef.Length; + var packet = new ItemDropResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.InventorySlot = @inventorySlot; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The fail reason. + /// + /// Is sent by the server when: The player requested to pick up an item from to ground to add it to his inventory, but it failed. + /// Causes reaction on client side: Depending on the reason, the game client shows a message. + /// + public static async ValueTask SendItemPickUpRequestFailedAsync(this IConnection? connection, ItemPickUpRequestFailed.ItemPickUpFailReason @failReason) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemPickUpRequestFailedRef.Length; + var packet = new ItemPickUpRequestFailedRef(connection.Output.GetSpan(length)[..length]); + packet.FailReason = @failReason; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The money. + /// + /// Is sent by the server when: The players money amount of the inventory has been changed and needs an update. + /// Causes reaction on client side: The money is updated in the inventory user interface. + /// + public static async ValueTask SendInventoryMoneyUpdateAsync(this IConnection? connection, uint @money) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = InventoryMoneyUpdateRef.Length; + var packet = new InventoryMoneyUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.Money = @money; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The target storage type. + /// The target slot. + /// The item data. + /// + /// Is sent by the server when: An item in the inventory or vault of the player has been moved. + /// Causes reaction on client side: The client updates the position of item in the user interface. + /// + public static async ValueTask SendItemMovedAsync(this IConnection? connection, ItemStorageKind @targetStorageType, byte @targetSlot, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemMovedRef.GetRequiredSize(itemData.Length); + var packet = new ItemMovedRef(connection.Output.GetSpan(length)[..length]); + packet.TargetStorageType = @targetStorageType; + packet.TargetSlot = @targetSlot; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The item data. + /// + /// Is sent by the server when: An item in the inventory or vault of the player could not be moved as requested by the player. + /// Causes reaction on client side: The client restores the position of item in the user interface. + /// + public static async ValueTask SendItemMoveRequestFailedAsync(this IConnection? connection, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemMoveRequestFailedRef.GetRequiredSize(itemData.Length); + var packet = new ItemMoveRequestFailedRef(connection.Output.GetSpan(length)[..length]); + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health. + /// The shield. + /// + /// Is sent by the server when: Periodically, or if the current health or shield changed on the server side, e.g. by hits. + /// Causes reaction on client side: The health and shield bar is updated on the game client user interface. + /// + public static async ValueTask SendCurrentHealthAndShieldAsync(this IConnection? connection, ushort @health, ushort @shield) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CurrentHealthAndShieldRef.Length; + var packet = new CurrentHealthAndShieldRef(connection.Output.GetSpan(length)[..length]); + packet.Health = @health; + packet.Shield = @shield; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health. + /// The shield. + /// + /// Is sent by the server when: When the maximum health changed, e.g. by adding stat points or changed items. + /// Causes reaction on client side: The health and shield bar is updated on the game client user interface. + /// + public static async ValueTask SendMaximumHealthAndShieldAsync(this IConnection? connection, ushort @health, ushort @shield) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MaximumHealthAndShieldRef.Length; + var packet = new MaximumHealthAndShieldRef(connection.Output.GetSpan(length)[..length]); + packet.Health = @health; + packet.Shield = @shield; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health. + /// The shield. + /// The mana. + /// The ability. + /// The attack speed. + /// The magic speed. + /// + /// Is sent by the server when: Periodically, or if the current stats, like health, shield, mana or attack speed changed on the server side, e.g. by hits. + /// Causes reaction on client side: The values are updated on the game client user interface. + /// + public static async ValueTask SendCurrentStatsExtendedAsync(this IConnection? connection, uint @health, uint @shield, uint @mana, uint @ability, ushort @attackSpeed, ushort @magicSpeed) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CurrentStatsExtendedRef.Length; + var packet = new CurrentStatsExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Health = @health; + packet.Shield = @shield; + packet.Mana = @mana; + packet.Ability = @ability; + packet.AttackSpeed = @attackSpeed; + packet.MagicSpeed = @magicSpeed; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health. + /// The shield. + /// The mana. + /// The ability. + /// + /// Is sent by the server when: When the maximum stats, like health, shield, mana or attack speed changed on the server side, e.g. by adding stat points or changed items. + /// Causes reaction on client side: The values are updated on the game client user interface. + /// + public static async ValueTask SendMaximumStatsExtendedAsync(this IConnection? connection, uint @health, uint @shield, uint @mana, uint @ability) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MaximumStatsExtendedRef.Length; + var packet = new MaximumStatsExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Health = @health; + packet.Shield = @shield; + packet.Mana = @mana; + packet.Ability = @ability; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health. + /// The shield. + /// + /// Is sent by the server when: When the consumption of an item failed. + /// Causes reaction on client side: The game client gets a feedback about a failed consumption, and allows for do further consumption requests. + /// + public static async ValueTask SendItemConsumptionFailedAsync(this IConnection? connection, ushort @health, ushort @shield) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemConsumptionFailedRef.Length; + var packet = new ItemConsumptionFailedRef(connection.Output.GetSpan(length)[..length]); + packet.Health = @health; + packet.Shield = @shield; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health. + /// The shield. + /// + /// Is sent by the server when: When the consumption of an item failed. + /// Causes reaction on client side: The game client gets a feedback about a failed consumption, and allows for do further consumption requests. + /// + public static async ValueTask SendItemConsumptionFailedExtendedAsync(this IConnection? connection, uint @health, uint @shield) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemConsumptionFailedExtendedRef.Length; + var packet = new ItemConsumptionFailedExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Health = @health; + packet.Shield = @shield; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The strength. + /// The agility. + /// The vitality. + /// The energy. + /// The command. + /// + /// Is sent by the server when: Setting the base stats of a character, e.g. set stats command or after a reset. + /// Causes reaction on client side: The values are updated on the game client user interface. + /// + public static async ValueTask SendBaseStatsExtendedAsync(this IConnection? connection, uint @strength, uint @agility, uint @vitality, uint @energy, uint @command) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = BaseStatsExtendedRef.Length; + var packet = new BaseStatsExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Strength = @strength; + packet.Agility = @agility; + packet.Vitality = @vitality; + packet.Energy = @energy; + packet.Command = @command; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The mana. + /// The ability. + /// + /// Is sent by the server when: The currently available mana or ability has changed, e.g. by using a skill. + /// Causes reaction on client side: The mana and ability bar is updated on the game client user interface. + /// + public static async ValueTask SendCurrentManaAndAbilityAsync(this IConnection? connection, ushort @mana, ushort @ability) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CurrentManaAndAbilityRef.Length; + var packet = new CurrentManaAndAbilityRef(connection.Output.GetSpan(length)[..length]); + packet.Mana = @mana; + packet.Ability = @ability; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The mana. + /// The ability. + /// + /// Is sent by the server when: The maximum available mana or ability has changed, e.g. by adding stat points. + /// Causes reaction on client side: The mana and ability bar is updated on the game client user interface. + /// + public static async ValueTask SendMaximumManaAndAbilityAsync(this IConnection? connection, ushort @mana, ushort @ability) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MaximumManaAndAbilityRef.Length; + var packet = new MaximumManaAndAbilityRef(connection.Output.GetSpan(length)[..length]); + packet.Mana = @mana; + packet.Ability = @ability; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The affected slot of the item in the inventory. + /// The true flag. + /// + /// Is sent by the server when: The item has been removed from the inventory of the player. + /// Causes reaction on client side: The client removes the item in the inventory user interface. + /// + public static async ValueTask SendItemRemovedAsync(this IConnection? connection, byte @inventorySlot, byte @trueFlag = 1) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemRemovedRef.Length; + var packet = new ItemRemovedRef(connection.Output.GetSpan(length)[..length]); + packet.InventorySlot = @inventorySlot; + packet.TrueFlag = @trueFlag; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The item type. + /// The effect time in seconds. + /// + /// Is sent by the server when: The client requested to consume a special item, e.g. a bottle of Ale. + /// Causes reaction on client side: The player is shown in a red color and has increased attack speed. + /// + public static async ValueTask SendConsumeItemWithEffectAsync(this IConnection? connection, ConsumeItemWithEffect.ConsumedItemType @itemType, ushort @effectTimeInSeconds) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ConsumeItemWithEffectRef.Length; + var packet = new ConsumeItemWithEffectRef(connection.Output.GetSpan(length)[..length]); + packet.ItemType = @itemType; + packet.EffectTimeInSeconds = @effectTimeInSeconds; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The inventory slot. + /// The durability. + /// true, if the change resulted from an item consumption; otherwise, false + /// + /// Is sent by the server when: The durability of an item in the inventory of the player has been changed. + /// Causes reaction on client side: The client updates the item in the inventory user interface. + /// + public static async ValueTask SendItemDurabilityChangedAsync(this IConnection? connection, byte @inventorySlot, byte @durability, bool @byConsumption) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemDurabilityChangedRef.Length; + var packet = new ItemDurabilityChangedRef(connection.Output.GetSpan(length)[..length]); + packet.InventorySlot = @inventorySlot; + packet.Durability = @durability; + packet.ByConsumption = @byConsumption; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The stat points. + /// The stat type. + /// + /// Is sent by the server when: The player requested to consume a fruit. + /// Causes reaction on client side: The client updates the user interface, by changing the added stat points and used fruit points. + /// + public static async ValueTask SendFruitConsumptionResponseAsync(this IConnection? connection, FruitConsumptionResponse.FruitConsumptionResult @result, ushort @statPoints, FruitConsumptionResponse.FruitStatType @statType) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = FruitConsumptionResponseRef.Length; + var packet = new FruitConsumptionResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.StatPoints = @statPoints; + packet.StatType = @statType; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The origin. + /// The type. + /// The action. + /// The remaining seconds. + /// The magic effect number. + /// + /// Is sent by the server when: The player requested to consume an item which gives a magic effect. + /// Causes reaction on client side: The client updates the user interface, it shows the remaining time at the effect icon. + /// + public static async ValueTask SendEffectItemConsumptionAsync(this IConnection? connection, EffectItemConsumption.EffectOrigin @origin, EffectItemConsumption.EffectType @type, EffectItemConsumption.EffectAction @action, uint @remainingSeconds, byte @magicEffectNumber) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = EffectItemConsumptionRef.Length; + var packet = new EffectItemConsumptionRef(connection.Output.GetSpan(length)[..length]); + packet.Origin = @origin; + packet.Type = @type; + packet.Action = @action; + packet.RemainingSeconds = @remainingSeconds; + packet.MagicEffectNumber = @magicEffectNumber; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The window. + /// + /// Is sent by the server when: After the client talked to an NPC which should cause a dialog to open on the client side. + /// Causes reaction on client side: The client opens the specified dialog. + /// + public static async ValueTask SendNpcWindowResponseAsync(this IConnection? connection, NpcWindowResponse.NpcWindow @window) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = NpcWindowResponseRef.Length; + var packet = new NpcWindowResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Window = @window; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: The request of buying an item from a NPC failed. + /// Causes reaction on client side: The client is responsive again. Without this message, it may stuck. + /// + public static async ValueTask SendNpcItemBuyFailedAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = NpcItemBuyFailedRef.Length; + var packet = new NpcItemBuyFailedRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The inventory slot. + /// The item data. + /// + /// Is sent by the server when: The request of buying an item from a player or npc was successful. + /// Causes reaction on client side: The bought item is added to the inventory. + /// + public static async ValueTask SendItemBoughtAsync(this IConnection? connection, byte @inventorySlot, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemBoughtRef.GetRequiredSize(itemData.Length); + var packet = new ItemBoughtRef(connection.Output.GetSpan(length)[..length]); + packet.InventorySlot = @inventorySlot; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The money. + /// + /// Is sent by the server when: The result of a previous item sell request. + /// Causes reaction on client side: The amount of specified money is set at the players inventory. + /// + public static async ValueTask SendNpcItemSellResultAsync(this IConnection? connection, bool @success, uint @money) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = NpcItemSellResultRef.Length; + var packet = new NpcItemSellResultRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.Money = @money; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The inventory slot. + /// The result. + /// + /// Is sent by the server when: The player requested to set a price for an item of the players shop. + /// Causes reaction on client side: The item gets a price on the user interface. + /// + public static async ValueTask SendPlayerShopSetItemPriceResponseAsync(this IConnection? connection, byte @inventorySlot, PlayerShopSetItemPriceResponse.ItemPriceSetResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayerShopSetItemPriceResponseRef.Length; + var packet = new PlayerShopSetItemPriceResponseRef(connection.Output.GetSpan(length)[..length]); + packet.InventorySlot = @inventorySlot; + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player id. + /// The success. + /// + /// Is sent by the server when: After a player in scope requested to close his shop or after all items has been sold. + /// Causes reaction on client side: The player shop not shown as open anymore. + /// + public static async ValueTask SendPlayerShopClosedAsync(this IConnection? connection, ushort @playerId, bool @success = true) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayerShopClosedRef.Length; + var packet = new PlayerShopClosedRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.PlayerId = @playerId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The inventory slot. + /// The buyer name. + /// + /// Is sent by the server when: An item of the players shop was sold to another player. + /// Causes reaction on client side: The item is removed from the players inventory and a blue system message appears. + /// + public static async ValueTask SendPlayerShopItemSoldToPlayerAsync(this IConnection? connection, byte @inventorySlot, string @buyerName) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayerShopItemSoldToPlayerRef.Length; + var packet = new PlayerShopItemSoldToPlayerRef(connection.Output.GetSpan(length)[..length]); + packet.InventorySlot = @inventorySlot; + packet.BuyerName = @buyerName; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player id. + /// + /// Is sent by the server when: After the player requested to close his shop or after all items has been sold. + /// Causes reaction on client side: The player shop dialog is closed for the shop of the specified player. + /// + public static async ValueTask SendClosePlayerShopDialogAsync(this IConnection? connection, ushort @playerId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ClosePlayerShopDialogRef.Length; + var packet = new ClosePlayerShopDialogRef(connection.Output.GetSpan(length)[..length]); + packet.PlayerId = @playerId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The seller id. + /// The item data. + /// The item slot. + /// + /// Is sent by the server when: After the player requested to buy an item of a shop of another player. + /// Causes reaction on client side: The result is shown to the player. If successful, the item is added to the inventory. + /// + public static async ValueTask SendPlayerShopBuyResultAsync(this IConnection? connection, PlayerShopBuyResult.ResultKind @result, ushort @sellerId, Memory @itemData, byte @itemSlot) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayerShopBuyResultRef.Length; + var packet = new PlayerShopBuyResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.SellerId = @sellerId; + @itemData.Span.CopyTo(packet.ItemData); + packet.ItemSlot = @itemSlot; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The seller id. + /// The result. + /// The item slot. + /// The item data. + /// + /// Is sent by the server when: After the player requested to buy an item of a shop of another player. + /// Causes reaction on client side: The result is shown to the player. If successful, the item is added to the inventory. + /// + public static async ValueTask SendPlayerShopBuyResultExtendedAsync(this IConnection? connection, ushort @sellerId, PlayerShopBuyResultExtended.ResultKind @result, byte @itemSlot, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayerShopBuyResultExtendedRef.GetRequiredSize(itemData.Length); + var packet = new PlayerShopBuyResultExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.SellerId = @sellerId; + packet.Result = @result; + packet.ItemSlot = @itemSlot; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player id. + /// The effect. + /// + /// Is sent by the server when: After a player achieved or lost something. + /// Causes reaction on client side: An effect is shown for the affected player. + /// + public static async ValueTask SendShowEffectAsync(this IConnection? connection, ushort @playerId, ShowEffect.EffectType @effect) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ShowEffectRef.Length; + var packet = new ShowEffectRef(connection.Output.GetSpan(length)[..length]); + packet.PlayerId = @playerId; + packet.Effect = @effect; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The unlock flags. + /// + /// Is sent by the server when: It's send right after the CharacterList, in the character selection screen, if the account has any unlocked character classes. + /// Causes reaction on client side: The client unlocks the specified character classes, so they can be created. + /// + public static async ValueTask SendCharacterClassCreationUnlockAsync(this IConnection? connection, CharacterCreationUnlockFlags @unlockFlags) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterClassCreationUnlockRef.Length; + var packet = new CharacterClassCreationUnlockRef(connection.Output.GetSpan(length)[..length]); + packet.UnlockFlags = @unlockFlags; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The character name. + /// The character slot. + /// The level. + /// The class. + /// The character status. + /// The preview data. + /// The success. + /// + /// Is sent by the server when: After the server successfully processed a character creation request. + /// Causes reaction on client side: The new character is shown in the character list + /// + public static async ValueTask SendCharacterCreationSuccessfulAsync(this IConnection? connection, string @characterName, byte @characterSlot, ushort @level, CharacterClassNumber @class, byte @characterStatus, Memory @previewData, bool @success = true) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterCreationSuccessfulRef.Length; + var packet = new CharacterCreationSuccessfulRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.CharacterName = @characterName; + packet.CharacterSlot = @characterSlot; + packet.Level = @level; + packet.Class = @class; + packet.CharacterStatus = @characterStatus; + @previewData.Span.CopyTo(packet.PreviewData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: After the server processed a character creation request without success. + /// Causes reaction on client side: A message is shown that it failed. + /// + public static async ValueTask SendCharacterCreationFailedAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterCreationFailedRef.Length; + var packet = new CharacterCreationFailedRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The position x. + /// The position y. + /// The map number. + /// The direction. + /// The current health. + /// The current mana. + /// The experience. + /// The money. + /// + /// Is sent by the server when: The character respawned after death. + /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. + /// + public static async ValueTask SendRespawnAfterDeath075Async(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, ushort @currentHealth, ushort @currentMana, uint @experience, uint @money) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RespawnAfterDeath075Ref.Length; + var packet = new RespawnAfterDeath075Ref(connection.Output.GetSpan(length)[..length]); + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.MapNumber = @mapNumber; + packet.Direction = @direction; + packet.CurrentHealth = @currentHealth; + packet.CurrentMana = @currentMana; + packet.Experience = @experience; + packet.Money = @money; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The position x. + /// The position y. + /// The map number. + /// The direction. + /// The current health. + /// The current mana. + /// The current ability. + /// The experience. + /// The money. + /// + /// Is sent by the server when: The character respawned after death. + /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. + /// + public static async ValueTask SendRespawnAfterDeath095Async(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, ushort @currentHealth, ushort @currentMana, ushort @currentAbility, uint @experience, uint @money) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RespawnAfterDeath095Ref.Length; + var packet = new RespawnAfterDeath095Ref(connection.Output.GetSpan(length)[..length]); + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.MapNumber = @mapNumber; + packet.Direction = @direction; + packet.CurrentHealth = @currentHealth; + packet.CurrentMana = @currentMana; + packet.CurrentAbility = @currentAbility; + packet.Experience = @experience; + packet.Money = @money; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The position x. + /// The position y. + /// The map number. + /// The direction. + /// The current health. + /// The current mana. + /// The current shield. + /// The current ability. + /// The experience. + /// The money. + /// + /// Is sent by the server when: The character respawned after death. + /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. + /// + public static async ValueTask SendRespawnAfterDeathAsync(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, ushort @currentHealth, ushort @currentMana, ushort @currentShield, ushort @currentAbility, ulong @experience, uint @money) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RespawnAfterDeathRef.Length; + var packet = new RespawnAfterDeathRef(connection.Output.GetSpan(length)[..length]); + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.MapNumber = @mapNumber; + packet.Direction = @direction; + packet.CurrentHealth = @currentHealth; + packet.CurrentMana = @currentMana; + packet.CurrentShield = @currentShield; + packet.CurrentAbility = @currentAbility; + packet.Experience = @experience; + packet.Money = @money; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The position x. + /// The position y. + /// The map number. + /// The direction. + /// The current health. + /// The current mana. + /// The current shield. + /// The current ability. + /// The experience. + /// The money. + /// + /// Is sent by the server when: The character respawned after death. + /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. + /// + public static async ValueTask SendRespawnAfterDeathExtendedAsync(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, uint @currentHealth, uint @currentMana, uint @currentShield, uint @currentAbility, ulong @experience, uint @money) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RespawnAfterDeathExtendedRef.Length; + var packet = new RespawnAfterDeathExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.MapNumber = @mapNumber; + packet.Direction = @direction; + packet.CurrentHealth = @currentHealth; + packet.CurrentMana = @currentMana; + packet.CurrentShield = @currentShield; + packet.CurrentAbility = @currentAbility; + packet.Experience = @experience; + packet.Money = @money; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health damage. + /// The current shield. + /// + /// Is sent by the server when: The character got damaged by being poisoned on old client versions. + /// Causes reaction on client side: Removes the damage from the health without showing a damage number. + /// + public static async ValueTask SendPoisonDamageAsync(this IConnection? connection, ushort @healthDamage, ushort @currentShield) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PoisonDamageRef.Length; + var packet = new PoisonDamageRef(connection.Output.GetSpan(length)[..length]); + packet.HealthDamage = @healthDamage; + packet.CurrentShield = @currentShield; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player id. + /// The new state. + /// + /// Is sent by the server when: After a the hero state of an observed character changed. + /// Causes reaction on client side: The color of the name of the character is changed accordingly and a message is shown. + /// + public static async ValueTask SendHeroStateChangedAsync(this IConnection? connection, ushort @playerId, CharacterHeroState @newState) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = HeroStateChangedRef.Length; + var packet = new HeroStateChangedRef(connection.Output.GetSpan(length)[..length]); + packet.PlayerId = @playerId; + packet.NewState = @newState; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill index. + /// The skill number. + /// The skill level. + /// The flag. + /// + /// Is sent by the server when: After a skill got added to the skill list, e.g. by equipping an item or learning a skill. + /// Causes reaction on client side: The skill is added to the skill list on client side. + /// + public static async ValueTask SendSkillAddedAsync(this IConnection? connection, byte @skillIndex, ushort @skillNumber, byte @skillLevel, byte @flag = 0xFE) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillAddedRef.Length; + var packet = new SkillAddedRef(connection.Output.GetSpan(length)[..length]); + packet.Flag = @flag; + packet.SkillIndex = @skillIndex; + packet.SkillNumber = @skillNumber; + packet.SkillLevel = @skillLevel; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill index. + /// The skill number. + /// The flag. + /// + /// Is sent by the server when: After a skill got removed from the skill list, e.g. by removing an equipped item. + /// Causes reaction on client side: The skill is added to the skill list on client side. + /// + public static async ValueTask SendSkillRemovedAsync(this IConnection? connection, byte @skillIndex, ushort @skillNumber, byte @flag = 0xFF) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillRemovedRef.Length; + var packet = new SkillRemovedRef(connection.Output.GetSpan(length)[..length]); + packet.Flag = @flag; + packet.SkillIndex = @skillIndex; + packet.SkillNumber = @skillNumber; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill index. + /// The skill number and level. + /// The flag. + /// + /// Is sent by the server when: After a skill got added to the skill list, e.g. by equipping an item or learning a skill. + /// Causes reaction on client side: The skill is added to the skill list on client side. + /// + public static async ValueTask SendSkillAdded075Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 1) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillAdded075Ref.Length; + var packet = new SkillAdded075Ref(connection.Output.GetSpan(length)[..length]); + packet.Flag = @flag; + packet.SkillIndex = @skillIndex; + packet.SkillNumberAndLevel = @skillNumberAndLevel; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill index. + /// The skill number and level. + /// The flag. + /// + /// Is sent by the server when: After a skill got removed from the skill list, e.g. by removing an equipped item. + /// Causes reaction on client side: The skill is added to the skill list on client side. + /// + public static async ValueTask SendSkillRemoved075Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 0) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillRemoved075Ref.Length; + var packet = new SkillRemoved075Ref(connection.Output.GetSpan(length)[..length]); + packet.Flag = @flag; + packet.SkillIndex = @skillIndex; + packet.SkillNumberAndLevel = @skillNumberAndLevel; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill index. + /// The skill number and level. + /// The flag. + /// + /// Is sent by the server when: After a skill got added to the skill list, e.g. by equipping an item or learning a skill. + /// Causes reaction on client side: The skill is added to the skill list on client side. + /// + public static async ValueTask SendSkillAdded095Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 0xFE) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillAdded095Ref.Length; + var packet = new SkillAdded095Ref(connection.Output.GetSpan(length)[..length]); + packet.Flag = @flag; + packet.SkillIndex = @skillIndex; + packet.SkillNumberAndLevel = @skillNumberAndLevel; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill index. + /// The skill number and level. + /// The flag. + /// + /// Is sent by the server when: After a skill got removed from the skill list, e.g. by removing an equipped item. + /// Causes reaction on client side: The skill is added to the skill list on client side. + /// + public static async ValueTask SendSkillRemoved095Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 0xFF) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillRemoved095Ref.Length; + var packet = new SkillRemoved095Ref(connection.Output.GetSpan(length)[..length]); + packet.Flag = @flag; + packet.SkillIndex = @skillIndex; + packet.SkillNumberAndLevel = @skillNumberAndLevel; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The character name. + /// + /// Is sent by the server when: After the client focused the character successfully on the server side. + /// Causes reaction on client side: The client highlights the focused character. + /// + public static async ValueTask SendCharacterFocusedAsync(this IConnection? connection, string @characterName) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterFocusedRef.Length; + var packet = new CharacterFocusedRef(connection.Output.GetSpan(length)[..length]); + packet.CharacterName = @characterName; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The attribute. + /// The updated dependent maximum stat. + /// The updated maximum shield. + /// The updated maximum ability. + /// + /// Is sent by the server when: After the server processed a character stat increase request packet. + /// Causes reaction on client side: If it was successful, adds a point to the requested stat type. + /// + public static async ValueTask SendCharacterStatIncreaseResponseAsync(this IConnection? connection, bool @success, CharacterStatAttribute @attribute, ushort @updatedDependentMaximumStat, ushort @updatedMaximumShield, ushort @updatedMaximumAbility) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterStatIncreaseResponseRef.Length; + var packet = new CharacterStatIncreaseResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.Attribute = @attribute; + packet.UpdatedDependentMaximumStat = @updatedDependentMaximumStat; + packet.UpdatedMaximumShield = @updatedMaximumShield; + packet.UpdatedMaximumAbility = @updatedMaximumAbility; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The attribute. + /// The added amount. + /// The updated maximum health. + /// The updated maximum mana. + /// The updated maximum shield. + /// The updated maximum ability. + /// + /// Is sent by the server when: After the server processed a character stat increase request packet. + /// Causes reaction on client side: If it was successful, adds a point to the requested stat type. + /// + public static async ValueTask SendCharacterStatIncreaseResponseExtendedAsync(this IConnection? connection, CharacterStatAttribute @attribute, ushort @addedAmount, uint @updatedMaximumHealth, uint @updatedMaximumMana, uint @updatedMaximumShield, uint @updatedMaximumAbility) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterStatIncreaseResponseExtendedRef.Length; + var packet = new CharacterStatIncreaseResponseExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Attribute = @attribute; + packet.AddedAmount = @addedAmount; + packet.UpdatedMaximumHealth = @updatedMaximumHealth; + packet.UpdatedMaximumMana = @updatedMaximumMana; + packet.UpdatedMaximumShield = @updatedMaximumShield; + packet.UpdatedMaximumAbility = @updatedMaximumAbility; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: After the server processed a character delete response of the client. + /// Causes reaction on client side: If successful, the character is deleted from the character selection screen. Otherwise, a message is shown. + /// + public static async ValueTask SendCharacterDeleteResponseAsync(this IConnection? connection, CharacterDeleteResponse.CharacterDeleteResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterDeleteResponseRef.Length; + var packet = new CharacterDeleteResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The level. + /// The level up points. + /// The maximum health. + /// The maximum mana. + /// The maximum shield. + /// The maximum ability. + /// The fruit points. + /// The maximum fruit points. + /// The negative fruit points. + /// The maximum negative fruit points. + /// + /// Is sent by the server when: After a character leveled up. + /// Causes reaction on client side: Updates the level (and other related stats) in the game client and shows an effect. + /// + public static async ValueTask SendCharacterLevelUpdateAsync(this IConnection? connection, ushort @level, ushort @levelUpPoints, ushort @maximumHealth, ushort @maximumMana, ushort @maximumShield, ushort @maximumAbility, ushort @fruitPoints, ushort @maximumFruitPoints, ushort @negativeFruitPoints, ushort @maximumNegativeFruitPoints) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterLevelUpdateRef.Length; + var packet = new CharacterLevelUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.Level = @level; + packet.LevelUpPoints = @levelUpPoints; + packet.MaximumHealth = @maximumHealth; + packet.MaximumMana = @maximumMana; + packet.MaximumShield = @maximumShield; + packet.MaximumAbility = @maximumAbility; + packet.FruitPoints = @fruitPoints; + packet.MaximumFruitPoints = @maximumFruitPoints; + packet.NegativeFruitPoints = @negativeFruitPoints; + packet.MaximumNegativeFruitPoints = @maximumNegativeFruitPoints; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The x. + /// The y. + /// The map id. + /// The current experience. + /// The experience for next level. + /// The level up points. + /// The strength. + /// The agility. + /// The vitality. + /// The energy. + /// The current health. + /// The maximum health. + /// The current mana. + /// The maximum mana. + /// The current shield. + /// The maximum shield. + /// The current ability. + /// The maximum ability. + /// The money. + /// The hero state. + /// The status. + /// The used fruit points. + /// The max fruit points. + /// The leadership. + /// The used negative fruit points. + /// The max negative fruit points. + /// The inventory extensions. + /// + /// Is sent by the server when: After the character was selected by the player and entered the game. + /// Causes reaction on client side: The characters enters the game world. + /// + public static async ValueTask SendCharacterInformationAsync(this IConnection? connection, byte @x, byte @y, ushort @mapId, ulong @currentExperience, ulong @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @currentHealth, ushort @maximumHealth, ushort @currentMana, ushort @maximumMana, ushort @currentShield, ushort @maximumShield, ushort @currentAbility, ushort @maximumAbility, uint @money, CharacterHeroState @heroState, CharacterStatus @status, ushort @usedFruitPoints, ushort @maxFruitPoints, ushort @leadership, ushort @usedNegativeFruitPoints, ushort @maxNegativeFruitPoints, byte @inventoryExtensions) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterInformationRef.Length; + var packet = new CharacterInformationRef(connection.Output.GetSpan(length)[..length]); + packet.X = @x; + packet.Y = @y; + packet.MapId = @mapId; + packet.CurrentExperience = @currentExperience; + packet.ExperienceForNextLevel = @experienceForNextLevel; + packet.LevelUpPoints = @levelUpPoints; + packet.Strength = @strength; + packet.Agility = @agility; + packet.Vitality = @vitality; + packet.Energy = @energy; + packet.CurrentHealth = @currentHealth; + packet.MaximumHealth = @maximumHealth; + packet.CurrentMana = @currentMana; + packet.MaximumMana = @maximumMana; + packet.CurrentShield = @currentShield; + packet.MaximumShield = @maximumShield; + packet.CurrentAbility = @currentAbility; + packet.MaximumAbility = @maximumAbility; + packet.Money = @money; + packet.HeroState = @heroState; + packet.Status = @status; + packet.UsedFruitPoints = @usedFruitPoints; + packet.MaxFruitPoints = @maxFruitPoints; + packet.Leadership = @leadership; + packet.UsedNegativeFruitPoints = @usedNegativeFruitPoints; + packet.MaxNegativeFruitPoints = @maxNegativeFruitPoints; + packet.InventoryExtensions = @inventoryExtensions; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The level. + /// The level up points. + /// The maximum health. + /// The maximum mana. + /// The maximum shield. + /// The maximum ability. + /// The fruit points. + /// The maximum fruit points. + /// The negative fruit points. + /// The maximum negative fruit points. + /// + /// Is sent by the server when: After a character leveled up. + /// Causes reaction on client side: Updates the level (and other related stats) in the game client and shows an effect. + /// + public static async ValueTask SendCharacterLevelUpdateExtendedAsync(this IConnection? connection, ushort @level, ushort @levelUpPoints, uint @maximumHealth, uint @maximumMana, uint @maximumShield, uint @maximumAbility, ushort @fruitPoints, ushort @maximumFruitPoints, ushort @negativeFruitPoints, ushort @maximumNegativeFruitPoints) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterLevelUpdateExtendedRef.Length; + var packet = new CharacterLevelUpdateExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Level = @level; + packet.LevelUpPoints = @levelUpPoints; + packet.MaximumHealth = @maximumHealth; + packet.MaximumMana = @maximumMana; + packet.MaximumShield = @maximumShield; + packet.MaximumAbility = @maximumAbility; + packet.FruitPoints = @fruitPoints; + packet.MaximumFruitPoints = @maximumFruitPoints; + packet.NegativeFruitPoints = @negativeFruitPoints; + packet.MaximumNegativeFruitPoints = @maximumNegativeFruitPoints; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The x. + /// The y. + /// The map id. + /// The current experience. + /// The experience for next level. + /// The level up points. + /// The strength. + /// The agility. + /// The vitality. + /// The energy. + /// The leadership. + /// The current health. + /// The maximum health. + /// The current mana. + /// The maximum mana. + /// The current shield. + /// The maximum shield. + /// The current ability. + /// The maximum ability. + /// The money. + /// The hero state. + /// The status. + /// The used fruit points. + /// The max fruit points. + /// The used negative fruit points. + /// The max negative fruit points. + /// The attack speed. + /// The magic speed. + /// The maximum attack speed. + /// The inventory extensions. + /// + /// Is sent by the server when: After the character was selected by the player and entered the game. + /// Causes reaction on client side: The characters enters the game world. + /// + public static async ValueTask SendCharacterInformationExtendedAsync(this IConnection? connection, byte @x, byte @y, ushort @mapId, ulong @currentExperience, ulong @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @leadership, uint @currentHealth, uint @maximumHealth, uint @currentMana, uint @maximumMana, uint @currentShield, uint @maximumShield, uint @currentAbility, uint @maximumAbility, uint @money, CharacterHeroState @heroState, CharacterStatus @status, ushort @usedFruitPoints, ushort @maxFruitPoints, ushort @usedNegativeFruitPoints, ushort @maxNegativeFruitPoints, ushort @attackSpeed, ushort @magicSpeed, ushort @maximumAttackSpeed, byte @inventoryExtensions) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterInformationExtendedRef.Length; + var packet = new CharacterInformationExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.X = @x; + packet.Y = @y; + packet.MapId = @mapId; + packet.CurrentExperience = @currentExperience; + packet.ExperienceForNextLevel = @experienceForNextLevel; + packet.LevelUpPoints = @levelUpPoints; + packet.Strength = @strength; + packet.Agility = @agility; + packet.Vitality = @vitality; + packet.Energy = @energy; + packet.Leadership = @leadership; + packet.CurrentHealth = @currentHealth; + packet.MaximumHealth = @maximumHealth; + packet.CurrentMana = @currentMana; + packet.MaximumMana = @maximumMana; + packet.CurrentShield = @currentShield; + packet.MaximumShield = @maximumShield; + packet.CurrentAbility = @currentAbility; + packet.MaximumAbility = @maximumAbility; + packet.Money = @money; + packet.HeroState = @heroState; + packet.Status = @status; + packet.UsedFruitPoints = @usedFruitPoints; + packet.MaxFruitPoints = @maxFruitPoints; + packet.UsedNegativeFruitPoints = @usedNegativeFruitPoints; + packet.MaxNegativeFruitPoints = @maxNegativeFruitPoints; + packet.AttackSpeed = @attackSpeed; + packet.MagicSpeed = @magicSpeed; + packet.MaximumAttackSpeed = @maximumAttackSpeed; + packet.InventoryExtensions = @inventoryExtensions; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The x. + /// The y. + /// The map id. + /// The current experience. + /// The experience for next level. + /// The level up points. + /// The strength. + /// The agility. + /// The vitality. + /// The energy. + /// The current health. + /// The maximum health. + /// The current mana. + /// The maximum mana. + /// The money. + /// The hero state. + /// The status. + /// + /// Is sent by the server when: After the character was selected by the player and entered the game. + /// Causes reaction on client side: The characters enters the game world. + /// + public static async ValueTask SendCharacterInformation075Async(this IConnection? connection, byte @x, byte @y, byte @mapId, uint @currentExperience, uint @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @currentHealth, ushort @maximumHealth, ushort @currentMana, ushort @maximumMana, uint @money, CharacterHeroState @heroState, CharacterStatus @status) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterInformation075Ref.Length; + var packet = new CharacterInformation075Ref(connection.Output.GetSpan(length)[..length]); + packet.X = @x; + packet.Y = @y; + packet.MapId = @mapId; + packet.CurrentExperience = @currentExperience; + packet.ExperienceForNextLevel = @experienceForNextLevel; + packet.LevelUpPoints = @levelUpPoints; + packet.Strength = @strength; + packet.Agility = @agility; + packet.Vitality = @vitality; + packet.Energy = @energy; + packet.CurrentHealth = @currentHealth; + packet.MaximumHealth = @maximumHealth; + packet.CurrentMana = @currentMana; + packet.MaximumMana = @maximumMana; + packet.Money = @money; + packet.HeroState = @heroState; + packet.Status = @status; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The x. + /// The y. + /// The map id. + /// The direction. + /// The current experience. + /// The experience for next level. + /// The level up points. + /// The strength. + /// The agility. + /// The vitality. + /// The energy. + /// The current health. + /// The maximum health. + /// The current mana. + /// The maximum mana. + /// The current ability. + /// The maximum ability. + /// The money. + /// The hero state. + /// The status. + /// The used fruit points. + /// The max fruit points. + /// The leadership. + /// + /// Is sent by the server when: After the character was selected by the player and entered the game. + /// Causes reaction on client side: The characters enters the game world. + /// + public static async ValueTask SendCharacterInformation097Async(this IConnection? connection, byte @x, byte @y, byte @mapId, byte @direction, uint @currentExperience, uint @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @currentHealth, ushort @maximumHealth, ushort @currentMana, ushort @maximumMana, ushort @currentAbility, ushort @maximumAbility, uint @money, CharacterHeroState @heroState, CharacterStatus @status, ushort @usedFruitPoints, ushort @maxFruitPoints, ushort @leadership) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterInformation097Ref.Length; + var packet = new CharacterInformation097Ref(connection.Output.GetSpan(length)[..length]); + packet.X = @x; + packet.Y = @y; + packet.MapId = @mapId; + packet.Direction = @direction; + packet.CurrentExperience = @currentExperience; + packet.ExperienceForNextLevel = @experienceForNextLevel; + packet.LevelUpPoints = @levelUpPoints; + packet.Strength = @strength; + packet.Agility = @agility; + packet.Vitality = @vitality; + packet.Energy = @energy; + packet.CurrentHealth = @currentHealth; + packet.MaximumHealth = @maximumHealth; + packet.CurrentMana = @currentMana; + packet.MaximumMana = @maximumMana; + packet.CurrentAbility = @currentAbility; + packet.MaximumAbility = @maximumAbility; + packet.Money = @money; + packet.HeroState = @heroState; + packet.Status = @status; + packet.UsedFruitPoints = @usedFruitPoints; + packet.MaxFruitPoints = @maxFruitPoints; + packet.Leadership = @leadership; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The inventory slot. + /// The item data. + /// + /// Is sent by the server when: An item in the inventory got upgraded by the player, e.g. by applying a jewel. + /// Causes reaction on client side: The item is updated on the user interface. + /// + public static async ValueTask SendInventoryItemUpgradedAsync(this IConnection? connection, byte @inventorySlot, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = InventoryItemUpgradedRef.GetRequiredSize(itemData.Length); + var packet = new InventoryItemUpgradedRef(connection.Output.GetSpan(length)[..length]); + packet.InventorySlot = @inventorySlot; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health percent. + /// + /// Is sent by the server when: When health of a summoned monster (Elf Skill) changed. + /// Causes reaction on client side: The health is updated on the user interface. + /// + public static async ValueTask SendSummonHealthUpdateAsync(this IConnection? connection, byte @healthPercent) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SummonHealthUpdateRef.Length; + var packet = new SummonHealthUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.HealthPercent = @healthPercent; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The seconds. + /// + /// Is sent by the server when: Every second during a guild soccer match. + /// Causes reaction on client side: The time is updated on the user interface. + /// + public static async ValueTask SendGuildSoccerTimeUpdateAsync(this IConnection? connection, ushort @seconds) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildSoccerTimeUpdateRef.Length; + var packet = new GuildSoccerTimeUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.Seconds = @seconds; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The red team name. + /// The red team goals. + /// The blue team name. + /// The blue team goals. + /// + /// Is sent by the server when: Whenever the score of the soccer game changed, and at the beginning of the match. + /// Causes reaction on client side: The score is updated on the user interface. + /// + public static async ValueTask SendGuildSoccerScoreUpdateAsync(this IConnection? connection, string @redTeamName, byte @redTeamGoals, string @blueTeamName, byte @blueTeamGoals) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildSoccerScoreUpdateRef.Length; + var packet = new GuildSoccerScoreUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.RedTeamName = @redTeamName; + packet.RedTeamGoals = @redTeamGoals; + packet.BlueTeamName = @blueTeamName; + packet.BlueTeamGoals = @blueTeamGoals; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The command type. + /// The parameter 1. + /// The parameter 2. + /// + /// Is sent by the server when: E.g. when event items are dropped to the floor, or a specific dialog should be shown. + /// Causes reaction on client side: The client shows an effect, e.g. a firework. + /// + public static async ValueTask SendServerCommandAsync(this IConnection? connection, byte @commandType, byte @parameter1, byte @parameter2) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ServerCommandRef.Length; + var packet = new ServerCommandRef(connection.Output.GetSpan(length)[..length]); + packet.CommandType = @commandType; + packet.Parameter1 = @parameter1; + packet.Parameter2 = @parameter2; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The x. + /// The y. + /// The effect type. + /// + /// Is sent by the server when: E.g. when event items are dropped to the floor. + /// Causes reaction on client side: The client shows an fireworks effect at the specified coordinates. + /// + public static async ValueTask SendShowFireworksAsync(this IConnection? connection, byte @x, byte @y, byte @effectType = 0) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ShowFireworksRef.Length; + var packet = new ShowFireworksRef(connection.Output.GetSpan(length)[..length]); + packet.EffectType = @effectType; + packet.X = @x; + packet.Y = @y; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The x. + /// The y. + /// The effect type. + /// + /// Is sent by the server when: E.g. when event items are dropped to the floor. + /// Causes reaction on client side: The client shows an christmas fireworks effect at the specified coordinates. + /// + public static async ValueTask SendShowChristmasFireworksAsync(this IConnection? connection, byte @x, byte @y, byte @effectType = 59) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ShowChristmasFireworksRef.Length; + var packet = new ShowChristmasFireworksRef(connection.Output.GetSpan(length)[..length]); + packet.EffectType = @effectType; + packet.X = @x; + packet.Y = @y; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The x. + /// The y. + /// The effect type. + /// + /// Is sent by the server when: E.g. when event items are dropped to the floor. + /// Causes reaction on client side: The client plays a fanfare sound at the specified coordinates. + /// + public static async ValueTask SendPlayFanfareSoundAsync(this IConnection? connection, byte @x, byte @y, byte @effectType = 2) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayFanfareSoundRef.Length; + var packet = new PlayFanfareSoundRef(connection.Output.GetSpan(length)[..length]); + packet.EffectType = @effectType; + packet.X = @x; + packet.Y = @y; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The target object id. + /// The effect type. + /// + /// Is sent by the server when: E.g. when event items are dropped to the floor. + /// Causes reaction on client side: The client shows a swirl effect at the specified object. + /// + public static async ValueTask SendShowSwirlAsync(this IConnection? connection, ushort @targetObjectId, byte @effectType = 58) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ShowSwirlRef.Length; + var packet = new ShowSwirlRef(connection.Output.GetSpan(length)[..length]); + packet.EffectType = @effectType; + packet.TargetObjectId = @targetObjectId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The master level. + /// The master experience. + /// The master experience of next level. + /// The master level up points. + /// The maximum health. + /// The maximum mana. + /// The maximum shield. + /// The maximum ability. + /// + /// Is sent by the server when: After entering the game with a master class character. + /// Causes reaction on client side: The master related data is available. + /// + public static async ValueTask SendMasterStatsUpdateAsync(this IConnection? connection, ushort @masterLevel, ulong @masterExperience, ulong @masterExperienceOfNextLevel, ushort @masterLevelUpPoints, ushort @maximumHealth, ushort @maximumMana, ushort @maximumShield, ushort @maximumAbility) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MasterStatsUpdateRef.Length; + var packet = new MasterStatsUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.MasterLevel = @masterLevel; + packet.MasterExperience = @masterExperience; + packet.MasterExperienceOfNextLevel = @masterExperienceOfNextLevel; + packet.MasterLevelUpPoints = @masterLevelUpPoints; + packet.MaximumHealth = @maximumHealth; + packet.MaximumMana = @maximumMana; + packet.MaximumShield = @maximumShield; + packet.MaximumAbility = @maximumAbility; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The master level. + /// The master experience. + /// The master experience of next level. + /// The master level up points. + /// The maximum health. + /// The maximum mana. + /// The maximum shield. + /// The maximum ability. + /// + /// Is sent by the server when: After entering the game with a master class character. + /// Causes reaction on client side: The master related data is available. + /// + public static async ValueTask SendMasterStatsUpdateExtendedAsync(this IConnection? connection, ushort @masterLevel, ulong @masterExperience, ulong @masterExperienceOfNextLevel, ushort @masterLevelUpPoints, uint @maximumHealth, uint @maximumMana, uint @maximumShield, uint @maximumAbility) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MasterStatsUpdateExtendedRef.Length; + var packet = new MasterStatsUpdateExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.MasterLevel = @masterLevel; + packet.MasterExperience = @masterExperience; + packet.MasterExperienceOfNextLevel = @masterExperienceOfNextLevel; + packet.MasterLevelUpPoints = @masterLevelUpPoints; + packet.MaximumHealth = @maximumHealth; + packet.MaximumMana = @maximumMana; + packet.MaximumShield = @maximumShield; + packet.MaximumAbility = @maximumAbility; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The master level. + /// The gained master points. + /// The current master points. + /// The maximum master points. + /// The maximum health. + /// The maximum mana. + /// The maximum shield. + /// The maximum ability. + /// + /// Is sent by the server when: After a master character leveled up. + /// Causes reaction on client side: Updates the master level (and other related stats) in the game client and shows an effect. + /// + public static async ValueTask SendMasterCharacterLevelUpdateAsync(this IConnection? connection, ushort @masterLevel, ushort @gainedMasterPoints, ushort @currentMasterPoints, ushort @maximumMasterPoints, ushort @maximumHealth, ushort @maximumMana, ushort @maximumShield, ushort @maximumAbility) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MasterCharacterLevelUpdateRef.Length; + var packet = new MasterCharacterLevelUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.MasterLevel = @masterLevel; + packet.GainedMasterPoints = @gainedMasterPoints; + packet.CurrentMasterPoints = @currentMasterPoints; + packet.MaximumMasterPoints = @maximumMasterPoints; + packet.MaximumHealth = @maximumHealth; + packet.MaximumMana = @maximumMana; + packet.MaximumShield = @maximumShield; + packet.MaximumAbility = @maximumAbility; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The master level. + /// The gained master points. + /// The current master points. + /// The maximum master points. + /// The maximum health. + /// The maximum mana. + /// The maximum shield. + /// The maximum ability. + /// + /// Is sent by the server when: After a master character leveled up. + /// Causes reaction on client side: Updates the master level (and other related stats) in the game client and shows an effect. + /// + public static async ValueTask SendMasterCharacterLevelUpdateExtendedAsync(this IConnection? connection, ushort @masterLevel, ushort @gainedMasterPoints, ushort @currentMasterPoints, ushort @maximumMasterPoints, uint @maximumHealth, uint @maximumMana, uint @maximumShield, uint @maximumAbility) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MasterCharacterLevelUpdateExtendedRef.Length; + var packet = new MasterCharacterLevelUpdateExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.MasterLevel = @masterLevel; + packet.GainedMasterPoints = @gainedMasterPoints; + packet.CurrentMasterPoints = @currentMasterPoints; + packet.MaximumMasterPoints = @maximumMasterPoints; + packet.MaximumHealth = @maximumHealth; + packet.MaximumMana = @maximumMana; + packet.MaximumShield = @maximumShield; + packet.MaximumAbility = @maximumAbility; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The master level up points. + /// The index of the master skill on the clients master skill tree for the given character class. + /// The master skill number. + /// The level. + /// The display value. + /// The display value of next level. + /// + /// Is sent by the server when: After a master skill level has been changed (usually increased). + /// Causes reaction on client side: The level is updated in the master skill tree. + /// + public static async ValueTask SendMasterSkillLevelUpdateAsync(this IConnection? connection, bool @success, ushort @masterLevelUpPoints, byte @masterSkillIndex, ushort @masterSkillNumber, byte @level, float @displayValue, float @displayValueOfNextLevel) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MasterSkillLevelUpdateRef.Length; + var packet = new MasterSkillLevelUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.MasterLevelUpPoints = @masterLevelUpPoints; + packet.MasterSkillIndex = @masterSkillIndex; + packet.MasterSkillNumber = @masterSkillNumber; + packet.Level = @level; + packet.DisplayValue = @displayValue; + packet.DisplayValueOfNextLevel = @displayValueOfNextLevel; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The type. + /// The message. + /// + /// Is sent by the server when: + /// Causes reaction on client side: + /// + public static async ValueTask SendServerMessageAsync(this IConnection? connection, ServerMessage.MessageType @type, string @message) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ServerMessageRef.GetRequiredSize(message); + var packet = new ServerMessageRef(connection.Output.GetSpan(length)[..length]); + packet.Type = @type; + packet.Message = @message; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The requester id. + /// + /// Is sent by the server when: A player requested to join a guild. This message is sent then to the guild master. + /// Causes reaction on client side: The guild master gets a message box with the request popping up. + /// + public static async ValueTask SendGuildJoinRequestAsync(this IConnection? connection, ushort @requesterId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildJoinRequestRef.Length; + var packet = new GuildJoinRequestRef(connection.Output.GetSpan(length)[..length]); + packet.RequesterId = @requesterId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: After a guild master responded to a request of a player to join his guild. This message is sent back to the requesting player. + /// Causes reaction on client side: The requester gets a corresponding message showing. + /// + public static async ValueTask SendGuildJoinResponseAsync(this IConnection? connection, GuildJoinResponse.GuildJoinRequestResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildJoinResponseRef.Length; + var packet = new GuildJoinResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: After a guild master sent a request to kick a player from its guild and the server processed this request. + /// Causes reaction on client side: The client shows a message depending on the result. + /// + public static async ValueTask SendGuildKickResponseAsync(this IConnection? connection, GuildKickResponse.GuildKickSuccess @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildKickResponseRef.Length; + var packet = new GuildKickResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: After a player started talking to the guild master NPC and the player is allowed to create a guild. + /// Causes reaction on client side: The client shows the guild master dialog. + /// + public static async ValueTask SendShowGuildMasterDialogAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ShowGuildMasterDialogRef.Length; + var packet = new ShowGuildMasterDialogRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: After a player started talking to the guild master NPC and the player proceeds to create a guild. + /// Causes reaction on client side: The client shows the guild creation dialog. + /// + public static async ValueTask SendShowGuildCreationDialogAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ShowGuildCreationDialogRef.Length; + var packet = new ShowGuildCreationDialogRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The error. + /// + /// Is sent by the server when: After a player requested to create a guild at the guild master NPC. + /// Causes reaction on client side: Depending on the result, a message is shown. + /// + public static async ValueTask SendGuildCreationResultAsync(this IConnection? connection, bool @success, GuildCreationResult.GuildCreationErrorType @error) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildCreationResultRef.Length; + var packet = new GuildCreationResultRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.Error = @error; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player id. + /// The is guild master. + /// + /// Is sent by the server when: A player left a guild. This message is sent to the player and all surrounding players. + /// Causes reaction on client side: The player is not longer shown as a guild member. + /// + public static async ValueTask SendGuildMemberLeftGuildAsync(this IConnection? connection, ushort @playerId, bool @isGuildMaster) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildMemberLeftGuildRef.Length; + var packet = new GuildMemberLeftGuildRef(connection.Output.GetSpan(length)[..length]); + packet.PlayerId = @playerId; + packet.IsGuildMaster = @isGuildMaster; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: A guild master requested a guild war against another guild. + /// Causes reaction on client side: The guild master of the other guild gets this request. + /// + public static async ValueTask SendGuildWarRequestResultAsync(this IConnection? connection, GuildWarRequestResult.RequestResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildWarRequestResultRef.Length; + var packet = new GuildWarRequestResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The guild name. + /// The type. + /// + /// Is sent by the server when: A guild master requested a guild war against another guild. + /// Causes reaction on client side: The guild master of the other guild gets this request. + /// + public static async ValueTask SendGuildWarRequestAsync(this IConnection? connection, string @guildName, GuildWarType @type) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildWarRequestRef.Length; + var packet = new GuildWarRequestRef(connection.Output.GetSpan(length)[..length]); + packet.GuildName = @guildName; + packet.Type = @type; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The guild name. + /// The type. + /// The team code. + /// + /// Is sent by the server when: A guild master requested a guild war against another guild. + /// Causes reaction on client side: The guild master of the other guild gets this request. + /// + public static async ValueTask SendGuildWarDeclaredAsync(this IConnection? connection, string @guildName, GuildWarType @type, byte @teamCode) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildWarDeclaredRef.Length; + var packet = new GuildWarDeclaredRef(connection.Output.GetSpan(length)[..length]); + packet.GuildName = @guildName; + packet.Type = @type; + packet.TeamCode = @teamCode; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The guild name. + /// + /// Is sent by the server when: The guild war ended. + /// Causes reaction on client side: The guild war is shown as ended on the client side. + /// + public static async ValueTask SendGuildWarEndedAsync(this IConnection? connection, GuildWarEnded.GuildWarResult @result, string @guildName) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildWarEndedRef.Length; + var packet = new GuildWarEndedRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.GuildName = @guildName; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The score of own guild. + /// The score of enemy guild. + /// The type. + /// + /// Is sent by the server when: The guild war score changed. + /// Causes reaction on client side: The guild score is updated on the client side. + /// + public static async ValueTask SendGuildWarScoreUpdateAsync(this IConnection? connection, byte @scoreOfOwnGuild, byte @scoreOfEnemyGuild, byte @type = 0) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildWarScoreUpdateRef.Length; + var packet = new GuildWarScoreUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.ScoreOfOwnGuild = @scoreOfOwnGuild; + packet.ScoreOfEnemyGuild = @scoreOfEnemyGuild; + packet.Type = @type; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The guild id. + /// The guild type. + /// The alliance guild name. + /// The guild name. + /// The logo. + /// + /// Is sent by the server when: A game client requested the (public) info of a guild, e.g. when it met a player of previously unknown guild. + /// Causes reaction on client side: The players which belong to the guild are shown as guild players. + /// + public static async ValueTask SendGuildInformationAsync(this IConnection? connection, uint @guildId, byte @guildType, string @allianceGuildName, string @guildName, Memory @logo) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildInformationRef.Length; + var packet = new GuildInformationRef(connection.Output.GetSpan(length)[..length]); + packet.GuildId = @guildId; + packet.GuildType = @guildType; + packet.AllianceGuildName = @allianceGuildName; + packet.GuildName = @guildName; + @logo.Span.CopyTo(packet.Logo); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The guild id. + /// The guild name. + /// The logo. + /// + /// Is sent by the server when: After a guild has been created. However, in OpenMU, we just send the GuildInformations075 message, because it works just the same. + /// Causes reaction on client side: The players which belong to the guild are shown as guild players. + /// + public static async ValueTask SendSingleGuildInformation075Async(this IConnection? connection, ushort @guildId, string @guildName, Memory @logo) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SingleGuildInformation075Ref.Length; + var packet = new SingleGuildInformation075Ref(connection.Output.GetSpan(length)[..length]); + packet.GuildId = @guildId; + packet.GuildName = @guildName; + @logo.Span.CopyTo(packet.Logo); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The vault money. + /// The inventory money. + /// + /// Is sent by the server when: After the player requested to move money between the vault and inventory. + /// Causes reaction on client side: The game client updates the money values of vault and inventory. + /// + public static async ValueTask SendVaultMoneyUpdateAsync(this IConnection? connection, bool @success, uint @vaultMoney, uint @inventoryMoney) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = VaultMoneyUpdateRef.Length; + var packet = new VaultMoneyUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.VaultMoney = @vaultMoney; + packet.InventoryMoney = @inventoryMoney; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: After the player requested to close the vault, this confirmation is sent back to the client. + /// Causes reaction on client side: The game client closes the vault dialog. + /// + public static async ValueTask SendVaultClosedAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = VaultClosedRef.Length; + var packet = new VaultClosedRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The protection state. + /// + /// Is sent by the server when: After the player requested to open the vault. + /// Causes reaction on client side: The game client updates the UI to show the current vault protection state. + /// + public static async ValueTask SendVaultProtectionInformationAsync(this IConnection? connection, VaultProtectionInformation.VaultProtectionState @protectionState) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = VaultProtectionInformationRef.Length; + var packet = new VaultProtectionInformationRef(connection.Output.GetSpan(length)[..length]); + packet.ProtectionState = @protectionState; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The item data. + /// + /// Is sent by the server when: After the player requested to execute an item crafting, e.g. at the chaos machine. + /// Causes reaction on client side: The game client updates the UI to show the resulting item. + /// + public static async ValueTask SendItemCraftingResultAsync(this IConnection? connection, ItemCraftingResult.CraftingResult @result, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemCraftingResultRef.GetRequiredSize(itemData.Length); + var packet = new ItemCraftingResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: After the player requested to close the crafting dialog, this confirmation is sent back to the client. + /// Causes reaction on client side: The game client closes the crafting dialog. + /// + public static async ValueTask SendCraftingDialogClosed075Async(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CraftingDialogClosed075Ref.Length; + var packet = new CraftingDialogClosed075Ref(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The quest index. + /// This is the complete byte with the state of four quests within the same byte. + /// + /// Is sent by the server when: When the player clicks on the quest npc. + /// Causes reaction on client side: The game client shows the next steps in the quest dialog. + /// + public static async ValueTask SendLegacyQuestStateDialogAsync(this IConnection? connection, byte @questIndex, byte @state) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = LegacyQuestStateDialogRef.Length; + var packet = new LegacyQuestStateDialogRef(connection.Output.GetSpan(length)[..length]); + packet.QuestIndex = @questIndex; + packet.State = @state; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The quest index. + /// This value is 0 if successful. Otherwise, 0xFF or even other magic values. + /// This is the complete byte with the state of four quests within the same byte. + /// + /// Is sent by the server when: As response to the set state request (C1A2). + /// Causes reaction on client side: The game client shows the new quest state. + /// + public static async ValueTask SendLegacySetQuestStateResponseAsync(this IConnection? connection, byte @questIndex, byte @result, byte @newState) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = LegacySetQuestStateResponseRef.Length; + var packet = new LegacySetQuestStateResponseRef(connection.Output.GetSpan(length)[..length]); + packet.QuestIndex = @questIndex; + packet.Result = @result; + packet.NewState = @newState; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player id. + /// The reward. + /// The count. + /// + /// Is sent by the server when: As response to the completed quest of a player in scope. + /// Causes reaction on client side: The game client shows the reward accordingly. + /// + public static async ValueTask SendLegacyQuestRewardAsync(this IConnection? connection, ushort @playerId, LegacyQuestReward.QuestRewardType @reward, byte @count) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = LegacyQuestRewardRef.Length; + var packet = new LegacyQuestRewardRef(connection.Output.GetSpan(length)[..length]); + packet.PlayerId = @playerId; + packet.Reward = @reward; + packet.Count = @count; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The pet command mode. + /// The target id. + /// The pet. + /// + /// Is sent by the server when: After the client sent a PetAttackCommand (as confirmation), or when the previous command finished and the pet is reset to Normal-mode. + /// Causes reaction on client side: The client updates the pet mode in its user interface. + /// + public static async ValueTask SendPetModeAsync(this IConnection? connection, ClientToServer.PetCommandMode @petCommandMode, ushort @targetId, ClientToServer.PetType @pet = ClientToServer.PetType.DarkRaven) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PetModeRef.Length; + var packet = new PetModeRef(connection.Output.GetSpan(length)[..length]); + packet.Pet = @pet; + packet.PetCommandMode = @petCommandMode; + packet.TargetId = @targetId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill type. + /// The owner id. + /// The target id. + /// The pet. + /// + /// Is sent by the server when: After the client sent a PetAttackCommand, the pet attacks automatically. For each attack, the player and all observing players get this message. + /// Causes reaction on client side: The client shows the pet attacking the target. + /// + public static async ValueTask SendPetAttackAsync(this IConnection? connection, PetAttack.PetSkillType @skillType, ushort @ownerId, ushort @targetId, ClientToServer.PetType @pet = ClientToServer.PetType.DarkRaven) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PetAttackRef.Length; + var packet = new PetAttackRef(connection.Output.GetSpan(length)[..length]); + packet.Pet = @pet; + packet.SkillType = @skillType; + packet.OwnerId = @ownerId; + packet.TargetId = @targetId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The pet. + /// The storage. + /// The item slot. + /// The level. + /// The experience. + /// The health. + /// + /// Is sent by the server when: After the client sent a PetInfoRequest for a pet (dark raven, horse). + /// Causes reaction on client side: The client shows the information about the pet. + /// + public static async ValueTask SendPetInfoResponseAsync(this IConnection? connection, ClientToServer.PetType @pet, ClientToServer.StorageType @storage, byte @itemSlot, byte @level, uint @experience, byte @health) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PetInfoResponseRef.Length; + var packet = new PetInfoResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Pet = @pet; + packet.Storage = @storage; + packet.ItemSlot = @itemSlot; + packet.Level = @level; + packet.Experience = @experience; + packet.Health = @health; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The opponent id. + /// The opponent name. + /// + /// Is sent by the server when: After the client sent a DuelStartRequest, and it either failed or the requested player sent a response. + /// Causes reaction on client side: The client shows the started or aborted duel. + /// + public static async ValueTask SendDuelStartResultAsync(this IConnection? connection, DuelStartResult.DuelStartResultType @result, ushort @opponentId, string @opponentName) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelStartResultRef.Length; + var packet = new DuelStartResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.OpponentId = @opponentId; + packet.OpponentName = @opponentName; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The requester id. + /// The requester name. + /// + /// Is sent by the server when: After another client sent a DuelStartRequest, to ask the requested player for a response. + /// Causes reaction on client side: The client shows the duel request. + /// + public static async ValueTask SendDuelStartRequestAsync(this IConnection? connection, ushort @requesterId, string @requesterName) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelStartRequestRef.Length; + var packet = new DuelStartRequestRef(connection.Output.GetSpan(length)[..length]); + packet.RequesterId = @requesterId; + packet.RequesterName = @requesterName; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The opponent id. + /// The opponent name. + /// The result. + /// + /// Is sent by the server when: After a duel ended. + /// Causes reaction on client side: The client updates its state. + /// + public static async ValueTask SendDuelEndAsync(this IConnection? connection, ushort @opponentId, string @opponentName, byte @result = 0) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelEndRef.Length; + var packet = new DuelEndRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.OpponentId = @opponentId; + packet.OpponentName = @opponentName; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player 1 id. + /// The player 2 id. + /// The player 1 score. + /// The player 2 score. + /// + /// Is sent by the server when: When the score of the duel has been changed. + /// Causes reaction on client side: The client updates the displayed duel score. + /// + public static async ValueTask SendDuelScoreAsync(this IConnection? connection, ushort @player1Id, ushort @player2Id, byte @player1Score, byte @player2Score) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelScoreRef.Length; + var packet = new DuelScoreRef(connection.Output.GetSpan(length)[..length]); + packet.Player1Id = @player1Id; + packet.Player2Id = @player2Id; + packet.Player1Score = @player1Score; + packet.Player2Score = @player2Score; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player 1 id. + /// The player 2 id. + /// The player 1 health percentage. + /// The player 2 health percentage. + /// The player 1 shield percentage. + /// The player 2 shield percentage. + /// + /// Is sent by the server when: When the health/shield of the duel players has been changed. + /// Causes reaction on client side: The client updates the displayed health and shield bars. + /// + public static async ValueTask SendDuelHealthUpdateAsync(this IConnection? connection, ushort @player1Id, ushort @player2Id, byte @player1HealthPercentage, byte @player2HealthPercentage, byte @player1ShieldPercentage, byte @player2ShieldPercentage) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelHealthUpdateRef.Length; + var packet = new DuelHealthUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.Player1Id = @player1Id; + packet.Player2Id = @player2Id; + packet.Player1HealthPercentage = @player1HealthPercentage; + packet.Player2HealthPercentage = @player2HealthPercentage; + packet.Player1ShieldPercentage = @player1ShieldPercentage; + packet.Player2ShieldPercentage = @player2ShieldPercentage; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The room index. + /// The player 1 name. + /// The player 2 name. + /// The player 1 id. + /// The player 2 id. + /// + /// Is sent by the server when: When the duel starts. + /// Causes reaction on client side: The client initializes the duel state. + /// + public static async ValueTask SendDuelInitAsync(this IConnection? connection, byte @result, byte @roomIndex, string @player1Name, string @player2Name, ushort @player1Id, ushort @player2Id) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelInitRef.Length; + var packet = new DuelInitRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.RoomIndex = @roomIndex; + packet.Player1Name = @player1Name; + packet.Player2Name = @player2Name; + packet.Player1Id = @player1Id; + packet.Player2Id = @player2Id; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: When the duel starts, after the DuelInit message. + /// Causes reaction on client side: The client updates the displayed health and shield bars. + /// + public static async ValueTask SendDuelHealthBarInitAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelHealthBarInitRef.Length; + var packet = new DuelHealthBarInitRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The name. + /// + /// Is sent by the server when: When a spectator joins a duel. + /// Causes reaction on client side: The client updates the list of spectators. + /// + public static async ValueTask SendDuelSpectatorAddedAsync(this IConnection? connection, string @name) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelSpectatorAddedRef.Length; + var packet = new DuelSpectatorAddedRef(connection.Output.GetSpan(length)[..length]); + packet.Name = @name; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The name. + /// + /// Is sent by the server when: When a spectator joins a duel. + /// Causes reaction on client side: The client updates the list of spectators. + /// + public static async ValueTask SendDuelSpectatorRemovedAsync(this IConnection? connection, string @name) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelSpectatorRemovedRef.Length; + var packet = new DuelSpectatorRemovedRef(connection.Output.GetSpan(length)[..length]); + packet.Name = @name; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The winner. + /// The loser. + /// + /// Is sent by the server when: When the duel finished. + /// Causes reaction on client side: The client shows the winner and loser names. + /// + public static async ValueTask SendDuelFinishedAsync(this IConnection? connection, string @winner, string @loser) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelFinishedRef.Length; + var packet = new DuelFinishedRef(connection.Output.GetSpan(length)[..length]); + packet.Winner = @winner; + packet.Loser = @loser; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The object id. + /// The stage. + /// The skill number. + /// + /// Is sent by the server when: After a player started a skill which needs to load up, like Nova. + /// Causes reaction on client side: The client may show the loading intensity. + /// + public static async ValueTask SendSkillStageUpdateAsync(this IConnection? connection, ushort @objectId, byte @stage, byte @skillNumber = 0x28) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillStageUpdateRef.Length; + var packet = new SkillStageUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.ObjectId = @objectId; + packet.SkillNumber = @skillNumber; + packet.Stage = @stage; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: The player requested to enter the illusion temple event. + /// Causes reaction on client side: The client shows the result. + /// + public static async ValueTask SendIllusionTempleEnterResultAsync(this IConnection? connection, byte @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = IllusionTempleEnterResultRef.Length; + var packet = new IllusionTempleEnterResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The skill number. + /// The source object id. + /// The target object id. + /// + /// Is sent by the server when: A player requested to use a specific skill in the illusion temple event. + /// Causes reaction on client side: The client shows the result. + /// + public static async ValueTask SendIllusionTempleSkillUsageResultAsync(this IConnection? connection, byte @result, ushort @skillNumber, ushort @sourceObjectId, ushort @targetObjectId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = IllusionTempleSkillUsageResultRef.Length; + var packet = new IllusionTempleSkillUsageResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.SkillNumber = @skillNumber; + packet.SourceObjectId = @sourceObjectId; + packet.TargetObjectId = @targetObjectId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The user count 1. + /// The user count 2. + /// The user count 3. + /// The user count 4. + /// The user count 5. + /// The user count 6. + /// + /// Is sent by the server when: ? + /// Causes reaction on client side: The client shows the counts. + /// + public static async ValueTask SendIllusionTempleUserCountAsync(this IConnection? connection, byte @userCount1, byte @userCount2, byte @userCount3, byte @userCount4, byte @userCount5, byte @userCount6) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = IllusionTempleUserCountRef.Length; + var packet = new IllusionTempleUserCountRef(connection.Output.GetSpan(length)[..length]); + packet.UserCount1 = @userCount1; + packet.UserCount2 = @userCount2; + packet.UserCount3 = @userCount3; + packet.UserCount4 = @userCount4; + packet.UserCount5 = @userCount5; + packet.UserCount6 = @userCount6; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill points. + /// + /// Is sent by the server when: ? + /// Causes reaction on client side: The client shows the skill points. + /// + public static async ValueTask SendIllusionTempleSkillPointUpdateAsync(this IConnection? connection, byte @skillPoints) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = IllusionTempleSkillPointUpdateRef.Length; + var packet = new IllusionTempleSkillPointUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.SkillPoints = @skillPoints; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill number. + /// The object index. + /// + /// Is sent by the server when: ? + /// Causes reaction on client side: The client shows the skill points. + /// + public static async ValueTask SendIllusionTempleSkillEndedAsync(this IConnection? connection, ushort @skillNumber, ushort @objectIndex) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = IllusionTempleSkillEndedRef.Length; + var packet = new IllusionTempleSkillEndedRef(connection.Output.GetSpan(length)[..length]); + packet.SkillNumber = @skillNumber; + packet.ObjectIndex = @objectIndex; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The user index. + /// The name. + /// + /// Is sent by the server when: ? + /// Causes reaction on client side: ?. + /// + public static async ValueTask SendIllusionTempleHolyItemRelicsAsync(this IConnection? connection, ushort @userIndex, string @name) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = IllusionTempleHolyItemRelicsRef.Length; + var packet = new IllusionTempleHolyItemRelicsRef(connection.Output.GetSpan(length)[..length]); + packet.UserIndex = @userIndex; + packet.Name = @name; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The temple number. + /// The state. + /// + /// Is sent by the server when: ? + /// Causes reaction on client side: The client shows the skill points. + /// + public static async ValueTask SendIllusionTempleSkillEndAsync(this IConnection? connection, byte @templeNumber, byte @state) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = IllusionTempleSkillEndRef.Length; + var packet = new IllusionTempleSkillEndRef(connection.Output.GetSpan(length)[..length]); + packet.TempleNumber = @templeNumber; + packet.State = @state; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The flag, if money should be consumed. If this is 'true', setting PauseStatus to 'false' doesn't cause starting the helper. + /// The money. + /// The pause status. A value of 'true' always works to stop the helper. However, it can only be started, with ConsumeMoney set to 'false'. + /// + /// Is sent by the server when: The server validated or changed the status of the MU Helper. + /// Causes reaction on client side: The client toggle the MU Helper status. + /// + public static async ValueTask SendMuHelperStatusUpdateAsync(this IConnection? connection, bool @consumeMoney, uint @money, bool @pauseStatus) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MuHelperStatusUpdateRef.Length; + var packet = new MuHelperStatusUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.ConsumeMoney = @consumeMoney; + packet.Money = @money; + packet.PauseStatus = @pauseStatus; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The helper data. + /// + /// Is sent by the server when: The server saved the users MU Helper data. + /// Causes reaction on client side: The user wants to save the MU Helper data. + /// + public static async ValueTask SendMuHelperConfigurationDataAsync(this IConnection? connection, Memory @helperData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MuHelperConfigurationDataRef.Length; + var packet = new MuHelperConfigurationDataRef(connection.Output.GetSpan(length)[..length]); + @helperData.Span.CopyTo(packet.HelperData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The friend name. + /// The server id on which the player currently is online. 0xFF means offline. + /// + /// Is sent by the server when: After a friend has been added to the friend list. + /// Causes reaction on client side: The friend appears in the friend list. + /// + public static async ValueTask SendFriendAddedAsync(this IConnection? connection, string @friendName, byte @serverId = 0xFF) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = FriendAddedRef.Length; + var packet = new FriendAddedRef(connection.Output.GetSpan(length)[..length]); + packet.FriendName = @friendName; + packet.ServerId = @serverId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The requester. + /// + /// Is sent by the server when: After a player has requested to add another player as friend. This other player gets this message. + /// Causes reaction on client side: The friend request appears on the user interface. + /// + public static async ValueTask SendFriendRequestAsync(this IConnection? connection, string @requester) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = FriendRequestRef.Length; + var packet = new FriendRequestRef(connection.Output.GetSpan(length)[..length]); + packet.Requester = @requester; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The friend name. + /// + /// Is sent by the server when: After a friend has been removed from the friend list. + /// Causes reaction on client side: The friend is removed from the friend list. + /// + public static async ValueTask SendFriendDeletedAsync(this IConnection? connection, string @friendName) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = FriendDeletedRef.Length; + var packet = new FriendDeletedRef(connection.Output.GetSpan(length)[..length]); + packet.FriendName = @friendName; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The friend name. + /// The server id on which the player currently is online. 0xFF means offline. + /// + /// Is sent by the server when: After a friend has been added to the friend list. + /// Causes reaction on client side: The friend appears in the friend list. + /// + public static async ValueTask SendFriendOnlineStateUpdateAsync(this IConnection? connection, string @friendName, byte @serverId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = FriendOnlineStateUpdateRef.Length; + var packet = new FriendOnlineStateUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.FriendName = @friendName; + packet.ServerId = @serverId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The letter id. + /// The result. + /// + /// Is sent by the server when: After the player requested to send a letter to another player. + /// Causes reaction on client side: Depending on the result, the letter send dialog closes or an error message appears. + /// + public static async ValueTask SendLetterSendResponseAsync(this IConnection? connection, uint @letterId, LetterSendResponse.LetterSendRequestResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = LetterSendResponseRef.Length; + var packet = new LetterSendResponseRef(connection.Output.GetSpan(length)[..length]); + packet.LetterId = @letterId; + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The letter index. + /// The sender name. + /// The timestamp. + /// The subject. + /// The state. + /// + /// Is sent by the server when: After a letter has been received or after the player entered the game with a character. + /// Causes reaction on client side: The letter appears in the letter list. + /// + public static async ValueTask SendAddLetterAsync(this IConnection? connection, ushort @letterIndex, string @senderName, string @timestamp, string @subject, AddLetter.LetterState @state) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = AddLetterRef.Length; + var packet = new AddLetterRef(connection.Output.GetSpan(length)[..length]); + packet.LetterIndex = @letterIndex; + packet.SenderName = @senderName; + packet.Timestamp = @timestamp; + packet.Subject = @subject; + packet.State = @state; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The letter index. + /// The message size. + /// The sender appearance. + /// The rotation. + /// The animation. + /// The message. + /// + /// Is sent by the server when: After the player requested to read a letter. + /// Causes reaction on client side: The letter is opened in a new dialog. + /// + public static async ValueTask SendOpenLetterAsync(this IConnection? connection, ushort @letterIndex, ushort @messageSize, Memory @senderAppearance, byte @rotation, byte @animation, string @message) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = OpenLetterRef.GetRequiredSize(message); + var packet = new OpenLetterRef(connection.Output.GetSpan(length)[..length]); + packet.LetterIndex = @letterIndex; + packet.MessageSize = @messageSize; + @senderAppearance.Span.CopyTo(packet.SenderAppearance); + packet.Rotation = @rotation; + packet.Animation = @animation; + packet.Message = @message; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The letter index. + /// The sender appearance. + /// The rotation. + /// The animation. + /// The message. + /// + /// Is sent by the server when: After the player requested to read a letter. + /// Causes reaction on client side: The letter is opened in a new dialog. + /// + public static async ValueTask SendOpenLetterExtendedAsync(this IConnection? connection, ushort @letterIndex, Memory @senderAppearance, byte @rotation, byte @animation, string @message) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = OpenLetterExtendedRef.GetRequiredSize(message); + var packet = new OpenLetterExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.LetterIndex = @letterIndex; + @senderAppearance.Span.CopyTo(packet.SenderAppearance); + packet.Rotation = @rotation; + packet.Animation = @animation; + packet.Message = @message; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The letter index. + /// The request successful. + /// + /// Is sent by the server when: After a letter has been deleted by the request of the player. + /// Causes reaction on client side: The letter is removed from the letter list. + /// + public static async ValueTask SendRemoveLetterAsync(this IConnection? connection, ushort @letterIndex, bool @requestSuccessful = true) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RemoveLetterRef.Length; + var packet = new RemoveLetterRef(connection.Output.GetSpan(length)[..length]); + packet.RequestSuccessful = @requestSuccessful; + packet.LetterIndex = @letterIndex; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The chat server ip. + /// The chat room id. + /// The authentication token. + /// The friend name. + /// The success. + /// The type. + /// + /// Is sent by the server when: The player is invited to join a chat room on the chat server. + /// Causes reaction on client side: The game client connects to the chat server and joins the chat room with the specified authentication data. + /// + public static async ValueTask SendChatRoomConnectionInfoAsync(this IConnection? connection, string @chatServerIp, ushort @chatRoomId, uint @authenticationToken, string @friendName, bool @success, byte @type = 1) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ChatRoomConnectionInfoRef.Length; + var packet = new ChatRoomConnectionInfoRef(connection.Output.GetSpan(length)[..length]); + packet.ChatServerIp = @chatServerIp; + packet.ChatRoomId = @chatRoomId; + packet.AuthenticationToken = @authenticationToken; + packet.Type = @type; + packet.FriendName = @friendName; + packet.Success = @success; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The request id. + /// + /// Is sent by the server when: The player requested to add another player to his friend list and the server processed this request. + /// Causes reaction on client side: The game client knows if the invitation could be sent to the other player. + /// + public static async ValueTask SendFriendInvitationResultAsync(this IConnection? connection, bool @success, uint @requestId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = FriendInvitationResultRef.Length; + var packet = new FriendInvitationResultRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.RequestId = @requestId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// A number specifying the description: A) when selecting a quest in the quest list, it's the "StartingNumber"; B) when a quest has been started it's the quest number; C) when the starting number has been sent previously and the player refused to start the quest, it sends a "RefuseNumber". + /// The quest group. + /// + /// Is sent by the server when: After the game client clicked on a quest in the quest list, proceeded with a quest or refused to start a quest. + /// Causes reaction on client side: The client shows the corresponding description about the current quest step. + /// + public static async ValueTask SendQuestStepInfoAsync(this IConnection? connection, ushort @questStepNumber, ushort @questGroup) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = QuestStepInfoRef.Length; + var packet = new QuestStepInfoRef(connection.Output.GetSpan(length)[..length]); + packet.QuestStepNumber = @questStepNumber; + packet.QuestGroup = @questGroup; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The quest number. + /// The quest group. + /// The is quest completed. + /// + /// Is sent by the server when: The server acknowledges the completion of a quest. + /// Causes reaction on client side: The client shows the success and possibly requests for the next available quests. + /// + public static async ValueTask SendQuestCompletionResponseAsync(this IConnection? connection, ushort @questNumber, ushort @questGroup, bool @isQuestCompleted) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = QuestCompletionResponseRef.Length; + var packet = new QuestCompletionResponseRef(connection.Output.GetSpan(length)[..length]); + packet.QuestNumber = @questNumber; + packet.QuestGroup = @questGroup; + packet.IsQuestCompleted = @isQuestCompleted; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The current quest number. In this message, it's always 0, because the group is relevant for the client. + /// The quest group. + /// + /// Is sent by the server when: The server acknowledges the requested cancellation of a quest. + /// Causes reaction on client side: The client resets the state of the quest and can request a new list of available quests again. This list would then probably contain the cancelled quest again. + /// + public static async ValueTask SendQuestCancelledAsync(this IConnection? connection, ushort @questNumber, ushort @questGroup) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = QuestCancelledRef.Length; + var packet = new QuestCancelledRef(connection.Output.GetSpan(length)[..length]); + packet.QuestNumber = @questNumber; + packet.QuestGroup = @questGroup; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The npc number. + /// The gens contribution points. + /// + /// Is sent by the server when: The server acknowledges the requested opening of an npc dialog. + /// Causes reaction on client side: The client opens the dialog of the specified npc. + /// + public static async ValueTask SendOpenNpcDialogAsync(this IConnection? connection, ushort @npcNumber, uint @gensContributionPoints) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = OpenNpcDialogRef.Length; + var packet = new OpenNpcDialogRef(connection.Output.GetSpan(length)[..length]); + packet.NpcNumber = @npcNumber; + packet.GensContributionPoints = @gensContributionPoints; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: The player requested to enter the devil square mini game through the Charon NPC. + /// Causes reaction on client side: In case it failed, it shows the corresponding error message. + /// + public static async ValueTask SendDevilSquareEnterResultAsync(this IConnection? connection, DevilSquareEnterResult.EnterResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DevilSquareEnterResultRef.Length; + var packet = new DevilSquareEnterResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The game type. + /// The remaining entering time minutes. + /// The user count. + /// Just used for Chaos Castle. In this case, this field contains the lower byte of the remaining minutes. For other event types, this field is not used. + /// + /// Is sent by the server when: The player requests to get the current opening state of a mini game event, by clicking on an ticket item. + /// Causes reaction on client side: The opening state of the event (remaining entering time, etc.) is shown at the client. + /// + public static async ValueTask SendMiniGameOpeningStateAsync(this IConnection? connection, MiniGameType @gameType, byte @remainingEnteringTimeMinutes, byte @userCount, byte @remainingEnteringTimeMinutesLow) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MiniGameOpeningStateRef.Length; + var packet = new MiniGameOpeningStateRef(connection.Output.GetSpan(length)[..length]); + packet.GameType = @gameType; + packet.RemainingEnteringTimeMinutes = @remainingEnteringTimeMinutes; + packet.UserCount = @userCount; + packet.RemainingEnteringTimeMinutesLow = @remainingEnteringTimeMinutesLow; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The state. + /// + /// Is sent by the server when: The state of a mini game event is about to change in 30 seconds. + /// Causes reaction on client side: The client side shows a message about the changing state. + /// + public static async ValueTask SendUpdateMiniGameStateAsync(this IConnection? connection, UpdateMiniGameState.MiniGameTypeState @state) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = UpdateMiniGameStateRef.Length; + var packet = new UpdateMiniGameStateRef(connection.Output.GetSpan(length)[..length]); + packet.State = @state; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The player name. + /// The total score. + /// The bonus experience. + /// The bonus money. + /// The type. + /// + /// Is sent by the server when: The blood castle mini game ended and the score of the player is sent to the player. + /// Causes reaction on client side: The score is shown at the client. + /// + public static async ValueTask SendBloodCastleScoreAsync(this IConnection? connection, bool @success, string @playerName, uint @totalScore, uint @bonusExperience, uint @bonusMoney, byte @type = 0xFF) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = BloodCastleScoreRef.Length; + var packet = new BloodCastleScoreRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.Type = @type; + packet.PlayerName = @playerName; + packet.TotalScore = @totalScore; + packet.BonusExperience = @bonusExperience; + packet.BonusMoney = @bonusMoney; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: The player requested to enter the blood castle mini game through the Archangel Messenger NPC. + /// Causes reaction on client side: In case it failed, it shows the corresponding error message. + /// + public static async ValueTask SendBloodCastleEnterResultAsync(this IConnection? connection, BloodCastleEnterResult.EnterResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = BloodCastleEnterResultRef.Length; + var packet = new BloodCastleEnterResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The state. + /// The remain second. + /// The max monster. + /// The cur monster. + /// The item owner id. + /// The item level. + /// + /// Is sent by the server when: The state of a blood castle event is about to change. + /// Causes reaction on client side: The client side shows a message about the changing state. + /// + public static async ValueTask SendBloodCastleStateAsync(this IConnection? connection, BloodCastleState.Status @state, ushort @remainSecond, ushort @maxMonster, ushort @curMonster, ushort @itemOwnerId, byte @itemLevel) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = BloodCastleStateRef.Length; + var packet = new BloodCastleStateRef(connection.Output.GetSpan(length)[..length]); + packet.State = @state; + packet.RemainSecond = @remainSecond; + packet.MaxMonster = @maxMonster; + packet.CurMonster = @curMonster; + packet.ItemOwnerId = @itemOwnerId; + packet.ItemLevel = @itemLevel; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: The player requested to enter the chaos castle mini game by using the 'Armor of Guardsman' item. + /// Causes reaction on client side: In case it failed, it shows the corresponding error message. + /// + public static async ValueTask SendChaosCastleEnterResultAsync(this IConnection? connection, ChaosCastleEnterResult.EnterResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ChaosCastleEnterResultRef.Length; + var packet = new ChaosCastleEnterResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The enable. + /// The event. + /// + /// Is sent by the server when: The state of event is about to change. + /// Causes reaction on client side: The event's effect is shown. + /// + public static async ValueTask SendMapEventStateAsync(this IConnection? connection, bool @enable, MapEventState.Events @event) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MapEventStateRef.Length; + var packet = new MapEventStateRef(connection.Output.GetSpan(length)[..length]); + packet.Enable = @enable; + packet.Event = @event; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + /// + /// Sends a to this connection. /// /// The connection. - /// The letter index. - /// The sender name. - /// The timestamp. - /// The subject. /// The state. + /// The detail state. + /// The can enter. + /// The user count. + /// The remain seconds. /// - /// Is sent by the server when: After a letter has been received or after the player entered the game with a character. - /// Causes reaction on client side: The letter appears in the letter list. + /// Is sent by the server when: The player requests state information from the Kanturu gateway NPC. + /// Causes reaction on client side: The client shows the Kanturu entry dialog with event state, detail state, whether entry is possible, current player count and remaining time. /// - public static async ValueTask SendAddLetterAsync(this IConnection? connection, ushort @letterIndex, string @senderName, string @timestamp, string @subject, AddLetter.LetterState @state) + public static async ValueTask SendKanturuStateInfoAsync(this IConnection? connection, KanturuStateInfo.StateType @state, byte @detailState, bool @canEnter, byte @userCount, uint @remainSeconds) { if (connection is null) { @@ -5630,13 +6211,13 @@ public static async ValueTask SendAddLetterAsync(this IConnection? connection, u int WritePacket() { - var length = AddLetterRef.Length; - var packet = new AddLetterRef(connection.Output.GetSpan(length)[..length]); - packet.LetterIndex = @letterIndex; - packet.SenderName = @senderName; - packet.Timestamp = @timestamp; - packet.Subject = @subject; + var length = KanturuStateInfoRef.Length; + var packet = new KanturuStateInfoRef(connection.Output.GetSpan(length)[..length]); packet.State = @state; + packet.DetailState = @detailState; + packet.CanEnter = @canEnter; + packet.UserCount = @userCount; + packet.RemainSeconds = @remainSeconds; return packet.Header.Length; } @@ -5645,309 +6226,15 @@ int WritePacket() } /// - /// Sends a to this connection. - /// - /// The connection. - /// The letter index. - /// The message size. - /// The sender appearance. - /// The rotation. - /// The animation. - /// The message. - /// - /// Is sent by the server when: After the player requested to read a letter. - /// Causes reaction on client side: The letter is opened in a new dialog. - /// - public static async ValueTask SendOpenLetterAsync(this IConnection? connection, ushort @letterIndex, ushort @messageSize, Memory @senderAppearance, byte @rotation, byte @animation, string @message) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = OpenLetterRef.GetRequiredSize(message); - var packet = new OpenLetterRef(connection.Output.GetSpan(length)[..length]); - packet.LetterIndex = @letterIndex; - packet.MessageSize = @messageSize; - @senderAppearance.Span.CopyTo(packet.SenderAppearance); - packet.Rotation = @rotation; - packet.Animation = @animation; - packet.Message = @message; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The letter index. - /// The sender appearance. - /// The rotation. - /// The animation. - /// The message. - /// - /// Is sent by the server when: After the player requested to read a letter. - /// Causes reaction on client side: The letter is opened in a new dialog. - /// - public static async ValueTask SendOpenLetterExtendedAsync(this IConnection? connection, ushort @letterIndex, Memory @senderAppearance, byte @rotation, byte @animation, string @message) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = OpenLetterExtendedRef.GetRequiredSize(message); - var packet = new OpenLetterExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.LetterIndex = @letterIndex; - @senderAppearance.Span.CopyTo(packet.SenderAppearance); - packet.Rotation = @rotation; - packet.Animation = @animation; - packet.Message = @message; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The letter index. - /// The request successful. - /// - /// Is sent by the server when: After a letter has been deleted by the request of the player. - /// Causes reaction on client side: The letter is removed from the letter list. - /// - public static async ValueTask SendRemoveLetterAsync(this IConnection? connection, ushort @letterIndex, bool @requestSuccessful = true) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = RemoveLetterRef.Length; - var packet = new RemoveLetterRef(connection.Output.GetSpan(length)[..length]); - packet.RequestSuccessful = @requestSuccessful; - packet.LetterIndex = @letterIndex; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The chat server ip. - /// The chat room id. - /// The authentication token. - /// The friend name. - /// The success. - /// The type. - /// - /// Is sent by the server when: The player is invited to join a chat room on the chat server. - /// Causes reaction on client side: The game client connects to the chat server and joins the chat room with the specified authentication data. - /// - public static async ValueTask SendChatRoomConnectionInfoAsync(this IConnection? connection, string @chatServerIp, ushort @chatRoomId, uint @authenticationToken, string @friendName, bool @success, byte @type = 1) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ChatRoomConnectionInfoRef.Length; - var packet = new ChatRoomConnectionInfoRef(connection.Output.GetSpan(length)[..length]); - packet.ChatServerIp = @chatServerIp; - packet.ChatRoomId = @chatRoomId; - packet.AuthenticationToken = @authenticationToken; - packet.Type = @type; - packet.FriendName = @friendName; - packet.Success = @success; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The request id. - /// - /// Is sent by the server when: The player requested to add another player to his friend list and the server processed this request. - /// Causes reaction on client side: The game client knows if the invitation could be sent to the other player. - /// - public static async ValueTask SendFriendInvitationResultAsync(this IConnection? connection, bool @success, uint @requestId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = FriendInvitationResultRef.Length; - var packet = new FriendInvitationResultRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.RequestId = @requestId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// A number specifying the description: A) when selecting a quest in the quest list, it's the "StartingNumber"; B) when a quest has been started it's the quest number; C) when the starting number has been sent previously and the player refused to start the quest, it sends a "RefuseNumber". - /// The quest group. - /// - /// Is sent by the server when: After the game client clicked on a quest in the quest list, proceeded with a quest or refused to start a quest. - /// Causes reaction on client side: The client shows the corresponding description about the current quest step. - /// - public static async ValueTask SendQuestStepInfoAsync(this IConnection? connection, ushort @questStepNumber, ushort @questGroup) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = QuestStepInfoRef.Length; - var packet = new QuestStepInfoRef(connection.Output.GetSpan(length)[..length]); - packet.QuestStepNumber = @questStepNumber; - packet.QuestGroup = @questGroup; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The quest number. - /// The quest group. - /// The is quest completed. - /// - /// Is sent by the server when: The server acknowledges the completion of a quest. - /// Causes reaction on client side: The client shows the success and possibly requests for the next available quests. - /// - public static async ValueTask SendQuestCompletionResponseAsync(this IConnection? connection, ushort @questNumber, ushort @questGroup, bool @isQuestCompleted) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = QuestCompletionResponseRef.Length; - var packet = new QuestCompletionResponseRef(connection.Output.GetSpan(length)[..length]); - packet.QuestNumber = @questNumber; - packet.QuestGroup = @questGroup; - packet.IsQuestCompleted = @isQuestCompleted; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The current quest number. In this message, it's always 0, because the group is relevant for the client. - /// The quest group. - /// - /// Is sent by the server when: The server acknowledges the requested cancellation of a quest. - /// Causes reaction on client side: The client resets the state of the quest and can request a new list of available quests again. This list would then probably contain the cancelled quest again. - /// - public static async ValueTask SendQuestCancelledAsync(this IConnection? connection, ushort @questNumber, ushort @questGroup) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = QuestCancelledRef.Length; - var packet = new QuestCancelledRef(connection.Output.GetSpan(length)[..length]); - packet.QuestNumber = @questNumber; - packet.QuestGroup = @questGroup; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The npc number. - /// The gens contribution points. - /// - /// Is sent by the server when: The server acknowledges the requested opening of an npc dialog. - /// Causes reaction on client side: The client opens the dialog of the specified npc. - /// - public static async ValueTask SendOpenNpcDialogAsync(this IConnection? connection, ushort @npcNumber, uint @gensContributionPoints) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = OpenNpcDialogRef.Length; - var packet = new OpenNpcDialogRef(connection.Output.GetSpan(length)[..length]); - packet.NpcNumber = @npcNumber; - packet.GensContributionPoints = @gensContributionPoints; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. + /// Sends a to this connection. /// /// The connection. /// The result. /// - /// Is sent by the server when: The player requested to enter the devil square mini game through the Charon NPC. - /// Causes reaction on client side: In case it failed, it shows the corresponding error message. + /// Is sent by the server when: The player attempted to enter the Kanturu event through the gateway NPC. + /// Causes reaction on client side: The client stops the NPC animation. On success the player has already been teleported. On failure a corresponding message is shown. /// - public static async ValueTask SendDevilSquareEnterResultAsync(this IConnection? connection, DevilSquareEnterResult.EnterResult @result) + public static async ValueTask SendKanturuEnterResultAsync(this IConnection? connection, KanturuEnterResult.EnterResult @result) { if (connection is null) { @@ -5956,8 +6243,8 @@ public static async ValueTask SendDevilSquareEnterResultAsync(this IConnection? int WritePacket() { - var length = DevilSquareEnterResultRef.Length; - var packet = new DevilSquareEnterResultRef(connection.Output.GetSpan(length)[..length]); + var length = KanturuEnterResultRef.Length; + var packet = new KanturuEnterResultRef(connection.Output.GetSpan(length)[..length]); packet.Result = @result; return packet.Header.Length; @@ -5967,49 +6254,16 @@ int WritePacket() } /// - /// Sends a to this connection. - /// - /// The connection. - /// The game type. - /// The remaining entering time minutes. - /// The user count. - /// Just used for Chaos Castle. In this case, this field contains the lower byte of the remaining minutes. For other event types, this field is not used. - /// - /// Is sent by the server when: The player requests to get the current opening state of a mini game event, by clicking on an ticket item. - /// Causes reaction on client side: The opening state of the event (remaining entering time, etc.) is shown at the client. - /// - public static async ValueTask SendMiniGameOpeningStateAsync(this IConnection? connection, MiniGameType @gameType, byte @remainingEnteringTimeMinutes, byte @userCount, byte @remainingEnteringTimeMinutesLow) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MiniGameOpeningStateRef.Length; - var packet = new MiniGameOpeningStateRef(connection.Output.GetSpan(length)[..length]); - packet.GameType = @gameType; - packet.RemainingEnteringTimeMinutes = @remainingEnteringTimeMinutes; - packet.UserCount = @userCount; - packet.RemainingEnteringTimeMinutesLow = @remainingEnteringTimeMinutesLow; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. + /// Sends a to this connection. /// /// The connection. /// The state. + /// The detail state. /// - /// Is sent by the server when: The state of a mini game event is about to change in 30 seconds. - /// Causes reaction on client side: The client side shows a message about the changing state. + /// Is sent by the server when: The Kanturu event transitions to a new phase or sub-phase. + /// Causes reaction on client side: The client shows or hides the in-map HUD, switches background music, and when entering the Tower state reloads the barrier-open terrain file to visually remove the Elphis barrier. /// - public static async ValueTask SendUpdateMiniGameStateAsync(this IConnection? connection, UpdateMiniGameState.MiniGameTypeState @state) + public static async ValueTask SendKanturuStateChangeAsync(this IConnection? connection, KanturuStateChange.StateType @state, byte @detailState) { if (connection is null) { @@ -6018,9 +6272,10 @@ public static async ValueTask SendUpdateMiniGameStateAsync(this IConnection? con int WritePacket() { - var length = UpdateMiniGameStateRef.Length; - var packet = new UpdateMiniGameStateRef(connection.Output.GetSpan(length)[..length]); + var length = KanturuStateChangeRef.Length; + var packet = new KanturuStateChangeRef(connection.Output.GetSpan(length)[..length]); packet.State = @state; + packet.DetailState = @detailState; return packet.Header.Length; } @@ -6029,53 +6284,15 @@ int WritePacket() } /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The player name. - /// The total score. - /// The bonus experience. - /// The bonus money. - /// The type. - /// - /// Is sent by the server when: The blood castle mini game ended and the score of the player is sent to the player. - /// Causes reaction on client side: The score is shown at the client. - /// - public static async ValueTask SendBloodCastleScoreAsync(this IConnection? connection, bool @success, string @playerName, uint @totalScore, uint @bonusExperience, uint @bonusMoney, byte @type = 0xFF) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = BloodCastleScoreRef.Length; - var packet = new BloodCastleScoreRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.Type = @type; - packet.PlayerName = @playerName; - packet.TotalScore = @totalScore; - packet.BonusExperience = @bonusExperience; - packet.BonusMoney = @bonusMoney; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. + /// Sends a to this connection. /// /// The connection. /// The result. /// - /// Is sent by the server when: The player requested to enter the blood castle mini game through the Archangel Messenger NPC. - /// Causes reaction on client side: In case it failed, it shows the corresponding error message. + /// Is sent by the server when: The Kanturu event ends with a victory or defeat outcome. + /// Causes reaction on client side: The client displays the Success_kantru.tga overlay on victory or the Failure_kantru.tga overlay on defeat. /// - public static async ValueTask SendBloodCastleEnterResultAsync(this IConnection? connection, BloodCastleEnterResult.EnterResult @result) + public static async ValueTask SendKanturuBattleResultAsync(this IConnection? connection, KanturuBattleResult.BattleResult @result) { if (connection is null) { @@ -6084,8 +6301,8 @@ public static async ValueTask SendBloodCastleEnterResultAsync(this IConnection? int WritePacket() { - var length = BloodCastleEnterResultRef.Length; - var packet = new BloodCastleEnterResultRef(connection.Output.GetSpan(length)[..length]); + var length = KanturuBattleResultRef.Length; + var packet = new KanturuBattleResultRef(connection.Output.GetSpan(length)[..length]); packet.Result = @result; return packet.Header.Length; @@ -6095,20 +6312,15 @@ int WritePacket() } /// - /// Sends a to this connection. + /// Sends a to this connection. /// /// The connection. - /// The state. - /// The remain second. - /// The max monster. - /// The cur monster. - /// The item owner id. - /// The item level. + /// The time limit milliseconds. /// - /// Is sent by the server when: The state of a blood castle event is about to change. - /// Causes reaction on client side: The client side shows a message about the changing state. + /// Is sent by the server when: A timed phase begins in the Kanturu event. + /// Causes reaction on client side: The client starts a countdown timer shown in the Kanturu HUD. The value is divided by 1000 to obtain seconds. /// - public static async ValueTask SendBloodCastleStateAsync(this IConnection? connection, BloodCastleState.Status @state, ushort @remainSecond, ushort @maxMonster, ushort @curMonster, ushort @itemOwnerId, byte @itemLevel) + public static async ValueTask SendKanturuTimeLimitAsync(this IConnection? connection, uint @timeLimitMilliseconds) { if (connection is null) { @@ -6117,14 +6329,9 @@ public static async ValueTask SendBloodCastleStateAsync(this IConnection? connec int WritePacket() { - var length = BloodCastleStateRef.Length; - var packet = new BloodCastleStateRef(connection.Output.GetSpan(length)[..length]); - packet.State = @state; - packet.RemainSecond = @remainSecond; - packet.MaxMonster = @maxMonster; - packet.CurMonster = @curMonster; - packet.ItemOwnerId = @itemOwnerId; - packet.ItemLevel = @itemLevel; + var length = KanturuTimeLimitRef.Length; + var packet = new KanturuTimeLimitRef(connection.Output.GetSpan(length)[..length]); + packet.TimeLimitMilliseconds = @timeLimitMilliseconds; return packet.Header.Length; } @@ -6133,15 +6340,15 @@ int WritePacket() } /// - /// Sends a to this connection. + /// Sends a to this connection. /// /// The connection. - /// The result. + /// The type. /// - /// Is sent by the server when: The player requested to enter the chaos castle mini game by using the 'Armor of Guardsman' item. - /// Causes reaction on client side: In case it failed, it shows the corresponding error message. + /// Is sent by the server when: The Maya body executes a wide-area attack during the Maya battle phase. + /// Causes reaction on client side: The client plays a visual attack sequence on the Maya body model: storm (0) or stone-rain (1). This is a purely cosmetic packet - damage is handled server-side. /// - public static async ValueTask SendChaosCastleEnterResultAsync(this IConnection? connection, ChaosCastleEnterResult.EnterResult @result) + public static async ValueTask SendKanturuMayaWideAreaAttackAsync(this IConnection? connection, KanturuMayaWideAreaAttack.AttackType @type) { if (connection is null) { @@ -6150,9 +6357,9 @@ public static async ValueTask SendChaosCastleEnterResultAsync(this IConnection? int WritePacket() { - var length = ChaosCastleEnterResultRef.Length; - var packet = new ChaosCastleEnterResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; + var length = KanturuMayaWideAreaAttackRef.Length; + var packet = new KanturuMayaWideAreaAttackRef(connection.Output.GetSpan(length)[..length]); + packet.Type = @type; return packet.Header.Length; } @@ -6161,16 +6368,16 @@ int WritePacket() } /// - /// Sends a to this connection. + /// Sends a to this connection. /// /// The connection. - /// The enable. - /// The event. + /// The monster count. + /// The user count. /// - /// Is sent by the server when: The state of event is about to change. - /// Causes reaction on client side: The event's effect is shown. + /// Is sent by the server when: A monster is killed or the player count changes during the Kanturu event. + /// Causes reaction on client side: The client updates the monster count and user count numbers displayed in the Kanturu HUD. /// - public static async ValueTask SendMapEventStateAsync(this IConnection? connection, bool @enable, MapEventState.Events @event) + public static async ValueTask SendKanturuMonsterUserCountAsync(this IConnection? connection, byte @monsterCount, byte @userCount) { if (connection is null) { @@ -6179,13 +6386,14 @@ public static async ValueTask SendMapEventStateAsync(this IConnection? connectio int WritePacket() { - var length = MapEventStateRef.Length; - var packet = new MapEventStateRef(connection.Output.GetSpan(length)[..length]); - packet.Enable = @enable; - packet.Event = @event; + var length = KanturuMonsterUserCountRef.Length; + var packet = new KanturuMonsterUserCountRef(connection.Output.GetSpan(length)[..length]); + packet.MonsterCount = @monsterCount; + packet.UserCount = @userCount; return packet.Header.Length; } await connection.SendAsync(WritePacket).ConfigureAwait(false); - }} \ No newline at end of file + } +} \ No newline at end of file From ec8267d61871820cdf2bb50c7a8d8d8dd1762ecc Mon Sep 17 00:00:00 2001 From: "Cristobal Barra (Sistema)" <80994201+apraxico@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:57:11 -0400 Subject: [PATCH 10/11] chore(kanturu): remove KanturuConnectionExtensions.cs workaround The Send*Async extension methods are now properly added to ConnectionExtensions.cs, matching the auto-generated pattern. The separate file is no longer needed. --- .../KanturuConnectionExtensions.cs | 174 ------------------ 1 file changed, 174 deletions(-) delete mode 100644 src/Network/Packets/ServerToClient/KanturuConnectionExtensions.cs diff --git a/src/Network/Packets/ServerToClient/KanturuConnectionExtensions.cs b/src/Network/Packets/ServerToClient/KanturuConnectionExtensions.cs deleted file mode 100644 index acc446636..000000000 --- a/src/Network/Packets/ServerToClient/KanturuConnectionExtensions.cs +++ /dev/null @@ -1,174 +0,0 @@ -// -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace MUnique.OpenMU.Network.Packets.ServerToClient; - -using MUnique.OpenMU.Network; - -/// -/// Extension methods to send Kanturu event packets (0xD1 group) on a . -/// Kept in a separate file so they survive regeneration of the auto-generated ConnectionExtensions.cs. -/// -public static class KanturuConnectionExtensions -{ - /// - /// Sends a to this connection. - /// - public static async ValueTask SendKanturuStateInfoAsync(this IConnection? connection, KanturuStateInfo.StateType @state, byte @detailState, bool @canEnter, byte @userCount, uint @remainSeconds) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = KanturuStateInfoRef.Length; - var packet = new KanturuStateInfoRef(connection.Output.GetSpan(length)[..length]); - packet.State = @state; - packet.DetailState = @detailState; - packet.CanEnter = @canEnter; - packet.UserCount = @userCount; - packet.RemainSeconds = @remainSeconds; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - public static async ValueTask SendKanturuEnterResultAsync(this IConnection? connection, KanturuEnterResult.EnterResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = KanturuEnterResultRef.Length; - var packet = new KanturuEnterResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - public static async ValueTask SendKanturuStateChangeAsync(this IConnection? connection, KanturuStateChange.StateType @state, byte @detailState) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = KanturuStateChangeRef.Length; - var packet = new KanturuStateChangeRef(connection.Output.GetSpan(length)[..length]); - packet.State = @state; - packet.DetailState = @detailState; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - public static async ValueTask SendKanturuBattleResultAsync(this IConnection? connection, KanturuBattleResult.BattleResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = KanturuBattleResultRef.Length; - var packet = new KanturuBattleResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - public static async ValueTask SendKanturuTimeLimitAsync(this IConnection? connection, uint @timeLimitMilliseconds) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = KanturuTimeLimitRef.Length; - var packet = new KanturuTimeLimitRef(connection.Output.GetSpan(length)[..length]); - packet.TimeLimitMilliseconds = @timeLimitMilliseconds; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - public static async ValueTask SendKanturuMayaWideAreaAttackAsync(this IConnection? connection, KanturuMayaWideAreaAttack.AttackType @type) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = KanturuMayaWideAreaAttackRef.Length; - var packet = new KanturuMayaWideAreaAttackRef(connection.Output.GetSpan(length)[..length]); - packet.Type = @type; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - public static async ValueTask SendKanturuMonsterUserCountAsync(this IConnection? connection, byte @monsterCount, byte @userCount) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = KanturuMonsterUserCountRef.Length; - var packet = new KanturuMonsterUserCountRef(connection.Output.GetSpan(length)[..length]); - packet.MonsterCount = @monsterCount; - packet.UserCount = @userCount; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } -} From d91c7038fb9cd0fa936c125e277861de5047b52f Mon Sep 17 00:00:00 2001 From: "Cristobal Barra (Sistema)" <80994201+apraxico@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:01:03 -0400 Subject: [PATCH 11/11] fix(kanturu): resolve merge conflict in ConnectionExtensions.cs with master master added SendGuildRelationship*Async and SendRemoveAllianceGuildResultAsync. Merged those additions with our 7 SendKanturu*Async extension methods. --- .../ServerToClient/ConnectionExtensions.cs | 12479 ++++++++-------- 1 file changed, 6289 insertions(+), 6190 deletions(-) diff --git a/src/Network/Packets/ServerToClient/ConnectionExtensions.cs b/src/Network/Packets/ServerToClient/ConnectionExtensions.cs index ee207a8ce..611e3a7b9 100644 --- a/src/Network/Packets/ServerToClient/ConnectionExtensions.cs +++ b/src/Network/Packets/ServerToClient/ConnectionExtensions.cs @@ -1,6193 +1,6291 @@ -// -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -//------------------------------------------------------------------------------ -// -// This source code was auto-generated by an XSL transformation. -// Do not change this file. Instead, change the XML data which contains -// the packet definitions and re-run the transformation (rebuild this project). -// -//------------------------------------------------------------------------------ - -// ReSharper disable RedundantVerbatimPrefix -// ReSharper disable AssignmentIsFullyDiscarded -// ReSharper disable UnusedMember.Global -// ReSharper disable UseObjectOrCollectionInitializer - -#nullable enable -namespace MUnique.OpenMU.Network.Packets.ServerToClient; - -using System; -using System.Threading; -using MUnique.OpenMU.Network; - -/// -/// Extension methods to start writing messages of this namespace on a . -/// -public static class ConnectionExtensions -{ - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player id. - /// The version string. - /// The version. - /// The success. - /// - /// Is sent by the server when: After a game client has connected to the game. - /// Causes reaction on client side: It shows the login dialog. - /// - public static async ValueTask SendGameServerEnteredAsync(this IConnection? connection, ushort @playerId, string @versionString, Memory @version, bool @success = true) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GameServerEnteredRef.Length; - var packet = new GameServerEnteredRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.PlayerId = @playerId; - packet.VersionString = @versionString; - @version.Span.CopyTo(packet.Version); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The is active. - /// The player id. - /// The effect id. - /// - /// Is sent by the server when: A magic effect was added or removed to the own or another player. - /// Causes reaction on client side: The user interface updates itself. If it's the effect of the own player, it's shown as icon at the top of the interface. - /// - public static async ValueTask SendMagicEffectStatusAsync(this IConnection? connection, bool @isActive, ushort @playerId, byte @effectId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MagicEffectStatusRef.Length; - var packet = new MagicEffectStatusRef(connection.Output.GetSpan(length)[..length]); - packet.IsActive = @isActive; - packet.PlayerId = @playerId; - packet.EffectId = @effectId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// A random value between 0 and 2 (inclusive). - /// A random value between 0 and 9 (inclusive). - /// - /// Is sent by the server when: The weather on the current map has been changed or the player entered the map. - /// Causes reaction on client side: The game client updates the weather effects. - /// - public static async ValueTask SendWeatherStatusUpdateAsync(this IConnection? connection, byte @weather, byte @variation) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = WeatherStatusUpdateRef.Length; - var packet = new WeatherStatusUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.Weather = @weather; - packet.Variation = @variation; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The id. - /// The current position x. - /// The current position y. - /// The target position x. - /// The target position y. - /// The rotation. - /// The hero state. - /// The attack speed. - /// The magic speed. - /// The name. - /// The appearance and effects. - /// - /// Is sent by the server when: One or more character got into the observed scope of the player. - /// Causes reaction on client side: The client adds the character to the shown map. - /// - public static async ValueTask SendAddCharacterToScopeExtendedAsync(this IConnection? connection, ushort @id, byte @currentPositionX, byte @currentPositionY, byte @targetPositionX, byte @targetPositionY, byte @rotation, CharacterHeroState @heroState, ushort @attackSpeed, ushort @magicSpeed, string @name, Memory @appearanceAndEffects) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = AddCharacterToScopeExtendedRef.GetRequiredSize(appearanceAndEffects.Length); - var packet = new AddCharacterToScopeExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Id = @id; - packet.CurrentPositionX = @currentPositionX; - packet.CurrentPositionY = @currentPositionY; - packet.TargetPositionX = @targetPositionX; - packet.TargetPositionY = @targetPositionY; - packet.Rotation = @rotation; - packet.HeroState = @heroState; - packet.AttackSpeed = @attackSpeed; - packet.MagicSpeed = @magicSpeed; - packet.Name = @name; - @appearanceAndEffects.Span.CopyTo(packet.AppearanceAndEffects); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The killed id. - /// The skill id. - /// The killer id. - /// - /// Is sent by the server when: An observed object was killed. - /// Causes reaction on client side: The object is shown as dead. - /// - public static async ValueTask SendObjectGotKilledAsync(this IConnection? connection, ushort @killedId, ushort @skillId, ushort @killerId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectGotKilledRef.Length; - var packet = new ObjectGotKilledRef(connection.Output.GetSpan(length)[..length]); - packet.KilledId = @killedId; - packet.SkillId = @skillId; - packet.KillerId = @killerId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The object id. - /// The direction. - /// The animation. - /// The target id. - /// - /// Is sent by the server when: An object performs an animation. - /// Causes reaction on client side: The animation is shown for the specified object. - /// - public static async ValueTask SendObjectAnimationAsync(this IConnection? connection, ushort @objectId, byte @direction, byte @animation, ushort @targetId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectAnimationRef.Length; - var packet = new ObjectAnimationRef(connection.Output.GetSpan(length)[..length]); - packet.ObjectId = @objectId; - packet.Direction = @direction; - packet.Animation = @animation; - packet.TargetId = @targetId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The player id. - /// The point x. - /// The point y. - /// The rotation. - /// - /// Is sent by the server when: An object performs a skill which has effect on an area. - /// Causes reaction on client side: The animation is shown on the user interface. - /// - public static async ValueTask SendAreaSkillAnimationAsync(this IConnection? connection, ushort @skillId, ushort @playerId, byte @pointX, byte @pointY, byte @rotation) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = AreaSkillAnimationRef.Length; - var packet = new AreaSkillAnimationRef(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.PlayerId = @playerId; - packet.PointX = @pointX; - packet.PointY = @pointY; - packet.Rotation = @rotation; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The player id. - /// The target id. - /// - /// Is sent by the server when: An object performs a skill which is directly targeted to another object. - /// Causes reaction on client side: The animation is shown on the user interface. - /// - public static async ValueTask SendSkillAnimationAsync(this IConnection? connection, ushort @skillId, ushort @playerId, ushort @targetId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillAnimationRef.Length; - var packet = new SkillAnimationRef(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.PlayerId = @playerId; - packet.TargetId = @targetId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The player id. - /// The point x. - /// The point y. - /// The rotation. - /// - /// Is sent by the server when: An object performs a skill which has effect on an area. - /// Causes reaction on client side: The animation is shown on the user interface. - /// - public static async ValueTask SendAreaSkillAnimation075Async(this IConnection? connection, byte @skillId, ushort @playerId, byte @pointX, byte @pointY, byte @rotation) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = AreaSkillAnimation075Ref.Length; - var packet = new AreaSkillAnimation075Ref(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.PlayerId = @playerId; - packet.PointX = @pointX; - packet.PointY = @pointY; - packet.Rotation = @rotation; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The player id. - /// The point x. - /// The point y. - /// The rotation. - /// - /// Is sent by the server when: An object performs a skill which has effect on an area. - /// Causes reaction on client side: The animation is shown on the user interface. - /// - public static async ValueTask SendAreaSkillAnimation095Async(this IConnection? connection, byte @skillId, ushort @playerId, byte @pointX, byte @pointY, byte @rotation) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = AreaSkillAnimation095Ref.Length; - var packet = new AreaSkillAnimation095Ref(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.PlayerId = @playerId; - packet.PointX = @pointX; - packet.PointY = @pointY; - packet.Rotation = @rotation; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The player id. - /// The target id. - /// The effect applied. - /// - /// Is sent by the server when: An object performs a skill which is directly targeted to another object. - /// Causes reaction on client side: The animation is shown on the user interface. - /// - public static async ValueTask SendSkillAnimation075Async(this IConnection? connection, byte @skillId, ushort @playerId, ushort @targetId, bool @effectApplied) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillAnimation075Ref.Length; - var packet = new SkillAnimation075Ref(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.PlayerId = @playerId; - packet.TargetId = @targetId; - packet.EffectApplied = @effectApplied; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The player id. - /// The target id. - /// The effect applied. - /// - /// Is sent by the server when: An object performs a skill which is directly targeted to another object. - /// Causes reaction on client side: The animation is shown on the user interface. - /// - public static async ValueTask SendSkillAnimation095Async(this IConnection? connection, byte @skillId, ushort @playerId, ushort @targetId, bool @effectApplied) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillAnimation095Ref.Length; - var packet = new SkillAnimation095Ref(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.PlayerId = @playerId; - packet.TargetId = @targetId; - packet.EffectApplied = @effectApplied; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The target id. - /// - /// Is sent by the server when: A player cancelled a specific magic effect of a skill (Infinity Arrow, Wizardry Enhance), or an effect was removed due a timeout (Ice, Poison) or antidote. - /// Causes reaction on client side: The effect is removed from the target object. - /// - public static async ValueTask SendMagicEffectCancelledAsync(this IConnection? connection, ushort @skillId, ushort @targetId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MagicEffectCancelledRef.Length; - var packet = new MagicEffectCancelledRef(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.TargetId = @targetId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The target id. - /// - /// Is sent by the server when: A player cancelled a specific magic effect of a skill (Infinity Arrow, Wizardry Enhance), or an effect was removed due a timeout (Ice, Poison) or antidote. - /// Causes reaction on client side: The effect is removed from the target object. - /// - public static async ValueTask SendMagicEffectCancelled075Async(this IConnection? connection, byte @skillId, ushort @targetId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MagicEffectCancelled075Ref.Length; - var packet = new MagicEffectCancelled075Ref(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.TargetId = @targetId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill id. - /// The source id. - /// The target id. - /// - /// Is sent by the server when: A player (rage fighter) performs the dark side skill on a target and sent a RageAttackRangeRequest. - /// Causes reaction on client side: The targets are attacked with visual effects. - /// - public static async ValueTask SendRageAttackAsync(this IConnection? connection, ushort @skillId, ushort @sourceId, ushort @targetId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = RageAttackRef.Length; - var packet = new RageAttackRef(connection.Output.GetSpan(length)[..length]); - packet.SkillId = @skillId; - packet.SourceId = @sourceId; - packet.TargetId = @targetId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The changed player id. - /// The item data. - /// - /// Is sent by the server when: The appearance of a player changed, all surrounding players are informed about it. - /// Causes reaction on client side: The appearance of the player is updated. - /// - public static async ValueTask SendAppearanceChangedAsync(this IConnection? connection, ushort @changedPlayerId, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = AppearanceChangedRef.GetRequiredSize(itemData.Length); - var packet = new AppearanceChangedRef(connection.Output.GetSpan(length)[..length]); - packet.ChangedPlayerId = @changedPlayerId; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The changed player id. - /// The item slot. - /// The item group. - /// The item number. - /// The item level. - /// The excellent flags. - /// The ancient discriminator. - /// The is ancient set complete. - /// - /// Is sent by the server when: The appearance of a player changed, all surrounding players are informed about it. - /// Causes reaction on client side: The appearance of the player is updated. - /// - public static async ValueTask SendAppearanceChangedExtendedAsync(this IConnection? connection, ushort @changedPlayerId, byte @itemSlot, byte @itemGroup, ushort @itemNumber, byte @itemLevel, byte @excellentFlags, byte @ancientDiscriminator, bool @isAncientSetComplete) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = AppearanceChangedExtendedRef.Length; - var packet = new AppearanceChangedExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.ChangedPlayerId = @changedPlayerId; - packet.ItemSlot = @itemSlot; - packet.ItemGroup = @itemGroup; - packet.ItemNumber = @itemNumber; - packet.ItemLevel = @itemLevel; - packet.ExcellentFlags = @excellentFlags; - packet.AncientDiscriminator = @ancientDiscriminator; - packet.IsAncientSetComplete = @isAncientSetComplete; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The object id. - /// The message. - /// - /// Is sent by the server when: The server wants to show a message above any kind of character, even NPCs. - /// Causes reaction on client side: The message is shown above the character. - /// - public static async ValueTask SendObjectMessageAsync(this IConnection? connection, ushort @objectId, string @message) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectMessageRef.GetRequiredSize(message); - var packet = new ObjectMessageRef(connection.Output.GetSpan(length)[..length]); - packet.ObjectId = @objectId; - packet.Message = @message; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The requester id. - /// - /// Is sent by the server when: Another player requests party from the receiver of this message. - /// Causes reaction on client side: The party request is shown. - /// - public static async ValueTask SendPartyRequestAsync(this IConnection? connection, ushort @requesterId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PartyRequestRef.Length; - var packet = new PartyRequestRef(connection.Output.GetSpan(length)[..length]); - packet.RequesterId = @requesterId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The index. - /// - /// Is sent by the server when: A party member got removed from a party in which the player is in. - /// Causes reaction on client side: The party member with the specified index is removed from the party list on the user interface. - /// - public static async ValueTask SendRemovePartyMemberAsync(this IConnection? connection, byte @index) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = RemovePartyMemberRef.Length; - var packet = new RemovePartyMemberRef(connection.Output.GetSpan(length)[..length]); - packet.Index = @index; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// - /// Is sent by the server when: After the player requested to open his shop and this request was successful. - /// Causes reaction on client side: The own player shop is shown as open. - /// - public static async ValueTask SendPlayerShopOpenSuccessfulAsync(this IConnection? connection, bool @success = true) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PlayerShopOpenSuccessfulRef.Length; - var packet = new PlayerShopOpenSuccessfulRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The state. - /// - /// Is sent by the server when: After the trading partner checked or unchecked the trade accept button. - /// Causes reaction on client side: The game client updates the trade button state accordingly. - /// - public static async ValueTask SendTradeButtonStateChangedAsync(this IConnection? connection, TradeButtonStateChanged.TradeButtonState @state) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeButtonStateChangedRef.Length; - var packet = new TradeButtonStateChangedRef(connection.Output.GetSpan(length)[..length]); - packet.State = @state; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: The trade money has been set by a previous request of the player. - /// Causes reaction on client side: The money which was set into the trade by the player is updated on the UI. - /// - public static async ValueTask SendTradeMoneySetResponseAsync(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeMoneySetResponseRef.Length; - var packet = new TradeMoneySetResponseRef(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The money amount. - /// - /// Is sent by the server when: This message is sent when the trading partner put a certain amount of money (also 0) into the trade. - /// Causes reaction on client side: It overrides all previous sent money values. - /// - public static async ValueTask SendTradeMoneyUpdateAsync(this IConnection? connection, uint @moneyAmount) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeMoneyUpdateRef.Length; - var packet = new TradeMoneyUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.MoneyAmount = @moneyAmount; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The accepted. - /// The name. - /// The trade partner level. - /// The guild id. - /// - /// Is sent by the server when: The player which receives this message, sent a trade request to another player. This message is sent when the other player responded to this request. - /// Causes reaction on client side: If the trade was accepted, a trade dialog is opened. Otherwise, a message is shown. - /// - public static async ValueTask SendTradeRequestAnswerAsync(this IConnection? connection, bool @accepted, string @name, ushort @tradePartnerLevel, uint @guildId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeRequestAnswerRef.Length; - var packet = new TradeRequestAnswerRef(connection.Output.GetSpan(length)[..length]); - packet.Accepted = @accepted; - packet.Name = @name; - packet.TradePartnerLevel = @tradePartnerLevel; - packet.GuildId = @guildId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The name. - /// - /// Is sent by the server when: A trade was requested by another player. - /// Causes reaction on client side: A trade request dialog is shown. - /// - public static async ValueTask SendTradeRequestAsync(this IConnection? connection, string @name) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeRequestRef.Length; - var packet = new TradeRequestRef(connection.Output.GetSpan(length)[..length]); - packet.Name = @name; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: A trade was finished. - /// Causes reaction on client side: The trade dialog is closed. Depending on the result, a message is shown. - /// - public static async ValueTask SendTradeFinishedAsync(this IConnection? connection, TradeFinished.TradeResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeFinishedRef.Length; - var packet = new TradeFinishedRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The to slot. - /// The item data. - /// - /// Is sent by the server when: The trading partner added an item to the trade. - /// Causes reaction on client side: The item is added in the trade dialog. - /// - public static async ValueTask SendTradeItemAddedAsync(this IConnection? connection, byte @toSlot, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeItemAddedRef.GetRequiredSize(itemData.Length); - var packet = new TradeItemAddedRef(connection.Output.GetSpan(length)[..length]); - packet.ToSlot = @toSlot; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The slot. - /// - /// Is sent by the server when: The trading partner removed an item from the trade. - /// Causes reaction on client side: The item is removed from the trade dialog. - /// - public static async ValueTask SendTradeItemRemovedAsync(this IConnection? connection, byte @slot) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = TradeItemRemovedRef.Length; - var packet = new TradeItemRemovedRef(connection.Output.GetSpan(length)[..length]); - packet.Slot = @slot; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// - /// Is sent by the server when: After the login request has been processed by the server. - /// Causes reaction on client side: Shows the result. When it was successful, the client proceeds by sending a character list request. - /// - public static async ValueTask SendLoginResponseAsync(this IConnection? connection, LoginResponse.LoginResult @success) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = LoginResponseRef.Length; - var packet = new LoginResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The type. - /// - /// Is sent by the server when: After the logout request has been processed by the server. - /// Causes reaction on client side: Depending on the result, the game client closes the game or changes to another selection screen. - /// - public static async ValueTask SendLogoutResponseAsync(this IConnection? connection, LogOutType @type) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = LogoutResponseRef.Length; - var packet = new LogoutResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Type = @type; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The type. - /// The sender. - /// The message. - /// - /// Is sent by the server when: A player sends a chat message. - /// Causes reaction on client side: The message is shown in the chat box and above the character of the sender. - /// - public static async ValueTask SendChatMessageAsync(this IConnection? connection, ChatMessage.ChatMessageType @type, string @sender, string @message) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ChatMessageRef.GetRequiredSize(message); - var packet = new ChatMessageRef(connection.Output.GetSpan(length)[..length]); - packet.Type = @type; - packet.Sender = @sender; - packet.Message = @message; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The header code. - /// The object id. - /// The health damage. - /// The kind. - /// The is rage fighter streak hit. - /// The is rage fighter streak final hit. - /// The is double damage. - /// The is triple damage. - /// The shield damage. - /// - /// Is sent by the server when: An object got hit in two cases: 1. When the own player is hit; 2. When the own player attacked some other object which got hit. - /// Causes reaction on client side: The damage is shown at the object which received the hit. - /// - public static async ValueTask SendObjectHitAsync(this IConnection? connection, byte @headerCode, ushort @objectId, ushort @healthDamage, DamageKind @kind, bool @isRageFighterStreakHit, bool @isRageFighterStreakFinalHit, bool @isDoubleDamage, bool @isTripleDamage, ushort @shieldDamage) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectHitRef.Length; - var packet = new ObjectHitRef(connection.Output.GetSpan(length)[..length]); - packet.HeaderCode = @headerCode; - packet.ObjectId = @objectId; - packet.HealthDamage = @healthDamage; - packet.Kind = @kind; - packet.IsRageFighterStreakHit = @isRageFighterStreakHit; - packet.IsRageFighterStreakFinalHit = @isRageFighterStreakFinalHit; - packet.IsDoubleDamage = @isDoubleDamage; - packet.IsTripleDamage = @isTripleDamage; - packet.ShieldDamage = @shieldDamage; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The kind. - /// The is rage fighter streak hit. - /// The is rage fighter streak final hit. - /// The is double damage. - /// The is triple damage. - /// The object id. - /// Gets or sets the status of the remaining health in fractions of 1/250. - /// Gets or sets the status of the remaining shield in fractions of 1/250. - /// The health damage. - /// The shield damage. - /// - /// Is sent by the server when: An object got hit in two cases: 1. When the own player is hit; 2. When the own player attacked some other object which got hit. - /// Causes reaction on client side: The damage is shown at the object which received the hit. - /// - public static async ValueTask SendObjectHitExtendedAsync(this IConnection? connection, DamageKind @kind, bool @isRageFighterStreakHit, bool @isRageFighterStreakFinalHit, bool @isDoubleDamage, bool @isTripleDamage, ushort @objectId, byte @healthStatus, byte @shieldStatus, uint @healthDamage, uint @shieldDamage) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectHitExtendedRef.Length; - var packet = new ObjectHitExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Kind = @kind; - packet.IsRageFighterStreakHit = @isRageFighterStreakHit; - packet.IsRageFighterStreakFinalHit = @isRageFighterStreakFinalHit; - packet.IsDoubleDamage = @isDoubleDamage; - packet.IsTripleDamage = @isTripleDamage; - packet.ObjectId = @objectId; - packet.HealthStatus = @healthStatus; - packet.ShieldStatus = @shieldStatus; - packet.HealthDamage = @healthDamage; - packet.ShieldDamage = @shieldDamage; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The header code. - /// The object id. - /// The position x. - /// The position y. - /// - /// Is sent by the server when: An object in the observed scope (including the own player) moved instantly. - /// Causes reaction on client side: The position of the object is updated on client side. - /// - public static async ValueTask SendObjectMovedAsync(this IConnection? connection, byte @headerCode, ushort @objectId, byte @positionX, byte @positionY) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectMovedRef.Length; - var packet = new ObjectMovedRef(connection.Output.GetSpan(length)[..length]); - packet.HeaderCode = @headerCode; - packet.ObjectId = @objectId; - packet.PositionX = @positionX; - packet.PositionY = @positionY; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The header code. - /// The object id. - /// The target x. - /// The target y. - /// The target rotation. - /// The step count. - /// The step data. - /// - /// Is sent by the server when: An object in the observed scope (including the own player) walked to another position. - /// Causes reaction on client side: The object is animated to walk to the new position. - /// - public static async ValueTask SendObjectWalkedAsync(this IConnection? connection, byte @headerCode, ushort @objectId, byte @targetX, byte @targetY, byte @targetRotation, byte @stepCount, Memory @stepData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectWalkedRef.GetRequiredSize(stepData.Length); - var packet = new ObjectWalkedRef(connection.Output.GetSpan(length)[..length]); - packet.HeaderCode = @headerCode; - packet.ObjectId = @objectId; - packet.TargetX = @targetX; - packet.TargetY = @targetY; - packet.TargetRotation = @targetRotation; - packet.StepCount = @stepCount; - @stepData.Span.CopyTo(packet.StepData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The header code. - /// The object id. - /// The source x. - /// The source y. - /// The target x. - /// The target y. - /// The target rotation. - /// The step count. - /// The step data. - /// - /// Is sent by the server when: An object in the observed scope (including the own player) walked to another position. - /// Causes reaction on client side: The object is animated to walk to the new position. - /// - public static async ValueTask SendObjectWalkedExtendedAsync(this IConnection? connection, byte @headerCode, ushort @objectId, byte @sourceX, byte @sourceY, byte @targetX, byte @targetY, byte @targetRotation, byte @stepCount, Memory @stepData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectWalkedExtendedRef.GetRequiredSize(stepData.Length); - var packet = new ObjectWalkedExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.HeaderCode = @headerCode; - packet.ObjectId = @objectId; - packet.SourceX = @sourceX; - packet.SourceY = @sourceY; - packet.TargetX = @targetX; - packet.TargetY = @targetY; - packet.TargetRotation = @targetRotation; - packet.StepCount = @stepCount; - @stepData.Span.CopyTo(packet.StepData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The object id. - /// The target x. - /// The target y. - /// The target rotation. - /// - /// Is sent by the server when: An object in the observed scope (including the own player) walked to another position. - /// Causes reaction on client side: The object is animated to walk to the new position. - /// - public static async ValueTask SendObjectWalked075Async(this IConnection? connection, ushort @objectId, byte @targetX, byte @targetY, byte @targetRotation) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ObjectWalked075Ref.Length; - var packet = new ObjectWalked075Ref(connection.Output.GetSpan(length)[..length]); - packet.ObjectId = @objectId; - packet.TargetX = @targetX; - packet.TargetY = @targetY; - packet.TargetRotation = @targetRotation; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The killed object id. - /// The added experience. - /// The damage of last hit. - /// - /// Is sent by the server when: A player gained experience. - /// Causes reaction on client side: The experience is added to the experience counter and bar. - /// - public static async ValueTask SendExperienceGainedAsync(this IConnection? connection, ushort @killedObjectId, ushort @addedExperience, ushort @damageOfLastHit) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ExperienceGainedRef.Length; - var packet = new ExperienceGainedRef(connection.Output.GetSpan(length)[..length]); - packet.KilledObjectId = @killedObjectId; - packet.AddedExperience = @addedExperience; - packet.DamageOfLastHit = @damageOfLastHit; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The type. - /// The added experience. - /// The damage of last hit. - /// The killed object id. - /// The killer object id. - /// - /// Is sent by the server when: A player gained experience. - /// Causes reaction on client side: The experience is added to the experience counter and bar. - /// - public static async ValueTask SendExperienceGainedExtendedAsync(this IConnection? connection, ExperienceGainedExtended.AddResult @type, uint @addedExperience, uint @damageOfLastHit, ushort @killedObjectId, ushort @killerObjectId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ExperienceGainedExtendedRef.Length; - var packet = new ExperienceGainedExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Type = @type; - packet.AddedExperience = @addedExperience; - packet.DamageOfLastHit = @damageOfLastHit; - packet.KilledObjectId = @killedObjectId; - packet.KillerObjectId = @killerObjectId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The map number. - /// The position x. - /// The position y. - /// The rotation. - /// If false, it shows the teleport animation (white bubbles), and the client doesn't remove all of the objects in its scope. - /// - /// Is sent by the server when: The map was changed on the server side. - /// Causes reaction on client side: The game client changes to the specified map and coordinates. - /// - public static async ValueTask SendMapChangedAsync(this IConnection? connection, ushort @mapNumber, byte @positionX, byte @positionY, byte @rotation, bool @isMapChange = true) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MapChangedRef.Length; - var packet = new MapChangedRef(connection.Output.GetSpan(length)[..length]); - packet.IsMapChange = @isMapChange; - packet.MapNumber = @mapNumber; - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.Rotation = @rotation; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The map number. - /// The position x. - /// The position y. - /// The rotation. - /// If false, it shows the teleport animation (white bubbles), and the client doesn't remove all of the objects in its scope. - /// - /// Is sent by the server when: The map was changed on the server side. - /// Causes reaction on client side: The game client changes to the specified map and coordinates. - /// - public static async ValueTask SendMapChanged075Async(this IConnection? connection, byte @mapNumber, byte @positionX, byte @positionY, byte @rotation, bool @isMapChange = true) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MapChanged075Ref.Length; - var packet = new MapChanged075Ref(connection.Output.GetSpan(length)[..length]); - packet.IsMapChange = @isMapChange; - packet.MapNumber = @mapNumber; - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.Rotation = @rotation; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The binary data of the key configuration - /// - /// Is sent by the server when: When entering the game world with a character. - /// Causes reaction on client side: The client restores this configuration in its user interface. - /// - public static async ValueTask SendApplyKeyConfigurationAsync(this IConnection? connection, Memory @configuration) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ApplyKeyConfigurationRef.GetRequiredSize(configuration.Length); - var packet = new ApplyKeyConfigurationRef(connection.Output.GetSpan(length)[..length]); - @configuration.Span.CopyTo(packet.Configuration); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The id. - /// If this flag is set, the money is added to the map with an animation and sound. Otherwise it's just added like it was already on the ground before. - /// The position x. - /// The position y. - /// The amount. - /// The item count. - /// The money group. - /// The money number. - /// - /// Is sent by the server when: Money dropped on the ground. - /// Causes reaction on client side: The client adds the money to the ground. - /// - public static async ValueTask SendMoneyDroppedAsync(this IConnection? connection, ushort @id, bool @isFreshDrop, byte @positionX, byte @positionY, uint @amount, byte @itemCount = 1, byte @moneyGroup = 14, byte @moneyNumber = 15) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MoneyDroppedRef.Length; - var packet = new MoneyDroppedRef(connection.Output.GetSpan(length)[..length]); - packet.ItemCount = @itemCount; - packet.Id = @id; - packet.IsFreshDrop = @isFreshDrop; - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.MoneyNumber = @moneyNumber; - packet.Amount = @amount; - packet.MoneyGroup = @moneyGroup; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// If this flag is set, the money is added to the map with an animation and sound. Otherwise, it's just added like it was already on the ground before. - /// The id. - /// The position x. - /// The position y. - /// The amount. - /// - /// Is sent by the server when: Money dropped on the ground. - /// Causes reaction on client side: The client adds the money to the ground. - /// - public static async ValueTask SendMoneyDroppedExtendedAsync(this IConnection? connection, bool @isFreshDrop, ushort @id, byte @positionX, byte @positionY, uint @amount) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MoneyDroppedExtendedRef.Length; - var packet = new MoneyDroppedExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.IsFreshDrop = @isFreshDrop; - packet.Id = @id; - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.Amount = @amount; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The id. - /// If this flag is set, the money is added to the map with an animation and sound. Otherwise it's just added like it was already on the ground before. - /// The position x. - /// The position y. - /// The amount. - /// The item count. - /// The money group. - /// The money number. - /// - /// Is sent by the server when: Money dropped on the ground. - /// Causes reaction on client side: The client adds the money to the ground. - /// - public static async ValueTask SendMoneyDropped075Async(this IConnection? connection, ushort @id, bool @isFreshDrop, byte @positionX, byte @positionY, uint @amount, byte @itemCount = 1, byte @moneyGroup = 14, byte @moneyNumber = 15) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MoneyDropped075Ref.Length; - var packet = new MoneyDropped075Ref(connection.Output.GetSpan(length)[..length]); - packet.ItemCount = @itemCount; - packet.Id = @id; - packet.IsFreshDrop = @isFreshDrop; - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.MoneyNumber = @moneyNumber; - packet.MoneyGroup = @moneyGroup; - packet.Amount = @amount; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The inventory slot. - /// The item data. - /// - /// Is sent by the server when: A new item was added to the inventory. - /// Causes reaction on client side: The client adds the item to the inventory user interface. - /// - public static async ValueTask SendItemAddedToInventoryAsync(this IConnection? connection, byte @inventorySlot, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemAddedToInventoryRef.GetRequiredSize(itemData.Length); - var packet = new ItemAddedToInventoryRef(connection.Output.GetSpan(length)[..length]); - packet.InventorySlot = @inventorySlot; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The inventory slot. - /// - /// Is sent by the server when: The player requested to drop an item of his inventory. This message is the response about the success of the request. - /// Causes reaction on client side: If successful, the client removes the item from the inventory user interface. - /// - public static async ValueTask SendItemDropResponseAsync(this IConnection? connection, bool @success, byte @inventorySlot) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemDropResponseRef.Length; - var packet = new ItemDropResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.InventorySlot = @inventorySlot; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The fail reason. - /// - /// Is sent by the server when: The player requested to pick up an item from to ground to add it to his inventory, but it failed. - /// Causes reaction on client side: Depending on the reason, the game client shows a message. - /// - public static async ValueTask SendItemPickUpRequestFailedAsync(this IConnection? connection, ItemPickUpRequestFailed.ItemPickUpFailReason @failReason) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemPickUpRequestFailedRef.Length; - var packet = new ItemPickUpRequestFailedRef(connection.Output.GetSpan(length)[..length]); - packet.FailReason = @failReason; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The money. - /// - /// Is sent by the server when: The players money amount of the inventory has been changed and needs an update. - /// Causes reaction on client side: The money is updated in the inventory user interface. - /// - public static async ValueTask SendInventoryMoneyUpdateAsync(this IConnection? connection, uint @money) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = InventoryMoneyUpdateRef.Length; - var packet = new InventoryMoneyUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.Money = @money; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The target storage type. - /// The target slot. - /// The item data. - /// - /// Is sent by the server when: An item in the inventory or vault of the player has been moved. - /// Causes reaction on client side: The client updates the position of item in the user interface. - /// - public static async ValueTask SendItemMovedAsync(this IConnection? connection, ItemStorageKind @targetStorageType, byte @targetSlot, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemMovedRef.GetRequiredSize(itemData.Length); - var packet = new ItemMovedRef(connection.Output.GetSpan(length)[..length]); - packet.TargetStorageType = @targetStorageType; - packet.TargetSlot = @targetSlot; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The item data. - /// - /// Is sent by the server when: An item in the inventory or vault of the player could not be moved as requested by the player. - /// Causes reaction on client side: The client restores the position of item in the user interface. - /// - public static async ValueTask SendItemMoveRequestFailedAsync(this IConnection? connection, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemMoveRequestFailedRef.GetRequiredSize(itemData.Length); - var packet = new ItemMoveRequestFailedRef(connection.Output.GetSpan(length)[..length]); - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health. - /// The shield. - /// - /// Is sent by the server when: Periodically, or if the current health or shield changed on the server side, e.g. by hits. - /// Causes reaction on client side: The health and shield bar is updated on the game client user interface. - /// - public static async ValueTask SendCurrentHealthAndShieldAsync(this IConnection? connection, ushort @health, ushort @shield) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CurrentHealthAndShieldRef.Length; - var packet = new CurrentHealthAndShieldRef(connection.Output.GetSpan(length)[..length]); - packet.Health = @health; - packet.Shield = @shield; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health. - /// The shield. - /// - /// Is sent by the server when: When the maximum health changed, e.g. by adding stat points or changed items. - /// Causes reaction on client side: The health and shield bar is updated on the game client user interface. - /// - public static async ValueTask SendMaximumHealthAndShieldAsync(this IConnection? connection, ushort @health, ushort @shield) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MaximumHealthAndShieldRef.Length; - var packet = new MaximumHealthAndShieldRef(connection.Output.GetSpan(length)[..length]); - packet.Health = @health; - packet.Shield = @shield; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health. - /// The shield. - /// The mana. - /// The ability. - /// The attack speed. - /// The magic speed. - /// - /// Is sent by the server when: Periodically, or if the current stats, like health, shield, mana or attack speed changed on the server side, e.g. by hits. - /// Causes reaction on client side: The values are updated on the game client user interface. - /// - public static async ValueTask SendCurrentStatsExtendedAsync(this IConnection? connection, uint @health, uint @shield, uint @mana, uint @ability, ushort @attackSpeed, ushort @magicSpeed) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CurrentStatsExtendedRef.Length; - var packet = new CurrentStatsExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Health = @health; - packet.Shield = @shield; - packet.Mana = @mana; - packet.Ability = @ability; - packet.AttackSpeed = @attackSpeed; - packet.MagicSpeed = @magicSpeed; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health. - /// The shield. - /// The mana. - /// The ability. - /// - /// Is sent by the server when: When the maximum stats, like health, shield, mana or attack speed changed on the server side, e.g. by adding stat points or changed items. - /// Causes reaction on client side: The values are updated on the game client user interface. - /// - public static async ValueTask SendMaximumStatsExtendedAsync(this IConnection? connection, uint @health, uint @shield, uint @mana, uint @ability) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MaximumStatsExtendedRef.Length; - var packet = new MaximumStatsExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Health = @health; - packet.Shield = @shield; - packet.Mana = @mana; - packet.Ability = @ability; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health. - /// The shield. - /// - /// Is sent by the server when: When the consumption of an item failed. - /// Causes reaction on client side: The game client gets a feedback about a failed consumption, and allows for do further consumption requests. - /// - public static async ValueTask SendItemConsumptionFailedAsync(this IConnection? connection, ushort @health, ushort @shield) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemConsumptionFailedRef.Length; - var packet = new ItemConsumptionFailedRef(connection.Output.GetSpan(length)[..length]); - packet.Health = @health; - packet.Shield = @shield; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health. - /// The shield. - /// - /// Is sent by the server when: When the consumption of an item failed. - /// Causes reaction on client side: The game client gets a feedback about a failed consumption, and allows for do further consumption requests. - /// - public static async ValueTask SendItemConsumptionFailedExtendedAsync(this IConnection? connection, uint @health, uint @shield) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemConsumptionFailedExtendedRef.Length; - var packet = new ItemConsumptionFailedExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Health = @health; - packet.Shield = @shield; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The strength. - /// The agility. - /// The vitality. - /// The energy. - /// The command. - /// - /// Is sent by the server when: Setting the base stats of a character, e.g. set stats command or after a reset. - /// Causes reaction on client side: The values are updated on the game client user interface. - /// - public static async ValueTask SendBaseStatsExtendedAsync(this IConnection? connection, uint @strength, uint @agility, uint @vitality, uint @energy, uint @command) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = BaseStatsExtendedRef.Length; - var packet = new BaseStatsExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Strength = @strength; - packet.Agility = @agility; - packet.Vitality = @vitality; - packet.Energy = @energy; - packet.Command = @command; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The mana. - /// The ability. - /// - /// Is sent by the server when: The currently available mana or ability has changed, e.g. by using a skill. - /// Causes reaction on client side: The mana and ability bar is updated on the game client user interface. - /// - public static async ValueTask SendCurrentManaAndAbilityAsync(this IConnection? connection, ushort @mana, ushort @ability) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CurrentManaAndAbilityRef.Length; - var packet = new CurrentManaAndAbilityRef(connection.Output.GetSpan(length)[..length]); - packet.Mana = @mana; - packet.Ability = @ability; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The mana. - /// The ability. - /// - /// Is sent by the server when: The maximum available mana or ability has changed, e.g. by adding stat points. - /// Causes reaction on client side: The mana and ability bar is updated on the game client user interface. - /// - public static async ValueTask SendMaximumManaAndAbilityAsync(this IConnection? connection, ushort @mana, ushort @ability) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MaximumManaAndAbilityRef.Length; - var packet = new MaximumManaAndAbilityRef(connection.Output.GetSpan(length)[..length]); - packet.Mana = @mana; - packet.Ability = @ability; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The affected slot of the item in the inventory. - /// The true flag. - /// - /// Is sent by the server when: The item has been removed from the inventory of the player. - /// Causes reaction on client side: The client removes the item in the inventory user interface. - /// - public static async ValueTask SendItemRemovedAsync(this IConnection? connection, byte @inventorySlot, byte @trueFlag = 1) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemRemovedRef.Length; - var packet = new ItemRemovedRef(connection.Output.GetSpan(length)[..length]); - packet.InventorySlot = @inventorySlot; - packet.TrueFlag = @trueFlag; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The item type. - /// The effect time in seconds. - /// - /// Is sent by the server when: The client requested to consume a special item, e.g. a bottle of Ale. - /// Causes reaction on client side: The player is shown in a red color and has increased attack speed. - /// - public static async ValueTask SendConsumeItemWithEffectAsync(this IConnection? connection, ConsumeItemWithEffect.ConsumedItemType @itemType, ushort @effectTimeInSeconds) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ConsumeItemWithEffectRef.Length; - var packet = new ConsumeItemWithEffectRef(connection.Output.GetSpan(length)[..length]); - packet.ItemType = @itemType; - packet.EffectTimeInSeconds = @effectTimeInSeconds; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The inventory slot. - /// The durability. - /// true, if the change resulted from an item consumption; otherwise, false - /// - /// Is sent by the server when: The durability of an item in the inventory of the player has been changed. - /// Causes reaction on client side: The client updates the item in the inventory user interface. - /// - public static async ValueTask SendItemDurabilityChangedAsync(this IConnection? connection, byte @inventorySlot, byte @durability, bool @byConsumption) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemDurabilityChangedRef.Length; - var packet = new ItemDurabilityChangedRef(connection.Output.GetSpan(length)[..length]); - packet.InventorySlot = @inventorySlot; - packet.Durability = @durability; - packet.ByConsumption = @byConsumption; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// The stat points. - /// The stat type. - /// - /// Is sent by the server when: The player requested to consume a fruit. - /// Causes reaction on client side: The client updates the user interface, by changing the added stat points and used fruit points. - /// - public static async ValueTask SendFruitConsumptionResponseAsync(this IConnection? connection, FruitConsumptionResponse.FruitConsumptionResult @result, ushort @statPoints, FruitConsumptionResponse.FruitStatType @statType) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = FruitConsumptionResponseRef.Length; - var packet = new FruitConsumptionResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - packet.StatPoints = @statPoints; - packet.StatType = @statType; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The origin. - /// The type. - /// The action. - /// The remaining seconds. - /// The magic effect number. - /// - /// Is sent by the server when: The player requested to consume an item which gives a magic effect. - /// Causes reaction on client side: The client updates the user interface, it shows the remaining time at the effect icon. - /// - public static async ValueTask SendEffectItemConsumptionAsync(this IConnection? connection, EffectItemConsumption.EffectOrigin @origin, EffectItemConsumption.EffectType @type, EffectItemConsumption.EffectAction @action, uint @remainingSeconds, byte @magicEffectNumber) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = EffectItemConsumptionRef.Length; - var packet = new EffectItemConsumptionRef(connection.Output.GetSpan(length)[..length]); - packet.Origin = @origin; - packet.Type = @type; - packet.Action = @action; - packet.RemainingSeconds = @remainingSeconds; - packet.MagicEffectNumber = @magicEffectNumber; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The window. - /// - /// Is sent by the server when: After the client talked to an NPC which should cause a dialog to open on the client side. - /// Causes reaction on client side: The client opens the specified dialog. - /// - public static async ValueTask SendNpcWindowResponseAsync(this IConnection? connection, NpcWindowResponse.NpcWindow @window) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = NpcWindowResponseRef.Length; - var packet = new NpcWindowResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Window = @window; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: The request of buying an item from a NPC failed. - /// Causes reaction on client side: The client is responsive again. Without this message, it may stuck. - /// - public static async ValueTask SendNpcItemBuyFailedAsync(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = NpcItemBuyFailedRef.Length; - var packet = new NpcItemBuyFailedRef(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The inventory slot. - /// The item data. - /// - /// Is sent by the server when: The request of buying an item from a player or npc was successful. - /// Causes reaction on client side: The bought item is added to the inventory. - /// - public static async ValueTask SendItemBoughtAsync(this IConnection? connection, byte @inventorySlot, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemBoughtRef.GetRequiredSize(itemData.Length); - var packet = new ItemBoughtRef(connection.Output.GetSpan(length)[..length]); - packet.InventorySlot = @inventorySlot; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The money. - /// - /// Is sent by the server when: The result of a previous item sell request. - /// Causes reaction on client side: The amount of specified money is set at the players inventory. - /// - public static async ValueTask SendNpcItemSellResultAsync(this IConnection? connection, bool @success, uint @money) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = NpcItemSellResultRef.Length; - var packet = new NpcItemSellResultRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.Money = @money; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The inventory slot. - /// The result. - /// - /// Is sent by the server when: The player requested to set a price for an item of the players shop. - /// Causes reaction on client side: The item gets a price on the user interface. - /// - public static async ValueTask SendPlayerShopSetItemPriceResponseAsync(this IConnection? connection, byte @inventorySlot, PlayerShopSetItemPriceResponse.ItemPriceSetResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PlayerShopSetItemPriceResponseRef.Length; - var packet = new PlayerShopSetItemPriceResponseRef(connection.Output.GetSpan(length)[..length]); - packet.InventorySlot = @inventorySlot; - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player id. - /// The success. - /// - /// Is sent by the server when: After a player in scope requested to close his shop or after all items has been sold. - /// Causes reaction on client side: The player shop not shown as open anymore. - /// - public static async ValueTask SendPlayerShopClosedAsync(this IConnection? connection, ushort @playerId, bool @success = true) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PlayerShopClosedRef.Length; - var packet = new PlayerShopClosedRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.PlayerId = @playerId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The inventory slot. - /// The buyer name. - /// - /// Is sent by the server when: An item of the players shop was sold to another player. - /// Causes reaction on client side: The item is removed from the players inventory and a blue system message appears. - /// - public static async ValueTask SendPlayerShopItemSoldToPlayerAsync(this IConnection? connection, byte @inventorySlot, string @buyerName) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PlayerShopItemSoldToPlayerRef.Length; - var packet = new PlayerShopItemSoldToPlayerRef(connection.Output.GetSpan(length)[..length]); - packet.InventorySlot = @inventorySlot; - packet.BuyerName = @buyerName; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player id. - /// - /// Is sent by the server when: After the player requested to close his shop or after all items has been sold. - /// Causes reaction on client side: The player shop dialog is closed for the shop of the specified player. - /// - public static async ValueTask SendClosePlayerShopDialogAsync(this IConnection? connection, ushort @playerId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ClosePlayerShopDialogRef.Length; - var packet = new ClosePlayerShopDialogRef(connection.Output.GetSpan(length)[..length]); - packet.PlayerId = @playerId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// The seller id. - /// The item data. - /// The item slot. - /// - /// Is sent by the server when: After the player requested to buy an item of a shop of another player. - /// Causes reaction on client side: The result is shown to the player. If successful, the item is added to the inventory. - /// - public static async ValueTask SendPlayerShopBuyResultAsync(this IConnection? connection, PlayerShopBuyResult.ResultKind @result, ushort @sellerId, Memory @itemData, byte @itemSlot) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PlayerShopBuyResultRef.Length; - var packet = new PlayerShopBuyResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - packet.SellerId = @sellerId; - @itemData.Span.CopyTo(packet.ItemData); - packet.ItemSlot = @itemSlot; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The seller id. - /// The result. - /// The item slot. - /// The item data. - /// - /// Is sent by the server when: After the player requested to buy an item of a shop of another player. - /// Causes reaction on client side: The result is shown to the player. If successful, the item is added to the inventory. - /// - public static async ValueTask SendPlayerShopBuyResultExtendedAsync(this IConnection? connection, ushort @sellerId, PlayerShopBuyResultExtended.ResultKind @result, byte @itemSlot, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PlayerShopBuyResultExtendedRef.GetRequiredSize(itemData.Length); - var packet = new PlayerShopBuyResultExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.SellerId = @sellerId; - packet.Result = @result; - packet.ItemSlot = @itemSlot; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player id. - /// The effect. - /// - /// Is sent by the server when: After a player achieved or lost something. - /// Causes reaction on client side: An effect is shown for the affected player. - /// - public static async ValueTask SendShowEffectAsync(this IConnection? connection, ushort @playerId, ShowEffect.EffectType @effect) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ShowEffectRef.Length; - var packet = new ShowEffectRef(connection.Output.GetSpan(length)[..length]); - packet.PlayerId = @playerId; - packet.Effect = @effect; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The unlock flags. - /// - /// Is sent by the server when: It's send right after the CharacterList, in the character selection screen, if the account has any unlocked character classes. - /// Causes reaction on client side: The client unlocks the specified character classes, so they can be created. - /// - public static async ValueTask SendCharacterClassCreationUnlockAsync(this IConnection? connection, CharacterCreationUnlockFlags @unlockFlags) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterClassCreationUnlockRef.Length; - var packet = new CharacterClassCreationUnlockRef(connection.Output.GetSpan(length)[..length]); - packet.UnlockFlags = @unlockFlags; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The character name. - /// The character slot. - /// The level. - /// The class. - /// The character status. - /// The preview data. - /// The success. - /// - /// Is sent by the server when: After the server successfully processed a character creation request. - /// Causes reaction on client side: The new character is shown in the character list - /// - public static async ValueTask SendCharacterCreationSuccessfulAsync(this IConnection? connection, string @characterName, byte @characterSlot, ushort @level, CharacterClassNumber @class, byte @characterStatus, Memory @previewData, bool @success = true) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterCreationSuccessfulRef.Length; - var packet = new CharacterCreationSuccessfulRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.CharacterName = @characterName; - packet.CharacterSlot = @characterSlot; - packet.Level = @level; - packet.Class = @class; - packet.CharacterStatus = @characterStatus; - @previewData.Span.CopyTo(packet.PreviewData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: After the server processed a character creation request without success. - /// Causes reaction on client side: A message is shown that it failed. - /// - public static async ValueTask SendCharacterCreationFailedAsync(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterCreationFailedRef.Length; - var packet = new CharacterCreationFailedRef(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The position x. - /// The position y. - /// The map number. - /// The direction. - /// The current health. - /// The current mana. - /// The experience. - /// The money. - /// - /// Is sent by the server when: The character respawned after death. - /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. - /// - public static async ValueTask SendRespawnAfterDeath075Async(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, ushort @currentHealth, ushort @currentMana, uint @experience, uint @money) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = RespawnAfterDeath075Ref.Length; - var packet = new RespawnAfterDeath075Ref(connection.Output.GetSpan(length)[..length]); - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.MapNumber = @mapNumber; - packet.Direction = @direction; - packet.CurrentHealth = @currentHealth; - packet.CurrentMana = @currentMana; - packet.Experience = @experience; - packet.Money = @money; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The position x. - /// The position y. - /// The map number. - /// The direction. - /// The current health. - /// The current mana. - /// The current ability. - /// The experience. - /// The money. - /// - /// Is sent by the server when: The character respawned after death. - /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. - /// - public static async ValueTask SendRespawnAfterDeath095Async(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, ushort @currentHealth, ushort @currentMana, ushort @currentAbility, uint @experience, uint @money) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = RespawnAfterDeath095Ref.Length; - var packet = new RespawnAfterDeath095Ref(connection.Output.GetSpan(length)[..length]); - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.MapNumber = @mapNumber; - packet.Direction = @direction; - packet.CurrentHealth = @currentHealth; - packet.CurrentMana = @currentMana; - packet.CurrentAbility = @currentAbility; - packet.Experience = @experience; - packet.Money = @money; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The position x. - /// The position y. - /// The map number. - /// The direction. - /// The current health. - /// The current mana. - /// The current shield. - /// The current ability. - /// The experience. - /// The money. - /// - /// Is sent by the server when: The character respawned after death. - /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. - /// - public static async ValueTask SendRespawnAfterDeathAsync(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, ushort @currentHealth, ushort @currentMana, ushort @currentShield, ushort @currentAbility, ulong @experience, uint @money) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = RespawnAfterDeathRef.Length; - var packet = new RespawnAfterDeathRef(connection.Output.GetSpan(length)[..length]); - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.MapNumber = @mapNumber; - packet.Direction = @direction; - packet.CurrentHealth = @currentHealth; - packet.CurrentMana = @currentMana; - packet.CurrentShield = @currentShield; - packet.CurrentAbility = @currentAbility; - packet.Experience = @experience; - packet.Money = @money; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The position x. - /// The position y. - /// The map number. - /// The direction. - /// The current health. - /// The current mana. - /// The current shield. - /// The current ability. - /// The experience. - /// The money. - /// - /// Is sent by the server when: The character respawned after death. - /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. - /// - public static async ValueTask SendRespawnAfterDeathExtendedAsync(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, uint @currentHealth, uint @currentMana, uint @currentShield, uint @currentAbility, ulong @experience, uint @money) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = RespawnAfterDeathExtendedRef.Length; - var packet = new RespawnAfterDeathExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.PositionX = @positionX; - packet.PositionY = @positionY; - packet.MapNumber = @mapNumber; - packet.Direction = @direction; - packet.CurrentHealth = @currentHealth; - packet.CurrentMana = @currentMana; - packet.CurrentShield = @currentShield; - packet.CurrentAbility = @currentAbility; - packet.Experience = @experience; - packet.Money = @money; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health damage. - /// The current shield. - /// - /// Is sent by the server when: The character got damaged by being poisoned on old client versions. - /// Causes reaction on client side: Removes the damage from the health without showing a damage number. - /// - public static async ValueTask SendPoisonDamageAsync(this IConnection? connection, ushort @healthDamage, ushort @currentShield) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PoisonDamageRef.Length; - var packet = new PoisonDamageRef(connection.Output.GetSpan(length)[..length]); - packet.HealthDamage = @healthDamage; - packet.CurrentShield = @currentShield; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player id. - /// The new state. - /// - /// Is sent by the server when: After a the hero state of an observed character changed. - /// Causes reaction on client side: The color of the name of the character is changed accordingly and a message is shown. - /// - public static async ValueTask SendHeroStateChangedAsync(this IConnection? connection, ushort @playerId, CharacterHeroState @newState) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = HeroStateChangedRef.Length; - var packet = new HeroStateChangedRef(connection.Output.GetSpan(length)[..length]); - packet.PlayerId = @playerId; - packet.NewState = @newState; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill index. - /// The skill number. - /// The skill level. - /// The flag. - /// - /// Is sent by the server when: After a skill got added to the skill list, e.g. by equipping an item or learning a skill. - /// Causes reaction on client side: The skill is added to the skill list on client side. - /// - public static async ValueTask SendSkillAddedAsync(this IConnection? connection, byte @skillIndex, ushort @skillNumber, byte @skillLevel, byte @flag = 0xFE) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillAddedRef.Length; - var packet = new SkillAddedRef(connection.Output.GetSpan(length)[..length]); - packet.Flag = @flag; - packet.SkillIndex = @skillIndex; - packet.SkillNumber = @skillNumber; - packet.SkillLevel = @skillLevel; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill index. - /// The skill number. - /// The flag. - /// - /// Is sent by the server when: After a skill got removed from the skill list, e.g. by removing an equipped item. - /// Causes reaction on client side: The skill is added to the skill list on client side. - /// - public static async ValueTask SendSkillRemovedAsync(this IConnection? connection, byte @skillIndex, ushort @skillNumber, byte @flag = 0xFF) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillRemovedRef.Length; - var packet = new SkillRemovedRef(connection.Output.GetSpan(length)[..length]); - packet.Flag = @flag; - packet.SkillIndex = @skillIndex; - packet.SkillNumber = @skillNumber; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill index. - /// The skill number and level. - /// The flag. - /// - /// Is sent by the server when: After a skill got added to the skill list, e.g. by equipping an item or learning a skill. - /// Causes reaction on client side: The skill is added to the skill list on client side. - /// - public static async ValueTask SendSkillAdded075Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 1) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillAdded075Ref.Length; - var packet = new SkillAdded075Ref(connection.Output.GetSpan(length)[..length]); - packet.Flag = @flag; - packet.SkillIndex = @skillIndex; - packet.SkillNumberAndLevel = @skillNumberAndLevel; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill index. - /// The skill number and level. - /// The flag. - /// - /// Is sent by the server when: After a skill got removed from the skill list, e.g. by removing an equipped item. - /// Causes reaction on client side: The skill is added to the skill list on client side. - /// - public static async ValueTask SendSkillRemoved075Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 0) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillRemoved075Ref.Length; - var packet = new SkillRemoved075Ref(connection.Output.GetSpan(length)[..length]); - packet.Flag = @flag; - packet.SkillIndex = @skillIndex; - packet.SkillNumberAndLevel = @skillNumberAndLevel; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill index. - /// The skill number and level. - /// The flag. - /// - /// Is sent by the server when: After a skill got added to the skill list, e.g. by equipping an item or learning a skill. - /// Causes reaction on client side: The skill is added to the skill list on client side. - /// - public static async ValueTask SendSkillAdded095Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 0xFE) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillAdded095Ref.Length; - var packet = new SkillAdded095Ref(connection.Output.GetSpan(length)[..length]); - packet.Flag = @flag; - packet.SkillIndex = @skillIndex; - packet.SkillNumberAndLevel = @skillNumberAndLevel; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill index. - /// The skill number and level. - /// The flag. - /// - /// Is sent by the server when: After a skill got removed from the skill list, e.g. by removing an equipped item. - /// Causes reaction on client side: The skill is added to the skill list on client side. - /// - public static async ValueTask SendSkillRemoved095Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 0xFF) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillRemoved095Ref.Length; - var packet = new SkillRemoved095Ref(connection.Output.GetSpan(length)[..length]); - packet.Flag = @flag; - packet.SkillIndex = @skillIndex; - packet.SkillNumberAndLevel = @skillNumberAndLevel; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The character name. - /// - /// Is sent by the server when: After the client focused the character successfully on the server side. - /// Causes reaction on client side: The client highlights the focused character. - /// - public static async ValueTask SendCharacterFocusedAsync(this IConnection? connection, string @characterName) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterFocusedRef.Length; - var packet = new CharacterFocusedRef(connection.Output.GetSpan(length)[..length]); - packet.CharacterName = @characterName; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The attribute. - /// The updated dependent maximum stat. - /// The updated maximum shield. - /// The updated maximum ability. - /// - /// Is sent by the server when: After the server processed a character stat increase request packet. - /// Causes reaction on client side: If it was successful, adds a point to the requested stat type. - /// - public static async ValueTask SendCharacterStatIncreaseResponseAsync(this IConnection? connection, bool @success, CharacterStatAttribute @attribute, ushort @updatedDependentMaximumStat, ushort @updatedMaximumShield, ushort @updatedMaximumAbility) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterStatIncreaseResponseRef.Length; - var packet = new CharacterStatIncreaseResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.Attribute = @attribute; - packet.UpdatedDependentMaximumStat = @updatedDependentMaximumStat; - packet.UpdatedMaximumShield = @updatedMaximumShield; - packet.UpdatedMaximumAbility = @updatedMaximumAbility; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The attribute. - /// The added amount. - /// The updated maximum health. - /// The updated maximum mana. - /// The updated maximum shield. - /// The updated maximum ability. - /// - /// Is sent by the server when: After the server processed a character stat increase request packet. - /// Causes reaction on client side: If it was successful, adds a point to the requested stat type. - /// - public static async ValueTask SendCharacterStatIncreaseResponseExtendedAsync(this IConnection? connection, CharacterStatAttribute @attribute, ushort @addedAmount, uint @updatedMaximumHealth, uint @updatedMaximumMana, uint @updatedMaximumShield, uint @updatedMaximumAbility) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterStatIncreaseResponseExtendedRef.Length; - var packet = new CharacterStatIncreaseResponseExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Attribute = @attribute; - packet.AddedAmount = @addedAmount; - packet.UpdatedMaximumHealth = @updatedMaximumHealth; - packet.UpdatedMaximumMana = @updatedMaximumMana; - packet.UpdatedMaximumShield = @updatedMaximumShield; - packet.UpdatedMaximumAbility = @updatedMaximumAbility; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: After the server processed a character delete response of the client. - /// Causes reaction on client side: If successful, the character is deleted from the character selection screen. Otherwise, a message is shown. - /// - public static async ValueTask SendCharacterDeleteResponseAsync(this IConnection? connection, CharacterDeleteResponse.CharacterDeleteResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterDeleteResponseRef.Length; - var packet = new CharacterDeleteResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The level. - /// The level up points. - /// The maximum health. - /// The maximum mana. - /// The maximum shield. - /// The maximum ability. - /// The fruit points. - /// The maximum fruit points. - /// The negative fruit points. - /// The maximum negative fruit points. - /// - /// Is sent by the server when: After a character leveled up. - /// Causes reaction on client side: Updates the level (and other related stats) in the game client and shows an effect. - /// - public static async ValueTask SendCharacterLevelUpdateAsync(this IConnection? connection, ushort @level, ushort @levelUpPoints, ushort @maximumHealth, ushort @maximumMana, ushort @maximumShield, ushort @maximumAbility, ushort @fruitPoints, ushort @maximumFruitPoints, ushort @negativeFruitPoints, ushort @maximumNegativeFruitPoints) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterLevelUpdateRef.Length; - var packet = new CharacterLevelUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.Level = @level; - packet.LevelUpPoints = @levelUpPoints; - packet.MaximumHealth = @maximumHealth; - packet.MaximumMana = @maximumMana; - packet.MaximumShield = @maximumShield; - packet.MaximumAbility = @maximumAbility; - packet.FruitPoints = @fruitPoints; - packet.MaximumFruitPoints = @maximumFruitPoints; - packet.NegativeFruitPoints = @negativeFruitPoints; - packet.MaximumNegativeFruitPoints = @maximumNegativeFruitPoints; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The x. - /// The y. - /// The map id. - /// The current experience. - /// The experience for next level. - /// The level up points. - /// The strength. - /// The agility. - /// The vitality. - /// The energy. - /// The current health. - /// The maximum health. - /// The current mana. - /// The maximum mana. - /// The current shield. - /// The maximum shield. - /// The current ability. - /// The maximum ability. - /// The money. - /// The hero state. - /// The status. - /// The used fruit points. - /// The max fruit points. - /// The leadership. - /// The used negative fruit points. - /// The max negative fruit points. - /// The inventory extensions. - /// - /// Is sent by the server when: After the character was selected by the player and entered the game. - /// Causes reaction on client side: The characters enters the game world. - /// - public static async ValueTask SendCharacterInformationAsync(this IConnection? connection, byte @x, byte @y, ushort @mapId, ulong @currentExperience, ulong @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @currentHealth, ushort @maximumHealth, ushort @currentMana, ushort @maximumMana, ushort @currentShield, ushort @maximumShield, ushort @currentAbility, ushort @maximumAbility, uint @money, CharacterHeroState @heroState, CharacterStatus @status, ushort @usedFruitPoints, ushort @maxFruitPoints, ushort @leadership, ushort @usedNegativeFruitPoints, ushort @maxNegativeFruitPoints, byte @inventoryExtensions) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterInformationRef.Length; - var packet = new CharacterInformationRef(connection.Output.GetSpan(length)[..length]); - packet.X = @x; - packet.Y = @y; - packet.MapId = @mapId; - packet.CurrentExperience = @currentExperience; - packet.ExperienceForNextLevel = @experienceForNextLevel; - packet.LevelUpPoints = @levelUpPoints; - packet.Strength = @strength; - packet.Agility = @agility; - packet.Vitality = @vitality; - packet.Energy = @energy; - packet.CurrentHealth = @currentHealth; - packet.MaximumHealth = @maximumHealth; - packet.CurrentMana = @currentMana; - packet.MaximumMana = @maximumMana; - packet.CurrentShield = @currentShield; - packet.MaximumShield = @maximumShield; - packet.CurrentAbility = @currentAbility; - packet.MaximumAbility = @maximumAbility; - packet.Money = @money; - packet.HeroState = @heroState; - packet.Status = @status; - packet.UsedFruitPoints = @usedFruitPoints; - packet.MaxFruitPoints = @maxFruitPoints; - packet.Leadership = @leadership; - packet.UsedNegativeFruitPoints = @usedNegativeFruitPoints; - packet.MaxNegativeFruitPoints = @maxNegativeFruitPoints; - packet.InventoryExtensions = @inventoryExtensions; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The level. - /// The level up points. - /// The maximum health. - /// The maximum mana. - /// The maximum shield. - /// The maximum ability. - /// The fruit points. - /// The maximum fruit points. - /// The negative fruit points. - /// The maximum negative fruit points. - /// - /// Is sent by the server when: After a character leveled up. - /// Causes reaction on client side: Updates the level (and other related stats) in the game client and shows an effect. - /// - public static async ValueTask SendCharacterLevelUpdateExtendedAsync(this IConnection? connection, ushort @level, ushort @levelUpPoints, uint @maximumHealth, uint @maximumMana, uint @maximumShield, uint @maximumAbility, ushort @fruitPoints, ushort @maximumFruitPoints, ushort @negativeFruitPoints, ushort @maximumNegativeFruitPoints) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterLevelUpdateExtendedRef.Length; - var packet = new CharacterLevelUpdateExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.Level = @level; - packet.LevelUpPoints = @levelUpPoints; - packet.MaximumHealth = @maximumHealth; - packet.MaximumMana = @maximumMana; - packet.MaximumShield = @maximumShield; - packet.MaximumAbility = @maximumAbility; - packet.FruitPoints = @fruitPoints; - packet.MaximumFruitPoints = @maximumFruitPoints; - packet.NegativeFruitPoints = @negativeFruitPoints; - packet.MaximumNegativeFruitPoints = @maximumNegativeFruitPoints; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The x. - /// The y. - /// The map id. - /// The current experience. - /// The experience for next level. - /// The level up points. - /// The strength. - /// The agility. - /// The vitality. - /// The energy. - /// The leadership. - /// The current health. - /// The maximum health. - /// The current mana. - /// The maximum mana. - /// The current shield. - /// The maximum shield. - /// The current ability. - /// The maximum ability. - /// The money. - /// The hero state. - /// The status. - /// The used fruit points. - /// The max fruit points. - /// The used negative fruit points. - /// The max negative fruit points. - /// The attack speed. - /// The magic speed. - /// The maximum attack speed. - /// The inventory extensions. - /// - /// Is sent by the server when: After the character was selected by the player and entered the game. - /// Causes reaction on client side: The characters enters the game world. - /// - public static async ValueTask SendCharacterInformationExtendedAsync(this IConnection? connection, byte @x, byte @y, ushort @mapId, ulong @currentExperience, ulong @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @leadership, uint @currentHealth, uint @maximumHealth, uint @currentMana, uint @maximumMana, uint @currentShield, uint @maximumShield, uint @currentAbility, uint @maximumAbility, uint @money, CharacterHeroState @heroState, CharacterStatus @status, ushort @usedFruitPoints, ushort @maxFruitPoints, ushort @usedNegativeFruitPoints, ushort @maxNegativeFruitPoints, ushort @attackSpeed, ushort @magicSpeed, ushort @maximumAttackSpeed, byte @inventoryExtensions) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterInformationExtendedRef.Length; - var packet = new CharacterInformationExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.X = @x; - packet.Y = @y; - packet.MapId = @mapId; - packet.CurrentExperience = @currentExperience; - packet.ExperienceForNextLevel = @experienceForNextLevel; - packet.LevelUpPoints = @levelUpPoints; - packet.Strength = @strength; - packet.Agility = @agility; - packet.Vitality = @vitality; - packet.Energy = @energy; - packet.Leadership = @leadership; - packet.CurrentHealth = @currentHealth; - packet.MaximumHealth = @maximumHealth; - packet.CurrentMana = @currentMana; - packet.MaximumMana = @maximumMana; - packet.CurrentShield = @currentShield; - packet.MaximumShield = @maximumShield; - packet.CurrentAbility = @currentAbility; - packet.MaximumAbility = @maximumAbility; - packet.Money = @money; - packet.HeroState = @heroState; - packet.Status = @status; - packet.UsedFruitPoints = @usedFruitPoints; - packet.MaxFruitPoints = @maxFruitPoints; - packet.UsedNegativeFruitPoints = @usedNegativeFruitPoints; - packet.MaxNegativeFruitPoints = @maxNegativeFruitPoints; - packet.AttackSpeed = @attackSpeed; - packet.MagicSpeed = @magicSpeed; - packet.MaximumAttackSpeed = @maximumAttackSpeed; - packet.InventoryExtensions = @inventoryExtensions; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The x. - /// The y. - /// The map id. - /// The current experience. - /// The experience for next level. - /// The level up points. - /// The strength. - /// The agility. - /// The vitality. - /// The energy. - /// The current health. - /// The maximum health. - /// The current mana. - /// The maximum mana. - /// The money. - /// The hero state. - /// The status. - /// - /// Is sent by the server when: After the character was selected by the player and entered the game. - /// Causes reaction on client side: The characters enters the game world. - /// - public static async ValueTask SendCharacterInformation075Async(this IConnection? connection, byte @x, byte @y, byte @mapId, uint @currentExperience, uint @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @currentHealth, ushort @maximumHealth, ushort @currentMana, ushort @maximumMana, uint @money, CharacterHeroState @heroState, CharacterStatus @status) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterInformation075Ref.Length; - var packet = new CharacterInformation075Ref(connection.Output.GetSpan(length)[..length]); - packet.X = @x; - packet.Y = @y; - packet.MapId = @mapId; - packet.CurrentExperience = @currentExperience; - packet.ExperienceForNextLevel = @experienceForNextLevel; - packet.LevelUpPoints = @levelUpPoints; - packet.Strength = @strength; - packet.Agility = @agility; - packet.Vitality = @vitality; - packet.Energy = @energy; - packet.CurrentHealth = @currentHealth; - packet.MaximumHealth = @maximumHealth; - packet.CurrentMana = @currentMana; - packet.MaximumMana = @maximumMana; - packet.Money = @money; - packet.HeroState = @heroState; - packet.Status = @status; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The x. - /// The y. - /// The map id. - /// The direction. - /// The current experience. - /// The experience for next level. - /// The level up points. - /// The strength. - /// The agility. - /// The vitality. - /// The energy. - /// The current health. - /// The maximum health. - /// The current mana. - /// The maximum mana. - /// The current ability. - /// The maximum ability. - /// The money. - /// The hero state. - /// The status. - /// The used fruit points. - /// The max fruit points. - /// The leadership. - /// - /// Is sent by the server when: After the character was selected by the player and entered the game. - /// Causes reaction on client side: The characters enters the game world. - /// - public static async ValueTask SendCharacterInformation097Async(this IConnection? connection, byte @x, byte @y, byte @mapId, byte @direction, uint @currentExperience, uint @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @currentHealth, ushort @maximumHealth, ushort @currentMana, ushort @maximumMana, ushort @currentAbility, ushort @maximumAbility, uint @money, CharacterHeroState @heroState, CharacterStatus @status, ushort @usedFruitPoints, ushort @maxFruitPoints, ushort @leadership) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CharacterInformation097Ref.Length; - var packet = new CharacterInformation097Ref(connection.Output.GetSpan(length)[..length]); - packet.X = @x; - packet.Y = @y; - packet.MapId = @mapId; - packet.Direction = @direction; - packet.CurrentExperience = @currentExperience; - packet.ExperienceForNextLevel = @experienceForNextLevel; - packet.LevelUpPoints = @levelUpPoints; - packet.Strength = @strength; - packet.Agility = @agility; - packet.Vitality = @vitality; - packet.Energy = @energy; - packet.CurrentHealth = @currentHealth; - packet.MaximumHealth = @maximumHealth; - packet.CurrentMana = @currentMana; - packet.MaximumMana = @maximumMana; - packet.CurrentAbility = @currentAbility; - packet.MaximumAbility = @maximumAbility; - packet.Money = @money; - packet.HeroState = @heroState; - packet.Status = @status; - packet.UsedFruitPoints = @usedFruitPoints; - packet.MaxFruitPoints = @maxFruitPoints; - packet.Leadership = @leadership; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The inventory slot. - /// The item data. - /// - /// Is sent by the server when: An item in the inventory got upgraded by the player, e.g. by applying a jewel. - /// Causes reaction on client side: The item is updated on the user interface. - /// - public static async ValueTask SendInventoryItemUpgradedAsync(this IConnection? connection, byte @inventorySlot, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = InventoryItemUpgradedRef.GetRequiredSize(itemData.Length); - var packet = new InventoryItemUpgradedRef(connection.Output.GetSpan(length)[..length]); - packet.InventorySlot = @inventorySlot; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The health percent. - /// - /// Is sent by the server when: When health of a summoned monster (Elf Skill) changed. - /// Causes reaction on client side: The health is updated on the user interface. - /// - public static async ValueTask SendSummonHealthUpdateAsync(this IConnection? connection, byte @healthPercent) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SummonHealthUpdateRef.Length; - var packet = new SummonHealthUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.HealthPercent = @healthPercent; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The seconds. - /// - /// Is sent by the server when: Every second during a guild soccer match. - /// Causes reaction on client side: The time is updated on the user interface. - /// - public static async ValueTask SendGuildSoccerTimeUpdateAsync(this IConnection? connection, ushort @seconds) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildSoccerTimeUpdateRef.Length; - var packet = new GuildSoccerTimeUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.Seconds = @seconds; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The red team name. - /// The red team goals. - /// The blue team name. - /// The blue team goals. - /// - /// Is sent by the server when: Whenever the score of the soccer game changed, and at the beginning of the match. - /// Causes reaction on client side: The score is updated on the user interface. - /// - public static async ValueTask SendGuildSoccerScoreUpdateAsync(this IConnection? connection, string @redTeamName, byte @redTeamGoals, string @blueTeamName, byte @blueTeamGoals) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildSoccerScoreUpdateRef.Length; - var packet = new GuildSoccerScoreUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.RedTeamName = @redTeamName; - packet.RedTeamGoals = @redTeamGoals; - packet.BlueTeamName = @blueTeamName; - packet.BlueTeamGoals = @blueTeamGoals; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The command type. - /// The parameter 1. - /// The parameter 2. - /// - /// Is sent by the server when: E.g. when event items are dropped to the floor, or a specific dialog should be shown. - /// Causes reaction on client side: The client shows an effect, e.g. a firework. - /// - public static async ValueTask SendServerCommandAsync(this IConnection? connection, byte @commandType, byte @parameter1, byte @parameter2) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ServerCommandRef.Length; - var packet = new ServerCommandRef(connection.Output.GetSpan(length)[..length]); - packet.CommandType = @commandType; - packet.Parameter1 = @parameter1; - packet.Parameter2 = @parameter2; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The x. - /// The y. - /// The effect type. - /// - /// Is sent by the server when: E.g. when event items are dropped to the floor. - /// Causes reaction on client side: The client shows an fireworks effect at the specified coordinates. - /// - public static async ValueTask SendShowFireworksAsync(this IConnection? connection, byte @x, byte @y, byte @effectType = 0) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ShowFireworksRef.Length; - var packet = new ShowFireworksRef(connection.Output.GetSpan(length)[..length]); - packet.EffectType = @effectType; - packet.X = @x; - packet.Y = @y; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The x. - /// The y. - /// The effect type. - /// - /// Is sent by the server when: E.g. when event items are dropped to the floor. - /// Causes reaction on client side: The client shows an christmas fireworks effect at the specified coordinates. - /// - public static async ValueTask SendShowChristmasFireworksAsync(this IConnection? connection, byte @x, byte @y, byte @effectType = 59) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ShowChristmasFireworksRef.Length; - var packet = new ShowChristmasFireworksRef(connection.Output.GetSpan(length)[..length]); - packet.EffectType = @effectType; - packet.X = @x; - packet.Y = @y; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The x. - /// The y. - /// The effect type. - /// - /// Is sent by the server when: E.g. when event items are dropped to the floor. - /// Causes reaction on client side: The client plays a fanfare sound at the specified coordinates. - /// - public static async ValueTask SendPlayFanfareSoundAsync(this IConnection? connection, byte @x, byte @y, byte @effectType = 2) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PlayFanfareSoundRef.Length; - var packet = new PlayFanfareSoundRef(connection.Output.GetSpan(length)[..length]); - packet.EffectType = @effectType; - packet.X = @x; - packet.Y = @y; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The target object id. - /// The effect type. - /// - /// Is sent by the server when: E.g. when event items are dropped to the floor. - /// Causes reaction on client side: The client shows a swirl effect at the specified object. - /// - public static async ValueTask SendShowSwirlAsync(this IConnection? connection, ushort @targetObjectId, byte @effectType = 58) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ShowSwirlRef.Length; - var packet = new ShowSwirlRef(connection.Output.GetSpan(length)[..length]); - packet.EffectType = @effectType; - packet.TargetObjectId = @targetObjectId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The master level. - /// The master experience. - /// The master experience of next level. - /// The master level up points. - /// The maximum health. - /// The maximum mana. - /// The maximum shield. - /// The maximum ability. - /// - /// Is sent by the server when: After entering the game with a master class character. - /// Causes reaction on client side: The master related data is available. - /// - public static async ValueTask SendMasterStatsUpdateAsync(this IConnection? connection, ushort @masterLevel, ulong @masterExperience, ulong @masterExperienceOfNextLevel, ushort @masterLevelUpPoints, ushort @maximumHealth, ushort @maximumMana, ushort @maximumShield, ushort @maximumAbility) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MasterStatsUpdateRef.Length; - var packet = new MasterStatsUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.MasterLevel = @masterLevel; - packet.MasterExperience = @masterExperience; - packet.MasterExperienceOfNextLevel = @masterExperienceOfNextLevel; - packet.MasterLevelUpPoints = @masterLevelUpPoints; - packet.MaximumHealth = @maximumHealth; - packet.MaximumMana = @maximumMana; - packet.MaximumShield = @maximumShield; - packet.MaximumAbility = @maximumAbility; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The master level. - /// The master experience. - /// The master experience of next level. - /// The master level up points. - /// The maximum health. - /// The maximum mana. - /// The maximum shield. - /// The maximum ability. - /// - /// Is sent by the server when: After entering the game with a master class character. - /// Causes reaction on client side: The master related data is available. - /// - public static async ValueTask SendMasterStatsUpdateExtendedAsync(this IConnection? connection, ushort @masterLevel, ulong @masterExperience, ulong @masterExperienceOfNextLevel, ushort @masterLevelUpPoints, uint @maximumHealth, uint @maximumMana, uint @maximumShield, uint @maximumAbility) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MasterStatsUpdateExtendedRef.Length; - var packet = new MasterStatsUpdateExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.MasterLevel = @masterLevel; - packet.MasterExperience = @masterExperience; - packet.MasterExperienceOfNextLevel = @masterExperienceOfNextLevel; - packet.MasterLevelUpPoints = @masterLevelUpPoints; - packet.MaximumHealth = @maximumHealth; - packet.MaximumMana = @maximumMana; - packet.MaximumShield = @maximumShield; - packet.MaximumAbility = @maximumAbility; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The master level. - /// The gained master points. - /// The current master points. - /// The maximum master points. - /// The maximum health. - /// The maximum mana. - /// The maximum shield. - /// The maximum ability. - /// - /// Is sent by the server when: After a master character leveled up. - /// Causes reaction on client side: Updates the master level (and other related stats) in the game client and shows an effect. - /// - public static async ValueTask SendMasterCharacterLevelUpdateAsync(this IConnection? connection, ushort @masterLevel, ushort @gainedMasterPoints, ushort @currentMasterPoints, ushort @maximumMasterPoints, ushort @maximumHealth, ushort @maximumMana, ushort @maximumShield, ushort @maximumAbility) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MasterCharacterLevelUpdateRef.Length; - var packet = new MasterCharacterLevelUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.MasterLevel = @masterLevel; - packet.GainedMasterPoints = @gainedMasterPoints; - packet.CurrentMasterPoints = @currentMasterPoints; - packet.MaximumMasterPoints = @maximumMasterPoints; - packet.MaximumHealth = @maximumHealth; - packet.MaximumMana = @maximumMana; - packet.MaximumShield = @maximumShield; - packet.MaximumAbility = @maximumAbility; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The master level. - /// The gained master points. - /// The current master points. - /// The maximum master points. - /// The maximum health. - /// The maximum mana. - /// The maximum shield. - /// The maximum ability. - /// - /// Is sent by the server when: After a master character leveled up. - /// Causes reaction on client side: Updates the master level (and other related stats) in the game client and shows an effect. - /// - public static async ValueTask SendMasterCharacterLevelUpdateExtendedAsync(this IConnection? connection, ushort @masterLevel, ushort @gainedMasterPoints, ushort @currentMasterPoints, ushort @maximumMasterPoints, uint @maximumHealth, uint @maximumMana, uint @maximumShield, uint @maximumAbility) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MasterCharacterLevelUpdateExtendedRef.Length; - var packet = new MasterCharacterLevelUpdateExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.MasterLevel = @masterLevel; - packet.GainedMasterPoints = @gainedMasterPoints; - packet.CurrentMasterPoints = @currentMasterPoints; - packet.MaximumMasterPoints = @maximumMasterPoints; - packet.MaximumHealth = @maximumHealth; - packet.MaximumMana = @maximumMana; - packet.MaximumShield = @maximumShield; - packet.MaximumAbility = @maximumAbility; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The master level up points. - /// The index of the master skill on the clients master skill tree for the given character class. - /// The master skill number. - /// The level. - /// The display value. - /// The display value of next level. - /// - /// Is sent by the server when: After a master skill level has been changed (usually increased). - /// Causes reaction on client side: The level is updated in the master skill tree. - /// - public static async ValueTask SendMasterSkillLevelUpdateAsync(this IConnection? connection, bool @success, ushort @masterLevelUpPoints, byte @masterSkillIndex, ushort @masterSkillNumber, byte @level, float @displayValue, float @displayValueOfNextLevel) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MasterSkillLevelUpdateRef.Length; - var packet = new MasterSkillLevelUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.MasterLevelUpPoints = @masterLevelUpPoints; - packet.MasterSkillIndex = @masterSkillIndex; - packet.MasterSkillNumber = @masterSkillNumber; - packet.Level = @level; - packet.DisplayValue = @displayValue; - packet.DisplayValueOfNextLevel = @displayValueOfNextLevel; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The type. - /// The message. - /// - /// Is sent by the server when: - /// Causes reaction on client side: - /// - public static async ValueTask SendServerMessageAsync(this IConnection? connection, ServerMessage.MessageType @type, string @message) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ServerMessageRef.GetRequiredSize(message); - var packet = new ServerMessageRef(connection.Output.GetSpan(length)[..length]); - packet.Type = @type; - packet.Message = @message; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The requester id. - /// - /// Is sent by the server when: A player requested to join a guild. This message is sent then to the guild master. - /// Causes reaction on client side: The guild master gets a message box with the request popping up. - /// - public static async ValueTask SendGuildJoinRequestAsync(this IConnection? connection, ushort @requesterId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildJoinRequestRef.Length; - var packet = new GuildJoinRequestRef(connection.Output.GetSpan(length)[..length]); - packet.RequesterId = @requesterId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: After a guild master responded to a request of a player to join his guild. This message is sent back to the requesting player. - /// Causes reaction on client side: The requester gets a corresponding message showing. - /// - public static async ValueTask SendGuildJoinResponseAsync(this IConnection? connection, GuildJoinResponse.GuildJoinRequestResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildJoinResponseRef.Length; - var packet = new GuildJoinResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: After a guild master sent a request to kick a player from its guild and the server processed this request. - /// Causes reaction on client side: The client shows a message depending on the result. - /// - public static async ValueTask SendGuildKickResponseAsync(this IConnection? connection, GuildKickResponse.GuildKickSuccess @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildKickResponseRef.Length; - var packet = new GuildKickResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: After a player started talking to the guild master NPC and the player is allowed to create a guild. - /// Causes reaction on client side: The client shows the guild master dialog. - /// - public static async ValueTask SendShowGuildMasterDialogAsync(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ShowGuildMasterDialogRef.Length; - var packet = new ShowGuildMasterDialogRef(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: After a player started talking to the guild master NPC and the player proceeds to create a guild. - /// Causes reaction on client side: The client shows the guild creation dialog. - /// - public static async ValueTask SendShowGuildCreationDialogAsync(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ShowGuildCreationDialogRef.Length; - var packet = new ShowGuildCreationDialogRef(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The error. - /// - /// Is sent by the server when: After a player requested to create a guild at the guild master NPC. - /// Causes reaction on client side: Depending on the result, a message is shown. - /// - public static async ValueTask SendGuildCreationResultAsync(this IConnection? connection, bool @success, GuildCreationResult.GuildCreationErrorType @error) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildCreationResultRef.Length; - var packet = new GuildCreationResultRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.Error = @error; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player id. - /// The is guild master. - /// - /// Is sent by the server when: A player left a guild. This message is sent to the player and all surrounding players. - /// Causes reaction on client side: The player is not longer shown as a guild member. - /// - public static async ValueTask SendGuildMemberLeftGuildAsync(this IConnection? connection, ushort @playerId, bool @isGuildMaster) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildMemberLeftGuildRef.Length; - var packet = new GuildMemberLeftGuildRef(connection.Output.GetSpan(length)[..length]); - packet.PlayerId = @playerId; - packet.IsGuildMaster = @isGuildMaster; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: A guild master requested a guild war against another guild. - /// Causes reaction on client side: The guild master of the other guild gets this request. - /// - public static async ValueTask SendGuildWarRequestResultAsync(this IConnection? connection, GuildWarRequestResult.RequestResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildWarRequestResultRef.Length; - var packet = new GuildWarRequestResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The guild name. - /// The type. - /// - /// Is sent by the server when: A guild master requested a guild war against another guild. - /// Causes reaction on client side: The guild master of the other guild gets this request. - /// - public static async ValueTask SendGuildWarRequestAsync(this IConnection? connection, string @guildName, GuildWarType @type) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildWarRequestRef.Length; - var packet = new GuildWarRequestRef(connection.Output.GetSpan(length)[..length]); - packet.GuildName = @guildName; - packet.Type = @type; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The guild name. - /// The type. - /// The team code. - /// - /// Is sent by the server when: A guild master requested a guild war against another guild. - /// Causes reaction on client side: The guild master of the other guild gets this request. - /// - public static async ValueTask SendGuildWarDeclaredAsync(this IConnection? connection, string @guildName, GuildWarType @type, byte @teamCode) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildWarDeclaredRef.Length; - var packet = new GuildWarDeclaredRef(connection.Output.GetSpan(length)[..length]); - packet.GuildName = @guildName; - packet.Type = @type; - packet.TeamCode = @teamCode; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// The guild name. - /// - /// Is sent by the server when: The guild war ended. - /// Causes reaction on client side: The guild war is shown as ended on the client side. - /// - public static async ValueTask SendGuildWarEndedAsync(this IConnection? connection, GuildWarEnded.GuildWarResult @result, string @guildName) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildWarEndedRef.Length; - var packet = new GuildWarEndedRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - packet.GuildName = @guildName; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The score of own guild. - /// The score of enemy guild. - /// The type. - /// - /// Is sent by the server when: The guild war score changed. - /// Causes reaction on client side: The guild score is updated on the client side. - /// - public static async ValueTask SendGuildWarScoreUpdateAsync(this IConnection? connection, byte @scoreOfOwnGuild, byte @scoreOfEnemyGuild, byte @type = 0) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildWarScoreUpdateRef.Length; - var packet = new GuildWarScoreUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.ScoreOfOwnGuild = @scoreOfOwnGuild; - packet.ScoreOfEnemyGuild = @scoreOfEnemyGuild; - packet.Type = @type; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The guild id. - /// The guild type. - /// The alliance guild name. - /// The guild name. - /// The logo. - /// - /// Is sent by the server when: A game client requested the (public) info of a guild, e.g. when it met a player of previously unknown guild. - /// Causes reaction on client side: The players which belong to the guild are shown as guild players. - /// - public static async ValueTask SendGuildInformationAsync(this IConnection? connection, uint @guildId, byte @guildType, string @allianceGuildName, string @guildName, Memory @logo) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = GuildInformationRef.Length; - var packet = new GuildInformationRef(connection.Output.GetSpan(length)[..length]); - packet.GuildId = @guildId; - packet.GuildType = @guildType; - packet.AllianceGuildName = @allianceGuildName; - packet.GuildName = @guildName; - @logo.Span.CopyTo(packet.Logo); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The guild id. - /// The guild name. - /// The logo. - /// - /// Is sent by the server when: After a guild has been created. However, in OpenMU, we just send the GuildInformations075 message, because it works just the same. - /// Causes reaction on client side: The players which belong to the guild are shown as guild players. - /// - public static async ValueTask SendSingleGuildInformation075Async(this IConnection? connection, ushort @guildId, string @guildName, Memory @logo) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SingleGuildInformation075Ref.Length; - var packet = new SingleGuildInformation075Ref(connection.Output.GetSpan(length)[..length]); - packet.GuildId = @guildId; - packet.GuildName = @guildName; - @logo.Span.CopyTo(packet.Logo); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The vault money. - /// The inventory money. - /// - /// Is sent by the server when: After the player requested to move money between the vault and inventory. - /// Causes reaction on client side: The game client updates the money values of vault and inventory. - /// - public static async ValueTask SendVaultMoneyUpdateAsync(this IConnection? connection, bool @success, uint @vaultMoney, uint @inventoryMoney) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = VaultMoneyUpdateRef.Length; - var packet = new VaultMoneyUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.VaultMoney = @vaultMoney; - packet.InventoryMoney = @inventoryMoney; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: After the player requested to close the vault, this confirmation is sent back to the client. - /// Causes reaction on client side: The game client closes the vault dialog. - /// - public static async ValueTask SendVaultClosedAsync(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = VaultClosedRef.Length; - var packet = new VaultClosedRef(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The protection state. - /// - /// Is sent by the server when: After the player requested to open the vault. - /// Causes reaction on client side: The game client updates the UI to show the current vault protection state. - /// - public static async ValueTask SendVaultProtectionInformationAsync(this IConnection? connection, VaultProtectionInformation.VaultProtectionState @protectionState) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = VaultProtectionInformationRef.Length; - var packet = new VaultProtectionInformationRef(connection.Output.GetSpan(length)[..length]); - packet.ProtectionState = @protectionState; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// The item data. - /// - /// Is sent by the server when: After the player requested to execute an item crafting, e.g. at the chaos machine. - /// Causes reaction on client side: The game client updates the UI to show the resulting item. - /// - public static async ValueTask SendItemCraftingResultAsync(this IConnection? connection, ItemCraftingResult.CraftingResult @result, Memory @itemData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ItemCraftingResultRef.GetRequiredSize(itemData.Length); - var packet = new ItemCraftingResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - @itemData.Span.CopyTo(packet.ItemData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: After the player requested to close the crafting dialog, this confirmation is sent back to the client. - /// Causes reaction on client side: The game client closes the crafting dialog. - /// - public static async ValueTask SendCraftingDialogClosed075Async(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = CraftingDialogClosed075Ref.Length; - var packet = new CraftingDialogClosed075Ref(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The quest index. - /// This is the complete byte with the state of four quests within the same byte. - /// - /// Is sent by the server when: When the player clicks on the quest npc. - /// Causes reaction on client side: The game client shows the next steps in the quest dialog. - /// - public static async ValueTask SendLegacyQuestStateDialogAsync(this IConnection? connection, byte @questIndex, byte @state) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = LegacyQuestStateDialogRef.Length; - var packet = new LegacyQuestStateDialogRef(connection.Output.GetSpan(length)[..length]); - packet.QuestIndex = @questIndex; - packet.State = @state; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The quest index. - /// This value is 0 if successful. Otherwise, 0xFF or even other magic values. - /// This is the complete byte with the state of four quests within the same byte. - /// - /// Is sent by the server when: As response to the set state request (C1A2). - /// Causes reaction on client side: The game client shows the new quest state. - /// - public static async ValueTask SendLegacySetQuestStateResponseAsync(this IConnection? connection, byte @questIndex, byte @result, byte @newState) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = LegacySetQuestStateResponseRef.Length; - var packet = new LegacySetQuestStateResponseRef(connection.Output.GetSpan(length)[..length]); - packet.QuestIndex = @questIndex; - packet.Result = @result; - packet.NewState = @newState; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player id. - /// The reward. - /// The count. - /// - /// Is sent by the server when: As response to the completed quest of a player in scope. - /// Causes reaction on client side: The game client shows the reward accordingly. - /// - public static async ValueTask SendLegacyQuestRewardAsync(this IConnection? connection, ushort @playerId, LegacyQuestReward.QuestRewardType @reward, byte @count) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = LegacyQuestRewardRef.Length; - var packet = new LegacyQuestRewardRef(connection.Output.GetSpan(length)[..length]); - packet.PlayerId = @playerId; - packet.Reward = @reward; - packet.Count = @count; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The pet command mode. - /// The target id. - /// The pet. - /// - /// Is sent by the server when: After the client sent a PetAttackCommand (as confirmation), or when the previous command finished and the pet is reset to Normal-mode. - /// Causes reaction on client side: The client updates the pet mode in its user interface. - /// - public static async ValueTask SendPetModeAsync(this IConnection? connection, ClientToServer.PetCommandMode @petCommandMode, ushort @targetId, ClientToServer.PetType @pet = ClientToServer.PetType.DarkRaven) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PetModeRef.Length; - var packet = new PetModeRef(connection.Output.GetSpan(length)[..length]); - packet.Pet = @pet; - packet.PetCommandMode = @petCommandMode; - packet.TargetId = @targetId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill type. - /// The owner id. - /// The target id. - /// The pet. - /// - /// Is sent by the server when: After the client sent a PetAttackCommand, the pet attacks automatically. For each attack, the player and all observing players get this message. - /// Causes reaction on client side: The client shows the pet attacking the target. - /// - public static async ValueTask SendPetAttackAsync(this IConnection? connection, PetAttack.PetSkillType @skillType, ushort @ownerId, ushort @targetId, ClientToServer.PetType @pet = ClientToServer.PetType.DarkRaven) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PetAttackRef.Length; - var packet = new PetAttackRef(connection.Output.GetSpan(length)[..length]); - packet.Pet = @pet; - packet.SkillType = @skillType; - packet.OwnerId = @ownerId; - packet.TargetId = @targetId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The pet. - /// The storage. - /// The item slot. - /// The level. - /// The experience. - /// The health. - /// - /// Is sent by the server when: After the client sent a PetInfoRequest for a pet (dark raven, horse). - /// Causes reaction on client side: The client shows the information about the pet. - /// - public static async ValueTask SendPetInfoResponseAsync(this IConnection? connection, ClientToServer.PetType @pet, ClientToServer.StorageType @storage, byte @itemSlot, byte @level, uint @experience, byte @health) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = PetInfoResponseRef.Length; - var packet = new PetInfoResponseRef(connection.Output.GetSpan(length)[..length]); - packet.Pet = @pet; - packet.Storage = @storage; - packet.ItemSlot = @itemSlot; - packet.Level = @level; - packet.Experience = @experience; - packet.Health = @health; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// The opponent id. - /// The opponent name. - /// - /// Is sent by the server when: After the client sent a DuelStartRequest, and it either failed or the requested player sent a response. - /// Causes reaction on client side: The client shows the started or aborted duel. - /// - public static async ValueTask SendDuelStartResultAsync(this IConnection? connection, DuelStartResult.DuelStartResultType @result, ushort @opponentId, string @opponentName) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelStartResultRef.Length; - var packet = new DuelStartResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - packet.OpponentId = @opponentId; - packet.OpponentName = @opponentName; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The requester id. - /// The requester name. - /// - /// Is sent by the server when: After another client sent a DuelStartRequest, to ask the requested player for a response. - /// Causes reaction on client side: The client shows the duel request. - /// - public static async ValueTask SendDuelStartRequestAsync(this IConnection? connection, ushort @requesterId, string @requesterName) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelStartRequestRef.Length; - var packet = new DuelStartRequestRef(connection.Output.GetSpan(length)[..length]); - packet.RequesterId = @requesterId; - packet.RequesterName = @requesterName; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The opponent id. - /// The opponent name. - /// The result. - /// - /// Is sent by the server when: After a duel ended. - /// Causes reaction on client side: The client updates its state. - /// - public static async ValueTask SendDuelEndAsync(this IConnection? connection, ushort @opponentId, string @opponentName, byte @result = 0) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelEndRef.Length; - var packet = new DuelEndRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - packet.OpponentId = @opponentId; - packet.OpponentName = @opponentName; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player 1 id. - /// The player 2 id. - /// The player 1 score. - /// The player 2 score. - /// - /// Is sent by the server when: When the score of the duel has been changed. - /// Causes reaction on client side: The client updates the displayed duel score. - /// - public static async ValueTask SendDuelScoreAsync(this IConnection? connection, ushort @player1Id, ushort @player2Id, byte @player1Score, byte @player2Score) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelScoreRef.Length; - var packet = new DuelScoreRef(connection.Output.GetSpan(length)[..length]); - packet.Player1Id = @player1Id; - packet.Player2Id = @player2Id; - packet.Player1Score = @player1Score; - packet.Player2Score = @player2Score; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The player 1 id. - /// The player 2 id. - /// The player 1 health percentage. - /// The player 2 health percentage. - /// The player 1 shield percentage. - /// The player 2 shield percentage. - /// - /// Is sent by the server when: When the health/shield of the duel players has been changed. - /// Causes reaction on client side: The client updates the displayed health and shield bars. - /// - public static async ValueTask SendDuelHealthUpdateAsync(this IConnection? connection, ushort @player1Id, ushort @player2Id, byte @player1HealthPercentage, byte @player2HealthPercentage, byte @player1ShieldPercentage, byte @player2ShieldPercentage) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelHealthUpdateRef.Length; - var packet = new DuelHealthUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.Player1Id = @player1Id; - packet.Player2Id = @player2Id; - packet.Player1HealthPercentage = @player1HealthPercentage; - packet.Player2HealthPercentage = @player2HealthPercentage; - packet.Player1ShieldPercentage = @player1ShieldPercentage; - packet.Player2ShieldPercentage = @player2ShieldPercentage; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// The room index. - /// The player 1 name. - /// The player 2 name. - /// The player 1 id. - /// The player 2 id. - /// - /// Is sent by the server when: When the duel starts. - /// Causes reaction on client side: The client initializes the duel state. - /// - public static async ValueTask SendDuelInitAsync(this IConnection? connection, byte @result, byte @roomIndex, string @player1Name, string @player2Name, ushort @player1Id, ushort @player2Id) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelInitRef.Length; - var packet = new DuelInitRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - packet.RoomIndex = @roomIndex; - packet.Player1Name = @player1Name; - packet.Player2Name = @player2Name; - packet.Player1Id = @player1Id; - packet.Player2Id = @player2Id; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// - /// Is sent by the server when: When the duel starts, after the DuelInit message. - /// Causes reaction on client side: The client updates the displayed health and shield bars. - /// - public static async ValueTask SendDuelHealthBarInitAsync(this IConnection? connection) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelHealthBarInitRef.Length; - var packet = new DuelHealthBarInitRef(connection.Output.GetSpan(length)[..length]); - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The name. - /// - /// Is sent by the server when: When a spectator joins a duel. - /// Causes reaction on client side: The client updates the list of spectators. - /// - public static async ValueTask SendDuelSpectatorAddedAsync(this IConnection? connection, string @name) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelSpectatorAddedRef.Length; - var packet = new DuelSpectatorAddedRef(connection.Output.GetSpan(length)[..length]); - packet.Name = @name; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The name. - /// - /// Is sent by the server when: When a spectator joins a duel. - /// Causes reaction on client side: The client updates the list of spectators. - /// - public static async ValueTask SendDuelSpectatorRemovedAsync(this IConnection? connection, string @name) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelSpectatorRemovedRef.Length; - var packet = new DuelSpectatorRemovedRef(connection.Output.GetSpan(length)[..length]); - packet.Name = @name; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The winner. - /// The loser. - /// - /// Is sent by the server when: When the duel finished. - /// Causes reaction on client side: The client shows the winner and loser names. - /// - public static async ValueTask SendDuelFinishedAsync(this IConnection? connection, string @winner, string @loser) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DuelFinishedRef.Length; - var packet = new DuelFinishedRef(connection.Output.GetSpan(length)[..length]); - packet.Winner = @winner; - packet.Loser = @loser; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The object id. - /// The stage. - /// The skill number. - /// - /// Is sent by the server when: After a player started a skill which needs to load up, like Nova. - /// Causes reaction on client side: The client may show the loading intensity. - /// - public static async ValueTask SendSkillStageUpdateAsync(this IConnection? connection, ushort @objectId, byte @stage, byte @skillNumber = 0x28) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = SkillStageUpdateRef.Length; - var packet = new SkillStageUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.ObjectId = @objectId; - packet.SkillNumber = @skillNumber; - packet.Stage = @stage; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: The player requested to enter the illusion temple event. - /// Causes reaction on client side: The client shows the result. - /// - public static async ValueTask SendIllusionTempleEnterResultAsync(this IConnection? connection, byte @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = IllusionTempleEnterResultRef.Length; - var packet = new IllusionTempleEnterResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// The skill number. - /// The source object id. - /// The target object id. - /// - /// Is sent by the server when: A player requested to use a specific skill in the illusion temple event. - /// Causes reaction on client side: The client shows the result. - /// - public static async ValueTask SendIllusionTempleSkillUsageResultAsync(this IConnection? connection, byte @result, ushort @skillNumber, ushort @sourceObjectId, ushort @targetObjectId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = IllusionTempleSkillUsageResultRef.Length; - var packet = new IllusionTempleSkillUsageResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - packet.SkillNumber = @skillNumber; - packet.SourceObjectId = @sourceObjectId; - packet.TargetObjectId = @targetObjectId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The user count 1. - /// The user count 2. - /// The user count 3. - /// The user count 4. - /// The user count 5. - /// The user count 6. - /// - /// Is sent by the server when: ? - /// Causes reaction on client side: The client shows the counts. - /// - public static async ValueTask SendIllusionTempleUserCountAsync(this IConnection? connection, byte @userCount1, byte @userCount2, byte @userCount3, byte @userCount4, byte @userCount5, byte @userCount6) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = IllusionTempleUserCountRef.Length; - var packet = new IllusionTempleUserCountRef(connection.Output.GetSpan(length)[..length]); - packet.UserCount1 = @userCount1; - packet.UserCount2 = @userCount2; - packet.UserCount3 = @userCount3; - packet.UserCount4 = @userCount4; - packet.UserCount5 = @userCount5; - packet.UserCount6 = @userCount6; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill points. - /// - /// Is sent by the server when: ? - /// Causes reaction on client side: The client shows the skill points. - /// - public static async ValueTask SendIllusionTempleSkillPointUpdateAsync(this IConnection? connection, byte @skillPoints) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = IllusionTempleSkillPointUpdateRef.Length; - var packet = new IllusionTempleSkillPointUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.SkillPoints = @skillPoints; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The skill number. - /// The object index. - /// - /// Is sent by the server when: ? - /// Causes reaction on client side: The client shows the skill points. - /// - public static async ValueTask SendIllusionTempleSkillEndedAsync(this IConnection? connection, ushort @skillNumber, ushort @objectIndex) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = IllusionTempleSkillEndedRef.Length; - var packet = new IllusionTempleSkillEndedRef(connection.Output.GetSpan(length)[..length]); - packet.SkillNumber = @skillNumber; - packet.ObjectIndex = @objectIndex; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The user index. - /// The name. - /// - /// Is sent by the server when: ? - /// Causes reaction on client side: ?. - /// - public static async ValueTask SendIllusionTempleHolyItemRelicsAsync(this IConnection? connection, ushort @userIndex, string @name) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = IllusionTempleHolyItemRelicsRef.Length; - var packet = new IllusionTempleHolyItemRelicsRef(connection.Output.GetSpan(length)[..length]); - packet.UserIndex = @userIndex; - packet.Name = @name; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The temple number. - /// The state. - /// - /// Is sent by the server when: ? - /// Causes reaction on client side: The client shows the skill points. - /// - public static async ValueTask SendIllusionTempleSkillEndAsync(this IConnection? connection, byte @templeNumber, byte @state) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = IllusionTempleSkillEndRef.Length; - var packet = new IllusionTempleSkillEndRef(connection.Output.GetSpan(length)[..length]); - packet.TempleNumber = @templeNumber; - packet.State = @state; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The flag, if money should be consumed. If this is 'true', setting PauseStatus to 'false' doesn't cause starting the helper. - /// The money. - /// The pause status. A value of 'true' always works to stop the helper. However, it can only be started, with ConsumeMoney set to 'false'. - /// - /// Is sent by the server when: The server validated or changed the status of the MU Helper. - /// Causes reaction on client side: The client toggle the MU Helper status. - /// - public static async ValueTask SendMuHelperStatusUpdateAsync(this IConnection? connection, bool @consumeMoney, uint @money, bool @pauseStatus) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MuHelperStatusUpdateRef.Length; - var packet = new MuHelperStatusUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.ConsumeMoney = @consumeMoney; - packet.Money = @money; - packet.PauseStatus = @pauseStatus; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The helper data. - /// - /// Is sent by the server when: The server saved the users MU Helper data. - /// Causes reaction on client side: The user wants to save the MU Helper data. - /// - public static async ValueTask SendMuHelperConfigurationDataAsync(this IConnection? connection, Memory @helperData) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MuHelperConfigurationDataRef.Length; - var packet = new MuHelperConfigurationDataRef(connection.Output.GetSpan(length)[..length]); - @helperData.Span.CopyTo(packet.HelperData); - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The friend name. - /// The server id on which the player currently is online. 0xFF means offline. - /// - /// Is sent by the server when: After a friend has been added to the friend list. - /// Causes reaction on client side: The friend appears in the friend list. - /// - public static async ValueTask SendFriendAddedAsync(this IConnection? connection, string @friendName, byte @serverId = 0xFF) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = FriendAddedRef.Length; - var packet = new FriendAddedRef(connection.Output.GetSpan(length)[..length]); - packet.FriendName = @friendName; - packet.ServerId = @serverId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The requester. - /// - /// Is sent by the server when: After a player has requested to add another player as friend. This other player gets this message. - /// Causes reaction on client side: The friend request appears on the user interface. - /// - public static async ValueTask SendFriendRequestAsync(this IConnection? connection, string @requester) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = FriendRequestRef.Length; - var packet = new FriendRequestRef(connection.Output.GetSpan(length)[..length]); - packet.Requester = @requester; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The friend name. - /// - /// Is sent by the server when: After a friend has been removed from the friend list. - /// Causes reaction on client side: The friend is removed from the friend list. - /// - public static async ValueTask SendFriendDeletedAsync(this IConnection? connection, string @friendName) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = FriendDeletedRef.Length; - var packet = new FriendDeletedRef(connection.Output.GetSpan(length)[..length]); - packet.FriendName = @friendName; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The friend name. - /// The server id on which the player currently is online. 0xFF means offline. - /// - /// Is sent by the server when: After a friend has been added to the friend list. - /// Causes reaction on client side: The friend appears in the friend list. - /// - public static async ValueTask SendFriendOnlineStateUpdateAsync(this IConnection? connection, string @friendName, byte @serverId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = FriendOnlineStateUpdateRef.Length; - var packet = new FriendOnlineStateUpdateRef(connection.Output.GetSpan(length)[..length]); - packet.FriendName = @friendName; - packet.ServerId = @serverId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The letter id. - /// The result. - /// - /// Is sent by the server when: After the player requested to send a letter to another player. - /// Causes reaction on client side: Depending on the result, the letter send dialog closes or an error message appears. - /// - public static async ValueTask SendLetterSendResponseAsync(this IConnection? connection, uint @letterId, LetterSendResponse.LetterSendRequestResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = LetterSendResponseRef.Length; - var packet = new LetterSendResponseRef(connection.Output.GetSpan(length)[..length]); - packet.LetterId = @letterId; - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The letter index. - /// The sender name. - /// The timestamp. - /// The subject. - /// The state. - /// - /// Is sent by the server when: After a letter has been received or after the player entered the game with a character. - /// Causes reaction on client side: The letter appears in the letter list. - /// - public static async ValueTask SendAddLetterAsync(this IConnection? connection, ushort @letterIndex, string @senderName, string @timestamp, string @subject, AddLetter.LetterState @state) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = AddLetterRef.Length; - var packet = new AddLetterRef(connection.Output.GetSpan(length)[..length]); - packet.LetterIndex = @letterIndex; - packet.SenderName = @senderName; - packet.Timestamp = @timestamp; - packet.Subject = @subject; - packet.State = @state; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The letter index. - /// The message size. - /// The sender appearance. - /// The rotation. - /// The animation. - /// The message. - /// - /// Is sent by the server when: After the player requested to read a letter. - /// Causes reaction on client side: The letter is opened in a new dialog. - /// - public static async ValueTask SendOpenLetterAsync(this IConnection? connection, ushort @letterIndex, ushort @messageSize, Memory @senderAppearance, byte @rotation, byte @animation, string @message) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = OpenLetterRef.GetRequiredSize(message); - var packet = new OpenLetterRef(connection.Output.GetSpan(length)[..length]); - packet.LetterIndex = @letterIndex; - packet.MessageSize = @messageSize; - @senderAppearance.Span.CopyTo(packet.SenderAppearance); - packet.Rotation = @rotation; - packet.Animation = @animation; - packet.Message = @message; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The letter index. - /// The sender appearance. - /// The rotation. - /// The animation. - /// The message. - /// - /// Is sent by the server when: After the player requested to read a letter. - /// Causes reaction on client side: The letter is opened in a new dialog. - /// - public static async ValueTask SendOpenLetterExtendedAsync(this IConnection? connection, ushort @letterIndex, Memory @senderAppearance, byte @rotation, byte @animation, string @message) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = OpenLetterExtendedRef.GetRequiredSize(message); - var packet = new OpenLetterExtendedRef(connection.Output.GetSpan(length)[..length]); - packet.LetterIndex = @letterIndex; - @senderAppearance.Span.CopyTo(packet.SenderAppearance); - packet.Rotation = @rotation; - packet.Animation = @animation; - packet.Message = @message; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The letter index. - /// The request successful. - /// - /// Is sent by the server when: After a letter has been deleted by the request of the player. - /// Causes reaction on client side: The letter is removed from the letter list. - /// - public static async ValueTask SendRemoveLetterAsync(this IConnection? connection, ushort @letterIndex, bool @requestSuccessful = true) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = RemoveLetterRef.Length; - var packet = new RemoveLetterRef(connection.Output.GetSpan(length)[..length]); - packet.RequestSuccessful = @requestSuccessful; - packet.LetterIndex = @letterIndex; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The chat server ip. - /// The chat room id. - /// The authentication token. - /// The friend name. - /// The success. - /// The type. - /// - /// Is sent by the server when: The player is invited to join a chat room on the chat server. - /// Causes reaction on client side: The game client connects to the chat server and joins the chat room with the specified authentication data. - /// - public static async ValueTask SendChatRoomConnectionInfoAsync(this IConnection? connection, string @chatServerIp, ushort @chatRoomId, uint @authenticationToken, string @friendName, bool @success, byte @type = 1) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ChatRoomConnectionInfoRef.Length; - var packet = new ChatRoomConnectionInfoRef(connection.Output.GetSpan(length)[..length]); - packet.ChatServerIp = @chatServerIp; - packet.ChatRoomId = @chatRoomId; - packet.AuthenticationToken = @authenticationToken; - packet.Type = @type; - packet.FriendName = @friendName; - packet.Success = @success; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The request id. - /// - /// Is sent by the server when: The player requested to add another player to his friend list and the server processed this request. - /// Causes reaction on client side: The game client knows if the invitation could be sent to the other player. - /// - public static async ValueTask SendFriendInvitationResultAsync(this IConnection? connection, bool @success, uint @requestId) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = FriendInvitationResultRef.Length; - var packet = new FriendInvitationResultRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.RequestId = @requestId; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// A number specifying the description: A) when selecting a quest in the quest list, it's the "StartingNumber"; B) when a quest has been started it's the quest number; C) when the starting number has been sent previously and the player refused to start the quest, it sends a "RefuseNumber". - /// The quest group. - /// - /// Is sent by the server when: After the game client clicked on a quest in the quest list, proceeded with a quest or refused to start a quest. - /// Causes reaction on client side: The client shows the corresponding description about the current quest step. - /// - public static async ValueTask SendQuestStepInfoAsync(this IConnection? connection, ushort @questStepNumber, ushort @questGroup) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = QuestStepInfoRef.Length; - var packet = new QuestStepInfoRef(connection.Output.GetSpan(length)[..length]); - packet.QuestStepNumber = @questStepNumber; - packet.QuestGroup = @questGroup; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The quest number. - /// The quest group. - /// The is quest completed. - /// - /// Is sent by the server when: The server acknowledges the completion of a quest. - /// Causes reaction on client side: The client shows the success and possibly requests for the next available quests. - /// - public static async ValueTask SendQuestCompletionResponseAsync(this IConnection? connection, ushort @questNumber, ushort @questGroup, bool @isQuestCompleted) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = QuestCompletionResponseRef.Length; - var packet = new QuestCompletionResponseRef(connection.Output.GetSpan(length)[..length]); - packet.QuestNumber = @questNumber; - packet.QuestGroup = @questGroup; - packet.IsQuestCompleted = @isQuestCompleted; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The current quest number. In this message, it's always 0, because the group is relevant for the client. - /// The quest group. - /// - /// Is sent by the server when: The server acknowledges the requested cancellation of a quest. - /// Causes reaction on client side: The client resets the state of the quest and can request a new list of available quests again. This list would then probably contain the cancelled quest again. - /// - public static async ValueTask SendQuestCancelledAsync(this IConnection? connection, ushort @questNumber, ushort @questGroup) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = QuestCancelledRef.Length; - var packet = new QuestCancelledRef(connection.Output.GetSpan(length)[..length]); - packet.QuestNumber = @questNumber; - packet.QuestGroup = @questGroup; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The npc number. - /// The gens contribution points. - /// - /// Is sent by the server when: The server acknowledges the requested opening of an npc dialog. - /// Causes reaction on client side: The client opens the dialog of the specified npc. - /// - public static async ValueTask SendOpenNpcDialogAsync(this IConnection? connection, ushort @npcNumber, uint @gensContributionPoints) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = OpenNpcDialogRef.Length; - var packet = new OpenNpcDialogRef(connection.Output.GetSpan(length)[..length]); - packet.NpcNumber = @npcNumber; - packet.GensContributionPoints = @gensContributionPoints; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: The player requested to enter the devil square mini game through the Charon NPC. - /// Causes reaction on client side: In case it failed, it shows the corresponding error message. - /// - public static async ValueTask SendDevilSquareEnterResultAsync(this IConnection? connection, DevilSquareEnterResult.EnterResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = DevilSquareEnterResultRef.Length; - var packet = new DevilSquareEnterResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The game type. - /// The remaining entering time minutes. - /// The user count. - /// Just used for Chaos Castle. In this case, this field contains the lower byte of the remaining minutes. For other event types, this field is not used. - /// - /// Is sent by the server when: The player requests to get the current opening state of a mini game event, by clicking on an ticket item. - /// Causes reaction on client side: The opening state of the event (remaining entering time, etc.) is shown at the client. - /// - public static async ValueTask SendMiniGameOpeningStateAsync(this IConnection? connection, MiniGameType @gameType, byte @remainingEnteringTimeMinutes, byte @userCount, byte @remainingEnteringTimeMinutesLow) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MiniGameOpeningStateRef.Length; - var packet = new MiniGameOpeningStateRef(connection.Output.GetSpan(length)[..length]); - packet.GameType = @gameType; - packet.RemainingEnteringTimeMinutes = @remainingEnteringTimeMinutes; - packet.UserCount = @userCount; - packet.RemainingEnteringTimeMinutesLow = @remainingEnteringTimeMinutesLow; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The state. - /// - /// Is sent by the server when: The state of a mini game event is about to change in 30 seconds. - /// Causes reaction on client side: The client side shows a message about the changing state. - /// - public static async ValueTask SendUpdateMiniGameStateAsync(this IConnection? connection, UpdateMiniGameState.MiniGameTypeState @state) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = UpdateMiniGameStateRef.Length; - var packet = new UpdateMiniGameStateRef(connection.Output.GetSpan(length)[..length]); - packet.State = @state; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The success. - /// The player name. - /// The total score. - /// The bonus experience. - /// The bonus money. - /// The type. - /// - /// Is sent by the server when: The blood castle mini game ended and the score of the player is sent to the player. - /// Causes reaction on client side: The score is shown at the client. - /// - public static async ValueTask SendBloodCastleScoreAsync(this IConnection? connection, bool @success, string @playerName, uint @totalScore, uint @bonusExperience, uint @bonusMoney, byte @type = 0xFF) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = BloodCastleScoreRef.Length; - var packet = new BloodCastleScoreRef(connection.Output.GetSpan(length)[..length]); - packet.Success = @success; - packet.Type = @type; - packet.PlayerName = @playerName; - packet.TotalScore = @totalScore; - packet.BonusExperience = @bonusExperience; - packet.BonusMoney = @bonusMoney; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: The player requested to enter the blood castle mini game through the Archangel Messenger NPC. - /// Causes reaction on client side: In case it failed, it shows the corresponding error message. - /// - public static async ValueTask SendBloodCastleEnterResultAsync(this IConnection? connection, BloodCastleEnterResult.EnterResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = BloodCastleEnterResultRef.Length; - var packet = new BloodCastleEnterResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The state. - /// The remain second. - /// The max monster. - /// The cur monster. - /// The item owner id. - /// The item level. - /// - /// Is sent by the server when: The state of a blood castle event is about to change. - /// Causes reaction on client side: The client side shows a message about the changing state. - /// - public static async ValueTask SendBloodCastleStateAsync(this IConnection? connection, BloodCastleState.Status @state, ushort @remainSecond, ushort @maxMonster, ushort @curMonster, ushort @itemOwnerId, byte @itemLevel) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = BloodCastleStateRef.Length; - var packet = new BloodCastleStateRef(connection.Output.GetSpan(length)[..length]); - packet.State = @state; - packet.RemainSecond = @remainSecond; - packet.MaxMonster = @maxMonster; - packet.CurMonster = @curMonster; - packet.ItemOwnerId = @itemOwnerId; - packet.ItemLevel = @itemLevel; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The result. - /// - /// Is sent by the server when: The player requested to enter the chaos castle mini game by using the 'Armor of Guardsman' item. - /// Causes reaction on client side: In case it failed, it shows the corresponding error message. - /// - public static async ValueTask SendChaosCastleEnterResultAsync(this IConnection? connection, ChaosCastleEnterResult.EnterResult @result) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = ChaosCastleEnterResultRef.Length; - var packet = new ChaosCastleEnterResultRef(connection.Output.GetSpan(length)[..length]); - packet.Result = @result; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); - } - - /// - /// Sends a to this connection. - /// - /// The connection. - /// The enable. - /// The event. - /// - /// Is sent by the server when: The state of event is about to change. - /// Causes reaction on client side: The event's effect is shown. - /// - public static async ValueTask SendMapEventStateAsync(this IConnection? connection, bool @enable, MapEventState.Events @event) - { - if (connection is null) - { - return; - } - - int WritePacket() - { - var length = MapEventStateRef.Length; - var packet = new MapEventStateRef(connection.Output.GetSpan(length)[..length]); - packet.Enable = @enable; - packet.Event = @event; - - return packet.Header.Length; - } - - await connection.SendAsync(WritePacket).ConfigureAwait(false); +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +//------------------------------------------------------------------------------ +// +// This source code was auto-generated by an XSL transformation. +// Do not change this file. Instead, change the XML data which contains +// the packet definitions and re-run the transformation (rebuild this project). +// +//------------------------------------------------------------------------------ + +// ReSharper disable RedundantVerbatimPrefix +// ReSharper disable AssignmentIsFullyDiscarded +// ReSharper disable UnusedMember.Global +// ReSharper disable UseObjectOrCollectionInitializer + +#nullable enable +namespace MUnique.OpenMU.Network.Packets.ServerToClient; + +using System; +using System.Threading; +using MUnique.OpenMU.Network; + +/// +/// Extension methods to start writing messages of this namespace on a . +/// +public static class ConnectionExtensions +{ + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player id. + /// The version string. + /// The version. + /// The success. + /// + /// Is sent by the server when: After a game client has connected to the game. + /// Causes reaction on client side: It shows the login dialog. + /// + public static async ValueTask SendGameServerEnteredAsync(this IConnection? connection, ushort @playerId, string @versionString, Memory @version, bool @success = true) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GameServerEnteredRef.Length; + var packet = new GameServerEnteredRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.PlayerId = @playerId; + packet.VersionString = @versionString; + @version.Span.CopyTo(packet.Version); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The is active. + /// The player id. + /// The effect id. + /// + /// Is sent by the server when: A magic effect was added or removed to the own or another player. + /// Causes reaction on client side: The user interface updates itself. If it's the effect of the own player, it's shown as icon at the top of the interface. + /// + public static async ValueTask SendMagicEffectStatusAsync(this IConnection? connection, bool @isActive, ushort @playerId, byte @effectId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MagicEffectStatusRef.Length; + var packet = new MagicEffectStatusRef(connection.Output.GetSpan(length)[..length]); + packet.IsActive = @isActive; + packet.PlayerId = @playerId; + packet.EffectId = @effectId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// A random value between 0 and 2 (inclusive). + /// A random value between 0 and 9 (inclusive). + /// + /// Is sent by the server when: The weather on the current map has been changed or the player entered the map. + /// Causes reaction on client side: The game client updates the weather effects. + /// + public static async ValueTask SendWeatherStatusUpdateAsync(this IConnection? connection, byte @weather, byte @variation) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = WeatherStatusUpdateRef.Length; + var packet = new WeatherStatusUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.Weather = @weather; + packet.Variation = @variation; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The id. + /// The current position x. + /// The current position y. + /// The target position x. + /// The target position y. + /// The rotation. + /// The hero state. + /// The attack speed. + /// The magic speed. + /// The name. + /// The appearance and effects. + /// + /// Is sent by the server when: One or more character got into the observed scope of the player. + /// Causes reaction on client side: The client adds the character to the shown map. + /// + public static async ValueTask SendAddCharacterToScopeExtendedAsync(this IConnection? connection, ushort @id, byte @currentPositionX, byte @currentPositionY, byte @targetPositionX, byte @targetPositionY, byte @rotation, CharacterHeroState @heroState, ushort @attackSpeed, ushort @magicSpeed, string @name, Memory @appearanceAndEffects) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = AddCharacterToScopeExtendedRef.GetRequiredSize(appearanceAndEffects.Length); + var packet = new AddCharacterToScopeExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Id = @id; + packet.CurrentPositionX = @currentPositionX; + packet.CurrentPositionY = @currentPositionY; + packet.TargetPositionX = @targetPositionX; + packet.TargetPositionY = @targetPositionY; + packet.Rotation = @rotation; + packet.HeroState = @heroState; + packet.AttackSpeed = @attackSpeed; + packet.MagicSpeed = @magicSpeed; + packet.Name = @name; + @appearanceAndEffects.Span.CopyTo(packet.AppearanceAndEffects); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The killed id. + /// The skill id. + /// The killer id. + /// + /// Is sent by the server when: An observed object was killed. + /// Causes reaction on client side: The object is shown as dead. + /// + public static async ValueTask SendObjectGotKilledAsync(this IConnection? connection, ushort @killedId, ushort @skillId, ushort @killerId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectGotKilledRef.Length; + var packet = new ObjectGotKilledRef(connection.Output.GetSpan(length)[..length]); + packet.KilledId = @killedId; + packet.SkillId = @skillId; + packet.KillerId = @killerId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The object id. + /// The direction. + /// The animation. + /// The target id. + /// + /// Is sent by the server when: An object performs an animation. + /// Causes reaction on client side: The animation is shown for the specified object. + /// + public static async ValueTask SendObjectAnimationAsync(this IConnection? connection, ushort @objectId, byte @direction, byte @animation, ushort @targetId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectAnimationRef.Length; + var packet = new ObjectAnimationRef(connection.Output.GetSpan(length)[..length]); + packet.ObjectId = @objectId; + packet.Direction = @direction; + packet.Animation = @animation; + packet.TargetId = @targetId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The player id. + /// The point x. + /// The point y. + /// The rotation. + /// + /// Is sent by the server when: An object performs a skill which has effect on an area. + /// Causes reaction on client side: The animation is shown on the user interface. + /// + public static async ValueTask SendAreaSkillAnimationAsync(this IConnection? connection, ushort @skillId, ushort @playerId, byte @pointX, byte @pointY, byte @rotation) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = AreaSkillAnimationRef.Length; + var packet = new AreaSkillAnimationRef(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.PlayerId = @playerId; + packet.PointX = @pointX; + packet.PointY = @pointY; + packet.Rotation = @rotation; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The player id. + /// The target id. + /// + /// Is sent by the server when: An object performs a skill which is directly targeted to another object. + /// Causes reaction on client side: The animation is shown on the user interface. + /// + public static async ValueTask SendSkillAnimationAsync(this IConnection? connection, ushort @skillId, ushort @playerId, ushort @targetId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillAnimationRef.Length; + var packet = new SkillAnimationRef(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.PlayerId = @playerId; + packet.TargetId = @targetId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The player id. + /// The point x. + /// The point y. + /// The rotation. + /// + /// Is sent by the server when: An object performs a skill which has effect on an area. + /// Causes reaction on client side: The animation is shown on the user interface. + /// + public static async ValueTask SendAreaSkillAnimation075Async(this IConnection? connection, byte @skillId, ushort @playerId, byte @pointX, byte @pointY, byte @rotation) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = AreaSkillAnimation075Ref.Length; + var packet = new AreaSkillAnimation075Ref(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.PlayerId = @playerId; + packet.PointX = @pointX; + packet.PointY = @pointY; + packet.Rotation = @rotation; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The player id. + /// The point x. + /// The point y. + /// The rotation. + /// + /// Is sent by the server when: An object performs a skill which has effect on an area. + /// Causes reaction on client side: The animation is shown on the user interface. + /// + public static async ValueTask SendAreaSkillAnimation095Async(this IConnection? connection, byte @skillId, ushort @playerId, byte @pointX, byte @pointY, byte @rotation) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = AreaSkillAnimation095Ref.Length; + var packet = new AreaSkillAnimation095Ref(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.PlayerId = @playerId; + packet.PointX = @pointX; + packet.PointY = @pointY; + packet.Rotation = @rotation; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The player id. + /// The target id. + /// The effect applied. + /// + /// Is sent by the server when: An object performs a skill which is directly targeted to another object. + /// Causes reaction on client side: The animation is shown on the user interface. + /// + public static async ValueTask SendSkillAnimation075Async(this IConnection? connection, byte @skillId, ushort @playerId, ushort @targetId, bool @effectApplied) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillAnimation075Ref.Length; + var packet = new SkillAnimation075Ref(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.PlayerId = @playerId; + packet.TargetId = @targetId; + packet.EffectApplied = @effectApplied; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The player id. + /// The target id. + /// The effect applied. + /// + /// Is sent by the server when: An object performs a skill which is directly targeted to another object. + /// Causes reaction on client side: The animation is shown on the user interface. + /// + public static async ValueTask SendSkillAnimation095Async(this IConnection? connection, byte @skillId, ushort @playerId, ushort @targetId, bool @effectApplied) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillAnimation095Ref.Length; + var packet = new SkillAnimation095Ref(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.PlayerId = @playerId; + packet.TargetId = @targetId; + packet.EffectApplied = @effectApplied; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The target id. + /// + /// Is sent by the server when: A player cancelled a specific magic effect of a skill (Infinity Arrow, Wizardry Enhance), or an effect was removed due a timeout (Ice, Poison) or antidote. + /// Causes reaction on client side: The effect is removed from the target object. + /// + public static async ValueTask SendMagicEffectCancelledAsync(this IConnection? connection, ushort @skillId, ushort @targetId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MagicEffectCancelledRef.Length; + var packet = new MagicEffectCancelledRef(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.TargetId = @targetId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The target id. + /// + /// Is sent by the server when: A player cancelled a specific magic effect of a skill (Infinity Arrow, Wizardry Enhance), or an effect was removed due a timeout (Ice, Poison) or antidote. + /// Causes reaction on client side: The effect is removed from the target object. + /// + public static async ValueTask SendMagicEffectCancelled075Async(this IConnection? connection, byte @skillId, ushort @targetId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MagicEffectCancelled075Ref.Length; + var packet = new MagicEffectCancelled075Ref(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.TargetId = @targetId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill id. + /// The source id. + /// The target id. + /// + /// Is sent by the server when: A player (rage fighter) performs the dark side skill on a target and sent a RageAttackRangeRequest. + /// Causes reaction on client side: The targets are attacked with visual effects. + /// + public static async ValueTask SendRageAttackAsync(this IConnection? connection, ushort @skillId, ushort @sourceId, ushort @targetId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RageAttackRef.Length; + var packet = new RageAttackRef(connection.Output.GetSpan(length)[..length]); + packet.SkillId = @skillId; + packet.SourceId = @sourceId; + packet.TargetId = @targetId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The changed player id. + /// The item data. + /// + /// Is sent by the server when: The appearance of a player changed, all surrounding players are informed about it. + /// Causes reaction on client side: The appearance of the player is updated. + /// + public static async ValueTask SendAppearanceChangedAsync(this IConnection? connection, ushort @changedPlayerId, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = AppearanceChangedRef.GetRequiredSize(itemData.Length); + var packet = new AppearanceChangedRef(connection.Output.GetSpan(length)[..length]); + packet.ChangedPlayerId = @changedPlayerId; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The changed player id. + /// The item slot. + /// The item group. + /// The item number. + /// The item level. + /// The excellent flags. + /// The ancient discriminator. + /// The is ancient set complete. + /// + /// Is sent by the server when: The appearance of a player changed, all surrounding players are informed about it. + /// Causes reaction on client side: The appearance of the player is updated. + /// + public static async ValueTask SendAppearanceChangedExtendedAsync(this IConnection? connection, ushort @changedPlayerId, byte @itemSlot, byte @itemGroup, ushort @itemNumber, byte @itemLevel, byte @excellentFlags, byte @ancientDiscriminator, bool @isAncientSetComplete) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = AppearanceChangedExtendedRef.Length; + var packet = new AppearanceChangedExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.ChangedPlayerId = @changedPlayerId; + packet.ItemSlot = @itemSlot; + packet.ItemGroup = @itemGroup; + packet.ItemNumber = @itemNumber; + packet.ItemLevel = @itemLevel; + packet.ExcellentFlags = @excellentFlags; + packet.AncientDiscriminator = @ancientDiscriminator; + packet.IsAncientSetComplete = @isAncientSetComplete; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The object id. + /// The message. + /// + /// Is sent by the server when: The server wants to show a message above any kind of character, even NPCs. + /// Causes reaction on client side: The message is shown above the character. + /// + public static async ValueTask SendObjectMessageAsync(this IConnection? connection, ushort @objectId, string @message) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectMessageRef.GetRequiredSize(message); + var packet = new ObjectMessageRef(connection.Output.GetSpan(length)[..length]); + packet.ObjectId = @objectId; + packet.Message = @message; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The requester id. + /// + /// Is sent by the server when: Another player requests party from the receiver of this message. + /// Causes reaction on client side: The party request is shown. + /// + public static async ValueTask SendPartyRequestAsync(this IConnection? connection, ushort @requesterId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PartyRequestRef.Length; + var packet = new PartyRequestRef(connection.Output.GetSpan(length)[..length]); + packet.RequesterId = @requesterId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The index. + /// + /// Is sent by the server when: A party member got removed from a party in which the player is in. + /// Causes reaction on client side: The party member with the specified index is removed from the party list on the user interface. + /// + public static async ValueTask SendRemovePartyMemberAsync(this IConnection? connection, byte @index) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RemovePartyMemberRef.Length; + var packet = new RemovePartyMemberRef(connection.Output.GetSpan(length)[..length]); + packet.Index = @index; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// + /// Is sent by the server when: After the player requested to open his shop and this request was successful. + /// Causes reaction on client side: The own player shop is shown as open. + /// + public static async ValueTask SendPlayerShopOpenSuccessfulAsync(this IConnection? connection, bool @success = true) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayerShopOpenSuccessfulRef.Length; + var packet = new PlayerShopOpenSuccessfulRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The state. + /// + /// Is sent by the server when: After the trading partner checked or unchecked the trade accept button. + /// Causes reaction on client side: The game client updates the trade button state accordingly. + /// + public static async ValueTask SendTradeButtonStateChangedAsync(this IConnection? connection, TradeButtonStateChanged.TradeButtonState @state) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeButtonStateChangedRef.Length; + var packet = new TradeButtonStateChangedRef(connection.Output.GetSpan(length)[..length]); + packet.State = @state; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: The trade money has been set by a previous request of the player. + /// Causes reaction on client side: The money which was set into the trade by the player is updated on the UI. + /// + public static async ValueTask SendTradeMoneySetResponseAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeMoneySetResponseRef.Length; + var packet = new TradeMoneySetResponseRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The money amount. + /// + /// Is sent by the server when: This message is sent when the trading partner put a certain amount of money (also 0) into the trade. + /// Causes reaction on client side: It overrides all previous sent money values. + /// + public static async ValueTask SendTradeMoneyUpdateAsync(this IConnection? connection, uint @moneyAmount) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeMoneyUpdateRef.Length; + var packet = new TradeMoneyUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.MoneyAmount = @moneyAmount; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The accepted. + /// The name. + /// The trade partner level. + /// The guild id. + /// + /// Is sent by the server when: The player which receives this message, sent a trade request to another player. This message is sent when the other player responded to this request. + /// Causes reaction on client side: If the trade was accepted, a trade dialog is opened. Otherwise, a message is shown. + /// + public static async ValueTask SendTradeRequestAnswerAsync(this IConnection? connection, bool @accepted, string @name, ushort @tradePartnerLevel, uint @guildId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeRequestAnswerRef.Length; + var packet = new TradeRequestAnswerRef(connection.Output.GetSpan(length)[..length]); + packet.Accepted = @accepted; + packet.Name = @name; + packet.TradePartnerLevel = @tradePartnerLevel; + packet.GuildId = @guildId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The name. + /// + /// Is sent by the server when: A trade was requested by another player. + /// Causes reaction on client side: A trade request dialog is shown. + /// + public static async ValueTask SendTradeRequestAsync(this IConnection? connection, string @name) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeRequestRef.Length; + var packet = new TradeRequestRef(connection.Output.GetSpan(length)[..length]); + packet.Name = @name; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: A trade was finished. + /// Causes reaction on client side: The trade dialog is closed. Depending on the result, a message is shown. + /// + public static async ValueTask SendTradeFinishedAsync(this IConnection? connection, TradeFinished.TradeResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeFinishedRef.Length; + var packet = new TradeFinishedRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The to slot. + /// The item data. + /// + /// Is sent by the server when: The trading partner added an item to the trade. + /// Causes reaction on client side: The item is added in the trade dialog. + /// + public static async ValueTask SendTradeItemAddedAsync(this IConnection? connection, byte @toSlot, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeItemAddedRef.GetRequiredSize(itemData.Length); + var packet = new TradeItemAddedRef(connection.Output.GetSpan(length)[..length]); + packet.ToSlot = @toSlot; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The slot. + /// + /// Is sent by the server when: The trading partner removed an item from the trade. + /// Causes reaction on client side: The item is removed from the trade dialog. + /// + public static async ValueTask SendTradeItemRemovedAsync(this IConnection? connection, byte @slot) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = TradeItemRemovedRef.Length; + var packet = new TradeItemRemovedRef(connection.Output.GetSpan(length)[..length]); + packet.Slot = @slot; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// + /// Is sent by the server when: After the login request has been processed by the server. + /// Causes reaction on client side: Shows the result. When it was successful, the client proceeds by sending a character list request. + /// + public static async ValueTask SendLoginResponseAsync(this IConnection? connection, LoginResponse.LoginResult @success) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = LoginResponseRef.Length; + var packet = new LoginResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The type. + /// + /// Is sent by the server when: After the logout request has been processed by the server. + /// Causes reaction on client side: Depending on the result, the game client closes the game or changes to another selection screen. + /// + public static async ValueTask SendLogoutResponseAsync(this IConnection? connection, LogOutType @type) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = LogoutResponseRef.Length; + var packet = new LogoutResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Type = @type; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The type. + /// The sender. + /// The message. + /// + /// Is sent by the server when: A player sends a chat message. + /// Causes reaction on client side: The message is shown in the chat box and above the character of the sender. + /// + public static async ValueTask SendChatMessageAsync(this IConnection? connection, ChatMessage.ChatMessageType @type, string @sender, string @message) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ChatMessageRef.GetRequiredSize(message); + var packet = new ChatMessageRef(connection.Output.GetSpan(length)[..length]); + packet.Type = @type; + packet.Sender = @sender; + packet.Message = @message; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The header code. + /// The object id. + /// The health damage. + /// The kind. + /// The is rage fighter streak hit. + /// The is rage fighter streak final hit. + /// The is double damage. + /// The is triple damage. + /// The shield damage. + /// + /// Is sent by the server when: An object got hit in two cases: 1. When the own player is hit; 2. When the own player attacked some other object which got hit. + /// Causes reaction on client side: The damage is shown at the object which received the hit. + /// + public static async ValueTask SendObjectHitAsync(this IConnection? connection, byte @headerCode, ushort @objectId, ushort @healthDamage, DamageKind @kind, bool @isRageFighterStreakHit, bool @isRageFighterStreakFinalHit, bool @isDoubleDamage, bool @isTripleDamage, ushort @shieldDamage) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectHitRef.Length; + var packet = new ObjectHitRef(connection.Output.GetSpan(length)[..length]); + packet.HeaderCode = @headerCode; + packet.ObjectId = @objectId; + packet.HealthDamage = @healthDamage; + packet.Kind = @kind; + packet.IsRageFighterStreakHit = @isRageFighterStreakHit; + packet.IsRageFighterStreakFinalHit = @isRageFighterStreakFinalHit; + packet.IsDoubleDamage = @isDoubleDamage; + packet.IsTripleDamage = @isTripleDamage; + packet.ShieldDamage = @shieldDamage; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The kind. + /// The is rage fighter streak hit. + /// The is rage fighter streak final hit. + /// The is double damage. + /// The is triple damage. + /// The object id. + /// Gets or sets the status of the remaining health in fractions of 1/250. + /// Gets or sets the status of the remaining shield in fractions of 1/250. + /// The health damage. + /// The shield damage. + /// + /// Is sent by the server when: An object got hit in two cases: 1. When the own player is hit; 2. When the own player attacked some other object which got hit. + /// Causes reaction on client side: The damage is shown at the object which received the hit. + /// + public static async ValueTask SendObjectHitExtendedAsync(this IConnection? connection, DamageKind @kind, bool @isRageFighterStreakHit, bool @isRageFighterStreakFinalHit, bool @isDoubleDamage, bool @isTripleDamage, ushort @objectId, byte @healthStatus, byte @shieldStatus, uint @healthDamage, uint @shieldDamage) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectHitExtendedRef.Length; + var packet = new ObjectHitExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Kind = @kind; + packet.IsRageFighterStreakHit = @isRageFighterStreakHit; + packet.IsRageFighterStreakFinalHit = @isRageFighterStreakFinalHit; + packet.IsDoubleDamage = @isDoubleDamage; + packet.IsTripleDamage = @isTripleDamage; + packet.ObjectId = @objectId; + packet.HealthStatus = @healthStatus; + packet.ShieldStatus = @shieldStatus; + packet.HealthDamage = @healthDamage; + packet.ShieldDamage = @shieldDamage; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The header code. + /// The object id. + /// The position x. + /// The position y. + /// + /// Is sent by the server when: An object in the observed scope (including the own player) moved instantly. + /// Causes reaction on client side: The position of the object is updated on client side. + /// + public static async ValueTask SendObjectMovedAsync(this IConnection? connection, byte @headerCode, ushort @objectId, byte @positionX, byte @positionY) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectMovedRef.Length; + var packet = new ObjectMovedRef(connection.Output.GetSpan(length)[..length]); + packet.HeaderCode = @headerCode; + packet.ObjectId = @objectId; + packet.PositionX = @positionX; + packet.PositionY = @positionY; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The header code. + /// The object id. + /// The target x. + /// The target y. + /// The target rotation. + /// The step count. + /// The step data. + /// + /// Is sent by the server when: An object in the observed scope (including the own player) walked to another position. + /// Causes reaction on client side: The object is animated to walk to the new position. + /// + public static async ValueTask SendObjectWalkedAsync(this IConnection? connection, byte @headerCode, ushort @objectId, byte @targetX, byte @targetY, byte @targetRotation, byte @stepCount, Memory @stepData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectWalkedRef.GetRequiredSize(stepData.Length); + var packet = new ObjectWalkedRef(connection.Output.GetSpan(length)[..length]); + packet.HeaderCode = @headerCode; + packet.ObjectId = @objectId; + packet.TargetX = @targetX; + packet.TargetY = @targetY; + packet.TargetRotation = @targetRotation; + packet.StepCount = @stepCount; + @stepData.Span.CopyTo(packet.StepData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The header code. + /// The object id. + /// The source x. + /// The source y. + /// The target x. + /// The target y. + /// The target rotation. + /// The step count. + /// The step data. + /// + /// Is sent by the server when: An object in the observed scope (including the own player) walked to another position. + /// Causes reaction on client side: The object is animated to walk to the new position. + /// + public static async ValueTask SendObjectWalkedExtendedAsync(this IConnection? connection, byte @headerCode, ushort @objectId, byte @sourceX, byte @sourceY, byte @targetX, byte @targetY, byte @targetRotation, byte @stepCount, Memory @stepData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectWalkedExtendedRef.GetRequiredSize(stepData.Length); + var packet = new ObjectWalkedExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.HeaderCode = @headerCode; + packet.ObjectId = @objectId; + packet.SourceX = @sourceX; + packet.SourceY = @sourceY; + packet.TargetX = @targetX; + packet.TargetY = @targetY; + packet.TargetRotation = @targetRotation; + packet.StepCount = @stepCount; + @stepData.Span.CopyTo(packet.StepData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The object id. + /// The target x. + /// The target y. + /// The target rotation. + /// + /// Is sent by the server when: An object in the observed scope (including the own player) walked to another position. + /// Causes reaction on client side: The object is animated to walk to the new position. + /// + public static async ValueTask SendObjectWalked075Async(this IConnection? connection, ushort @objectId, byte @targetX, byte @targetY, byte @targetRotation) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ObjectWalked075Ref.Length; + var packet = new ObjectWalked075Ref(connection.Output.GetSpan(length)[..length]); + packet.ObjectId = @objectId; + packet.TargetX = @targetX; + packet.TargetY = @targetY; + packet.TargetRotation = @targetRotation; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The killed object id. + /// The added experience. + /// The damage of last hit. + /// + /// Is sent by the server when: A player gained experience. + /// Causes reaction on client side: The experience is added to the experience counter and bar. + /// + public static async ValueTask SendExperienceGainedAsync(this IConnection? connection, ushort @killedObjectId, ushort @addedExperience, ushort @damageOfLastHit) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ExperienceGainedRef.Length; + var packet = new ExperienceGainedRef(connection.Output.GetSpan(length)[..length]); + packet.KilledObjectId = @killedObjectId; + packet.AddedExperience = @addedExperience; + packet.DamageOfLastHit = @damageOfLastHit; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The type. + /// The added experience. + /// The damage of last hit. + /// The killed object id. + /// The killer object id. + /// + /// Is sent by the server when: A player gained experience. + /// Causes reaction on client side: The experience is added to the experience counter and bar. + /// + public static async ValueTask SendExperienceGainedExtendedAsync(this IConnection? connection, ExperienceGainedExtended.AddResult @type, uint @addedExperience, uint @damageOfLastHit, ushort @killedObjectId, ushort @killerObjectId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ExperienceGainedExtendedRef.Length; + var packet = new ExperienceGainedExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Type = @type; + packet.AddedExperience = @addedExperience; + packet.DamageOfLastHit = @damageOfLastHit; + packet.KilledObjectId = @killedObjectId; + packet.KillerObjectId = @killerObjectId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The map number. + /// The position x. + /// The position y. + /// The rotation. + /// If false, it shows the teleport animation (white bubbles), and the client doesn't remove all of the objects in its scope. + /// + /// Is sent by the server when: The map was changed on the server side. + /// Causes reaction on client side: The game client changes to the specified map and coordinates. + /// + public static async ValueTask SendMapChangedAsync(this IConnection? connection, ushort @mapNumber, byte @positionX, byte @positionY, byte @rotation, bool @isMapChange = true) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MapChangedRef.Length; + var packet = new MapChangedRef(connection.Output.GetSpan(length)[..length]); + packet.IsMapChange = @isMapChange; + packet.MapNumber = @mapNumber; + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.Rotation = @rotation; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The map number. + /// The position x. + /// The position y. + /// The rotation. + /// If false, it shows the teleport animation (white bubbles), and the client doesn't remove all of the objects in its scope. + /// + /// Is sent by the server when: The map was changed on the server side. + /// Causes reaction on client side: The game client changes to the specified map and coordinates. + /// + public static async ValueTask SendMapChanged075Async(this IConnection? connection, byte @mapNumber, byte @positionX, byte @positionY, byte @rotation, bool @isMapChange = true) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MapChanged075Ref.Length; + var packet = new MapChanged075Ref(connection.Output.GetSpan(length)[..length]); + packet.IsMapChange = @isMapChange; + packet.MapNumber = @mapNumber; + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.Rotation = @rotation; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The binary data of the key configuration + /// + /// Is sent by the server when: When entering the game world with a character. + /// Causes reaction on client side: The client restores this configuration in its user interface. + /// + public static async ValueTask SendApplyKeyConfigurationAsync(this IConnection? connection, Memory @configuration) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ApplyKeyConfigurationRef.GetRequiredSize(configuration.Length); + var packet = new ApplyKeyConfigurationRef(connection.Output.GetSpan(length)[..length]); + @configuration.Span.CopyTo(packet.Configuration); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The id. + /// If this flag is set, the money is added to the map with an animation and sound. Otherwise it's just added like it was already on the ground before. + /// The position x. + /// The position y. + /// The amount. + /// The item count. + /// The money group. + /// The money number. + /// + /// Is sent by the server when: Money dropped on the ground. + /// Causes reaction on client side: The client adds the money to the ground. + /// + public static async ValueTask SendMoneyDroppedAsync(this IConnection? connection, ushort @id, bool @isFreshDrop, byte @positionX, byte @positionY, uint @amount, byte @itemCount = 1, byte @moneyGroup = 14, byte @moneyNumber = 15) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MoneyDroppedRef.Length; + var packet = new MoneyDroppedRef(connection.Output.GetSpan(length)[..length]); + packet.ItemCount = @itemCount; + packet.Id = @id; + packet.IsFreshDrop = @isFreshDrop; + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.MoneyNumber = @moneyNumber; + packet.Amount = @amount; + packet.MoneyGroup = @moneyGroup; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// If this flag is set, the money is added to the map with an animation and sound. Otherwise, it's just added like it was already on the ground before. + /// The id. + /// The position x. + /// The position y. + /// The amount. + /// + /// Is sent by the server when: Money dropped on the ground. + /// Causes reaction on client side: The client adds the money to the ground. + /// + public static async ValueTask SendMoneyDroppedExtendedAsync(this IConnection? connection, bool @isFreshDrop, ushort @id, byte @positionX, byte @positionY, uint @amount) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MoneyDroppedExtendedRef.Length; + var packet = new MoneyDroppedExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.IsFreshDrop = @isFreshDrop; + packet.Id = @id; + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.Amount = @amount; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The id. + /// If this flag is set, the money is added to the map with an animation and sound. Otherwise it's just added like it was already on the ground before. + /// The position x. + /// The position y. + /// The amount. + /// The item count. + /// The money group. + /// The money number. + /// + /// Is sent by the server when: Money dropped on the ground. + /// Causes reaction on client side: The client adds the money to the ground. + /// + public static async ValueTask SendMoneyDropped075Async(this IConnection? connection, ushort @id, bool @isFreshDrop, byte @positionX, byte @positionY, uint @amount, byte @itemCount = 1, byte @moneyGroup = 14, byte @moneyNumber = 15) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MoneyDropped075Ref.Length; + var packet = new MoneyDropped075Ref(connection.Output.GetSpan(length)[..length]); + packet.ItemCount = @itemCount; + packet.Id = @id; + packet.IsFreshDrop = @isFreshDrop; + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.MoneyNumber = @moneyNumber; + packet.MoneyGroup = @moneyGroup; + packet.Amount = @amount; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The inventory slot. + /// The item data. + /// + /// Is sent by the server when: A new item was added to the inventory. + /// Causes reaction on client side: The client adds the item to the inventory user interface. + /// + public static async ValueTask SendItemAddedToInventoryAsync(this IConnection? connection, byte @inventorySlot, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemAddedToInventoryRef.GetRequiredSize(itemData.Length); + var packet = new ItemAddedToInventoryRef(connection.Output.GetSpan(length)[..length]); + packet.InventorySlot = @inventorySlot; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The inventory slot. + /// + /// Is sent by the server when: The player requested to drop an item of his inventory. This message is the response about the success of the request. + /// Causes reaction on client side: If successful, the client removes the item from the inventory user interface. + /// + public static async ValueTask SendItemDropResponseAsync(this IConnection? connection, bool @success, byte @inventorySlot) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemDropResponseRef.Length; + var packet = new ItemDropResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.InventorySlot = @inventorySlot; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The fail reason. + /// + /// Is sent by the server when: The player requested to pick up an item from to ground to add it to his inventory, but it failed. + /// Causes reaction on client side: Depending on the reason, the game client shows a message. + /// + public static async ValueTask SendItemPickUpRequestFailedAsync(this IConnection? connection, ItemPickUpRequestFailed.ItemPickUpFailReason @failReason) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemPickUpRequestFailedRef.Length; + var packet = new ItemPickUpRequestFailedRef(connection.Output.GetSpan(length)[..length]); + packet.FailReason = @failReason; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The money. + /// + /// Is sent by the server when: The players money amount of the inventory has been changed and needs an update. + /// Causes reaction on client side: The money is updated in the inventory user interface. + /// + public static async ValueTask SendInventoryMoneyUpdateAsync(this IConnection? connection, uint @money) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = InventoryMoneyUpdateRef.Length; + var packet = new InventoryMoneyUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.Money = @money; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The target storage type. + /// The target slot. + /// The item data. + /// + /// Is sent by the server when: An item in the inventory or vault of the player has been moved. + /// Causes reaction on client side: The client updates the position of item in the user interface. + /// + public static async ValueTask SendItemMovedAsync(this IConnection? connection, ItemStorageKind @targetStorageType, byte @targetSlot, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemMovedRef.GetRequiredSize(itemData.Length); + var packet = new ItemMovedRef(connection.Output.GetSpan(length)[..length]); + packet.TargetStorageType = @targetStorageType; + packet.TargetSlot = @targetSlot; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The item data. + /// + /// Is sent by the server when: An item in the inventory or vault of the player could not be moved as requested by the player. + /// Causes reaction on client side: The client restores the position of item in the user interface. + /// + public static async ValueTask SendItemMoveRequestFailedAsync(this IConnection? connection, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemMoveRequestFailedRef.GetRequiredSize(itemData.Length); + var packet = new ItemMoveRequestFailedRef(connection.Output.GetSpan(length)[..length]); + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health. + /// The shield. + /// + /// Is sent by the server when: Periodically, or if the current health or shield changed on the server side, e.g. by hits. + /// Causes reaction on client side: The health and shield bar is updated on the game client user interface. + /// + public static async ValueTask SendCurrentHealthAndShieldAsync(this IConnection? connection, ushort @health, ushort @shield) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CurrentHealthAndShieldRef.Length; + var packet = new CurrentHealthAndShieldRef(connection.Output.GetSpan(length)[..length]); + packet.Health = @health; + packet.Shield = @shield; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health. + /// The shield. + /// + /// Is sent by the server when: When the maximum health changed, e.g. by adding stat points or changed items. + /// Causes reaction on client side: The health and shield bar is updated on the game client user interface. + /// + public static async ValueTask SendMaximumHealthAndShieldAsync(this IConnection? connection, ushort @health, ushort @shield) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MaximumHealthAndShieldRef.Length; + var packet = new MaximumHealthAndShieldRef(connection.Output.GetSpan(length)[..length]); + packet.Health = @health; + packet.Shield = @shield; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health. + /// The shield. + /// The mana. + /// The ability. + /// The attack speed. + /// The magic speed. + /// + /// Is sent by the server when: Periodically, or if the current stats, like health, shield, mana or attack speed changed on the server side, e.g. by hits. + /// Causes reaction on client side: The values are updated on the game client user interface. + /// + public static async ValueTask SendCurrentStatsExtendedAsync(this IConnection? connection, uint @health, uint @shield, uint @mana, uint @ability, ushort @attackSpeed, ushort @magicSpeed) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CurrentStatsExtendedRef.Length; + var packet = new CurrentStatsExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Health = @health; + packet.Shield = @shield; + packet.Mana = @mana; + packet.Ability = @ability; + packet.AttackSpeed = @attackSpeed; + packet.MagicSpeed = @magicSpeed; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health. + /// The shield. + /// The mana. + /// The ability. + /// + /// Is sent by the server when: When the maximum stats, like health, shield, mana or attack speed changed on the server side, e.g. by adding stat points or changed items. + /// Causes reaction on client side: The values are updated on the game client user interface. + /// + public static async ValueTask SendMaximumStatsExtendedAsync(this IConnection? connection, uint @health, uint @shield, uint @mana, uint @ability) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MaximumStatsExtendedRef.Length; + var packet = new MaximumStatsExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Health = @health; + packet.Shield = @shield; + packet.Mana = @mana; + packet.Ability = @ability; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health. + /// The shield. + /// + /// Is sent by the server when: When the consumption of an item failed. + /// Causes reaction on client side: The game client gets a feedback about a failed consumption, and allows for do further consumption requests. + /// + public static async ValueTask SendItemConsumptionFailedAsync(this IConnection? connection, ushort @health, ushort @shield) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemConsumptionFailedRef.Length; + var packet = new ItemConsumptionFailedRef(connection.Output.GetSpan(length)[..length]); + packet.Health = @health; + packet.Shield = @shield; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health. + /// The shield. + /// + /// Is sent by the server when: When the consumption of an item failed. + /// Causes reaction on client side: The game client gets a feedback about a failed consumption, and allows for do further consumption requests. + /// + public static async ValueTask SendItemConsumptionFailedExtendedAsync(this IConnection? connection, uint @health, uint @shield) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemConsumptionFailedExtendedRef.Length; + var packet = new ItemConsumptionFailedExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Health = @health; + packet.Shield = @shield; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The strength. + /// The agility. + /// The vitality. + /// The energy. + /// The command. + /// + /// Is sent by the server when: Setting the base stats of a character, e.g. set stats command or after a reset. + /// Causes reaction on client side: The values are updated on the game client user interface. + /// + public static async ValueTask SendBaseStatsExtendedAsync(this IConnection? connection, uint @strength, uint @agility, uint @vitality, uint @energy, uint @command) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = BaseStatsExtendedRef.Length; + var packet = new BaseStatsExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Strength = @strength; + packet.Agility = @agility; + packet.Vitality = @vitality; + packet.Energy = @energy; + packet.Command = @command; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The mana. + /// The ability. + /// + /// Is sent by the server when: The currently available mana or ability has changed, e.g. by using a skill. + /// Causes reaction on client side: The mana and ability bar is updated on the game client user interface. + /// + public static async ValueTask SendCurrentManaAndAbilityAsync(this IConnection? connection, ushort @mana, ushort @ability) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CurrentManaAndAbilityRef.Length; + var packet = new CurrentManaAndAbilityRef(connection.Output.GetSpan(length)[..length]); + packet.Mana = @mana; + packet.Ability = @ability; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The mana. + /// The ability. + /// + /// Is sent by the server when: The maximum available mana or ability has changed, e.g. by adding stat points. + /// Causes reaction on client side: The mana and ability bar is updated on the game client user interface. + /// + public static async ValueTask SendMaximumManaAndAbilityAsync(this IConnection? connection, ushort @mana, ushort @ability) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MaximumManaAndAbilityRef.Length; + var packet = new MaximumManaAndAbilityRef(connection.Output.GetSpan(length)[..length]); + packet.Mana = @mana; + packet.Ability = @ability; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The affected slot of the item in the inventory. + /// The true flag. + /// + /// Is sent by the server when: The item has been removed from the inventory of the player. + /// Causes reaction on client side: The client removes the item in the inventory user interface. + /// + public static async ValueTask SendItemRemovedAsync(this IConnection? connection, byte @inventorySlot, byte @trueFlag = 1) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemRemovedRef.Length; + var packet = new ItemRemovedRef(connection.Output.GetSpan(length)[..length]); + packet.InventorySlot = @inventorySlot; + packet.TrueFlag = @trueFlag; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The item type. + /// The effect time in seconds. + /// + /// Is sent by the server when: The client requested to consume a special item, e.g. a bottle of Ale. + /// Causes reaction on client side: The player is shown in a red color and has increased attack speed. + /// + public static async ValueTask SendConsumeItemWithEffectAsync(this IConnection? connection, ConsumeItemWithEffect.ConsumedItemType @itemType, ushort @effectTimeInSeconds) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ConsumeItemWithEffectRef.Length; + var packet = new ConsumeItemWithEffectRef(connection.Output.GetSpan(length)[..length]); + packet.ItemType = @itemType; + packet.EffectTimeInSeconds = @effectTimeInSeconds; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The inventory slot. + /// The durability. + /// true, if the change resulted from an item consumption; otherwise, false + /// + /// Is sent by the server when: The durability of an item in the inventory of the player has been changed. + /// Causes reaction on client side: The client updates the item in the inventory user interface. + /// + public static async ValueTask SendItemDurabilityChangedAsync(this IConnection? connection, byte @inventorySlot, byte @durability, bool @byConsumption) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemDurabilityChangedRef.Length; + var packet = new ItemDurabilityChangedRef(connection.Output.GetSpan(length)[..length]); + packet.InventorySlot = @inventorySlot; + packet.Durability = @durability; + packet.ByConsumption = @byConsumption; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The stat points. + /// The stat type. + /// + /// Is sent by the server when: The player requested to consume a fruit. + /// Causes reaction on client side: The client updates the user interface, by changing the added stat points and used fruit points. + /// + public static async ValueTask SendFruitConsumptionResponseAsync(this IConnection? connection, FruitConsumptionResponse.FruitConsumptionResult @result, ushort @statPoints, FruitConsumptionResponse.FruitStatType @statType) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = FruitConsumptionResponseRef.Length; + var packet = new FruitConsumptionResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.StatPoints = @statPoints; + packet.StatType = @statType; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The origin. + /// The type. + /// The action. + /// The remaining seconds. + /// The magic effect number. + /// + /// Is sent by the server when: The player requested to consume an item which gives a magic effect. + /// Causes reaction on client side: The client updates the user interface, it shows the remaining time at the effect icon. + /// + public static async ValueTask SendEffectItemConsumptionAsync(this IConnection? connection, EffectItemConsumption.EffectOrigin @origin, EffectItemConsumption.EffectType @type, EffectItemConsumption.EffectAction @action, uint @remainingSeconds, byte @magicEffectNumber) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = EffectItemConsumptionRef.Length; + var packet = new EffectItemConsumptionRef(connection.Output.GetSpan(length)[..length]); + packet.Origin = @origin; + packet.Type = @type; + packet.Action = @action; + packet.RemainingSeconds = @remainingSeconds; + packet.MagicEffectNumber = @magicEffectNumber; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The window. + /// + /// Is sent by the server when: After the client talked to an NPC which should cause a dialog to open on the client side. + /// Causes reaction on client side: The client opens the specified dialog. + /// + public static async ValueTask SendNpcWindowResponseAsync(this IConnection? connection, NpcWindowResponse.NpcWindow @window) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = NpcWindowResponseRef.Length; + var packet = new NpcWindowResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Window = @window; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: The request of buying an item from a NPC failed. + /// Causes reaction on client side: The client is responsive again. Without this message, it may stuck. + /// + public static async ValueTask SendNpcItemBuyFailedAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = NpcItemBuyFailedRef.Length; + var packet = new NpcItemBuyFailedRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The inventory slot. + /// The item data. + /// + /// Is sent by the server when: The request of buying an item from a player or npc was successful. + /// Causes reaction on client side: The bought item is added to the inventory. + /// + public static async ValueTask SendItemBoughtAsync(this IConnection? connection, byte @inventorySlot, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemBoughtRef.GetRequiredSize(itemData.Length); + var packet = new ItemBoughtRef(connection.Output.GetSpan(length)[..length]); + packet.InventorySlot = @inventorySlot; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The money. + /// + /// Is sent by the server when: The result of a previous item sell request. + /// Causes reaction on client side: The amount of specified money is set at the players inventory. + /// + public static async ValueTask SendNpcItemSellResultAsync(this IConnection? connection, bool @success, uint @money) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = NpcItemSellResultRef.Length; + var packet = new NpcItemSellResultRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.Money = @money; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The inventory slot. + /// The result. + /// + /// Is sent by the server when: The player requested to set a price for an item of the players shop. + /// Causes reaction on client side: The item gets a price on the user interface. + /// + public static async ValueTask SendPlayerShopSetItemPriceResponseAsync(this IConnection? connection, byte @inventorySlot, PlayerShopSetItemPriceResponse.ItemPriceSetResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayerShopSetItemPriceResponseRef.Length; + var packet = new PlayerShopSetItemPriceResponseRef(connection.Output.GetSpan(length)[..length]); + packet.InventorySlot = @inventorySlot; + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player id. + /// The success. + /// + /// Is sent by the server when: After a player in scope requested to close his shop or after all items has been sold. + /// Causes reaction on client side: The player shop not shown as open anymore. + /// + public static async ValueTask SendPlayerShopClosedAsync(this IConnection? connection, ushort @playerId, bool @success = true) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayerShopClosedRef.Length; + var packet = new PlayerShopClosedRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.PlayerId = @playerId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The inventory slot. + /// The buyer name. + /// + /// Is sent by the server when: An item of the players shop was sold to another player. + /// Causes reaction on client side: The item is removed from the players inventory and a blue system message appears. + /// + public static async ValueTask SendPlayerShopItemSoldToPlayerAsync(this IConnection? connection, byte @inventorySlot, string @buyerName) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayerShopItemSoldToPlayerRef.Length; + var packet = new PlayerShopItemSoldToPlayerRef(connection.Output.GetSpan(length)[..length]); + packet.InventorySlot = @inventorySlot; + packet.BuyerName = @buyerName; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player id. + /// + /// Is sent by the server when: After the player requested to close his shop or after all items has been sold. + /// Causes reaction on client side: The player shop dialog is closed for the shop of the specified player. + /// + public static async ValueTask SendClosePlayerShopDialogAsync(this IConnection? connection, ushort @playerId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ClosePlayerShopDialogRef.Length; + var packet = new ClosePlayerShopDialogRef(connection.Output.GetSpan(length)[..length]); + packet.PlayerId = @playerId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The seller id. + /// The item data. + /// The item slot. + /// + /// Is sent by the server when: After the player requested to buy an item of a shop of another player. + /// Causes reaction on client side: The result is shown to the player. If successful, the item is added to the inventory. + /// + public static async ValueTask SendPlayerShopBuyResultAsync(this IConnection? connection, PlayerShopBuyResult.ResultKind @result, ushort @sellerId, Memory @itemData, byte @itemSlot) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayerShopBuyResultRef.Length; + var packet = new PlayerShopBuyResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.SellerId = @sellerId; + @itemData.Span.CopyTo(packet.ItemData); + packet.ItemSlot = @itemSlot; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The seller id. + /// The result. + /// The item slot. + /// The item data. + /// + /// Is sent by the server when: After the player requested to buy an item of a shop of another player. + /// Causes reaction on client side: The result is shown to the player. If successful, the item is added to the inventory. + /// + public static async ValueTask SendPlayerShopBuyResultExtendedAsync(this IConnection? connection, ushort @sellerId, PlayerShopBuyResultExtended.ResultKind @result, byte @itemSlot, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayerShopBuyResultExtendedRef.GetRequiredSize(itemData.Length); + var packet = new PlayerShopBuyResultExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.SellerId = @sellerId; + packet.Result = @result; + packet.ItemSlot = @itemSlot; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player id. + /// The effect. + /// + /// Is sent by the server when: After a player achieved or lost something. + /// Causes reaction on client side: An effect is shown for the affected player. + /// + public static async ValueTask SendShowEffectAsync(this IConnection? connection, ushort @playerId, ShowEffect.EffectType @effect) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ShowEffectRef.Length; + var packet = new ShowEffectRef(connection.Output.GetSpan(length)[..length]); + packet.PlayerId = @playerId; + packet.Effect = @effect; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The unlock flags. + /// + /// Is sent by the server when: It's send right after the CharacterList, in the character selection screen, if the account has any unlocked character classes. + /// Causes reaction on client side: The client unlocks the specified character classes, so they can be created. + /// + public static async ValueTask SendCharacterClassCreationUnlockAsync(this IConnection? connection, CharacterCreationUnlockFlags @unlockFlags) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterClassCreationUnlockRef.Length; + var packet = new CharacterClassCreationUnlockRef(connection.Output.GetSpan(length)[..length]); + packet.UnlockFlags = @unlockFlags; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The character name. + /// The character slot. + /// The level. + /// The class. + /// The character status. + /// The preview data. + /// The success. + /// + /// Is sent by the server when: After the server successfully processed a character creation request. + /// Causes reaction on client side: The new character is shown in the character list + /// + public static async ValueTask SendCharacterCreationSuccessfulAsync(this IConnection? connection, string @characterName, byte @characterSlot, ushort @level, CharacterClassNumber @class, byte @characterStatus, Memory @previewData, bool @success = true) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterCreationSuccessfulRef.Length; + var packet = new CharacterCreationSuccessfulRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.CharacterName = @characterName; + packet.CharacterSlot = @characterSlot; + packet.Level = @level; + packet.Class = @class; + packet.CharacterStatus = @characterStatus; + @previewData.Span.CopyTo(packet.PreviewData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: After the server processed a character creation request without success. + /// Causes reaction on client side: A message is shown that it failed. + /// + public static async ValueTask SendCharacterCreationFailedAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterCreationFailedRef.Length; + var packet = new CharacterCreationFailedRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The position x. + /// The position y. + /// The map number. + /// The direction. + /// The current health. + /// The current mana. + /// The experience. + /// The money. + /// + /// Is sent by the server when: The character respawned after death. + /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. + /// + public static async ValueTask SendRespawnAfterDeath075Async(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, ushort @currentHealth, ushort @currentMana, uint @experience, uint @money) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RespawnAfterDeath075Ref.Length; + var packet = new RespawnAfterDeath075Ref(connection.Output.GetSpan(length)[..length]); + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.MapNumber = @mapNumber; + packet.Direction = @direction; + packet.CurrentHealth = @currentHealth; + packet.CurrentMana = @currentMana; + packet.Experience = @experience; + packet.Money = @money; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The position x. + /// The position y. + /// The map number. + /// The direction. + /// The current health. + /// The current mana. + /// The current ability. + /// The experience. + /// The money. + /// + /// Is sent by the server when: The character respawned after death. + /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. + /// + public static async ValueTask SendRespawnAfterDeath095Async(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, ushort @currentHealth, ushort @currentMana, ushort @currentAbility, uint @experience, uint @money) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RespawnAfterDeath095Ref.Length; + var packet = new RespawnAfterDeath095Ref(connection.Output.GetSpan(length)[..length]); + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.MapNumber = @mapNumber; + packet.Direction = @direction; + packet.CurrentHealth = @currentHealth; + packet.CurrentMana = @currentMana; + packet.CurrentAbility = @currentAbility; + packet.Experience = @experience; + packet.Money = @money; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The position x. + /// The position y. + /// The map number. + /// The direction. + /// The current health. + /// The current mana. + /// The current shield. + /// The current ability. + /// The experience. + /// The money. + /// + /// Is sent by the server when: The character respawned after death. + /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. + /// + public static async ValueTask SendRespawnAfterDeathAsync(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, ushort @currentHealth, ushort @currentMana, ushort @currentShield, ushort @currentAbility, ulong @experience, uint @money) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RespawnAfterDeathRef.Length; + var packet = new RespawnAfterDeathRef(connection.Output.GetSpan(length)[..length]); + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.MapNumber = @mapNumber; + packet.Direction = @direction; + packet.CurrentHealth = @currentHealth; + packet.CurrentMana = @currentMana; + packet.CurrentShield = @currentShield; + packet.CurrentAbility = @currentAbility; + packet.Experience = @experience; + packet.Money = @money; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The position x. + /// The position y. + /// The map number. + /// The direction. + /// The current health. + /// The current mana. + /// The current shield. + /// The current ability. + /// The experience. + /// The money. + /// + /// Is sent by the server when: The character respawned after death. + /// Causes reaction on client side: The character respawns with the specified attributes at the specified map. + /// + public static async ValueTask SendRespawnAfterDeathExtendedAsync(this IConnection? connection, byte @positionX, byte @positionY, byte @mapNumber, byte @direction, uint @currentHealth, uint @currentMana, uint @currentShield, uint @currentAbility, ulong @experience, uint @money) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RespawnAfterDeathExtendedRef.Length; + var packet = new RespawnAfterDeathExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.PositionX = @positionX; + packet.PositionY = @positionY; + packet.MapNumber = @mapNumber; + packet.Direction = @direction; + packet.CurrentHealth = @currentHealth; + packet.CurrentMana = @currentMana; + packet.CurrentShield = @currentShield; + packet.CurrentAbility = @currentAbility; + packet.Experience = @experience; + packet.Money = @money; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health damage. + /// The current shield. + /// + /// Is sent by the server when: The character got damaged by being poisoned on old client versions. + /// Causes reaction on client side: Removes the damage from the health without showing a damage number. + /// + public static async ValueTask SendPoisonDamageAsync(this IConnection? connection, ushort @healthDamage, ushort @currentShield) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PoisonDamageRef.Length; + var packet = new PoisonDamageRef(connection.Output.GetSpan(length)[..length]); + packet.HealthDamage = @healthDamage; + packet.CurrentShield = @currentShield; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player id. + /// The new state. + /// + /// Is sent by the server when: After a the hero state of an observed character changed. + /// Causes reaction on client side: The color of the name of the character is changed accordingly and a message is shown. + /// + public static async ValueTask SendHeroStateChangedAsync(this IConnection? connection, ushort @playerId, CharacterHeroState @newState) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = HeroStateChangedRef.Length; + var packet = new HeroStateChangedRef(connection.Output.GetSpan(length)[..length]); + packet.PlayerId = @playerId; + packet.NewState = @newState; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill index. + /// The skill number. + /// The skill level. + /// The flag. + /// + /// Is sent by the server when: After a skill got added to the skill list, e.g. by equipping an item or learning a skill. + /// Causes reaction on client side: The skill is added to the skill list on client side. + /// + public static async ValueTask SendSkillAddedAsync(this IConnection? connection, byte @skillIndex, ushort @skillNumber, byte @skillLevel, byte @flag = 0xFE) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillAddedRef.Length; + var packet = new SkillAddedRef(connection.Output.GetSpan(length)[..length]); + packet.Flag = @flag; + packet.SkillIndex = @skillIndex; + packet.SkillNumber = @skillNumber; + packet.SkillLevel = @skillLevel; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill index. + /// The skill number. + /// The flag. + /// + /// Is sent by the server when: After a skill got removed from the skill list, e.g. by removing an equipped item. + /// Causes reaction on client side: The skill is added to the skill list on client side. + /// + public static async ValueTask SendSkillRemovedAsync(this IConnection? connection, byte @skillIndex, ushort @skillNumber, byte @flag = 0xFF) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillRemovedRef.Length; + var packet = new SkillRemovedRef(connection.Output.GetSpan(length)[..length]); + packet.Flag = @flag; + packet.SkillIndex = @skillIndex; + packet.SkillNumber = @skillNumber; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill index. + /// The skill number and level. + /// The flag. + /// + /// Is sent by the server when: After a skill got added to the skill list, e.g. by equipping an item or learning a skill. + /// Causes reaction on client side: The skill is added to the skill list on client side. + /// + public static async ValueTask SendSkillAdded075Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 1) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillAdded075Ref.Length; + var packet = new SkillAdded075Ref(connection.Output.GetSpan(length)[..length]); + packet.Flag = @flag; + packet.SkillIndex = @skillIndex; + packet.SkillNumberAndLevel = @skillNumberAndLevel; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill index. + /// The skill number and level. + /// The flag. + /// + /// Is sent by the server when: After a skill got removed from the skill list, e.g. by removing an equipped item. + /// Causes reaction on client side: The skill is added to the skill list on client side. + /// + public static async ValueTask SendSkillRemoved075Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 0) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillRemoved075Ref.Length; + var packet = new SkillRemoved075Ref(connection.Output.GetSpan(length)[..length]); + packet.Flag = @flag; + packet.SkillIndex = @skillIndex; + packet.SkillNumberAndLevel = @skillNumberAndLevel; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill index. + /// The skill number and level. + /// The flag. + /// + /// Is sent by the server when: After a skill got added to the skill list, e.g. by equipping an item or learning a skill. + /// Causes reaction on client side: The skill is added to the skill list on client side. + /// + public static async ValueTask SendSkillAdded095Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 0xFE) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillAdded095Ref.Length; + var packet = new SkillAdded095Ref(connection.Output.GetSpan(length)[..length]); + packet.Flag = @flag; + packet.SkillIndex = @skillIndex; + packet.SkillNumberAndLevel = @skillNumberAndLevel; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill index. + /// The skill number and level. + /// The flag. + /// + /// Is sent by the server when: After a skill got removed from the skill list, e.g. by removing an equipped item. + /// Causes reaction on client side: The skill is added to the skill list on client side. + /// + public static async ValueTask SendSkillRemoved095Async(this IConnection? connection, byte @skillIndex, ushort @skillNumberAndLevel, byte @flag = 0xFF) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillRemoved095Ref.Length; + var packet = new SkillRemoved095Ref(connection.Output.GetSpan(length)[..length]); + packet.Flag = @flag; + packet.SkillIndex = @skillIndex; + packet.SkillNumberAndLevel = @skillNumberAndLevel; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The character name. + /// + /// Is sent by the server when: After the client focused the character successfully on the server side. + /// Causes reaction on client side: The client highlights the focused character. + /// + public static async ValueTask SendCharacterFocusedAsync(this IConnection? connection, string @characterName) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterFocusedRef.Length; + var packet = new CharacterFocusedRef(connection.Output.GetSpan(length)[..length]); + packet.CharacterName = @characterName; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The attribute. + /// The updated dependent maximum stat. + /// The updated maximum shield. + /// The updated maximum ability. + /// + /// Is sent by the server when: After the server processed a character stat increase request packet. + /// Causes reaction on client side: If it was successful, adds a point to the requested stat type. + /// + public static async ValueTask SendCharacterStatIncreaseResponseAsync(this IConnection? connection, bool @success, CharacterStatAttribute @attribute, ushort @updatedDependentMaximumStat, ushort @updatedMaximumShield, ushort @updatedMaximumAbility) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterStatIncreaseResponseRef.Length; + var packet = new CharacterStatIncreaseResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.Attribute = @attribute; + packet.UpdatedDependentMaximumStat = @updatedDependentMaximumStat; + packet.UpdatedMaximumShield = @updatedMaximumShield; + packet.UpdatedMaximumAbility = @updatedMaximumAbility; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The attribute. + /// The added amount. + /// The updated maximum health. + /// The updated maximum mana. + /// The updated maximum shield. + /// The updated maximum ability. + /// + /// Is sent by the server when: After the server processed a character stat increase request packet. + /// Causes reaction on client side: If it was successful, adds a point to the requested stat type. + /// + public static async ValueTask SendCharacterStatIncreaseResponseExtendedAsync(this IConnection? connection, CharacterStatAttribute @attribute, ushort @addedAmount, uint @updatedMaximumHealth, uint @updatedMaximumMana, uint @updatedMaximumShield, uint @updatedMaximumAbility) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterStatIncreaseResponseExtendedRef.Length; + var packet = new CharacterStatIncreaseResponseExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Attribute = @attribute; + packet.AddedAmount = @addedAmount; + packet.UpdatedMaximumHealth = @updatedMaximumHealth; + packet.UpdatedMaximumMana = @updatedMaximumMana; + packet.UpdatedMaximumShield = @updatedMaximumShield; + packet.UpdatedMaximumAbility = @updatedMaximumAbility; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: After the server processed a character delete response of the client. + /// Causes reaction on client side: If successful, the character is deleted from the character selection screen. Otherwise, a message is shown. + /// + public static async ValueTask SendCharacterDeleteResponseAsync(this IConnection? connection, CharacterDeleteResponse.CharacterDeleteResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterDeleteResponseRef.Length; + var packet = new CharacterDeleteResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The level. + /// The level up points. + /// The maximum health. + /// The maximum mana. + /// The maximum shield. + /// The maximum ability. + /// The fruit points. + /// The maximum fruit points. + /// The negative fruit points. + /// The maximum negative fruit points. + /// + /// Is sent by the server when: After a character leveled up. + /// Causes reaction on client side: Updates the level (and other related stats) in the game client and shows an effect. + /// + public static async ValueTask SendCharacterLevelUpdateAsync(this IConnection? connection, ushort @level, ushort @levelUpPoints, ushort @maximumHealth, ushort @maximumMana, ushort @maximumShield, ushort @maximumAbility, ushort @fruitPoints, ushort @maximumFruitPoints, ushort @negativeFruitPoints, ushort @maximumNegativeFruitPoints) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterLevelUpdateRef.Length; + var packet = new CharacterLevelUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.Level = @level; + packet.LevelUpPoints = @levelUpPoints; + packet.MaximumHealth = @maximumHealth; + packet.MaximumMana = @maximumMana; + packet.MaximumShield = @maximumShield; + packet.MaximumAbility = @maximumAbility; + packet.FruitPoints = @fruitPoints; + packet.MaximumFruitPoints = @maximumFruitPoints; + packet.NegativeFruitPoints = @negativeFruitPoints; + packet.MaximumNegativeFruitPoints = @maximumNegativeFruitPoints; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The x. + /// The y. + /// The map id. + /// The current experience. + /// The experience for next level. + /// The level up points. + /// The strength. + /// The agility. + /// The vitality. + /// The energy. + /// The current health. + /// The maximum health. + /// The current mana. + /// The maximum mana. + /// The current shield. + /// The maximum shield. + /// The current ability. + /// The maximum ability. + /// The money. + /// The hero state. + /// The status. + /// The used fruit points. + /// The max fruit points. + /// The leadership. + /// The used negative fruit points. + /// The max negative fruit points. + /// The inventory extensions. + /// + /// Is sent by the server when: After the character was selected by the player and entered the game. + /// Causes reaction on client side: The characters enters the game world. + /// + public static async ValueTask SendCharacterInformationAsync(this IConnection? connection, byte @x, byte @y, ushort @mapId, ulong @currentExperience, ulong @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @currentHealth, ushort @maximumHealth, ushort @currentMana, ushort @maximumMana, ushort @currentShield, ushort @maximumShield, ushort @currentAbility, ushort @maximumAbility, uint @money, CharacterHeroState @heroState, CharacterStatus @status, ushort @usedFruitPoints, ushort @maxFruitPoints, ushort @leadership, ushort @usedNegativeFruitPoints, ushort @maxNegativeFruitPoints, byte @inventoryExtensions) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterInformationRef.Length; + var packet = new CharacterInformationRef(connection.Output.GetSpan(length)[..length]); + packet.X = @x; + packet.Y = @y; + packet.MapId = @mapId; + packet.CurrentExperience = @currentExperience; + packet.ExperienceForNextLevel = @experienceForNextLevel; + packet.LevelUpPoints = @levelUpPoints; + packet.Strength = @strength; + packet.Agility = @agility; + packet.Vitality = @vitality; + packet.Energy = @energy; + packet.CurrentHealth = @currentHealth; + packet.MaximumHealth = @maximumHealth; + packet.CurrentMana = @currentMana; + packet.MaximumMana = @maximumMana; + packet.CurrentShield = @currentShield; + packet.MaximumShield = @maximumShield; + packet.CurrentAbility = @currentAbility; + packet.MaximumAbility = @maximumAbility; + packet.Money = @money; + packet.HeroState = @heroState; + packet.Status = @status; + packet.UsedFruitPoints = @usedFruitPoints; + packet.MaxFruitPoints = @maxFruitPoints; + packet.Leadership = @leadership; + packet.UsedNegativeFruitPoints = @usedNegativeFruitPoints; + packet.MaxNegativeFruitPoints = @maxNegativeFruitPoints; + packet.InventoryExtensions = @inventoryExtensions; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The level. + /// The level up points. + /// The maximum health. + /// The maximum mana. + /// The maximum shield. + /// The maximum ability. + /// The fruit points. + /// The maximum fruit points. + /// The negative fruit points. + /// The maximum negative fruit points. + /// + /// Is sent by the server when: After a character leveled up. + /// Causes reaction on client side: Updates the level (and other related stats) in the game client and shows an effect. + /// + public static async ValueTask SendCharacterLevelUpdateExtendedAsync(this IConnection? connection, ushort @level, ushort @levelUpPoints, uint @maximumHealth, uint @maximumMana, uint @maximumShield, uint @maximumAbility, ushort @fruitPoints, ushort @maximumFruitPoints, ushort @negativeFruitPoints, ushort @maximumNegativeFruitPoints) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterLevelUpdateExtendedRef.Length; + var packet = new CharacterLevelUpdateExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.Level = @level; + packet.LevelUpPoints = @levelUpPoints; + packet.MaximumHealth = @maximumHealth; + packet.MaximumMana = @maximumMana; + packet.MaximumShield = @maximumShield; + packet.MaximumAbility = @maximumAbility; + packet.FruitPoints = @fruitPoints; + packet.MaximumFruitPoints = @maximumFruitPoints; + packet.NegativeFruitPoints = @negativeFruitPoints; + packet.MaximumNegativeFruitPoints = @maximumNegativeFruitPoints; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The x. + /// The y. + /// The map id. + /// The current experience. + /// The experience for next level. + /// The level up points. + /// The strength. + /// The agility. + /// The vitality. + /// The energy. + /// The leadership. + /// The current health. + /// The maximum health. + /// The current mana. + /// The maximum mana. + /// The current shield. + /// The maximum shield. + /// The current ability. + /// The maximum ability. + /// The money. + /// The hero state. + /// The status. + /// The used fruit points. + /// The max fruit points. + /// The used negative fruit points. + /// The max negative fruit points. + /// The attack speed. + /// The magic speed. + /// The maximum attack speed. + /// The inventory extensions. + /// + /// Is sent by the server when: After the character was selected by the player and entered the game. + /// Causes reaction on client side: The characters enters the game world. + /// + public static async ValueTask SendCharacterInformationExtendedAsync(this IConnection? connection, byte @x, byte @y, ushort @mapId, ulong @currentExperience, ulong @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @leadership, uint @currentHealth, uint @maximumHealth, uint @currentMana, uint @maximumMana, uint @currentShield, uint @maximumShield, uint @currentAbility, uint @maximumAbility, uint @money, CharacterHeroState @heroState, CharacterStatus @status, ushort @usedFruitPoints, ushort @maxFruitPoints, ushort @usedNegativeFruitPoints, ushort @maxNegativeFruitPoints, ushort @attackSpeed, ushort @magicSpeed, ushort @maximumAttackSpeed, byte @inventoryExtensions) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterInformationExtendedRef.Length; + var packet = new CharacterInformationExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.X = @x; + packet.Y = @y; + packet.MapId = @mapId; + packet.CurrentExperience = @currentExperience; + packet.ExperienceForNextLevel = @experienceForNextLevel; + packet.LevelUpPoints = @levelUpPoints; + packet.Strength = @strength; + packet.Agility = @agility; + packet.Vitality = @vitality; + packet.Energy = @energy; + packet.Leadership = @leadership; + packet.CurrentHealth = @currentHealth; + packet.MaximumHealth = @maximumHealth; + packet.CurrentMana = @currentMana; + packet.MaximumMana = @maximumMana; + packet.CurrentShield = @currentShield; + packet.MaximumShield = @maximumShield; + packet.CurrentAbility = @currentAbility; + packet.MaximumAbility = @maximumAbility; + packet.Money = @money; + packet.HeroState = @heroState; + packet.Status = @status; + packet.UsedFruitPoints = @usedFruitPoints; + packet.MaxFruitPoints = @maxFruitPoints; + packet.UsedNegativeFruitPoints = @usedNegativeFruitPoints; + packet.MaxNegativeFruitPoints = @maxNegativeFruitPoints; + packet.AttackSpeed = @attackSpeed; + packet.MagicSpeed = @magicSpeed; + packet.MaximumAttackSpeed = @maximumAttackSpeed; + packet.InventoryExtensions = @inventoryExtensions; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The x. + /// The y. + /// The map id. + /// The current experience. + /// The experience for next level. + /// The level up points. + /// The strength. + /// The agility. + /// The vitality. + /// The energy. + /// The current health. + /// The maximum health. + /// The current mana. + /// The maximum mana. + /// The money. + /// The hero state. + /// The status. + /// + /// Is sent by the server when: After the character was selected by the player and entered the game. + /// Causes reaction on client side: The characters enters the game world. + /// + public static async ValueTask SendCharacterInformation075Async(this IConnection? connection, byte @x, byte @y, byte @mapId, uint @currentExperience, uint @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @currentHealth, ushort @maximumHealth, ushort @currentMana, ushort @maximumMana, uint @money, CharacterHeroState @heroState, CharacterStatus @status) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterInformation075Ref.Length; + var packet = new CharacterInformation075Ref(connection.Output.GetSpan(length)[..length]); + packet.X = @x; + packet.Y = @y; + packet.MapId = @mapId; + packet.CurrentExperience = @currentExperience; + packet.ExperienceForNextLevel = @experienceForNextLevel; + packet.LevelUpPoints = @levelUpPoints; + packet.Strength = @strength; + packet.Agility = @agility; + packet.Vitality = @vitality; + packet.Energy = @energy; + packet.CurrentHealth = @currentHealth; + packet.MaximumHealth = @maximumHealth; + packet.CurrentMana = @currentMana; + packet.MaximumMana = @maximumMana; + packet.Money = @money; + packet.HeroState = @heroState; + packet.Status = @status; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The x. + /// The y. + /// The map id. + /// The direction. + /// The current experience. + /// The experience for next level. + /// The level up points. + /// The strength. + /// The agility. + /// The vitality. + /// The energy. + /// The current health. + /// The maximum health. + /// The current mana. + /// The maximum mana. + /// The current ability. + /// The maximum ability. + /// The money. + /// The hero state. + /// The status. + /// The used fruit points. + /// The max fruit points. + /// The leadership. + /// + /// Is sent by the server when: After the character was selected by the player and entered the game. + /// Causes reaction on client side: The characters enters the game world. + /// + public static async ValueTask SendCharacterInformation097Async(this IConnection? connection, byte @x, byte @y, byte @mapId, byte @direction, uint @currentExperience, uint @experienceForNextLevel, ushort @levelUpPoints, ushort @strength, ushort @agility, ushort @vitality, ushort @energy, ushort @currentHealth, ushort @maximumHealth, ushort @currentMana, ushort @maximumMana, ushort @currentAbility, ushort @maximumAbility, uint @money, CharacterHeroState @heroState, CharacterStatus @status, ushort @usedFruitPoints, ushort @maxFruitPoints, ushort @leadership) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CharacterInformation097Ref.Length; + var packet = new CharacterInformation097Ref(connection.Output.GetSpan(length)[..length]); + packet.X = @x; + packet.Y = @y; + packet.MapId = @mapId; + packet.Direction = @direction; + packet.CurrentExperience = @currentExperience; + packet.ExperienceForNextLevel = @experienceForNextLevel; + packet.LevelUpPoints = @levelUpPoints; + packet.Strength = @strength; + packet.Agility = @agility; + packet.Vitality = @vitality; + packet.Energy = @energy; + packet.CurrentHealth = @currentHealth; + packet.MaximumHealth = @maximumHealth; + packet.CurrentMana = @currentMana; + packet.MaximumMana = @maximumMana; + packet.CurrentAbility = @currentAbility; + packet.MaximumAbility = @maximumAbility; + packet.Money = @money; + packet.HeroState = @heroState; + packet.Status = @status; + packet.UsedFruitPoints = @usedFruitPoints; + packet.MaxFruitPoints = @maxFruitPoints; + packet.Leadership = @leadership; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The inventory slot. + /// The item data. + /// + /// Is sent by the server when: An item in the inventory got upgraded by the player, e.g. by applying a jewel. + /// Causes reaction on client side: The item is updated on the user interface. + /// + public static async ValueTask SendInventoryItemUpgradedAsync(this IConnection? connection, byte @inventorySlot, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = InventoryItemUpgradedRef.GetRequiredSize(itemData.Length); + var packet = new InventoryItemUpgradedRef(connection.Output.GetSpan(length)[..length]); + packet.InventorySlot = @inventorySlot; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The health percent. + /// + /// Is sent by the server when: When health of a summoned monster (Elf Skill) changed. + /// Causes reaction on client side: The health is updated on the user interface. + /// + public static async ValueTask SendSummonHealthUpdateAsync(this IConnection? connection, byte @healthPercent) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SummonHealthUpdateRef.Length; + var packet = new SummonHealthUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.HealthPercent = @healthPercent; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The seconds. + /// + /// Is sent by the server when: Every second during a guild soccer match. + /// Causes reaction on client side: The time is updated on the user interface. + /// + public static async ValueTask SendGuildSoccerTimeUpdateAsync(this IConnection? connection, ushort @seconds) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildSoccerTimeUpdateRef.Length; + var packet = new GuildSoccerTimeUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.Seconds = @seconds; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The red team name. + /// The red team goals. + /// The blue team name. + /// The blue team goals. + /// + /// Is sent by the server when: Whenever the score of the soccer game changed, and at the beginning of the match. + /// Causes reaction on client side: The score is updated on the user interface. + /// + public static async ValueTask SendGuildSoccerScoreUpdateAsync(this IConnection? connection, string @redTeamName, byte @redTeamGoals, string @blueTeamName, byte @blueTeamGoals) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildSoccerScoreUpdateRef.Length; + var packet = new GuildSoccerScoreUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.RedTeamName = @redTeamName; + packet.RedTeamGoals = @redTeamGoals; + packet.BlueTeamName = @blueTeamName; + packet.BlueTeamGoals = @blueTeamGoals; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The command type. + /// The parameter 1. + /// The parameter 2. + /// + /// Is sent by the server when: E.g. when event items are dropped to the floor, or a specific dialog should be shown. + /// Causes reaction on client side: The client shows an effect, e.g. a firework. + /// + public static async ValueTask SendServerCommandAsync(this IConnection? connection, byte @commandType, byte @parameter1, byte @parameter2) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ServerCommandRef.Length; + var packet = new ServerCommandRef(connection.Output.GetSpan(length)[..length]); + packet.CommandType = @commandType; + packet.Parameter1 = @parameter1; + packet.Parameter2 = @parameter2; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The x. + /// The y. + /// The effect type. + /// + /// Is sent by the server when: E.g. when event items are dropped to the floor. + /// Causes reaction on client side: The client shows an fireworks effect at the specified coordinates. + /// + public static async ValueTask SendShowFireworksAsync(this IConnection? connection, byte @x, byte @y, byte @effectType = 0) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ShowFireworksRef.Length; + var packet = new ShowFireworksRef(connection.Output.GetSpan(length)[..length]); + packet.EffectType = @effectType; + packet.X = @x; + packet.Y = @y; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The x. + /// The y. + /// The effect type. + /// + /// Is sent by the server when: E.g. when event items are dropped to the floor. + /// Causes reaction on client side: The client shows an christmas fireworks effect at the specified coordinates. + /// + public static async ValueTask SendShowChristmasFireworksAsync(this IConnection? connection, byte @x, byte @y, byte @effectType = 59) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ShowChristmasFireworksRef.Length; + var packet = new ShowChristmasFireworksRef(connection.Output.GetSpan(length)[..length]); + packet.EffectType = @effectType; + packet.X = @x; + packet.Y = @y; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The x. + /// The y. + /// The effect type. + /// + /// Is sent by the server when: E.g. when event items are dropped to the floor. + /// Causes reaction on client side: The client plays a fanfare sound at the specified coordinates. + /// + public static async ValueTask SendPlayFanfareSoundAsync(this IConnection? connection, byte @x, byte @y, byte @effectType = 2) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayFanfareSoundRef.Length; + var packet = new PlayFanfareSoundRef(connection.Output.GetSpan(length)[..length]); + packet.EffectType = @effectType; + packet.X = @x; + packet.Y = @y; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The target object id. + /// The effect type. + /// + /// Is sent by the server when: E.g. when event items are dropped to the floor. + /// Causes reaction on client side: The client shows a swirl effect at the specified object. + /// + public static async ValueTask SendShowSwirlAsync(this IConnection? connection, ushort @targetObjectId, byte @effectType = 58) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ShowSwirlRef.Length; + var packet = new ShowSwirlRef(connection.Output.GetSpan(length)[..length]); + packet.EffectType = @effectType; + packet.TargetObjectId = @targetObjectId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The master level. + /// The master experience. + /// The master experience of next level. + /// The master level up points. + /// The maximum health. + /// The maximum mana. + /// The maximum shield. + /// The maximum ability. + /// + /// Is sent by the server when: After entering the game with a master class character. + /// Causes reaction on client side: The master related data is available. + /// + public static async ValueTask SendMasterStatsUpdateAsync(this IConnection? connection, ushort @masterLevel, ulong @masterExperience, ulong @masterExperienceOfNextLevel, ushort @masterLevelUpPoints, ushort @maximumHealth, ushort @maximumMana, ushort @maximumShield, ushort @maximumAbility) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MasterStatsUpdateRef.Length; + var packet = new MasterStatsUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.MasterLevel = @masterLevel; + packet.MasterExperience = @masterExperience; + packet.MasterExperienceOfNextLevel = @masterExperienceOfNextLevel; + packet.MasterLevelUpPoints = @masterLevelUpPoints; + packet.MaximumHealth = @maximumHealth; + packet.MaximumMana = @maximumMana; + packet.MaximumShield = @maximumShield; + packet.MaximumAbility = @maximumAbility; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The master level. + /// The master experience. + /// The master experience of next level. + /// The master level up points. + /// The maximum health. + /// The maximum mana. + /// The maximum shield. + /// The maximum ability. + /// + /// Is sent by the server when: After entering the game with a master class character. + /// Causes reaction on client side: The master related data is available. + /// + public static async ValueTask SendMasterStatsUpdateExtendedAsync(this IConnection? connection, ushort @masterLevel, ulong @masterExperience, ulong @masterExperienceOfNextLevel, ushort @masterLevelUpPoints, uint @maximumHealth, uint @maximumMana, uint @maximumShield, uint @maximumAbility) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MasterStatsUpdateExtendedRef.Length; + var packet = new MasterStatsUpdateExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.MasterLevel = @masterLevel; + packet.MasterExperience = @masterExperience; + packet.MasterExperienceOfNextLevel = @masterExperienceOfNextLevel; + packet.MasterLevelUpPoints = @masterLevelUpPoints; + packet.MaximumHealth = @maximumHealth; + packet.MaximumMana = @maximumMana; + packet.MaximumShield = @maximumShield; + packet.MaximumAbility = @maximumAbility; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The master level. + /// The gained master points. + /// The current master points. + /// The maximum master points. + /// The maximum health. + /// The maximum mana. + /// The maximum shield. + /// The maximum ability. + /// + /// Is sent by the server when: After a master character leveled up. + /// Causes reaction on client side: Updates the master level (and other related stats) in the game client and shows an effect. + /// + public static async ValueTask SendMasterCharacterLevelUpdateAsync(this IConnection? connection, ushort @masterLevel, ushort @gainedMasterPoints, ushort @currentMasterPoints, ushort @maximumMasterPoints, ushort @maximumHealth, ushort @maximumMana, ushort @maximumShield, ushort @maximumAbility) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MasterCharacterLevelUpdateRef.Length; + var packet = new MasterCharacterLevelUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.MasterLevel = @masterLevel; + packet.GainedMasterPoints = @gainedMasterPoints; + packet.CurrentMasterPoints = @currentMasterPoints; + packet.MaximumMasterPoints = @maximumMasterPoints; + packet.MaximumHealth = @maximumHealth; + packet.MaximumMana = @maximumMana; + packet.MaximumShield = @maximumShield; + packet.MaximumAbility = @maximumAbility; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The master level. + /// The gained master points. + /// The current master points. + /// The maximum master points. + /// The maximum health. + /// The maximum mana. + /// The maximum shield. + /// The maximum ability. + /// + /// Is sent by the server when: After a master character leveled up. + /// Causes reaction on client side: Updates the master level (and other related stats) in the game client and shows an effect. + /// + public static async ValueTask SendMasterCharacterLevelUpdateExtendedAsync(this IConnection? connection, ushort @masterLevel, ushort @gainedMasterPoints, ushort @currentMasterPoints, ushort @maximumMasterPoints, uint @maximumHealth, uint @maximumMana, uint @maximumShield, uint @maximumAbility) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MasterCharacterLevelUpdateExtendedRef.Length; + var packet = new MasterCharacterLevelUpdateExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.MasterLevel = @masterLevel; + packet.GainedMasterPoints = @gainedMasterPoints; + packet.CurrentMasterPoints = @currentMasterPoints; + packet.MaximumMasterPoints = @maximumMasterPoints; + packet.MaximumHealth = @maximumHealth; + packet.MaximumMana = @maximumMana; + packet.MaximumShield = @maximumShield; + packet.MaximumAbility = @maximumAbility; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The master level up points. + /// The index of the master skill on the clients master skill tree for the given character class. + /// The master skill number. + /// The level. + /// The display value. + /// The display value of next level. + /// + /// Is sent by the server when: After a master skill level has been changed (usually increased). + /// Causes reaction on client side: The level is updated in the master skill tree. + /// + public static async ValueTask SendMasterSkillLevelUpdateAsync(this IConnection? connection, bool @success, ushort @masterLevelUpPoints, byte @masterSkillIndex, ushort @masterSkillNumber, byte @level, float @displayValue, float @displayValueOfNextLevel) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MasterSkillLevelUpdateRef.Length; + var packet = new MasterSkillLevelUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.MasterLevelUpPoints = @masterLevelUpPoints; + packet.MasterSkillIndex = @masterSkillIndex; + packet.MasterSkillNumber = @masterSkillNumber; + packet.Level = @level; + packet.DisplayValue = @displayValue; + packet.DisplayValueOfNextLevel = @displayValueOfNextLevel; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The type. + /// The message. + /// + /// Is sent by the server when: + /// Causes reaction on client side: + /// + public static async ValueTask SendServerMessageAsync(this IConnection? connection, ServerMessage.MessageType @type, string @message) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ServerMessageRef.GetRequiredSize(message); + var packet = new ServerMessageRef(connection.Output.GetSpan(length)[..length]); + packet.Type = @type; + packet.Message = @message; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The requester id. + /// + /// Is sent by the server when: A player requested to join a guild. This message is sent then to the guild master. + /// Causes reaction on client side: The guild master gets a message box with the request popping up. + /// + public static async ValueTask SendGuildJoinRequestAsync(this IConnection? connection, ushort @requesterId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildJoinRequestRef.Length; + var packet = new GuildJoinRequestRef(connection.Output.GetSpan(length)[..length]); + packet.RequesterId = @requesterId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: After a guild master responded to a request of a player to join his guild. This message is sent back to the requesting player. + /// Causes reaction on client side: The requester gets a corresponding message showing. + /// + public static async ValueTask SendGuildJoinResponseAsync(this IConnection? connection, GuildJoinResponse.GuildJoinRequestResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildJoinResponseRef.Length; + var packet = new GuildJoinResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: After a guild master sent a request to kick a player from its guild and the server processed this request. + /// Causes reaction on client side: The client shows a message depending on the result. + /// + public static async ValueTask SendGuildKickResponseAsync(this IConnection? connection, GuildKickResponse.GuildKickSuccess @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildKickResponseRef.Length; + var packet = new GuildKickResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: After a player started talking to the guild master NPC and the player is allowed to create a guild. + /// Causes reaction on client side: The client shows the guild master dialog. + /// + public static async ValueTask SendShowGuildMasterDialogAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ShowGuildMasterDialogRef.Length; + var packet = new ShowGuildMasterDialogRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: After a player started talking to the guild master NPC and the player proceeds to create a guild. + /// Causes reaction on client side: The client shows the guild creation dialog. + /// + public static async ValueTask SendShowGuildCreationDialogAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ShowGuildCreationDialogRef.Length; + var packet = new ShowGuildCreationDialogRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The error. + /// + /// Is sent by the server when: After a player requested to create a guild at the guild master NPC. + /// Causes reaction on client side: Depending on the result, a message is shown. + /// + public static async ValueTask SendGuildCreationResultAsync(this IConnection? connection, bool @success, GuildCreationResult.GuildCreationErrorType @error) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildCreationResultRef.Length; + var packet = new GuildCreationResultRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.Error = @error; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player id. + /// The is guild master. + /// + /// Is sent by the server when: A player left a guild. This message is sent to the player and all surrounding players. + /// Causes reaction on client side: The player is not longer shown as a guild member. + /// + public static async ValueTask SendGuildMemberLeftGuildAsync(this IConnection? connection, ushort @playerId, bool @isGuildMaster) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildMemberLeftGuildRef.Length; + var packet = new GuildMemberLeftGuildRef(connection.Output.GetSpan(length)[..length]); + packet.PlayerId = @playerId; + packet.IsGuildMaster = @isGuildMaster; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: A guild master requested a guild war against another guild. + /// Causes reaction on client side: The guild master of the other guild gets this request. + /// + public static async ValueTask SendGuildWarRequestResultAsync(this IConnection? connection, GuildWarRequestResult.RequestResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildWarRequestResultRef.Length; + var packet = new GuildWarRequestResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The guild name. + /// The type. + /// + /// Is sent by the server when: A guild master requested a guild war against another guild. + /// Causes reaction on client side: The guild master of the other guild gets this request. + /// + public static async ValueTask SendGuildWarRequestAsync(this IConnection? connection, string @guildName, GuildWarType @type) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildWarRequestRef.Length; + var packet = new GuildWarRequestRef(connection.Output.GetSpan(length)[..length]); + packet.GuildName = @guildName; + packet.Type = @type; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The guild name. + /// The type. + /// The team code. + /// + /// Is sent by the server when: A guild master requested a guild war against another guild. + /// Causes reaction on client side: The guild master of the other guild gets this request. + /// + public static async ValueTask SendGuildWarDeclaredAsync(this IConnection? connection, string @guildName, GuildWarType @type, byte @teamCode) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildWarDeclaredRef.Length; + var packet = new GuildWarDeclaredRef(connection.Output.GetSpan(length)[..length]); + packet.GuildName = @guildName; + packet.Type = @type; + packet.TeamCode = @teamCode; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The guild name. + /// + /// Is sent by the server when: The guild war ended. + /// Causes reaction on client side: The guild war is shown as ended on the client side. + /// + public static async ValueTask SendGuildWarEndedAsync(this IConnection? connection, GuildWarEnded.GuildWarResult @result, string @guildName) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildWarEndedRef.Length; + var packet = new GuildWarEndedRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.GuildName = @guildName; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The score of own guild. + /// The score of enemy guild. + /// The type. + /// + /// Is sent by the server when: The guild war score changed. + /// Causes reaction on client side: The guild score is updated on the client side. + /// + public static async ValueTask SendGuildWarScoreUpdateAsync(this IConnection? connection, byte @scoreOfOwnGuild, byte @scoreOfEnemyGuild, byte @type = 0) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildWarScoreUpdateRef.Length; + var packet = new GuildWarScoreUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.ScoreOfOwnGuild = @scoreOfOwnGuild; + packet.ScoreOfEnemyGuild = @scoreOfEnemyGuild; + packet.Type = @type; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The relationship type. + /// The request type. + /// The sender id. + /// + /// Is sent by the server when: A guild master sent a relationship change request (alliance or hostility) and the server forwards this request to the target guild master. + /// Causes reaction on client side: The target guild master (receiver of this message) sees the incoming request dialog. + /// + public static async ValueTask SendGuildRelationshipRequestAsync(this IConnection? connection, GuildRelationshipType @relationshipType, GuildRelationshipRequestType @requestType, ushort @senderId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildRelationshipRequestRef.Length; + var packet = new GuildRelationshipRequestRef(connection.Output.GetSpan(length)[..length]); + packet.RelationshipType = @relationshipType; + packet.RequestType = @requestType; + packet.SenderId = @senderId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The relationship type. + /// The request type. + /// The result. + /// The guild master id. + /// + /// Is sent by the server when: The result of a guild relationship change request (alliance or hostility) is sent back to the requester. + /// Causes reaction on client side: The requester sees the result of the relationship change. + /// + public static async ValueTask SendGuildRelationshipChangeResultAsync(this IConnection? connection, GuildRelationshipType @relationshipType, GuildRelationshipRequestType @requestType, GuildRelationshipChangeResult.GuildRelationshipChangeResultType @result, ushort @guildMasterId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildRelationshipChangeResultRef.Length; + var packet = new GuildRelationshipChangeResultRef(connection.Output.GetSpan(length)[..length]); + packet.RelationshipType = @relationshipType; + packet.RequestType = @requestType; + packet.Result = @result; + packet.GuildMasterId = @guildMasterId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The request type. + /// The relationship type. + /// + /// Is sent by the server when: A guild master sent a message to kick the guild from the alliance and it has been processed. + /// Causes reaction on client side: The list of guilds is updated accordingly. + /// + public static async ValueTask SendRemoveAllianceGuildResultAsync(this IConnection? connection, bool @result, GuildRelationshipRequestType @requestType, GuildRelationshipType @relationshipType) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RemoveAllianceGuildResultRef.Length; + var packet = new RemoveAllianceGuildResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.RequestType = @requestType; + packet.RelationshipType = @relationshipType; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The guild id. + /// The guild type. + /// The alliance guild name. + /// The guild name. + /// The logo. + /// + /// Is sent by the server when: A game client requested the (public) info of a guild, e.g. when it met a player of previously unknown guild. + /// Causes reaction on client side: The players which belong to the guild are shown as guild players. + /// + public static async ValueTask SendGuildInformationAsync(this IConnection? connection, uint @guildId, byte @guildType, string @allianceGuildName, string @guildName, Memory @logo) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = GuildInformationRef.Length; + var packet = new GuildInformationRef(connection.Output.GetSpan(length)[..length]); + packet.GuildId = @guildId; + packet.GuildType = @guildType; + packet.AllianceGuildName = @allianceGuildName; + packet.GuildName = @guildName; + @logo.Span.CopyTo(packet.Logo); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The guild id. + /// The guild name. + /// The logo. + /// + /// Is sent by the server when: After a guild has been created. However, in OpenMU, we just send the GuildInformations075 message, because it works just the same. + /// Causes reaction on client side: The players which belong to the guild are shown as guild players. + /// + public static async ValueTask SendSingleGuildInformation075Async(this IConnection? connection, ushort @guildId, string @guildName, Memory @logo) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SingleGuildInformation075Ref.Length; + var packet = new SingleGuildInformation075Ref(connection.Output.GetSpan(length)[..length]); + packet.GuildId = @guildId; + packet.GuildName = @guildName; + @logo.Span.CopyTo(packet.Logo); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The vault money. + /// The inventory money. + /// + /// Is sent by the server when: After the player requested to move money between the vault and inventory. + /// Causes reaction on client side: The game client updates the money values of vault and inventory. + /// + public static async ValueTask SendVaultMoneyUpdateAsync(this IConnection? connection, bool @success, uint @vaultMoney, uint @inventoryMoney) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = VaultMoneyUpdateRef.Length; + var packet = new VaultMoneyUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.VaultMoney = @vaultMoney; + packet.InventoryMoney = @inventoryMoney; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: After the player requested to close the vault, this confirmation is sent back to the client. + /// Causes reaction on client side: The game client closes the vault dialog. + /// + public static async ValueTask SendVaultClosedAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = VaultClosedRef.Length; + var packet = new VaultClosedRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The protection state. + /// + /// Is sent by the server when: After the player requested to open the vault. + /// Causes reaction on client side: The game client updates the UI to show the current vault protection state. + /// + public static async ValueTask SendVaultProtectionInformationAsync(this IConnection? connection, VaultProtectionInformation.VaultProtectionState @protectionState) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = VaultProtectionInformationRef.Length; + var packet = new VaultProtectionInformationRef(connection.Output.GetSpan(length)[..length]); + packet.ProtectionState = @protectionState; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The item data. + /// + /// Is sent by the server when: After the player requested to execute an item crafting, e.g. at the chaos machine. + /// Causes reaction on client side: The game client updates the UI to show the resulting item. + /// + public static async ValueTask SendItemCraftingResultAsync(this IConnection? connection, ItemCraftingResult.CraftingResult @result, Memory @itemData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ItemCraftingResultRef.GetRequiredSize(itemData.Length); + var packet = new ItemCraftingResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + @itemData.Span.CopyTo(packet.ItemData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: After the player requested to close the crafting dialog, this confirmation is sent back to the client. + /// Causes reaction on client side: The game client closes the crafting dialog. + /// + public static async ValueTask SendCraftingDialogClosed075Async(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = CraftingDialogClosed075Ref.Length; + var packet = new CraftingDialogClosed075Ref(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The quest index. + /// This is the complete byte with the state of four quests within the same byte. + /// + /// Is sent by the server when: When the player clicks on the quest npc. + /// Causes reaction on client side: The game client shows the next steps in the quest dialog. + /// + public static async ValueTask SendLegacyQuestStateDialogAsync(this IConnection? connection, byte @questIndex, byte @state) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = LegacyQuestStateDialogRef.Length; + var packet = new LegacyQuestStateDialogRef(connection.Output.GetSpan(length)[..length]); + packet.QuestIndex = @questIndex; + packet.State = @state; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The quest index. + /// This value is 0 if successful. Otherwise, 0xFF or even other magic values. + /// This is the complete byte with the state of four quests within the same byte. + /// + /// Is sent by the server when: As response to the set state request (C1A2). + /// Causes reaction on client side: The game client shows the new quest state. + /// + public static async ValueTask SendLegacySetQuestStateResponseAsync(this IConnection? connection, byte @questIndex, byte @result, byte @newState) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = LegacySetQuestStateResponseRef.Length; + var packet = new LegacySetQuestStateResponseRef(connection.Output.GetSpan(length)[..length]); + packet.QuestIndex = @questIndex; + packet.Result = @result; + packet.NewState = @newState; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player id. + /// The reward. + /// The count. + /// + /// Is sent by the server when: As response to the completed quest of a player in scope. + /// Causes reaction on client side: The game client shows the reward accordingly. + /// + public static async ValueTask SendLegacyQuestRewardAsync(this IConnection? connection, ushort @playerId, LegacyQuestReward.QuestRewardType @reward, byte @count) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = LegacyQuestRewardRef.Length; + var packet = new LegacyQuestRewardRef(connection.Output.GetSpan(length)[..length]); + packet.PlayerId = @playerId; + packet.Reward = @reward; + packet.Count = @count; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The pet command mode. + /// The target id. + /// The pet. + /// + /// Is sent by the server when: After the client sent a PetAttackCommand (as confirmation), or when the previous command finished and the pet is reset to Normal-mode. + /// Causes reaction on client side: The client updates the pet mode in its user interface. + /// + public static async ValueTask SendPetModeAsync(this IConnection? connection, ClientToServer.PetCommandMode @petCommandMode, ushort @targetId, ClientToServer.PetType @pet = ClientToServer.PetType.DarkRaven) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PetModeRef.Length; + var packet = new PetModeRef(connection.Output.GetSpan(length)[..length]); + packet.Pet = @pet; + packet.PetCommandMode = @petCommandMode; + packet.TargetId = @targetId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill type. + /// The owner id. + /// The target id. + /// The pet. + /// + /// Is sent by the server when: After the client sent a PetAttackCommand, the pet attacks automatically. For each attack, the player and all observing players get this message. + /// Causes reaction on client side: The client shows the pet attacking the target. + /// + public static async ValueTask SendPetAttackAsync(this IConnection? connection, PetAttack.PetSkillType @skillType, ushort @ownerId, ushort @targetId, ClientToServer.PetType @pet = ClientToServer.PetType.DarkRaven) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PetAttackRef.Length; + var packet = new PetAttackRef(connection.Output.GetSpan(length)[..length]); + packet.Pet = @pet; + packet.SkillType = @skillType; + packet.OwnerId = @ownerId; + packet.TargetId = @targetId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The pet. + /// The storage. + /// The item slot. + /// The level. + /// The experience. + /// The health. + /// + /// Is sent by the server when: After the client sent a PetInfoRequest for a pet (dark raven, horse). + /// Causes reaction on client side: The client shows the information about the pet. + /// + public static async ValueTask SendPetInfoResponseAsync(this IConnection? connection, ClientToServer.PetType @pet, ClientToServer.StorageType @storage, byte @itemSlot, byte @level, uint @experience, byte @health) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PetInfoResponseRef.Length; + var packet = new PetInfoResponseRef(connection.Output.GetSpan(length)[..length]); + packet.Pet = @pet; + packet.Storage = @storage; + packet.ItemSlot = @itemSlot; + packet.Level = @level; + packet.Experience = @experience; + packet.Health = @health; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The opponent id. + /// The opponent name. + /// + /// Is sent by the server when: After the client sent a DuelStartRequest, and it either failed or the requested player sent a response. + /// Causes reaction on client side: The client shows the started or aborted duel. + /// + public static async ValueTask SendDuelStartResultAsync(this IConnection? connection, DuelStartResult.DuelStartResultType @result, ushort @opponentId, string @opponentName) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelStartResultRef.Length; + var packet = new DuelStartResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.OpponentId = @opponentId; + packet.OpponentName = @opponentName; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The requester id. + /// The requester name. + /// + /// Is sent by the server when: After another client sent a DuelStartRequest, to ask the requested player for a response. + /// Causes reaction on client side: The client shows the duel request. + /// + public static async ValueTask SendDuelStartRequestAsync(this IConnection? connection, ushort @requesterId, string @requesterName) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelStartRequestRef.Length; + var packet = new DuelStartRequestRef(connection.Output.GetSpan(length)[..length]); + packet.RequesterId = @requesterId; + packet.RequesterName = @requesterName; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The opponent id. + /// The opponent name. + /// The result. + /// + /// Is sent by the server when: After a duel ended. + /// Causes reaction on client side: The client updates its state. + /// + public static async ValueTask SendDuelEndAsync(this IConnection? connection, ushort @opponentId, string @opponentName, byte @result = 0) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelEndRef.Length; + var packet = new DuelEndRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.OpponentId = @opponentId; + packet.OpponentName = @opponentName; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player 1 id. + /// The player 2 id. + /// The player 1 score. + /// The player 2 score. + /// + /// Is sent by the server when: When the score of the duel has been changed. + /// Causes reaction on client side: The client updates the displayed duel score. + /// + public static async ValueTask SendDuelScoreAsync(this IConnection? connection, ushort @player1Id, ushort @player2Id, byte @player1Score, byte @player2Score) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelScoreRef.Length; + var packet = new DuelScoreRef(connection.Output.GetSpan(length)[..length]); + packet.Player1Id = @player1Id; + packet.Player2Id = @player2Id; + packet.Player1Score = @player1Score; + packet.Player2Score = @player2Score; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The player 1 id. + /// The player 2 id. + /// The player 1 health percentage. + /// The player 2 health percentage. + /// The player 1 shield percentage. + /// The player 2 shield percentage. + /// + /// Is sent by the server when: When the health/shield of the duel players has been changed. + /// Causes reaction on client side: The client updates the displayed health and shield bars. + /// + public static async ValueTask SendDuelHealthUpdateAsync(this IConnection? connection, ushort @player1Id, ushort @player2Id, byte @player1HealthPercentage, byte @player2HealthPercentage, byte @player1ShieldPercentage, byte @player2ShieldPercentage) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelHealthUpdateRef.Length; + var packet = new DuelHealthUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.Player1Id = @player1Id; + packet.Player2Id = @player2Id; + packet.Player1HealthPercentage = @player1HealthPercentage; + packet.Player2HealthPercentage = @player2HealthPercentage; + packet.Player1ShieldPercentage = @player1ShieldPercentage; + packet.Player2ShieldPercentage = @player2ShieldPercentage; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The room index. + /// The player 1 name. + /// The player 2 name. + /// The player 1 id. + /// The player 2 id. + /// + /// Is sent by the server when: When the duel starts. + /// Causes reaction on client side: The client initializes the duel state. + /// + public static async ValueTask SendDuelInitAsync(this IConnection? connection, byte @result, byte @roomIndex, string @player1Name, string @player2Name, ushort @player1Id, ushort @player2Id) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelInitRef.Length; + var packet = new DuelInitRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.RoomIndex = @roomIndex; + packet.Player1Name = @player1Name; + packet.Player2Name = @player2Name; + packet.Player1Id = @player1Id; + packet.Player2Id = @player2Id; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the server when: When the duel starts, after the DuelInit message. + /// Causes reaction on client side: The client updates the displayed health and shield bars. + /// + public static async ValueTask SendDuelHealthBarInitAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelHealthBarInitRef.Length; + var packet = new DuelHealthBarInitRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The name. + /// + /// Is sent by the server when: When a spectator joins a duel. + /// Causes reaction on client side: The client updates the list of spectators. + /// + public static async ValueTask SendDuelSpectatorAddedAsync(this IConnection? connection, string @name) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelSpectatorAddedRef.Length; + var packet = new DuelSpectatorAddedRef(connection.Output.GetSpan(length)[..length]); + packet.Name = @name; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The name. + /// + /// Is sent by the server when: When a spectator joins a duel. + /// Causes reaction on client side: The client updates the list of spectators. + /// + public static async ValueTask SendDuelSpectatorRemovedAsync(this IConnection? connection, string @name) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelSpectatorRemovedRef.Length; + var packet = new DuelSpectatorRemovedRef(connection.Output.GetSpan(length)[..length]); + packet.Name = @name; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The winner. + /// The loser. + /// + /// Is sent by the server when: When the duel finished. + /// Causes reaction on client side: The client shows the winner and loser names. + /// + public static async ValueTask SendDuelFinishedAsync(this IConnection? connection, string @winner, string @loser) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DuelFinishedRef.Length; + var packet = new DuelFinishedRef(connection.Output.GetSpan(length)[..length]); + packet.Winner = @winner; + packet.Loser = @loser; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The object id. + /// The stage. + /// The skill number. + /// + /// Is sent by the server when: After a player started a skill which needs to load up, like Nova. + /// Causes reaction on client side: The client may show the loading intensity. + /// + public static async ValueTask SendSkillStageUpdateAsync(this IConnection? connection, ushort @objectId, byte @stage, byte @skillNumber = 0x28) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = SkillStageUpdateRef.Length; + var packet = new SkillStageUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.ObjectId = @objectId; + packet.SkillNumber = @skillNumber; + packet.Stage = @stage; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: The player requested to enter the illusion temple event. + /// Causes reaction on client side: The client shows the result. + /// + public static async ValueTask SendIllusionTempleEnterResultAsync(this IConnection? connection, byte @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = IllusionTempleEnterResultRef.Length; + var packet = new IllusionTempleEnterResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// The skill number. + /// The source object id. + /// The target object id. + /// + /// Is sent by the server when: A player requested to use a specific skill in the illusion temple event. + /// Causes reaction on client side: The client shows the result. + /// + public static async ValueTask SendIllusionTempleSkillUsageResultAsync(this IConnection? connection, byte @result, ushort @skillNumber, ushort @sourceObjectId, ushort @targetObjectId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = IllusionTempleSkillUsageResultRef.Length; + var packet = new IllusionTempleSkillUsageResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + packet.SkillNumber = @skillNumber; + packet.SourceObjectId = @sourceObjectId; + packet.TargetObjectId = @targetObjectId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The user count 1. + /// The user count 2. + /// The user count 3. + /// The user count 4. + /// The user count 5. + /// The user count 6. + /// + /// Is sent by the server when: ? + /// Causes reaction on client side: The client shows the counts. + /// + public static async ValueTask SendIllusionTempleUserCountAsync(this IConnection? connection, byte @userCount1, byte @userCount2, byte @userCount3, byte @userCount4, byte @userCount5, byte @userCount6) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = IllusionTempleUserCountRef.Length; + var packet = new IllusionTempleUserCountRef(connection.Output.GetSpan(length)[..length]); + packet.UserCount1 = @userCount1; + packet.UserCount2 = @userCount2; + packet.UserCount3 = @userCount3; + packet.UserCount4 = @userCount4; + packet.UserCount5 = @userCount5; + packet.UserCount6 = @userCount6; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill points. + /// + /// Is sent by the server when: ? + /// Causes reaction on client side: The client shows the skill points. + /// + public static async ValueTask SendIllusionTempleSkillPointUpdateAsync(this IConnection? connection, byte @skillPoints) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = IllusionTempleSkillPointUpdateRef.Length; + var packet = new IllusionTempleSkillPointUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.SkillPoints = @skillPoints; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The skill number. + /// The object index. + /// + /// Is sent by the server when: ? + /// Causes reaction on client side: The client shows the skill points. + /// + public static async ValueTask SendIllusionTempleSkillEndedAsync(this IConnection? connection, ushort @skillNumber, ushort @objectIndex) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = IllusionTempleSkillEndedRef.Length; + var packet = new IllusionTempleSkillEndedRef(connection.Output.GetSpan(length)[..length]); + packet.SkillNumber = @skillNumber; + packet.ObjectIndex = @objectIndex; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The user index. + /// The name. + /// + /// Is sent by the server when: ? + /// Causes reaction on client side: ?. + /// + public static async ValueTask SendIllusionTempleHolyItemRelicsAsync(this IConnection? connection, ushort @userIndex, string @name) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = IllusionTempleHolyItemRelicsRef.Length; + var packet = new IllusionTempleHolyItemRelicsRef(connection.Output.GetSpan(length)[..length]); + packet.UserIndex = @userIndex; + packet.Name = @name; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The temple number. + /// The state. + /// + /// Is sent by the server when: ? + /// Causes reaction on client side: The client shows the skill points. + /// + public static async ValueTask SendIllusionTempleSkillEndAsync(this IConnection? connection, byte @templeNumber, byte @state) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = IllusionTempleSkillEndRef.Length; + var packet = new IllusionTempleSkillEndRef(connection.Output.GetSpan(length)[..length]); + packet.TempleNumber = @templeNumber; + packet.State = @state; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The flag, if money should be consumed. If this is 'true', setting PauseStatus to 'false' doesn't cause starting the helper. + /// The money. + /// The pause status. A value of 'true' always works to stop the helper. However, it can only be started, with ConsumeMoney set to 'false'. + /// + /// Is sent by the server when: The server validated or changed the status of the MU Helper. + /// Causes reaction on client side: The client toggle the MU Helper status. + /// + public static async ValueTask SendMuHelperStatusUpdateAsync(this IConnection? connection, bool @consumeMoney, uint @money, bool @pauseStatus) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MuHelperStatusUpdateRef.Length; + var packet = new MuHelperStatusUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.ConsumeMoney = @consumeMoney; + packet.Money = @money; + packet.PauseStatus = @pauseStatus; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The helper data. + /// + /// Is sent by the server when: The server saved the users MU Helper data. + /// Causes reaction on client side: The user wants to save the MU Helper data. + /// + public static async ValueTask SendMuHelperConfigurationDataAsync(this IConnection? connection, Memory @helperData) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MuHelperConfigurationDataRef.Length; + var packet = new MuHelperConfigurationDataRef(connection.Output.GetSpan(length)[..length]); + @helperData.Span.CopyTo(packet.HelperData); + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The friend name. + /// The server id on which the player currently is online. 0xFF means offline. + /// + /// Is sent by the server when: After a friend has been added to the friend list. + /// Causes reaction on client side: The friend appears in the friend list. + /// + public static async ValueTask SendFriendAddedAsync(this IConnection? connection, string @friendName, byte @serverId = 0xFF) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = FriendAddedRef.Length; + var packet = new FriendAddedRef(connection.Output.GetSpan(length)[..length]); + packet.FriendName = @friendName; + packet.ServerId = @serverId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The requester. + /// + /// Is sent by the server when: After a player has requested to add another player as friend. This other player gets this message. + /// Causes reaction on client side: The friend request appears on the user interface. + /// + public static async ValueTask SendFriendRequestAsync(this IConnection? connection, string @requester) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = FriendRequestRef.Length; + var packet = new FriendRequestRef(connection.Output.GetSpan(length)[..length]); + packet.Requester = @requester; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The friend name. + /// + /// Is sent by the server when: After a friend has been removed from the friend list. + /// Causes reaction on client side: The friend is removed from the friend list. + /// + public static async ValueTask SendFriendDeletedAsync(this IConnection? connection, string @friendName) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = FriendDeletedRef.Length; + var packet = new FriendDeletedRef(connection.Output.GetSpan(length)[..length]); + packet.FriendName = @friendName; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The friend name. + /// The server id on which the player currently is online. 0xFF means offline. + /// + /// Is sent by the server when: After a friend has been added to the friend list. + /// Causes reaction on client side: The friend appears in the friend list. + /// + public static async ValueTask SendFriendOnlineStateUpdateAsync(this IConnection? connection, string @friendName, byte @serverId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = FriendOnlineStateUpdateRef.Length; + var packet = new FriendOnlineStateUpdateRef(connection.Output.GetSpan(length)[..length]); + packet.FriendName = @friendName; + packet.ServerId = @serverId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The letter id. + /// The result. + /// + /// Is sent by the server when: After the player requested to send a letter to another player. + /// Causes reaction on client side: Depending on the result, the letter send dialog closes or an error message appears. + /// + public static async ValueTask SendLetterSendResponseAsync(this IConnection? connection, uint @letterId, LetterSendResponse.LetterSendRequestResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = LetterSendResponseRef.Length; + var packet = new LetterSendResponseRef(connection.Output.GetSpan(length)[..length]); + packet.LetterId = @letterId; + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The letter index. + /// The sender name. + /// The timestamp. + /// The subject. + /// The state. + /// + /// Is sent by the server when: After a letter has been received or after the player entered the game with a character. + /// Causes reaction on client side: The letter appears in the letter list. + /// + public static async ValueTask SendAddLetterAsync(this IConnection? connection, ushort @letterIndex, string @senderName, string @timestamp, string @subject, AddLetter.LetterState @state) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = AddLetterRef.Length; + var packet = new AddLetterRef(connection.Output.GetSpan(length)[..length]); + packet.LetterIndex = @letterIndex; + packet.SenderName = @senderName; + packet.Timestamp = @timestamp; + packet.Subject = @subject; + packet.State = @state; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The letter index. + /// The message size. + /// The sender appearance. + /// The rotation. + /// The animation. + /// The message. + /// + /// Is sent by the server when: After the player requested to read a letter. + /// Causes reaction on client side: The letter is opened in a new dialog. + /// + public static async ValueTask SendOpenLetterAsync(this IConnection? connection, ushort @letterIndex, ushort @messageSize, Memory @senderAppearance, byte @rotation, byte @animation, string @message) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = OpenLetterRef.GetRequiredSize(message); + var packet = new OpenLetterRef(connection.Output.GetSpan(length)[..length]); + packet.LetterIndex = @letterIndex; + packet.MessageSize = @messageSize; + @senderAppearance.Span.CopyTo(packet.SenderAppearance); + packet.Rotation = @rotation; + packet.Animation = @animation; + packet.Message = @message; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The letter index. + /// The sender appearance. + /// The rotation. + /// The animation. + /// The message. + /// + /// Is sent by the server when: After the player requested to read a letter. + /// Causes reaction on client side: The letter is opened in a new dialog. + /// + public static async ValueTask SendOpenLetterExtendedAsync(this IConnection? connection, ushort @letterIndex, Memory @senderAppearance, byte @rotation, byte @animation, string @message) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = OpenLetterExtendedRef.GetRequiredSize(message); + var packet = new OpenLetterExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.LetterIndex = @letterIndex; + @senderAppearance.Span.CopyTo(packet.SenderAppearance); + packet.Rotation = @rotation; + packet.Animation = @animation; + packet.Message = @message; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The letter index. + /// The request successful. + /// + /// Is sent by the server when: After a letter has been deleted by the request of the player. + /// Causes reaction on client side: The letter is removed from the letter list. + /// + public static async ValueTask SendRemoveLetterAsync(this IConnection? connection, ushort @letterIndex, bool @requestSuccessful = true) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = RemoveLetterRef.Length; + var packet = new RemoveLetterRef(connection.Output.GetSpan(length)[..length]); + packet.RequestSuccessful = @requestSuccessful; + packet.LetterIndex = @letterIndex; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The chat server ip. + /// The chat room id. + /// The authentication token. + /// The friend name. + /// The success. + /// The type. + /// + /// Is sent by the server when: The player is invited to join a chat room on the chat server. + /// Causes reaction on client side: The game client connects to the chat server and joins the chat room with the specified authentication data. + /// + public static async ValueTask SendChatRoomConnectionInfoAsync(this IConnection? connection, string @chatServerIp, ushort @chatRoomId, uint @authenticationToken, string @friendName, bool @success, byte @type = 1) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ChatRoomConnectionInfoRef.Length; + var packet = new ChatRoomConnectionInfoRef(connection.Output.GetSpan(length)[..length]); + packet.ChatServerIp = @chatServerIp; + packet.ChatRoomId = @chatRoomId; + packet.AuthenticationToken = @authenticationToken; + packet.Type = @type; + packet.FriendName = @friendName; + packet.Success = @success; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The request id. + /// + /// Is sent by the server when: The player requested to add another player to his friend list and the server processed this request. + /// Causes reaction on client side: The game client knows if the invitation could be sent to the other player. + /// + public static async ValueTask SendFriendInvitationResultAsync(this IConnection? connection, bool @success, uint @requestId) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = FriendInvitationResultRef.Length; + var packet = new FriendInvitationResultRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.RequestId = @requestId; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// A number specifying the description: A) when selecting a quest in the quest list, it's the "StartingNumber"; B) when a quest has been started it's the quest number; C) when the starting number has been sent previously and the player refused to start the quest, it sends a "RefuseNumber". + /// The quest group. + /// + /// Is sent by the server when: After the game client clicked on a quest in the quest list, proceeded with a quest or refused to start a quest. + /// Causes reaction on client side: The client shows the corresponding description about the current quest step. + /// + public static async ValueTask SendQuestStepInfoAsync(this IConnection? connection, ushort @questStepNumber, ushort @questGroup) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = QuestStepInfoRef.Length; + var packet = new QuestStepInfoRef(connection.Output.GetSpan(length)[..length]); + packet.QuestStepNumber = @questStepNumber; + packet.QuestGroup = @questGroup; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The quest number. + /// The quest group. + /// The is quest completed. + /// + /// Is sent by the server when: The server acknowledges the completion of a quest. + /// Causes reaction on client side: The client shows the success and possibly requests for the next available quests. + /// + public static async ValueTask SendQuestCompletionResponseAsync(this IConnection? connection, ushort @questNumber, ushort @questGroup, bool @isQuestCompleted) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = QuestCompletionResponseRef.Length; + var packet = new QuestCompletionResponseRef(connection.Output.GetSpan(length)[..length]); + packet.QuestNumber = @questNumber; + packet.QuestGroup = @questGroup; + packet.IsQuestCompleted = @isQuestCompleted; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The current quest number. In this message, it's always 0, because the group is relevant for the client. + /// The quest group. + /// + /// Is sent by the server when: The server acknowledges the requested cancellation of a quest. + /// Causes reaction on client side: The client resets the state of the quest and can request a new list of available quests again. This list would then probably contain the cancelled quest again. + /// + public static async ValueTask SendQuestCancelledAsync(this IConnection? connection, ushort @questNumber, ushort @questGroup) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = QuestCancelledRef.Length; + var packet = new QuestCancelledRef(connection.Output.GetSpan(length)[..length]); + packet.QuestNumber = @questNumber; + packet.QuestGroup = @questGroup; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The npc number. + /// The gens contribution points. + /// + /// Is sent by the server when: The server acknowledges the requested opening of an npc dialog. + /// Causes reaction on client side: The client opens the dialog of the specified npc. + /// + public static async ValueTask SendOpenNpcDialogAsync(this IConnection? connection, ushort @npcNumber, uint @gensContributionPoints) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = OpenNpcDialogRef.Length; + var packet = new OpenNpcDialogRef(connection.Output.GetSpan(length)[..length]); + packet.NpcNumber = @npcNumber; + packet.GensContributionPoints = @gensContributionPoints; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: The player requested to enter the devil square mini game through the Charon NPC. + /// Causes reaction on client side: In case it failed, it shows the corresponding error message. + /// + public static async ValueTask SendDevilSquareEnterResultAsync(this IConnection? connection, DevilSquareEnterResult.EnterResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = DevilSquareEnterResultRef.Length; + var packet = new DevilSquareEnterResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The game type. + /// The remaining entering time minutes. + /// The user count. + /// Just used for Chaos Castle. In this case, this field contains the lower byte of the remaining minutes. For other event types, this field is not used. + /// + /// Is sent by the server when: The player requests to get the current opening state of a mini game event, by clicking on an ticket item. + /// Causes reaction on client side: The opening state of the event (remaining entering time, etc.) is shown at the client. + /// + public static async ValueTask SendMiniGameOpeningStateAsync(this IConnection? connection, MiniGameType @gameType, byte @remainingEnteringTimeMinutes, byte @userCount, byte @remainingEnteringTimeMinutesLow) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MiniGameOpeningStateRef.Length; + var packet = new MiniGameOpeningStateRef(connection.Output.GetSpan(length)[..length]); + packet.GameType = @gameType; + packet.RemainingEnteringTimeMinutes = @remainingEnteringTimeMinutes; + packet.UserCount = @userCount; + packet.RemainingEnteringTimeMinutesLow = @remainingEnteringTimeMinutesLow; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The state. + /// + /// Is sent by the server when: The state of a mini game event is about to change in 30 seconds. + /// Causes reaction on client side: The client side shows a message about the changing state. + /// + public static async ValueTask SendUpdateMiniGameStateAsync(this IConnection? connection, UpdateMiniGameState.MiniGameTypeState @state) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = UpdateMiniGameStateRef.Length; + var packet = new UpdateMiniGameStateRef(connection.Output.GetSpan(length)[..length]); + packet.State = @state; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The success. + /// The player name. + /// The total score. + /// The bonus experience. + /// The bonus money. + /// The type. + /// + /// Is sent by the server when: The blood castle mini game ended and the score of the player is sent to the player. + /// Causes reaction on client side: The score is shown at the client. + /// + public static async ValueTask SendBloodCastleScoreAsync(this IConnection? connection, bool @success, string @playerName, uint @totalScore, uint @bonusExperience, uint @bonusMoney, byte @type = 0xFF) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = BloodCastleScoreRef.Length; + var packet = new BloodCastleScoreRef(connection.Output.GetSpan(length)[..length]); + packet.Success = @success; + packet.Type = @type; + packet.PlayerName = @playerName; + packet.TotalScore = @totalScore; + packet.BonusExperience = @bonusExperience; + packet.BonusMoney = @bonusMoney; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: The player requested to enter the blood castle mini game through the Archangel Messenger NPC. + /// Causes reaction on client side: In case it failed, it shows the corresponding error message. + /// + public static async ValueTask SendBloodCastleEnterResultAsync(this IConnection? connection, BloodCastleEnterResult.EnterResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = BloodCastleEnterResultRef.Length; + var packet = new BloodCastleEnterResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The state. + /// The remain second. + /// The max monster. + /// The cur monster. + /// The item owner id. + /// The item level. + /// + /// Is sent by the server when: The state of a blood castle event is about to change. + /// Causes reaction on client side: The client side shows a message about the changing state. + /// + public static async ValueTask SendBloodCastleStateAsync(this IConnection? connection, BloodCastleState.Status @state, ushort @remainSecond, ushort @maxMonster, ushort @curMonster, ushort @itemOwnerId, byte @itemLevel) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = BloodCastleStateRef.Length; + var packet = new BloodCastleStateRef(connection.Output.GetSpan(length)[..length]); + packet.State = @state; + packet.RemainSecond = @remainSecond; + packet.MaxMonster = @maxMonster; + packet.CurMonster = @curMonster; + packet.ItemOwnerId = @itemOwnerId; + packet.ItemLevel = @itemLevel; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The result. + /// + /// Is sent by the server when: The player requested to enter the chaos castle mini game by using the 'Armor of Guardsman' item. + /// Causes reaction on client side: In case it failed, it shows the corresponding error message. + /// + public static async ValueTask SendChaosCastleEnterResultAsync(this IConnection? connection, ChaosCastleEnterResult.EnterResult @result) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ChaosCastleEnterResultRef.Length; + var packet = new ChaosCastleEnterResultRef(connection.Output.GetSpan(length)[..length]); + packet.Result = @result; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// The enable. + /// The event. + /// + /// Is sent by the server when: The state of event is about to change. + /// Causes reaction on client side: The event's effect is shown. + /// + public static async ValueTask SendMapEventStateAsync(this IConnection? connection, bool @enable, MapEventState.Events @event) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = MapEventStateRef.Length; + var packet = new MapEventStateRef(connection.Output.GetSpan(length)[..length]); + packet.Enable = @enable; + packet.Event = @event; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); } /// /// Sends a to this connection. @@ -6396,4 +6494,5 @@ int WritePacket() await connection.SendAsync(WritePacket).ConfigureAwait(false); } + } \ No newline at end of file