From 583df657fbac76b73fe9decd5041e31004ddb104 Mon Sep 17 00:00:00 2001 From: Acentech Dev Date: Tue, 21 Jul 2026 01:22:51 +0300 Subject: [PATCH] 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) --- .../AddHeykelSavasiEventUpdateSeason6.cs | 124 ++++++++++++++++++ .../Initialization/Updates/UpdateVersion.cs | 5 + 2 files changed, 129 insertions(+) create mode 100644 src/Persistence/Initialization/Updates/AddHeykelSavasiEventUpdateSeason6.cs diff --git a/src/Persistence/Initialization/Updates/AddHeykelSavasiEventUpdateSeason6.cs b/src/Persistence/Initialization/Updates/AddHeykelSavasiEventUpdateSeason6.cs new file mode 100644 index 0000000..3a2f3f9 --- /dev/null +++ b/src/Persistence/Initialization/Updates/AddHeykelSavasiEventUpdateSeason6.cs @@ -0,0 +1,124 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +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; + +/// +/// 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. +/// +[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."; + + /// + public override UpdateVersion Version => UpdateVersion.AddHeykelSavasiEventSeason6; + + /// + public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id; + + /// + public override string Name => PlugInName; + + /// + public override string Description => PlugInDescription; + + /// + public override bool IsMandatory => true; + + /// + public override DateTime CreatedAt => new(2026, 07, 21, 12, 0, 0, DateTimeKind.Utc); + + /// + 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().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(); + 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(); + 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(); + 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(); + 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(); + } +} diff --git a/src/Persistence/Initialization/Updates/UpdateVersion.cs b/src/Persistence/Initialization/Updates/UpdateVersion.cs index cc9332a..8a361b1 100644 --- a/src/Persistence/Initialization/Updates/UpdateVersion.cs +++ b/src/Persistence/Initialization/Updates/UpdateVersion.cs @@ -504,4 +504,9 @@ public enum UpdateVersion /// The version of the (Warrior Wings/Cape 12/242-247). /// AddWarriorWingsSeason6 = 99, + + /// + /// The version of the (Heykel Savasi team event: map 92, NPC 560, mini game definition). + /// + AddHeykelSavasiEventSeason6 = 100, } \ No newline at end of file