Compare commits
10 Commits
a61890ef6e
...
3d5d40b753
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d5d40b753 | ||
|
|
1cadaa08ab | ||
|
|
3e8cd8fdae | ||
|
|
affa4bd73b | ||
|
|
fc9ba0e594 | ||
|
|
d84b29eac3 | ||
|
|
80ec466ab1 | ||
|
|
e3d37467d5 | ||
|
|
7e21fc1fb3 | ||
|
|
4628902f11 |
34
docs/superpowers/specs/2026-07-19-poison-wings-design.md
Normal file
34
docs/superpowers/specs/2026-07-19-poison-wings-design.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Add Poison Wing & Poison Cape — Design
|
||||||
|
|
||||||
|
**Date:** 2026-07-19
|
||||||
|
**Status:** Approved
|
||||||
|
**Scope:** Add TWO wing items (basic: render + equippable + base defense). Source pack: `C:\Users\efpa\Downloads\asa devilzmu\asa devilzmu\6` (Wing241, Wing242).
|
||||||
|
|
||||||
|
## Items
|
||||||
|
|
||||||
|
| Item | Slot (group/number) | Model | Name | Classes |
|
||||||
|
|------|---------------------|-------|------|---------|
|
||||||
|
| Wing241 | 12 / 240 | MODEL_WING+240 | Poison Wing | Dark Knight |
|
||||||
|
| Wing242 | 12 / 241 | MODEL_WING+241 | Poison Cape | Dark Lord + Rage Fighter |
|
||||||
|
|
||||||
|
Wings are item group 12; `MODEL_WING = MODEL_ITEM + ITEM_WING (12*512)`, so item (12, N) auto-maps to model `MODEL_WING + N`. Numbers 240/241 are high/free in standard S6. Model files `Wing241.bmd`/`Wing242.bmd` load at `MODEL_WING+240`/`+241` (filename `Wing{N}` where N=number+1).
|
||||||
|
|
||||||
|
## Client (vendor/MuMain → D:\AdaMu\AdaMu)
|
||||||
|
|
||||||
|
1. `ZzzOpenData.cpp OpenItems()`: `AccessModel(MODEL_WING+240, L"Data\\Item\\", L"Wing", 241)` and `+241 → "Wing242"`. `OpenItemTextures()`: `OpenTexture(MODEL_WING+240/241, L"Item\\")`.
|
||||||
|
2. Copy `Wing241.bmd` + its texture, `Wing242.bmd` + its texture → `Data\Item\`. Extract the BMD's referenced texture names (decrypt) and copy the matching files (the pack ships `.ozt`/`.tga`; the engine loads the encrypted `.OZT`/`.OZJ`, so provide those; missing effect textures now log instead of blocking).
|
||||||
|
3. `Item_eng.bmd`: add name/stat records at slots 12/240 ("Poison Wing", DK) and 12/241 ("Poison Cape", DL+RF), via the 84-byte-record binary patcher (see `android-add-new-weapon-item`). Slot field = wing slot.
|
||||||
|
4. Rebuild `main.exe` + deploy.
|
||||||
|
|
||||||
|
## Server (AdamuSw)
|
||||||
|
|
||||||
|
5. `VersionSeasonSix/Items/Wings.cs`: new method `CreatePoisonWings()` with two `CreateWing` calls — `CreateWing(240, W, H, "Poison Wing", ...DK flag...)` and `CreateWing(241, W, H, "Poison Cape", ...DL+RF flags...)`, basic defense + level table, minimal options. W/H from the item icon size (default 12x12 or per pack).
|
||||||
|
6. Register in `Wings.Initialize()` (fresh seed) + update-plugin `AddPoisonWingsUpdateSeason6.cs` (UpdateVersion=98, idempotency guard on 12/240).
|
||||||
|
7. `docker build` + apply via AdminPanel → Updates (existing DB) / auto on fresh seed.
|
||||||
|
|
||||||
|
## Verify
|
||||||
|
|
||||||
|
Give the wings to a character (AdminPanel/inventory edit), equip in the wing slot, confirm: correct name/stats in tooltip (from Item_eng.bmd), 3D model renders on the character's back, class restriction enforced.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
Wing options (excellent), flight, per-level ancient/socket bonuses, balance. Just render + equip + base defense.
|
||||||
@@ -24,6 +24,11 @@
|
|||||||
<None Remove="Resources\075_Terrain5.att" />
|
<None Remove="Resources\075_Terrain5.att" />
|
||||||
<None Remove="Resources\075_Terrain8.att" />
|
<None Remove="Resources\075_Terrain8.att" />
|
||||||
<None Remove="Resources\Terrain1.att" />
|
<None Remove="Resources\Terrain1.att" />
|
||||||
|
<None Remove="Resources\Terrain83.att" />
|
||||||
|
<None Remove="Resources\Terrain84.att" />
|
||||||
|
<None Remove="Resources\Terrain85.att" />
|
||||||
|
<None Remove="Resources\Terrain86.att" />
|
||||||
|
<None Remove="Resources\Terrain87.att" />
|
||||||
<None Remove="Resources\Terrain10_1.att" />
|
<None Remove="Resources\Terrain10_1.att" />
|
||||||
<None Remove="Resources\Terrain10_2.att" />
|
<None Remove="Resources\Terrain10_2.att" />
|
||||||
<None Remove="Resources\Terrain10_3.att" />
|
<None Remove="Resources\Terrain10_3.att" />
|
||||||
@@ -103,6 +108,11 @@
|
|||||||
<EmbeddedResource Include="Resources\075_Terrain5.att" />
|
<EmbeddedResource Include="Resources\075_Terrain5.att" />
|
||||||
<EmbeddedResource Include="Resources\075_Terrain8.att" />
|
<EmbeddedResource Include="Resources\075_Terrain8.att" />
|
||||||
<EmbeddedResource Include="Resources\Terrain1.att" />
|
<EmbeddedResource Include="Resources\Terrain1.att" />
|
||||||
|
<EmbeddedResource Include="Resources\Terrain83.att" />
|
||||||
|
<EmbeddedResource Include="Resources\Terrain84.att" />
|
||||||
|
<EmbeddedResource Include="Resources\Terrain85.att" />
|
||||||
|
<EmbeddedResource Include="Resources\Terrain86.att" />
|
||||||
|
<EmbeddedResource Include="Resources\Terrain87.att" />
|
||||||
<EmbeddedResource Include="Resources\Terrain10_4.att" />
|
<EmbeddedResource Include="Resources\Terrain10_4.att" />
|
||||||
<EmbeddedResource Include="Resources\Terrain10_3.att" />
|
<EmbeddedResource Include="Resources\Terrain10_3.att" />
|
||||||
<EmbeddedResource Include="Resources\Terrain10_2.att" />
|
<EmbeddedResource Include="Resources\Terrain10_2.att" />
|
||||||
|
|||||||
BIN
src/Persistence/Initialization/Resources/Terrain83.att
Normal file
BIN
src/Persistence/Initialization/Resources/Terrain83.att
Normal file
Binary file not shown.
BIN
src/Persistence/Initialization/Resources/Terrain84.att
Normal file
BIN
src/Persistence/Initialization/Resources/Terrain84.att
Normal file
Binary file not shown.
BIN
src/Persistence/Initialization/Resources/Terrain85.att
Normal file
BIN
src/Persistence/Initialization/Resources/Terrain85.att
Normal file
Binary file not shown.
BIN
src/Persistence/Initialization/Resources/Terrain86.att
Normal file
BIN
src/Persistence/Initialization/Resources/Terrain86.att
Normal file
Binary file not shown.
BIN
src/Persistence/Initialization/Resources/Terrain87.att
Normal file
BIN
src/Persistence/Initialization/Resources/Terrain87.att
Normal file
Binary file not shown.
@@ -0,0 +1,91 @@
|
|||||||
|
// <copyright file="AddArkaniaMapUpdateSeason6.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.Maps;
|
||||||
|
using MUnique.OpenMU.PlugIns;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the imported Arkania map (number 83): the map definition with its terrain,
|
||||||
|
/// a spawn gate on a walkable tile, and a /move warp entry so players can reach it.
|
||||||
|
/// Monsters and NPCs are added afterwards via the admin panel.
|
||||||
|
/// </summary>
|
||||||
|
[PlugIn]
|
||||||
|
[Display(Name = PlugInName, Description = PlugInDescription)]
|
||||||
|
[Guid("2C8B0A61-6B2E-4D7A-9E3C-7F1A4B2D9E01")]
|
||||||
|
public class AddArkaniaMapUpdateSeason6 : UpdatePlugInBase
|
||||||
|
{
|
||||||
|
internal const string PlugInName = "Add Arkania map (83)";
|
||||||
|
internal const string PlugInDescription = "Adds the imported Arkania map (number 83) with terrain, a spawn gate, and a /move warp. Monsters/NPCs are added via the admin panel.";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override UpdateVersion Version => UpdateVersion.AddArkaniaMapSeason6;
|
||||||
|
|
||||||
|
/// <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, 19, 12, 0, 0, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
// Idempotency guard: skip if Arkania (82) already exists.
|
||||||
|
if (gameConfiguration.Maps.Any(m => m.Number == Arkania.Number))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var initializer = new Arkania(context, gameConfiguration);
|
||||||
|
initializer.Initialize(); // creates map 82 + loads terrain from Terrain83.att
|
||||||
|
initializer.SetSafezoneMap();
|
||||||
|
|
||||||
|
var arkania = gameConfiguration.Maps.First(m => m.Number == Arkania.Number);
|
||||||
|
|
||||||
|
// 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 != Arkania.Number))
|
||||||
|
{
|
||||||
|
serverConfig.Maps.Add(arkania);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn gate on a walkable tile (center of the map; attr 0x00, verified walkable).
|
||||||
|
var spawnGate = context.CreateNew<ExitGate>();
|
||||||
|
arkania.ExitGates.Add(spawnGate);
|
||||||
|
spawnGate.Map = arkania;
|
||||||
|
spawnGate.X1 = 128;
|
||||||
|
spawnGate.Y1 = 128;
|
||||||
|
spawnGate.X2 = 128;
|
||||||
|
spawnGate.Y2 = 128;
|
||||||
|
spawnGate.IsSpawnGate = true;
|
||||||
|
|
||||||
|
// /move warp entry.
|
||||||
|
if (gameConfiguration.WarpList.All(w => w.Name != Arkania.Name))
|
||||||
|
{
|
||||||
|
var warp = context.CreateNew<WarpInfo>();
|
||||||
|
gameConfiguration.WarpList.Add(warp);
|
||||||
|
warp.Index = 83;
|
||||||
|
warp.Name = Arkania.Name;
|
||||||
|
warp.Costs = 5000;
|
||||||
|
warp.LevelRequirement = 10;
|
||||||
|
warp.Gate = spawnGate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
// <copyright file="AddImportedMapsUpdateSeason6.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.Maps;
|
||||||
|
using MUnique.OpenMU.PlugIns;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the four imported maps Acheron (83), Debenter (84), Uruk (85) and Ferea (86):
|
||||||
|
/// the map definitions with their terrain, a spawn gate and a /move warp each, and
|
||||||
|
/// assigns each to the game server configuration so it is hosted and its spawns run.
|
||||||
|
/// Monsters and NPCs are added afterwards via the admin panel.
|
||||||
|
/// </summary>
|
||||||
|
[PlugIn]
|
||||||
|
[Display(Name = PlugInName, Description = PlugInDescription)]
|
||||||
|
[Guid("3D9C1B72-7C3F-4E8B-AF14-E29D47A8B012")]
|
||||||
|
public class AddImportedMapsUpdateSeason6 : UpdatePlugInBase
|
||||||
|
{
|
||||||
|
internal const string PlugInName = "Add imported maps (Acheron, Debenter, Uruk, Ferea)";
|
||||||
|
internal const string PlugInDescription = "Adds maps 83-86 (Acheron, Debenter, Uruk, Ferea) with terrain, spawn gate and /move warp, hosted by the game server. Monsters/NPCs are added via the admin panel.";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override UpdateVersion Version => UpdateVersion.AddImportedMapsSeason6;
|
||||||
|
|
||||||
|
/// <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, 19, 15, 0, 0, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
var serverConfigs = (await context.GetAsync<GameServerConfiguration>().ConfigureAwait(false)).ToList();
|
||||||
|
|
||||||
|
AddMap(context, gameConfiguration, serverConfigs, new Acheron(context, gameConfiguration), Acheron.Number, Acheron.Name, 84, 127, 126);
|
||||||
|
AddMap(context, gameConfiguration, serverConfigs, new Debenter(context, gameConfiguration), Debenter.Number, Debenter.Name, 85, 132, 105);
|
||||||
|
AddMap(context, gameConfiguration, serverConfigs, new Uruk(context, gameConfiguration), Uruk.Number, Uruk.Name, 86, 128, 128);
|
||||||
|
AddMap(context, gameConfiguration, serverConfigs, new Ferea(context, gameConfiguration), Ferea.Number, Ferea.Name, 87, 128, 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddMap(IContext context, GameConfiguration gameConfiguration, IList<GameServerConfiguration> serverConfigs, IMapInitializer initializer, byte number, string name, short warpIndex, byte x, byte y)
|
||||||
|
{
|
||||||
|
// Idempotency guard: skip if the map already exists.
|
||||||
|
if (gameConfiguration.Maps.Any(m => m.Number == number))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
initializer.Initialize(); // creates the map + loads terrain from Terrain{number+1}.att
|
||||||
|
initializer.SetSafezoneMap();
|
||||||
|
|
||||||
|
var map = gameConfiguration.Maps.First(m => m.Number == number);
|
||||||
|
|
||||||
|
// Spawn gate on a walkable tile.
|
||||||
|
var spawnGate = context.CreateNew<ExitGate>();
|
||||||
|
map.ExitGates.Add(spawnGate);
|
||||||
|
spawnGate.Map = map;
|
||||||
|
spawnGate.X1 = x;
|
||||||
|
spawnGate.Y1 = y;
|
||||||
|
spawnGate.X2 = x;
|
||||||
|
spawnGate.Y2 = y;
|
||||||
|
spawnGate.IsSpawnGate = true;
|
||||||
|
|
||||||
|
// /move warp entry.
|
||||||
|
if (gameConfiguration.WarpList.All(w => w.Name != name))
|
||||||
|
{
|
||||||
|
var warp = context.CreateNew<WarpInfo>();
|
||||||
|
gameConfiguration.WarpList.Add(warp);
|
||||||
|
warp.Index = warpIndex;
|
||||||
|
warp.Name = name;
|
||||||
|
warp.Costs = 5000;
|
||||||
|
warp.LevelRequirement = 10;
|
||||||
|
warp.Gate = spawnGate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host the map on every game server configuration (fresh seeds do this
|
||||||
|
// automatically, but existing databases need it added explicitly).
|
||||||
|
foreach (var serverConfig in serverConfigs)
|
||||||
|
{
|
||||||
|
if (serverConfig.Maps.All(m => m.Number != number))
|
||||||
|
{
|
||||||
|
serverConfig.Maps.Add(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// <copyright file="AddPoisonWingsUpdateSeason6.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.Items;
|
||||||
|
using MUnique.OpenMU.PlugIns;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds two imported wings at free item slots: Poison Wing (12/240, Dark Knight) and
|
||||||
|
/// Poison Cape (12/241, Dark Lord + Rage Fighter). Basic wings: base defense, class
|
||||||
|
/// restriction, CanFly; no wing options.
|
||||||
|
/// </summary>
|
||||||
|
[PlugIn]
|
||||||
|
[Display(Name = PlugInName, Description = PlugInDescription)]
|
||||||
|
[Guid("7E4A2C63-9B1D-4F8A-A5C6-1D3E8F2B4A07")]
|
||||||
|
public class AddPoisonWingsUpdateSeason6 : UpdatePlugInBase
|
||||||
|
{
|
||||||
|
internal const string PlugInName = "Add Poison Wing & Poison Cape (12/240, 12/241)";
|
||||||
|
internal const string PlugInDescription = "Adds Poison Wing (12/240, Dark Knight) and Poison Cape (12/241, Dark Lord + Rage Fighter) as high-tier wings (defense, 3rd Wing Options, damage increase, fast flight).";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override UpdateVersion Version => UpdateVersion.AddPoisonWingsSeason6;
|
||||||
|
|
||||||
|
/// <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, 19, 17, 0, 0, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
#pragma warning disable CS1998
|
||||||
|
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
|
||||||
|
#pragma warning restore CS1998
|
||||||
|
{
|
||||||
|
// Idempotency guard: skip if Poison Wing (12/240) already exists.
|
||||||
|
if (gameConfiguration.Items.Any(i => i.Group == 12 && i.Number == 240))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new Wings(context, gameConfiguration).CreatePoisonWings();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// <copyright file="AddWarriorWingsUpdateSeason6.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.Items;
|
||||||
|
using MUnique.OpenMU.PlugIns;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds six imported high-tier "Warrior" wings at free item slots (12/242-247), one per class line:
|
||||||
|
/// Wizard (Soul Master), Magic (Duel Master), Elf (High Elf), Summoner (Dimension Master),
|
||||||
|
/// Warrior Cape (Lord Emperor + Fist Master) and Knight (Blade Master). Each carries base defense,
|
||||||
|
/// class restriction, 3rd Wing Options, damage increase and fast flight.
|
||||||
|
/// </summary>
|
||||||
|
[PlugIn]
|
||||||
|
[Display(Name = PlugInName, Description = PlugInDescription)]
|
||||||
|
[Guid("8F5B3D74-AC2E-4F9B-B6D7-2E4F9A3C5B18")]
|
||||||
|
public class AddWarriorWingsUpdateSeason6 : UpdatePlugInBase
|
||||||
|
{
|
||||||
|
internal const string PlugInName = "Add Warrior Wings & Cape (12/242-247)";
|
||||||
|
internal const string PlugInDescription = "Adds six high-tier Warrior wings (12/242-247): Wizard, Magic, Elf, Summoner, Warrior Cape (DL+RF) and Knight, each with defense, 3rd Wing Options, damage increase and fast flight.";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override UpdateVersion Version => UpdateVersion.AddWarriorWingsSeason6;
|
||||||
|
|
||||||
|
/// <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, 19, 20, 0, 0, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
#pragma warning disable CS1998
|
||||||
|
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
|
||||||
|
#pragma warning restore CS1998
|
||||||
|
{
|
||||||
|
// Idempotency guard: skip if the first Warrior wing (12/242) already exists.
|
||||||
|
if (gameConfiguration.Items.Any(i => i.Group == 12 && i.Number == 242))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new Wings(context, gameConfiguration).CreateWarriorWings();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -484,4 +484,24 @@ public enum UpdateVersion
|
|||||||
/// The version of the <see cref="AddImperialWeapons050UpdateSeason6"/>.
|
/// The version of the <see cref="AddImperialWeapons050UpdateSeason6"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AddImperialWeapons050Season6 = 95,
|
AddImperialWeapons050Season6 = 95,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The version of the <see cref="AddArkaniaMapUpdateSeason6"/>.
|
||||||
|
/// </summary>
|
||||||
|
AddArkaniaMapSeason6 = 96,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The version of the <see cref="AddImportedMapsUpdateSeason6"/> (Acheron, Debenter, Uruk, Ferea).
|
||||||
|
/// </summary>
|
||||||
|
AddImportedMapsSeason6 = 97,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The version of the <see cref="AddPoisonWingsUpdateSeason6"/> (Poison Wing 12/240, Poison Cape 12/241).
|
||||||
|
/// </summary>
|
||||||
|
AddPoisonWingsSeason6 = 98,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The version of the <see cref="AddWarriorWingsUpdateSeason6"/> (Warrior Wings/Cape 12/242-247).
|
||||||
|
/// </summary>
|
||||||
|
AddWarriorWingsSeason6 = 99,
|
||||||
}
|
}
|
||||||
@@ -100,6 +100,11 @@ public class GameMapsInitializer : GameMapsInitializerBase
|
|||||||
yield return typeof(FortressOfImperialGuardian2);
|
yield return typeof(FortressOfImperialGuardian2);
|
||||||
yield return typeof(FortressOfImperialGuardian3);
|
yield return typeof(FortressOfImperialGuardian3);
|
||||||
yield return typeof(FortressOfImperialGuardian4);
|
yield return typeof(FortressOfImperialGuardian4);
|
||||||
|
yield return typeof(Arkania);
|
||||||
|
yield return typeof(Acheron);
|
||||||
|
yield return typeof(Debenter);
|
||||||
|
yield return typeof(Uruk);
|
||||||
|
yield return typeof(Ferea);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,6 +102,64 @@ public class Wings : WingsInitializerBase
|
|||||||
this.CreateWing(40, 2, 3, "Cape of Emperor", 150, 45, 220, 400, 0, 0, 0, 0, 3, 0, 0, this.BuildOptions((0b00, OptionType.HealthRecover), (0b11, OptionType.PhysDamage), (0b10, OptionType.Defense)), 39, 24, this._damageIncreaseByLevelTable, thirdWingOptions);
|
this.CreateWing(40, 2, 3, "Cape of Emperor", 150, 45, 220, 400, 0, 0, 0, 0, 3, 0, 0, this.BuildOptions((0b00, OptionType.HealthRecover), (0b11, OptionType.PhysDamage), (0b10, OptionType.Defense)), 39, 24, this._damageIncreaseByLevelTable, thirdWingOptions);
|
||||||
this.CreateWing(43, 4, 3, "Wing of Dimension", 150, 45, 220, 400, 0, 0, 0, 0, 0, 3, 0, this.BuildOptions((0b00, OptionType.HealthRecover), (0b11, OptionType.WizDamage), (0b10, OptionType.CurseDamage)), 39, 39, this._damageIncreaseByLevelTable, thirdWingOptions);
|
this.CreateWing(43, 4, 3, "Wing of Dimension", 150, 45, 220, 400, 0, 0, 0, 0, 0, 3, 0, this.BuildOptions((0b00, OptionType.HealthRecover), (0b11, OptionType.WizDamage), (0b10, OptionType.CurseDamage)), 39, 39, this._damageIncreaseByLevelTable, thirdWingOptions);
|
||||||
this.CreateWing(50, 2, 3, "Cape of Overrule", 150, 45, 220, 400, 0, 0, 0, 0, 0, 0, 3, this.BuildOptions((0b00, OptionType.HealthRecover), (0b11, OptionType.PhysDamage), (0b10, OptionType.Defense)), 39, 39, this._damageIncreaseByLevelTable, thirdWingOptions);
|
this.CreateWing(50, 2, 3, "Cape of Overrule", 150, 45, 220, 400, 0, 0, 0, 0, 0, 0, 3, this.BuildOptions((0b00, OptionType.HealthRecover), (0b11, OptionType.PhysDamage), (0b10, OptionType.Defense)), 39, 39, this._damageIncreaseByLevelTable, thirdWingOptions);
|
||||||
|
|
||||||
|
this.CreatePoisonWings();
|
||||||
|
this.CreateWarriorWings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ADAMU-CUSTOM: two imported wings (basic: base defense + class restriction, no wing options).
|
||||||
|
/// Poison Wing (12/240, Dark Knight), Poison Cape (12/241, Dark Lord + Rage Fighter).
|
||||||
|
/// </summary>
|
||||||
|
public void CreatePoisonWings()
|
||||||
|
{
|
||||||
|
// High-tier imported wings. Reuse the EXISTING shared "3rd Wing Options" option set and
|
||||||
|
// level-bonus tables (they already exist in the database) by fetching them from the
|
||||||
|
// configuration instead of re-creating them - re-creating uses fixed Guids and would
|
||||||
|
// collide when this runs as an update on an existing database.
|
||||||
|
var wingOptions = this.GameConfiguration.ItemOptions.First(o => o.Name == "3rd Wing Options");
|
||||||
|
var damageTable = this.GameConfiguration.ItemLevelBonusTables.First(t => t.Name == "Damage Increase (1st and 3rd Wings)");
|
||||||
|
this._defenseBonusByLevelTable ??= this.GameConfiguration.ItemLevelBonusTables.First(t => t.Name == "Defense Bonus (1st and 2nd Wings)");
|
||||||
|
|
||||||
|
// Poison Wing (12/240, Dark Knight): defense 65 + defense-per-level, 3rd Wing Options,
|
||||||
|
// damage increase, HP-recover / phys-damage / defense wing options, fast flight.
|
||||||
|
this.CreateWing(240, 5, 3, "Poison Wing", 150, 65, 220, 100, 0, 1, 0, 0, 0, 0, 0, this.BuildOptions((0b00, OptionType.HealthRecover), (0b11, OptionType.PhysDamage), (0b10, OptionType.Defense)), 42, 42, damageTable, wingOptions, movementSpeed: MovementSpeedConstants.FastWingMovementSpeed);
|
||||||
|
|
||||||
|
// Poison Cape (12/241, Dark Lord + Rage Fighter): defense 60, same rich options.
|
||||||
|
this.CreateWing(241, 5, 3, "Poison Cape", 150, 60, 220, 100, 0, 0, 0, 0, 1, 0, 1, this.BuildOptions((0b00, OptionType.HealthRecover), (0b11, OptionType.PhysDamage), (0b10, OptionType.Defense)), 42, 42, damageTable, wingOptions, movementSpeed: MovementSpeedConstants.FastWingMovementSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ADAMU-CUSTOM: six imported high-tier "Warrior" wings (base defense + 3rd Wing Options + damage
|
||||||
|
/// increase, fast flight). One per class line:
|
||||||
|
/// 242 Wizard (Soul Master), 243 Magic (Duel Master), 244 Elf (High Elf), 245 Summoner (Dimension
|
||||||
|
/// Master), 246 Warrior Cape (Lord Emperor + Fist Master), 247 Knight (Blade Master).
|
||||||
|
/// </summary>
|
||||||
|
public void CreateWarriorWings()
|
||||||
|
{
|
||||||
|
// Reuse the EXISTING shared "3rd Wing Options" set and level-bonus tables (fixed Guids already in
|
||||||
|
// the database) by fetching them, to avoid Guid collisions when this runs as an update.
|
||||||
|
var wingOptions = this.GameConfiguration.ItemOptions.First(o => o.Name == "3rd Wing Options");
|
||||||
|
var damageTable = this.GameConfiguration.ItemLevelBonusTables.First(t => t.Name == "Damage Increase (1st and 3rd Wings)");
|
||||||
|
this._defenseBonusByLevelTable ??= this.GameConfiguration.ItemLevelBonusTables.First(t => t.Name == "Defense Bonus (1st and 2nd Wings)");
|
||||||
|
|
||||||
|
// Warrior Wings Wizard (12/242, Soul Master / Dark Wizard): magic damage option.
|
||||||
|
this.CreateWing(242, 5, 3, "Warrior Wings Wizard", 150, 65, 220, 100, 1, 0, 0, 0, 0, 0, 0, this.BuildOptions((0b00, OptionType.HealthRecover), (0b11, OptionType.WizDamage), (0b10, OptionType.Defense)), 42, 42, damageTable, wingOptions, movementSpeed: MovementSpeedConstants.FastWingMovementSpeed);
|
||||||
|
|
||||||
|
// Warrior Wings Magic (12/243, Duel Master / Magic Gladiator).
|
||||||
|
this.CreateWing(243, 5, 3, "Warrior Wings Magic", 150, 65, 220, 100, 0, 0, 0, 1, 0, 0, 0, this.BuildOptions((0b00, OptionType.HealthRecover), (0b11, OptionType.PhysDamage), (0b10, OptionType.Defense)), 42, 42, damageTable, wingOptions, movementSpeed: MovementSpeedConstants.FastWingMovementSpeed);
|
||||||
|
|
||||||
|
// Warrior Wings Elf (12/244, High Elf).
|
||||||
|
this.CreateWing(244, 5, 3, "Warrior Wings Elf", 150, 65, 220, 100, 0, 0, 1, 0, 0, 0, 0, this.BuildOptions((0b00, OptionType.HealthRecover), (0b11, OptionType.PhysDamage), (0b10, OptionType.Defense)), 42, 42, damageTable, wingOptions, movementSpeed: MovementSpeedConstants.FastWingMovementSpeed);
|
||||||
|
|
||||||
|
// Warrior Wings Summoner (12/245, Dimension Master / Summoner): magic + curse damage.
|
||||||
|
this.CreateWing(245, 5, 3, "Warrior Wings Summoner", 150, 65, 220, 100, 0, 0, 0, 0, 0, 1, 0, this.BuildOptions((0b00, OptionType.HealthRecover), (0b11, OptionType.WizDamage), (0b10, OptionType.CurseDamage)), 42, 42, damageTable, wingOptions, movementSpeed: MovementSpeedConstants.FastWingMovementSpeed);
|
||||||
|
|
||||||
|
// Warrior Cape (12/246, Lord Emperor / Dark Lord + Fist Master / Rage Fighter): defense 60.
|
||||||
|
this.CreateWing(246, 5, 3, "Warrior Cape", 150, 60, 220, 100, 0, 0, 0, 0, 1, 0, 1, this.BuildOptions((0b00, OptionType.HealthRecover), (0b11, OptionType.PhysDamage), (0b10, OptionType.Defense)), 42, 42, damageTable, wingOptions, movementSpeed: MovementSpeedConstants.FastWingMovementSpeed);
|
||||||
|
|
||||||
|
// Warrior Wings Knight (12/247, Blade Master / Dark Knight).
|
||||||
|
this.CreateWing(247, 5, 3, "Warrior Wings Knight", 150, 65, 220, 100, 0, 1, 0, 0, 0, 0, 0, this.BuildOptions((0b00, OptionType.HealthRecover), (0b11, OptionType.PhysDamage), (0b10, OptionType.Defense)), 42, 42, damageTable, wingOptions, movementSpeed: MovementSpeedConstants.FastWingMovementSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateFeather()
|
private void CreateFeather()
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// <copyright file="Acheron.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.VersionSeasonSix.Maps;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The initialization for the Acheron map (imported map). Seeded empty; monster/NPC
|
||||||
|
/// spawns and gates are added via the admin panel.
|
||||||
|
/// </summary>
|
||||||
|
internal class Acheron : BaseMapInitializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The Number of the Map.
|
||||||
|
/// </summary>
|
||||||
|
internal const byte Number = 83;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Name of the Map.
|
||||||
|
/// </summary>
|
||||||
|
internal const string Name = "Acheron";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Acheron"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context.</param>
|
||||||
|
/// <param name="gameConfiguration">The game configuration.</param>
|
||||||
|
public Acheron(IContext context, GameConfiguration gameConfiguration)
|
||||||
|
: base(context, gameConfiguration)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override byte MapNumber => Number;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override string MapName => Name;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// <copyright file="Arkania.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.VersionSeasonSix.Maps;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The initialization for the Arkania map (imported map, world 83).
|
||||||
|
/// Seeded empty; monster/NPC spawns and gates are added via the admin panel.
|
||||||
|
/// </summary>
|
||||||
|
internal class Arkania : BaseMapInitializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The Number of the Map. OpenMU map numbers are 0-based; the MU client loads
|
||||||
|
/// world folder <c>World{Number + 1}</c> (e.g. Lorencia 0 -> World1). Arkania's
|
||||||
|
/// client assets are World83, so the server map number is 82.
|
||||||
|
/// </summary>
|
||||||
|
internal const byte Number = 82;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Name of the Map.
|
||||||
|
/// </summary>
|
||||||
|
internal const string Name = "Arkania";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Arkania"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context.</param>
|
||||||
|
/// <param name="gameConfiguration">The game configuration.</param>
|
||||||
|
public Arkania(IContext context, GameConfiguration gameConfiguration)
|
||||||
|
: base(context, gameConfiguration)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override byte MapNumber => Number;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override string MapName => Name;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// <copyright file="Debenter.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.VersionSeasonSix.Maps;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The initialization for the Debenter map (imported map). Seeded empty; monster/NPC
|
||||||
|
/// spawns and gates are added via the admin panel.
|
||||||
|
/// </summary>
|
||||||
|
internal class Debenter : BaseMapInitializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The Number of the Map.
|
||||||
|
/// </summary>
|
||||||
|
internal const byte Number = 84;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Name of the Map.
|
||||||
|
/// </summary>
|
||||||
|
internal const string Name = "Debenter";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Debenter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context.</param>
|
||||||
|
/// <param name="gameConfiguration">The game configuration.</param>
|
||||||
|
public Debenter(IContext context, GameConfiguration gameConfiguration)
|
||||||
|
: base(context, gameConfiguration)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override byte MapNumber => Number;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override string MapName => Name;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// <copyright file="Ferea.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.VersionSeasonSix.Maps;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The initialization for the Ferea map (imported map). Seeded empty; monster/NPC
|
||||||
|
/// spawns and gates are added via the admin panel.
|
||||||
|
/// </summary>
|
||||||
|
internal class Ferea : BaseMapInitializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The Number of the Map.
|
||||||
|
/// </summary>
|
||||||
|
internal const byte Number = 86;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Name of the Map.
|
||||||
|
/// </summary>
|
||||||
|
internal const string Name = "Ferea";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Ferea"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context.</param>
|
||||||
|
/// <param name="gameConfiguration">The game configuration.</param>
|
||||||
|
public Ferea(IContext context, GameConfiguration gameConfiguration)
|
||||||
|
: base(context, gameConfiguration)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override byte MapNumber => Number;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override string MapName => Name;
|
||||||
|
}
|
||||||
40
src/Persistence/Initialization/VersionSeasonSix/Maps/Uruk.cs
Normal file
40
src/Persistence/Initialization/VersionSeasonSix/Maps/Uruk.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// <copyright file="Uruk.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.VersionSeasonSix.Maps;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The initialization for the Uruk map (imported map). Seeded empty; monster/NPC
|
||||||
|
/// spawns and gates are added via the admin panel.
|
||||||
|
/// </summary>
|
||||||
|
internal class Uruk : BaseMapInitializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The Number of the Map.
|
||||||
|
/// </summary>
|
||||||
|
internal const byte Number = 85;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Name of the Map.
|
||||||
|
/// </summary>
|
||||||
|
internal const string Name = "Uruk";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Uruk"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context.</param>
|
||||||
|
/// <param name="gameConfiguration">The game configuration.</param>
|
||||||
|
public Uruk(IContext context, GameConfiguration gameConfiguration)
|
||||||
|
: base(context, gameConfiguration)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override byte MapNumber => Number;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override string MapName => Name;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user