feat(hs): web-applicable update plugin for the Heykel Savasi event

AddHeykelSavasiEventUpdateSeason6 (version 100): adds map 92 + terrain,
statue/guard monsters, red/blue base gates, event NPC 560 in Lorencia, and
the mini-game definition to an EXISTING config via the admin Updates page
(no DB wipe). Reuses HeykelSavasiMap/HeykelSavasiInitializer; idempotent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Acentech Dev
2026-07-21 01:22:51 +03:00
parent 917831e198
commit 583df657fb
2 changed files with 129 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
// <copyright file="AddHeykelSavasiEventUpdateSeason6.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.Persistence.Initialization.Updates;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Events;
using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Adds the Heykel Savasi team event data to an existing configuration: the imported LoL arena map
/// (server number 92 / client World93) with its terrain, the statue (561) and guard (580) monster
/// definitions, the red/blue base spawn gates, the event NPC (560) placed in Lorencia, and the
/// mini-game definition. The event logic (context, packet handlers, chat commands, buffs) ships in
/// the game-logic assemblies and needs no configuration update.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("F2A7C4D9-3E1B-4A86-9C0D-5B7E2A1F8D42")]
public class AddHeykelSavasiEventUpdateSeason6 : UpdatePlugInBase
{
internal const string PlugInName = "Add Heykel Savasi event (map 92)";
internal const string PlugInDescription = "Adds the Heykel Savasi team-vs-team event: the LoL arena map (server 92 / World93) with terrain, statue+guard monsters, red/blue base spawn gates, the event NPC (560) in Lorencia, and the mini-game definition.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddHeykelSavasiEventSeason6;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2026, 07, 21, 12, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Idempotency guard: skip if the Heykel Savasi map (92) already exists.
if (gameConfiguration.Maps.Any(m => m.Number == HeykelSavasiMap.Number))
{
return;
}
// 1. Map (No 92) + terrain (Terrain93.att) + statue (561) / guard (580) monster definitions.
var mapInitializer = new HeykelSavasiMap(context, gameConfiguration);
mapInitializer.Initialize();
mapInitializer.SetSafezoneMap();
var map = gameConfiguration.Maps.First(m => m.Number == HeykelSavasiMap.Number);
// 2. Host the map on every game server configuration (fresh seeds do this automatically,
// but existing databases need it added explicitly).
var serverConfigs = await context.GetAsync<GameServerConfiguration>().ConfigureAwait(false);
foreach (var serverConfig in serverConfigs)
{
if (serverConfig.Maps.All(m => m.Number != HeykelSavasiMap.Number))
{
serverConfig.Maps.Add(map);
}
}
// 3. Red/Blue base spawn gates. The X1/Y1 anchors MUST match HeykelSavasiContext.GetTeamSpawnGate
// (Red = 42,10 top tip of the arena diamond; Blue = 42,92 bottom tip).
var redBase = context.CreateNew<ExitGate>();
map.ExitGates.Add(redBase);
redBase.Map = map;
redBase.X1 = 42;
redBase.Y1 = 10;
redBase.X2 = 42;
redBase.Y2 = 10;
redBase.Direction = Direction.South;
redBase.IsSpawnGate = true;
var blueBase = context.CreateNew<ExitGate>();
map.ExitGates.Add(blueBase);
blueBase.Map = map;
blueBase.X1 = 42;
blueBase.Y1 = 92;
blueBase.X2 = 42;
blueBase.Y2 = 92;
blueBase.Direction = Direction.North;
blueBase.IsSpawnGate = true;
// 4. Event NPC (560) placed in Lorencia.
if (gameConfiguration.Monsters.All(m => m.Number != 560))
{
var npc = context.CreateNew<MonsterDefinition>();
gameConfiguration.Monsters.Add(npc);
npc.Number = 560;
npc.Designation = "Heykel Savasi Gorevlisi";
npc.NpcWindow = NpcWindow.Undefined;
npc.ObjectKind = NpcObjectKind.PassiveNpc;
npc.SetGuid(npc.Number);
var lorencia = gameConfiguration.Maps.First(m => m.Number == Lorencia.Number);
var spawn = context.CreateNew<MonsterSpawnArea>();
lorencia.MonsterSpawns.Add(spawn);
spawn.GameMap = lorencia;
spawn.MonsterDefinition = npc;
spawn.Quantity = 1;
spawn.X1 = 140;
spawn.X2 = 140;
spawn.Y1 = 120;
spawn.Y2 = 120;
spawn.Direction = Direction.SouthEast;
spawn.SpawnTrigger = SpawnTrigger.Automatic;
}
// 5. Mini-game definition (its Entrance references a spawn gate on the map -> after step 3).
new HeykelSavasiInitializer(context, gameConfiguration).Initialize();
}
}

View File

@@ -504,4 +504,9 @@ public enum UpdateVersion
/// The version of the <see cref="AddWarriorWingsUpdateSeason6"/> (Warrior Wings/Cape 12/242-247).
/// </summary>
AddWarriorWingsSeason6 = 99,
/// <summary>
/// The version of the <see cref="AddHeykelSavasiEventUpdateSeason6"/> (Heykel Savasi team event: map 92, NPC 560, mini game definition).
/// </summary>
AddHeykelSavasiEventSeason6 = 100,
}