diff --git a/src/Persistence/Initialization/MUnique.OpenMU.Persistence.Initialization.csproj b/src/Persistence/Initialization/MUnique.OpenMU.Persistence.Initialization.csproj
index 310b825..00da802 100644
--- a/src/Persistence/Initialization/MUnique.OpenMU.Persistence.Initialization.csproj
+++ b/src/Persistence/Initialization/MUnique.OpenMU.Persistence.Initialization.csproj
@@ -25,6 +25,10 @@
+
+
+
+
@@ -105,6 +109,10 @@
+
+
+
+
diff --git a/src/Persistence/Initialization/Resources/Terrain84.att b/src/Persistence/Initialization/Resources/Terrain84.att
new file mode 100644
index 0000000..26fe415
Binary files /dev/null and b/src/Persistence/Initialization/Resources/Terrain84.att differ
diff --git a/src/Persistence/Initialization/Resources/Terrain85.att b/src/Persistence/Initialization/Resources/Terrain85.att
new file mode 100644
index 0000000..d124c84
Binary files /dev/null and b/src/Persistence/Initialization/Resources/Terrain85.att differ
diff --git a/src/Persistence/Initialization/Resources/Terrain86.att b/src/Persistence/Initialization/Resources/Terrain86.att
new file mode 100644
index 0000000..c0bcb3a
Binary files /dev/null and b/src/Persistence/Initialization/Resources/Terrain86.att differ
diff --git a/src/Persistence/Initialization/Resources/Terrain87.att b/src/Persistence/Initialization/Resources/Terrain87.att
new file mode 100644
index 0000000..a67a76f
Binary files /dev/null and b/src/Persistence/Initialization/Resources/Terrain87.att differ
diff --git a/src/Persistence/Initialization/Updates/AddArkaniaMapUpdateSeason6.cs b/src/Persistence/Initialization/Updates/AddArkaniaMapUpdateSeason6.cs
index afd6b3e..fe7372f 100644
--- a/src/Persistence/Initialization/Updates/AddArkaniaMapUpdateSeason6.cs
+++ b/src/Persistence/Initialization/Updates/AddArkaniaMapUpdateSeason6.cs
@@ -41,20 +41,31 @@ public class AddArkaniaMapUpdateSeason6 : UpdatePlugInBase
public override DateTime CreatedAt => new(2026, 07, 19, 12, 0, 0, DateTimeKind.Utc);
///
- protected override ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
+ protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
- // Idempotency guard: skip if Arkania (83) already exists.
+ // Idempotency guard: skip if Arkania (82) already exists.
if (gameConfiguration.Maps.Any(m => m.Number == Arkania.Number))
{
- return default;
+ return;
}
var initializer = new Arkania(context, gameConfiguration);
- initializer.Initialize(); // creates map 83 + loads terrain from Terrain84.att
+ 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().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();
arkania.ExitGates.Add(spawnGate);
@@ -76,7 +87,5 @@ public class AddArkaniaMapUpdateSeason6 : UpdatePlugInBase
warp.LevelRequirement = 10;
warp.Gate = spawnGate;
}
-
- return default;
}
}
diff --git a/src/Persistence/Initialization/Updates/AddImportedMapsUpdateSeason6.cs b/src/Persistence/Initialization/Updates/AddImportedMapsUpdateSeason6.cs
new file mode 100644
index 0000000..13f05a0
--- /dev/null
+++ b/src/Persistence/Initialization/Updates/AddImportedMapsUpdateSeason6.cs
@@ -0,0 +1,100 @@
+//
+// 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.Maps;
+using MUnique.OpenMU.PlugIns;
+
+///
+/// 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.
+///
+[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.";
+
+ ///
+ public override UpdateVersion Version => UpdateVersion.AddImportedMapsSeason6;
+
+ ///
+ 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, 19, 15, 0, 0, DateTimeKind.Utc);
+
+ ///
+ protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
+ {
+ var serverConfigs = (await context.GetAsync().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 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();
+ 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();
+ 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);
+ }
+ }
+ }
+}
diff --git a/src/Persistence/Initialization/Updates/UpdateVersion.cs b/src/Persistence/Initialization/Updates/UpdateVersion.cs
index 891f5fe..4ef0224 100644
--- a/src/Persistence/Initialization/Updates/UpdateVersion.cs
+++ b/src/Persistence/Initialization/Updates/UpdateVersion.cs
@@ -489,4 +489,9 @@ public enum UpdateVersion
/// The version of the .
///
AddArkaniaMapSeason6 = 96,
+
+ ///
+ /// The version of the (Acheron, Debenter, Uruk, Ferea).
+ ///
+ AddImportedMapsSeason6 = 97,
}
\ No newline at end of file
diff --git a/src/Persistence/Initialization/VersionSeasonSix/GameMapsInitializer.cs b/src/Persistence/Initialization/VersionSeasonSix/GameMapsInitializer.cs
index d9b0b40..d85548d 100644
--- a/src/Persistence/Initialization/VersionSeasonSix/GameMapsInitializer.cs
+++ b/src/Persistence/Initialization/VersionSeasonSix/GameMapsInitializer.cs
@@ -101,6 +101,10 @@ public class GameMapsInitializer : GameMapsInitializerBase
yield return typeof(FortressOfImperialGuardian3);
yield return typeof(FortressOfImperialGuardian4);
yield return typeof(Arkania);
+ yield return typeof(Acheron);
+ yield return typeof(Debenter);
+ yield return typeof(Uruk);
+ yield return typeof(Ferea);
}
}
}
\ No newline at end of file
diff --git a/src/Persistence/Initialization/VersionSeasonSix/Maps/Acheron.cs b/src/Persistence/Initialization/VersionSeasonSix/Maps/Acheron.cs
new file mode 100644
index 0000000..1e4b67e
--- /dev/null
+++ b/src/Persistence/Initialization/VersionSeasonSix/Maps/Acheron.cs
@@ -0,0 +1,40 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps;
+
+using MUnique.OpenMU.DataModel.Configuration;
+
+///
+/// The initialization for the Acheron map (imported map). Seeded empty; monster/NPC
+/// spawns and gates are added via the admin panel.
+///
+internal class Acheron : BaseMapInitializer
+{
+ ///
+ /// The Number of the Map.
+ ///
+ internal const byte Number = 83;
+
+ ///
+ /// The Name of the Map.
+ ///
+ internal const string Name = "Acheron";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The context.
+ /// The game configuration.
+ public Acheron(IContext context, GameConfiguration gameConfiguration)
+ : base(context, gameConfiguration)
+ {
+ }
+
+ ///
+ protected override byte MapNumber => Number;
+
+ ///
+ protected override string MapName => Name;
+}
diff --git a/src/Persistence/Initialization/VersionSeasonSix/Maps/Debenter.cs b/src/Persistence/Initialization/VersionSeasonSix/Maps/Debenter.cs
new file mode 100644
index 0000000..36454d5
--- /dev/null
+++ b/src/Persistence/Initialization/VersionSeasonSix/Maps/Debenter.cs
@@ -0,0 +1,40 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps;
+
+using MUnique.OpenMU.DataModel.Configuration;
+
+///
+/// The initialization for the Debenter map (imported map). Seeded empty; monster/NPC
+/// spawns and gates are added via the admin panel.
+///
+internal class Debenter : BaseMapInitializer
+{
+ ///
+ /// The Number of the Map.
+ ///
+ internal const byte Number = 84;
+
+ ///
+ /// The Name of the Map.
+ ///
+ internal const string Name = "Debenter";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The context.
+ /// The game configuration.
+ public Debenter(IContext context, GameConfiguration gameConfiguration)
+ : base(context, gameConfiguration)
+ {
+ }
+
+ ///
+ protected override byte MapNumber => Number;
+
+ ///
+ protected override string MapName => Name;
+}
diff --git a/src/Persistence/Initialization/VersionSeasonSix/Maps/Ferea.cs b/src/Persistence/Initialization/VersionSeasonSix/Maps/Ferea.cs
new file mode 100644
index 0000000..9bba6fc
--- /dev/null
+++ b/src/Persistence/Initialization/VersionSeasonSix/Maps/Ferea.cs
@@ -0,0 +1,40 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps;
+
+using MUnique.OpenMU.DataModel.Configuration;
+
+///
+/// The initialization for the Ferea map (imported map). Seeded empty; monster/NPC
+/// spawns and gates are added via the admin panel.
+///
+internal class Ferea : BaseMapInitializer
+{
+ ///
+ /// The Number of the Map.
+ ///
+ internal const byte Number = 86;
+
+ ///
+ /// The Name of the Map.
+ ///
+ internal const string Name = "Ferea";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The context.
+ /// The game configuration.
+ public Ferea(IContext context, GameConfiguration gameConfiguration)
+ : base(context, gameConfiguration)
+ {
+ }
+
+ ///
+ protected override byte MapNumber => Number;
+
+ ///
+ protected override string MapName => Name;
+}
diff --git a/src/Persistence/Initialization/VersionSeasonSix/Maps/Uruk.cs b/src/Persistence/Initialization/VersionSeasonSix/Maps/Uruk.cs
new file mode 100644
index 0000000..05cd401
--- /dev/null
+++ b/src/Persistence/Initialization/VersionSeasonSix/Maps/Uruk.cs
@@ -0,0 +1,40 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps;
+
+using MUnique.OpenMU.DataModel.Configuration;
+
+///
+/// The initialization for the Uruk map (imported map). Seeded empty; monster/NPC
+/// spawns and gates are added via the admin panel.
+///
+internal class Uruk : BaseMapInitializer
+{
+ ///
+ /// The Number of the Map.
+ ///
+ internal const byte Number = 85;
+
+ ///
+ /// The Name of the Map.
+ ///
+ internal const string Name = "Uruk";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The context.
+ /// The game configuration.
+ public Uruk(IContext context, GameConfiguration gameConfiguration)
+ : base(context, gameConfiguration)
+ {
+ }
+
+ ///
+ protected override byte MapNumber => Number;
+
+ ///
+ protected override string MapName => Name;
+}