10 Commits

Author SHA1 Message Date
Acentech Dev
3d5d40b753 Add Warrior Wings & Cape (12/242-247)
Some checks failed
.NET Core / build (push) Has been cancelled
Six high-tier imported wings, one per class line: Wizard (242, Soul Master),
Magic (243, Duel Master), Elf (244, High Elf), Summoner (245, Dimension Master),
Warrior Cape (246, Lord Emperor + Fist Master) and Knight (247, Blade Master).
Each: base defense + defense-per-level, 3rd Wing Options, damage increase and
fast flight. Applied via update v99 (AddWarriorWingsUpdateSeason6), reusing the
shared "3rd Wing Options" option set to avoid Guid collisions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 23:27:22 +03:00
Acentech Dev
1cadaa08ab fix(items): reuse shared 3rd Wing Options + level tables for Poison wings (avoid Guid collision) 2026-07-19 18:36:38 +03:00
Acentech Dev
3e8cd8fdae feat(items): Poison Wing/Cape as high-tier wings (defense + options + level bonuses)
Switch from the basic CreateWing overload to the full one: base
defense 65/60, defense-per-level, wing options (HP recover / phys
damage / defense), damage increase, fast flight. Level req 100.
2026-07-19 18:14:46 +03:00
Acentech Dev
affa4bd73b feat(items): add Poison Wing (12/240) and Poison Cape (12/241)
Basic wings via the simple CreateWing overload (base defense, class
restriction, CanFly; no options). Poison Wing=Dark Knight, Poison
Cape=Dark Lord+Rage Fighter. Update-plugin v98 + fresh-seed via
Wings.Initialize().
2026-07-19 16:54:33 +03:00
Acentech Dev
fc9ba0e594 docs: design spec for Poison Wing & Poison Cape (wings 12/240, 12/241) 2026-07-19 16:44:35 +03:00
Acentech Dev
d84b29eac3 feat(maps): add Acheron, Debenter, Uruk, Ferea (maps 83-86)
Same pattern as Arkania: terrain resources Terrain84-87.att, map
initializer classes, registration, and update-plugin (v97) that
creates each map + spawn gate + /move warp AND assigns it to the
game server configuration so it is hosted. Also fix the Arkania
update-plugin to assign the game server config (was missing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 15:47:05 +03:00
Acentech Dev
80ec466ab1 fix(maps): Arkania is map number 82 (client World = number+1 off-by-one)
Client tried to load World84 for map 83; MU client world index is
server map number + 1 (Lorencia 0 -> World1). Arkania client assets
are World83, so the server map number is 82 and the terrain resource
is Terrain83.att (number+1).
2026-07-19 14:08:01 +03:00
Acentech Dev
e3d37467d5 feat(maps): add Arkania DB update-plugin with spawn gate and /move warp 2026-07-19 13:45:37 +03:00
Acentech Dev
7e21fc1fb3 feat(maps): add Arkania map initializer (number 83) and register it 2026-07-19 13:43:26 +03:00
Acentech Dev
4628902f11 feat(maps): add Arkania (map 83) terrain resource Terrain84.att 2026-07-19 13:42:46 +03:00
19 changed files with 633 additions and 0 deletions

View 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.

View File

@@ -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" />

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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,
} }

View File

@@ -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);
} }
} }
} }

View File

@@ -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()

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View 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;
}