Some checks failed
.NET Core / build (push) Has been cancelled
#1 Castle flags now show the owner guild's logo: resolve owner name -> guild logo (cached), broadcast C1 B9 02 (32-byte mark) to players on the castle map every 15s. #3 War atmosphere: spawn catapult NPCs 221/222 at siege start (client renders them as siege weapons); the 0xB2/0x17 start flag already flips the map to warzone mode. All raw S6 packets, no client changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
105 lines
3.2 KiB
C#
105 lines
3.2 KiB
C#
// <copyright file="CastleSiegeStatusViewPlugIn.cs" company="MUnique">
|
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
|
// </copyright>
|
|
|
|
namespace MUnique.OpenMU.GameServer.RemoteView.CastleSiege;
|
|
|
|
using System.Runtime.InteropServices;
|
|
using MUnique.OpenMU.GameLogic.Views.CastleSiege;
|
|
using MUnique.OpenMU.Network;
|
|
using MUnique.OpenMU.PlugIns;
|
|
|
|
/// <summary>
|
|
/// The default implementation of <see cref="ICastleSiegeStatusViewPlugIn"/> which sends the Season 6
|
|
/// Castle Siege countdown packets straight to the game client as raw bytes (no packet-struct exists yet).
|
|
/// </summary>
|
|
[PlugIn]
|
|
[Display(Name = "Castle Siege Status View", Description = "Sends the Castle Siege on-map countdown/state packets (C1 B2 17 / C1 B2 1E).")]
|
|
[Guid("CA5710A0-7A1B-4C2D-8E3F-0000000017E0")]
|
|
public class CastleSiegeStatusViewPlugIn : ICastleSiegeStatusViewPlugIn
|
|
{
|
|
private readonly RemotePlayer _player;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="CastleSiegeStatusViewPlugIn"/> class.
|
|
/// </summary>
|
|
/// <param name="player">The player.</param>
|
|
public CastleSiegeStatusViewPlugIn(RemotePlayer player) => this._player = player;
|
|
|
|
/// <inheritdoc />
|
|
public async ValueTask SetBattleStateAsync(bool started)
|
|
{
|
|
var connection = this._player.Connection;
|
|
if (connection is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int WritePacket()
|
|
{
|
|
// C1 09 B2 17 <started> <crownAccessTime:4 bytes = 0>
|
|
var span = connection.Output.GetSpan(9)[..9];
|
|
span.Clear();
|
|
span[0] = 0xC1;
|
|
span[1] = 0x09;
|
|
span[2] = 0xB2;
|
|
span[3] = 0x17;
|
|
span[4] = (byte)(started ? 1 : 0);
|
|
return span.Length;
|
|
}
|
|
|
|
await connection.SendAsync(WritePacket).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async ValueTask SetTimerAsync(byte hour, byte minute)
|
|
{
|
|
var connection = this._player.Connection;
|
|
if (connection is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int WritePacket()
|
|
{
|
|
// C1 06 B2 1E <hour> <minute>
|
|
var span = connection.Output.GetSpan(6)[..6];
|
|
span[0] = 0xC1;
|
|
span[1] = 0x06;
|
|
span[2] = 0xB2;
|
|
span[3] = 0x1E;
|
|
span[4] = hour;
|
|
span[5] = minute;
|
|
return span.Length;
|
|
}
|
|
|
|
await connection.SendAsync(WritePacket).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async ValueTask SetCastleFlagAsync(ReadOnlyMemory<byte> guildLogo)
|
|
{
|
|
var connection = this._player.Connection;
|
|
if (connection is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int WritePacket()
|
|
{
|
|
// C1 24 B9 02 <32-byte guild logo>
|
|
var span = connection.Output.GetSpan(36)[..36];
|
|
span.Clear();
|
|
span[0] = 0xC1;
|
|
span[1] = 0x24;
|
|
span[2] = 0xB9;
|
|
span[3] = 0x02;
|
|
var logo = guildLogo.Span;
|
|
logo[..Math.Min(32, logo.Length)].CopyTo(span.Slice(4, 32));
|
|
return span.Length;
|
|
}
|
|
|
|
await connection.SendAsync(WritePacket).ConfigureAwait(false);
|
|
}
|
|
}
|