Compare commits
6 Commits
0fdb455cec
...
797e17b666
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
797e17b666 | ||
|
|
d249fca935 | ||
|
|
f8e856c7c6 | ||
|
|
af46499279 | ||
|
|
6f7e58ff35 | ||
|
|
3aa9815b10 |
@@ -9,7 +9,7 @@ services:
|
|||||||
# sessizce calismaya devam eder (2026-07-31'de tam olarak bu oldu: sunucu 26 Temmuz
|
# sessizce calismaya devam eder (2026-07-31'de tam olarak bu oldu: sunucu 26 Temmuz
|
||||||
# image'ini calistiriyordu). SHA etiketi daha once gorulmedigi icin pull zorunlu olur.
|
# image'ini calistiriyordu). SHA etiketi daha once gorulmedigi icin pull zorunlu olur.
|
||||||
# Yeni surum yayinlarken bu satiri yeni SHA ile guncelle.
|
# Yeni surum yayinlarken bu satiri yeni SHA ile guncelle.
|
||||||
image: ${OPENMU_IMAGE:-atfatmc/adamu-openmu:452f0bb139de}
|
image: ${OPENMU_IMAGE:-atfatmc/adamu-openmu:d249fca93514}
|
||||||
container_name: openmu-startup
|
container_name: openmu-startup
|
||||||
networks:
|
networks:
|
||||||
- coolify
|
- coolify
|
||||||
|
|||||||
152
src/DataModel/Configuration/CastleSiegeConfiguration.cs
Normal file
152
src/DataModel/Configuration/CastleSiegeConfiguration.cs
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
// <copyright file="CastleSiegeConfiguration.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.Annotations;
|
||||||
|
using MUnique.OpenMU.DataModel.Configuration.Items;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Main configuration for the castle siege event.
|
||||||
|
/// </summary>
|
||||||
|
[Cloneable]
|
||||||
|
public partial class CastleSiegeConfiguration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the castle siege feature is enabled.
|
||||||
|
/// </summary>
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the number of seconds a guild must hold the crown to capture the castle.
|
||||||
|
/// </summary>
|
||||||
|
public int CrownHoldTimeSeconds { get; set; } = 30;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the minimum combined level of a guild master required to register for the siege.
|
||||||
|
/// </summary>
|
||||||
|
public int RegisterMinLevel { get; set; } = 200;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the minimum number of guild members required to register for the siege.
|
||||||
|
/// </summary>
|
||||||
|
public int RegisterMinMembers { get; set; } = 20;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the minimum number of seconds a participant must be present in the battle to be eligible for a reward.
|
||||||
|
/// </summary>
|
||||||
|
public int ParticipantRewardMinSeconds { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum number of attacking alliance slots.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxAttackingGuilds { get; set; } = 3;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the guild score awarded to the guild that wins the siege.
|
||||||
|
/// </summary>
|
||||||
|
public int GuildScoreCastleSiege { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the guild score awarded to alliance member guilds of the winning side.
|
||||||
|
/// </summary>
|
||||||
|
public int GuildScoreCastleSiegeMembers { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Zen cost for the castle owner to re-purchase a destroyed gate.
|
||||||
|
/// </summary>
|
||||||
|
public int GateBuyPrice { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Zen cost for the castle owner to re-purchase a destroyed statue.
|
||||||
|
/// </summary>
|
||||||
|
public int StatueBuyPrice { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the map definition for the Valley of Loren (map 30), where the siege takes place.
|
||||||
|
/// </summary>
|
||||||
|
public virtual GameMapDefinition? CastleSiegeMapDefinition { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the map definition for the Land of Trials (map 31), the castle-owner's exclusive zone.
|
||||||
|
/// </summary>
|
||||||
|
public virtual GameMapDefinition? LandOfTrialsMapDefinition { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the item definition for the participation reward item.
|
||||||
|
/// </summary>
|
||||||
|
public virtual ItemDefinition? RewardItemDefinition { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the schedule entries that define when each siege state begins.
|
||||||
|
/// </summary>
|
||||||
|
[MemberOfAggregate]
|
||||||
|
public virtual ICollection<CastleSiegeStateScheduleEntry> StateSchedule { get; protected set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the definitions for all castle siege NPCs (gates, statues, etc.).
|
||||||
|
/// </summary>
|
||||||
|
[MemberOfAggregate]
|
||||||
|
public virtual ICollection<CastleSiegeNpcDefinition> NpcDefinitions { get; protected set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the upgrade levels for gate defense.
|
||||||
|
/// </summary>
|
||||||
|
[MemberOfAggregate]
|
||||||
|
public virtual ICollection<CastleSiegeUpgradeDefinition> GateDefenseUpgrades { get; protected set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the upgrade levels for gate maximum HP.
|
||||||
|
/// </summary>
|
||||||
|
[MemberOfAggregate]
|
||||||
|
public virtual ICollection<CastleSiegeUpgradeDefinition> GateLifeUpgrades { get; protected set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the upgrade levels for statue defense.
|
||||||
|
/// </summary>
|
||||||
|
[MemberOfAggregate]
|
||||||
|
public virtual ICollection<CastleSiegeUpgradeDefinition> StatueDefenseUpgrades { get; protected set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the upgrade levels for statue maximum HP.
|
||||||
|
/// </summary>
|
||||||
|
[MemberOfAggregate]
|
||||||
|
public virtual ICollection<CastleSiegeUpgradeDefinition> StatueLifeUpgrades { get; protected set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the upgrade levels for statue HP regeneration.
|
||||||
|
/// </summary>
|
||||||
|
[MemberOfAggregate]
|
||||||
|
public virtual ICollection<CastleSiegeUpgradeDefinition> StatueRegenUpgrades { get; protected set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the zones on the siege map where attacking siege machines may be placed.
|
||||||
|
/// </summary>
|
||||||
|
[MemberOfAggregate]
|
||||||
|
public virtual ICollection<CastleSiegeZoneDefinition> AttackMachineZones { get; protected set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the zones on the siege map where defensive siege machines may be placed.
|
||||||
|
/// </summary>
|
||||||
|
[MemberOfAggregate]
|
||||||
|
public virtual ICollection<CastleSiegeZoneDefinition> DefenseMachineZones { get; protected set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the zone where defending players respawn during the siege.
|
||||||
|
/// </summary>
|
||||||
|
[MemberOfAggregate]
|
||||||
|
public virtual CastleSiegeZoneDefinition? DefenseRespawnArea { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the zone where attacking players respawn during the siege.
|
||||||
|
/// </summary>
|
||||||
|
[MemberOfAggregate]
|
||||||
|
public virtual CastleSiegeZoneDefinition? AttackRespawnArea { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "Castle Siege Configuration";
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/DataModel/Configuration/CastleSiegeJoinSide.cs
Normal file
36
src/DataModel/Configuration/CastleSiegeJoinSide.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// <copyright file="CastleSiegeJoinSide.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the side (defending or attacking) a guild or NPC belongs to in the castle siege.
|
||||||
|
/// </summary>
|
||||||
|
public enum CastleSiegeJoinSide : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No side assigned.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The defending guild side.
|
||||||
|
/// </summary>
|
||||||
|
Defense = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The first attacking alliance slot.
|
||||||
|
/// </summary>
|
||||||
|
Attack1 = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The second attacking alliance slot.
|
||||||
|
/// </summary>
|
||||||
|
Attack2 = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The third attacking alliance slot.
|
||||||
|
/// </summary>
|
||||||
|
Attack3 = 4,
|
||||||
|
}
|
||||||
55
src/DataModel/Configuration/CastleSiegeNpcDefinition.cs
Normal file
55
src/DataModel/Configuration/CastleSiegeNpcDefinition.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// <copyright file="CastleSiegeNpcDefinition.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.Annotations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a castle siege NPC instance, including its spawn location, side, and persistence settings.
|
||||||
|
/// </summary>
|
||||||
|
[Cloneable]
|
||||||
|
public partial class CastleSiegeNpcDefinition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the monster definition template for this NPC.
|
||||||
|
/// </summary>
|
||||||
|
public virtual MonsterDefinition? MonsterDefinition { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the unique instance identifier within its NPC type.
|
||||||
|
/// </summary>
|
||||||
|
public byte InstanceId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this NPC's state is persisted to the database between sieges.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsPersistedToDatabase { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the default join side this NPC belongs to.
|
||||||
|
/// </summary>
|
||||||
|
public CastleSiegeJoinSide DefaultSide { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the X coordinate of the NPC's spawn position.
|
||||||
|
/// </summary>
|
||||||
|
public byte SpawnX { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Y coordinate of the NPC's spawn position.
|
||||||
|
/// </summary>
|
||||||
|
public byte SpawnY { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the facing direction of the NPC at spawn.
|
||||||
|
/// </summary>
|
||||||
|
public Direction Direction { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{this.MonsterDefinition} #{this.InstanceId} at ({this.SpawnX},{this.SpawnY})";
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/DataModel/Configuration/CastleSiegeState.cs
Normal file
61
src/DataModel/Configuration/CastleSiegeState.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// <copyright file="CastleSiegeState.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The state of the castle siege event cycle.
|
||||||
|
/// </summary>
|
||||||
|
public enum CastleSiegeState : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Idle state before guild registration opens.
|
||||||
|
/// </summary>
|
||||||
|
Idle1 = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Guilds may register for the siege.
|
||||||
|
/// </summary>
|
||||||
|
RegisterGuild = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Idle state after guild registration.
|
||||||
|
/// </summary>
|
||||||
|
Idle2 = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Guilds may register emblems (Marks of Lord) to determine the attacking guilds.
|
||||||
|
/// </summary>
|
||||||
|
RegisterMark = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Idle state after mark registration.
|
||||||
|
/// </summary>
|
||||||
|
Idle3 = 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Players are notified that the siege is about to start.
|
||||||
|
/// </summary>
|
||||||
|
Notify = 5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The siege map is prepared and entry is allowed.
|
||||||
|
/// </summary>
|
||||||
|
Ready = 6,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The siege battle is in progress.
|
||||||
|
/// </summary>
|
||||||
|
Start = 7,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The siege battle has ended and results are being processed.
|
||||||
|
/// </summary>
|
||||||
|
End = 8,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The full siege cycle has completed.
|
||||||
|
/// </summary>
|
||||||
|
EndCycle = 9,
|
||||||
|
}
|
||||||
40
src/DataModel/Configuration/CastleSiegeStateScheduleEntry.cs
Normal file
40
src/DataModel/Configuration/CastleSiegeStateScheduleEntry.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// <copyright file="CastleSiegeStateScheduleEntry.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.Annotations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a scheduled transition to a specific <see cref="CastleSiegeState"/> at a given day and time.
|
||||||
|
/// </summary>
|
||||||
|
[Cloneable]
|
||||||
|
public partial class CastleSiegeStateScheduleEntry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the siege state that becomes active at the scheduled time.
|
||||||
|
/// </summary>
|
||||||
|
public CastleSiegeState State { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the day of the week on which this state transition occurs.
|
||||||
|
/// </summary>
|
||||||
|
public DayOfWeek DayOfWeek { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the hour (0–23) at which this state transition occurs.
|
||||||
|
/// </summary>
|
||||||
|
public byte Hour { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the minute (0–59) at which this state transition occurs.
|
||||||
|
/// </summary>
|
||||||
|
public byte Minute { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{this.State} on {this.DayOfWeek} at {this.Hour:D2}:{this.Minute:D2}";
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/DataModel/Configuration/CastleSiegeUpgradeDefinition.cs
Normal file
40
src/DataModel/Configuration/CastleSiegeUpgradeDefinition.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// <copyright file="CastleSiegeUpgradeDefinition.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.Annotations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines one level of an upgrade that the castle owner can apply to a gate or statue NPC.
|
||||||
|
/// </summary>
|
||||||
|
[Cloneable]
|
||||||
|
public partial class CastleSiegeUpgradeDefinition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the upgrade level (0–3), where 0 represents the base/unupgraded state.
|
||||||
|
/// </summary>
|
||||||
|
public byte Level { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the number of Jewels of Guardian required to perform this upgrade.
|
||||||
|
/// </summary>
|
||||||
|
public int RequiredJewelOfGuardianCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the amount of Zen required to perform this upgrade.
|
||||||
|
/// </summary>
|
||||||
|
public int RequiredZen { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the resulting stat value granted by this upgrade level (defense or max HP).
|
||||||
|
/// </summary>
|
||||||
|
public int Value { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"Level {this.Level}: Value={this.Value}, Jewels={this.RequiredJewelOfGuardianCount}, Zen={this.RequiredZen}";
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/DataModel/Configuration/CastleSiegeUpgradeType.cs
Normal file
31
src/DataModel/Configuration/CastleSiegeUpgradeType.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// <copyright file="CastleSiegeUpgradeType.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of upgrade applied to a castle siege NPC (gate or statue).
|
||||||
|
/// </summary>
|
||||||
|
public enum CastleSiegeUpgradeType : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No upgrade type assigned.
|
||||||
|
/// </summary>
|
||||||
|
Undefined = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Increases the defense stat of the NPC.
|
||||||
|
/// </summary>
|
||||||
|
Defense = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Increases the HP regeneration rate of the NPC.
|
||||||
|
/// </summary>
|
||||||
|
Regen = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Increases the maximum HP of the NPC.
|
||||||
|
/// </summary>
|
||||||
|
Life = 3,
|
||||||
|
}
|
||||||
40
src/DataModel/Configuration/CastleSiegeZoneDefinition.cs
Normal file
40
src/DataModel/Configuration/CastleSiegeZoneDefinition.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// <copyright file="CastleSiegeZoneDefinition.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.Annotations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a rectangular zone on the castle siege map, used for spawn areas and machine zones.
|
||||||
|
/// </summary>
|
||||||
|
[Cloneable]
|
||||||
|
public partial class CastleSiegeZoneDefinition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the top-left X coordinate of the zone.
|
||||||
|
/// </summary>
|
||||||
|
public byte X1 { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the top-left Y coordinate of the zone.
|
||||||
|
/// </summary>
|
||||||
|
public byte Y1 { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the bottom-right X coordinate of the zone.
|
||||||
|
/// </summary>
|
||||||
|
public byte X2 { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the bottom-right Y coordinate of the zone.
|
||||||
|
/// </summary>
|
||||||
|
public byte Y2 { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{this.X1} / {this.Y1} to {this.X2} / {this.Y2}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -300,6 +300,12 @@ public partial class GameConfiguration
|
|||||||
[MemberOfAggregate]
|
[MemberOfAggregate]
|
||||||
public virtual ICollection<MiniGameDefinition> MiniGameDefinitions { get; protected set; } = null!;
|
public virtual ICollection<MiniGameDefinition> MiniGameDefinitions { get; protected set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the castle siege configuration.
|
||||||
|
/// </summary>
|
||||||
|
[MemberOfAggregate]
|
||||||
|
public virtual CastleSiegeConfiguration? CastleSiegeConfiguration { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -165,6 +165,16 @@ public enum NpcWindow
|
|||||||
/// The dialog for the legacy quest system.
|
/// The dialog for the legacy quest system.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LegacyQuest,
|
LegacyQuest,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The castle siege gate NPC interaction window.
|
||||||
|
/// </summary>
|
||||||
|
CastleSiegeGateNpc,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The castle siege lever NPC interaction window.
|
||||||
|
/// </summary>
|
||||||
|
CastleSiegeLeverNpc,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
67
src/DataModel/Entities/CastleSiegeData.cs
Normal file
67
src/DataModel/Entities/CastleSiegeData.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// <copyright file="CastleSiegeData.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.DataModel.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Persistent state of the castle siege, stored as a single row across siege cycles.
|
||||||
|
/// </summary>
|
||||||
|
[AggregateRoot]
|
||||||
|
public class CastleSiegeData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the unique identifier of this record.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the persistent identifier of the guild that currently owns the castle.
|
||||||
|
/// <see langword="null"/> when no guild owns the castle.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? OwnerGuildId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether any guild currently occupies the castle.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsOccupied { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Chaos Machine tax rate applied by the castle owner (0–3).
|
||||||
|
/// </summary>
|
||||||
|
public byte TaxChaos { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the personal store tax rate applied by the castle owner (0–3).
|
||||||
|
/// </summary>
|
||||||
|
public byte TaxStore { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the entry fee (in Zen) for the castle owner's hunt zone (0–300000).
|
||||||
|
/// </summary>
|
||||||
|
public int TaxHunt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the hunt zone (Land of Trials) is currently open to the public.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsHuntZoneEnabled { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the accumulated tribute money collected from the hunt zone and taxes.
|
||||||
|
/// </summary>
|
||||||
|
public long TributeMoney { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the persisted states of all castle NPCs.
|
||||||
|
/// </summary>
|
||||||
|
[MemberOfAggregate]
|
||||||
|
public virtual ICollection<CastleSiegeNpcState> NpcStates { get; protected set; } = null!;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return this.IsOccupied
|
||||||
|
? $"Castle owned by guild {this.OwnerGuildId}"
|
||||||
|
: "Castle unoccupied";
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/DataModel/Entities/CastleSiegeGuildRegistration.cs
Normal file
44
src/DataModel/Entities/CastleSiegeGuildRegistration.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// <copyright file="CastleSiegeGuildRegistration.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.DataModel.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores a guild's registration data for the current castle siege cycle,
|
||||||
|
/// including the number of emblems submitted to determine attacking guilds.
|
||||||
|
/// </summary>
|
||||||
|
[AggregateRoot]
|
||||||
|
public class CastleSiegeGuildRegistration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the unique identifier of this registration record.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the persistent identifier of the registered guild.
|
||||||
|
/// </summary>
|
||||||
|
public Guid GuildId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the guild name, denormalized for convenience to avoid extra lookups during siege processing.
|
||||||
|
/// </summary>
|
||||||
|
public string GuildName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the number of Emblems of Lord registered by this guild.
|
||||||
|
/// </summary>
|
||||||
|
public int Marks { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the insertion order of this registration, used for tie-breaking when guilds have equal marks.
|
||||||
|
/// </summary>
|
||||||
|
public int RegistrationOrder { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{this.GuildName} (Marks={this.Marks}, Order={this.RegistrationOrder})";
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/DataModel/Entities/CastleSiegeNpcState.cs
Normal file
52
src/DataModel/Entities/CastleSiegeNpcState.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// <copyright file="CastleSiegeNpcState.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.DataModel.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Persistent state of a single castle siege NPC between siege cycles.
|
||||||
|
/// </summary>
|
||||||
|
public class CastleSiegeNpcState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the unique identifier of this NPC state.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the monster definition number that identifies the NPC template.
|
||||||
|
/// </summary>
|
||||||
|
public short MonsterNumber { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the instance identifier matching <see cref="MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition.InstanceId"/>.
|
||||||
|
/// </summary>
|
||||||
|
public byte InstanceId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current defense upgrade level (0–3).
|
||||||
|
/// </summary>
|
||||||
|
public byte DefenseLevel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current HP regeneration upgrade level (0–3).
|
||||||
|
/// </summary>
|
||||||
|
public byte RegenLevel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current maximum HP upgrade level (0–3).
|
||||||
|
/// </summary>
|
||||||
|
public byte LifeLevel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current HP of the NPC. A value of 0 means the NPC is destroyed.
|
||||||
|
/// </summary>
|
||||||
|
public int CurrentHp { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"NPC {this.MonsterNumber} #{this.InstanceId} (HP={this.CurrentHp})";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,109 +4,151 @@
|
|||||||
|
|
||||||
namespace MUnique.OpenMU.GameLogic.CastleSiege;
|
namespace MUnique.OpenMU.GameLogic.CastleSiege;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// In-memory Castle Siege phase state machine and battle contention (P3).
|
/// In-memory Castle Siege state machine and battle contention.
|
||||||
/// Time is injected via method parameters so it can be tested deterministically.
|
/// Time is injected via method parameters so it can be tested deterministically.
|
||||||
/// Battle rule: attackers must destroy all castle defenses (gates + guardian statues) and then hold BOTH
|
/// <para>
|
||||||
/// Crown Switches at the same time — the switches are held by standing on them (evaluated per tick by the
|
/// The cycle uses the original Season 6 <see cref="CastleSiegeState"/> values, which are exactly the values
|
||||||
/// plugin), and once both are held by one guild with the defenses down, that guild captures the throne.
|
/// the game client expects (see <c>CASTLESIEGE_STATE</c> in the client's <c>WSclient.h</c>). AdaMu drives only
|
||||||
/// The throne holder when the siege ends becomes the castle owner.
|
/// a subset of them, because it registers guilds directly and has no Mark of Lord step:
|
||||||
|
/// </para>
|
||||||
|
/// <code>
|
||||||
|
/// Idle1(0) -> RegisterGuild(1) -> Ready(6) -> Start(7) -> End(8) -> EndCycle(9) -> Idle1(0)
|
||||||
|
/// </code>
|
||||||
|
/// <para>
|
||||||
|
/// The skipped states (<see cref="CastleSiegeState.Idle2"/>, <see cref="CastleSiegeState.RegisterMark"/>,
|
||||||
|
/// <see cref="CastleSiegeState.Idle3"/>, <see cref="CastleSiegeState.Notify"/>) keep their numbers so the
|
||||||
|
/// client stays compatible; the server simply never enters them.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Guilds are identified by their persistent <see cref="Guid"/>, not by name. A guild rename (or a delete and
|
||||||
|
/// re-create under the same name) therefore can no longer transfer castle ownership to the wrong guild. Names
|
||||||
|
/// are carried alongside purely for display and for the packets that send a name to the client.
|
||||||
|
/// </para>
|
||||||
|
/// Battle rule: a guild takes the throne by holding BOTH Crown Switches at the same time. A switch is
|
||||||
|
/// operated by clicking it and then staying in its area: the operation needs
|
||||||
|
/// <see cref="CastleSiegeSettings.SwitchPushSeconds"/> to complete, after which the switch counts as held
|
||||||
|
/// until its operator leaves. While one guild holds both switches the crown's shield drops for it, and its
|
||||||
|
/// guild master can start the crown hold to capture the throne. The throne can change hands as often as the
|
||||||
|
/// switches do; whoever holds it when the siege ends becomes the castle owner.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CastleSiegeContext
|
public class CastleSiegeContext
|
||||||
{
|
{
|
||||||
/// <summary>The Crown Switch NPC numbers on Valley of Loren; both must be held to take the throne.</summary>
|
/// <summary>The Crown Switch NPC numbers on Valley of Loren; both must be held to take the throne.</summary>
|
||||||
public static readonly short[] SwitchNumbers = { 217, 218 };
|
public static readonly short[] SwitchNumbers = { 217, 218 };
|
||||||
|
|
||||||
private readonly List<string> _registeredGuilds = new();
|
private readonly Dictionary<Guid, string> _registeredGuilds = new();
|
||||||
private readonly Dictionary<short, string?> _switchHolders = new() { { 217, null }, { 218, null } };
|
private readonly Dictionary<short, CastleSiegeSwitchOperation?> _switches = new() { { 217, null }, { 218, null } };
|
||||||
private DateTime _phaseStartedUtc;
|
private DateTime _stateStartedUtc;
|
||||||
private string? _occupier;
|
private Guid? _occupier;
|
||||||
|
private string? _occupierName;
|
||||||
private int _defensesRemaining;
|
private int _defensesRemaining;
|
||||||
private bool _dirty;
|
private bool _dirty;
|
||||||
private string? _crownHoldGuild;
|
private Guid? _crownHoldGuild;
|
||||||
private DateTime? _crownHoldStartUtc;
|
private DateTime? _crownHoldStartUtc;
|
||||||
|
private Guid? _crownHoldRequestedBy;
|
||||||
private bool _lastShieldDown;
|
private bool _lastShieldDown;
|
||||||
|
|
||||||
/// <summary>Initializes a new instance of the <see cref="CastleSiegeContext"/> class.</summary>
|
/// <summary>Initializes a new instance of the <see cref="CastleSiegeContext"/> class.</summary>
|
||||||
/// <param name="configuration">The cycle timing configuration.</param>
|
/// <param name="configuration">The cycle timing configuration.</param>
|
||||||
public CastleSiegeContext(CastleSiegeConfiguration configuration)
|
public CastleSiegeContext(CastleSiegeSettings configuration)
|
||||||
{
|
{
|
||||||
this.Configuration = configuration;
|
this.Configuration = configuration;
|
||||||
this.Phase = CastleSiegePhase.Ownership;
|
this.State = CastleSiegeState.Idle1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Raised after the phase changes. Argument is the new phase.</summary>
|
/// <summary>Raised after the state changes. Argument is the new state.</summary>
|
||||||
public event Action<CastleSiegePhase>? PhaseChanged;
|
public event Action<CastleSiegeState>? StateChanged;
|
||||||
|
|
||||||
/// <summary>Gets the configuration (durations + schedule). Refreshed each tick from the live plugin config
|
/// <summary>Gets the configuration (durations + schedule). Refreshed each tick from the live plugin config
|
||||||
/// so AdminPanel edits take effect without a restart.</summary>
|
/// so AdminPanel edits take effect without a restart.</summary>
|
||||||
public CastleSiegeConfiguration Configuration { get; private set; }
|
public CastleSiegeSettings Configuration { get; private set; }
|
||||||
|
|
||||||
/// <summary>Points the context at the current (possibly AdminPanel-edited) plugin configuration.</summary>
|
/// <summary>Gets the current state.</summary>
|
||||||
/// <param name="configuration">The live configuration.</param>
|
public CastleSiegeState State { get; private set; }
|
||||||
public void UpdateConfiguration(CastleSiegeConfiguration configuration) => this.Configuration = configuration;
|
|
||||||
|
|
||||||
/// <summary>Gets the current phase.</summary>
|
/// <summary>Gets the UTC time the current state started (used for persistence/restore).</summary>
|
||||||
public CastleSiegePhase Phase { get; private set; }
|
public DateTime StateStartedUtc => this._stateStartedUtc;
|
||||||
|
|
||||||
/// <summary>Gets the UTC time the current phase started (used for persistence/restore).</summary>
|
/// <summary>Gets the persistent identifier of the owner guild, or <see langword="null"/> if unowned.</summary>
|
||||||
public DateTime PhaseStartedUtc => this._phaseStartedUtc;
|
public Guid? OwnerGuildId { get; private set; }
|
||||||
|
|
||||||
/// <summary>Gets the current owner guild name, or null if unowned.</summary>
|
/// <summary>Gets the owner guild's name for display and client packets, or null.</summary>
|
||||||
public string? OwnerGuildName { get; private set; }
|
public string? OwnerGuildName { get; private set; }
|
||||||
|
|
||||||
/// <summary>Gets the guild currently holding the throne during the siege (P3), or null.</summary>
|
/// <summary>Gets the guild currently holding the throne during the siege, or null.</summary>
|
||||||
public string? OccupierGuildName => this._occupier;
|
public Guid? OccupierGuildId => this._occupier;
|
||||||
|
|
||||||
|
/// <summary>Gets the throne holder's name for display and client packets, or null.</summary>
|
||||||
|
public string? OccupierGuildName => this._occupierName;
|
||||||
|
|
||||||
/// <summary>Gets the number of castle defenses (gates + statues) still standing; the throne needs 0.</summary>
|
/// <summary>Gets the number of castle defenses (gates + statues) still standing; the throne needs 0.</summary>
|
||||||
public int DefensesRemaining => this._defensesRemaining;
|
public int DefensesRemaining => this._defensesRemaining;
|
||||||
|
|
||||||
/// <summary>Gets the guild names registered for the current cycle.</summary>
|
/// <summary>Gets the persistent identifiers of the guilds registered for the current cycle.</summary>
|
||||||
public IReadOnlyList<string> RegisteredGuilds => this._registeredGuilds;
|
public IReadOnlyCollection<Guid> RegisteredGuildIds => this._registeredGuilds.Keys;
|
||||||
|
|
||||||
|
/// <summary>Gets the names of the guilds registered for the current cycle (display only).</summary>
|
||||||
|
public IReadOnlyCollection<string> RegisteredGuildNames => this._registeredGuilds.Values;
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether the siege battle is currently running.</summary>
|
||||||
|
public bool IsSiegeRunning => this.State == CastleSiegeState.Start;
|
||||||
|
|
||||||
|
/// <summary>Points the context at the current (possibly AdminPanel-edited) plugin configuration.</summary>
|
||||||
|
/// <param name="configuration">The live configuration.</param>
|
||||||
|
public void UpdateConfiguration(CastleSiegeSettings configuration) => this.Configuration = configuration;
|
||||||
|
|
||||||
|
/// <summary>Returns whether the given guild is registered for the current cycle.</summary>
|
||||||
|
/// <param name="guildId">The persistent guild identifier.</param>
|
||||||
|
public bool IsRegistered(Guid guildId) => this._registeredGuilds.ContainsKey(guildId);
|
||||||
|
|
||||||
/// <summary>Advances the state machine based on the current time.</summary>
|
/// <summary>Advances the state machine based on the current time.</summary>
|
||||||
/// <param name="now">The current UTC time.</param>
|
/// <param name="now">The current UTC time.</param>
|
||||||
public ValueTask TickAsync(DateTime now)
|
public ValueTask TickAsync(DateTime now)
|
||||||
{
|
{
|
||||||
switch (this.Phase)
|
switch (this.State)
|
||||||
{
|
{
|
||||||
case CastleSiegePhase.Ownership:
|
case CastleSiegeState.Idle1:
|
||||||
if (this.Configuration.IsRegistrationOpenTime(now))
|
if (this.Configuration.IsRegistrationOpenTime(now))
|
||||||
{
|
{
|
||||||
return this.ForceStartRegistrationAsync(now);
|
return this.ForceStartRegistrationAsync(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case CastleSiegePhase.Registration:
|
case CastleSiegeState.RegisterGuild:
|
||||||
if (now >= this._phaseStartedUtc + this.Configuration.RegistrationDuration)
|
if (now >= this._stateStartedUtc + this.Configuration.RegistrationDuration)
|
||||||
{
|
{
|
||||||
return this.TransitionAsync(CastleSiegePhase.Preparation, now);
|
return this.TransitionAsync(CastleSiegeState.Ready, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case CastleSiegePhase.Preparation:
|
case CastleSiegeState.Ready:
|
||||||
if (now >= this._phaseStartedUtc + this.Configuration.PreparationDuration)
|
if (now >= this._stateStartedUtc + this.Configuration.PreparationDuration)
|
||||||
{
|
{
|
||||||
return this.TransitionAsync(CastleSiegePhase.Siege, now);
|
return this.TransitionAsync(CastleSiegeState.Start, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case CastleSiegePhase.Siege:
|
case CastleSiegeState.Start:
|
||||||
if (now >= this._phaseStartedUtc + this.Configuration.SiegeDuration)
|
if (now >= this._stateStartedUtc + this.Configuration.SiegeDuration)
|
||||||
{
|
{
|
||||||
return this.TransitionAsync(CastleSiegePhase.Settlement, now);
|
return this.TransitionAsync(CastleSiegeState.End, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case CastleSiegePhase.Settlement:
|
case CastleSiegeState.End:
|
||||||
// Winner = guild holding the throne at siege end. If none captured, owner unchanged.
|
// Winner = guild holding the throne at siege end. If none captured, owner unchanged.
|
||||||
if (this._occupier is { } occupier)
|
if (this._occupier is { } occupier)
|
||||||
{
|
{
|
||||||
this.SetOwner(occupier);
|
this.SetOwner(occupier, this._occupierName);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ClearBattleState();
|
this.ClearBattleState();
|
||||||
return this.TransitionAsync(CastleSiegePhase.Ownership, now);
|
return this.TransitionAsync(CastleSiegeState.EndCycle, now);
|
||||||
|
case CastleSiegeState.EndCycle:
|
||||||
|
return this.TransitionAsync(CastleSiegeState.Idle1, now);
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -114,62 +156,67 @@ public class CastleSiegeContext
|
|||||||
return ValueTask.CompletedTask;
|
return ValueTask.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Admin: forces the cycle into registration now (from any phase).</summary>
|
/// <summary>Admin: forces the cycle into guild registration now (from any state).</summary>
|
||||||
/// <param name="now">The current UTC time.</param>
|
/// <param name="now">The current UTC time.</param>
|
||||||
public ValueTask ForceStartRegistrationAsync(DateTime now)
|
public ValueTask ForceStartRegistrationAsync(DateTime now)
|
||||||
{
|
{
|
||||||
this._registeredGuilds.Clear();
|
this._registeredGuilds.Clear();
|
||||||
this.ClearBattleState();
|
this.ClearBattleState();
|
||||||
return this.TransitionAsync(CastleSiegePhase.Registration, now);
|
return this.TransitionAsync(CastleSiegeState.RegisterGuild, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Admin: forces a specific phase now.</summary>
|
/// <summary>Admin: forces a specific state now.</summary>
|
||||||
/// <param name="phase">The target phase.</param>
|
/// <param name="state">The target state.</param>
|
||||||
/// <param name="now">The current UTC time.</param>
|
/// <param name="now">The current UTC time.</param>
|
||||||
public ValueTask ForcePhaseAsync(CastleSiegePhase phase, DateTime now)
|
public ValueTask ForceStateAsync(CastleSiegeState state, DateTime now)
|
||||||
=> this.TransitionAsync(phase, now);
|
=> this.TransitionAsync(state, now);
|
||||||
|
|
||||||
/// <summary>Admin: resets to the ownership (resting) phase and clears registrations/battle state.</summary>
|
/// <summary>Admin: resets to the idle (resting) state and clears registrations/battle state.</summary>
|
||||||
/// <param name="now">The current UTC time.</param>
|
/// <param name="now">The current UTC time.</param>
|
||||||
public ValueTask ResetAsync(DateTime now)
|
public ValueTask ResetAsync(DateTime now)
|
||||||
{
|
{
|
||||||
this._registeredGuilds.Clear();
|
this._registeredGuilds.Clear();
|
||||||
this.ClearBattleState();
|
this.ClearBattleState();
|
||||||
return this.TransitionAsync(CastleSiegePhase.Ownership, now);
|
return this.TransitionAsync(CastleSiegeState.Idle1, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Registers a guild (by name) for the current cycle. No-op outside registration.</summary>
|
/// <summary>Registers a guild for the current cycle. No-op outside the registration state.</summary>
|
||||||
/// <param name="guildName">The guild name.</param>
|
/// <param name="guildId">The persistent guild identifier.</param>
|
||||||
public void RegisterGuild(string guildName)
|
/// <param name="guildName">The guild name, for display.</param>
|
||||||
|
public void RegisterGuild(Guid guildId, string guildName)
|
||||||
{
|
{
|
||||||
if (this.Phase == CastleSiegePhase.Registration
|
if (this.State == CastleSiegeState.RegisterGuild
|
||||||
&& !this._registeredGuilds.Contains(guildName))
|
&& this._registeredGuilds.TryAdd(guildId, guildName))
|
||||||
{
|
{
|
||||||
this._registeredGuilds.Add(guildName);
|
|
||||||
this._dirty = true;
|
this._dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Admin: sets (or clears) the current owner guild name.</summary>
|
/// <summary>Admin: sets (or clears) the current owner guild.</summary>
|
||||||
/// <param name="guildName">The owner guild name, or null to clear.</param>
|
/// <param name="guildId">The owner guild identifier, or null to clear.</param>
|
||||||
public void SetOwner(string? guildName)
|
/// <param name="guildName">The owner guild name, or null.</param>
|
||||||
|
public void SetOwner(Guid? guildId, string? guildName)
|
||||||
{
|
{
|
||||||
this.OwnerGuildName = guildName;
|
this.OwnerGuildId = guildId;
|
||||||
|
this.OwnerGuildName = guildId is null ? null : guildName;
|
||||||
this._dirty = true;
|
this._dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mirrors the shared castle owner from the configuration. Used on game servers that do NOT host the
|
/// Mirrors the shared castle owner loaded from the database. Used on game servers that do NOT host the
|
||||||
/// siege, so their hunting-map gate and castle flag still reflect the current owner. Does not mark the
|
/// siege, so their hunting-map gate and castle flag still reflect the current owner. Does not mark the
|
||||||
/// state dirty (these servers never persist).
|
/// state dirty (these servers never persist).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SyncOwnerFromConfig()
|
/// <param name="guildId">The owner guild identifier, or null.</param>
|
||||||
|
/// <param name="guildName">The owner guild name, or null.</param>
|
||||||
|
public void SyncOwner(Guid? guildId, string? guildName)
|
||||||
{
|
{
|
||||||
this.OwnerGuildName = this.Configuration.PersistedOwnerGuildName;
|
this.OwnerGuildId = guildId;
|
||||||
|
this.OwnerGuildName = guildId is null ? null : guildName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the weekly auto-schedule (days of week + UTC time) into the configuration and marks the state
|
/// Sets the auto-open schedule (days of week + UTC time) into the configuration and marks the state
|
||||||
/// dirty for persistence. Empty days disables auto-start (manual only). The configuration is the single
|
/// dirty for persistence. Empty days disables auto-start (manual only). The configuration is the single
|
||||||
/// source of truth, so this is equivalent to editing the plugin config in the AdminPanel.
|
/// source of truth, so this is equivalent to editing the plugin config in the AdminPanel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -192,83 +239,127 @@ public class CastleSiegeContext
|
|||||||
/// <param name="nowUtc">The current UTC time.</param>
|
/// <param name="nowUtc">The current UTC time.</param>
|
||||||
public TimeSpan GetRemainingSiegeTime(DateTime nowUtc)
|
public TimeSpan GetRemainingSiegeTime(DateTime nowUtc)
|
||||||
{
|
{
|
||||||
if (this.Phase != CastleSiegePhase.Siege)
|
if (!this.IsSiegeRunning)
|
||||||
{
|
{
|
||||||
return TimeSpan.Zero;
|
return TimeSpan.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
var remaining = (this._phaseStartedUtc + this.Configuration.SiegeDuration) - nowUtc;
|
var remaining = (this._stateStartedUtc + this.Configuration.SiegeDuration) - nowUtc;
|
||||||
return remaining > TimeSpan.Zero ? remaining : TimeSpan.Zero;
|
return remaining > TimeSpan.Zero ? remaining : TimeSpan.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the guild that currently holds BOTH crown switches while all castle defenses are down (so the
|
/// Returns how much time is left in the current state, or <see cref="TimeSpan.Zero"/> when the state has
|
||||||
/// crown's shield is dropped for them), or null. Only meaningful during the siege.
|
/// no duration (idle states wait for an admin command or the auto-open time).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? GetShieldEligibleGuild()
|
/// <param name="nowUtc">The current UTC time.</param>
|
||||||
|
public TimeSpan GetRemainingStateTime(DateTime nowUtc)
|
||||||
{
|
{
|
||||||
if (this.Phase != CastleSiegePhase.Siege || this._defensesRemaining > 0)
|
var duration = this.State switch
|
||||||
|
{
|
||||||
|
CastleSiegeState.RegisterGuild => this.Configuration.RegistrationDuration,
|
||||||
|
CastleSiegeState.Ready => this.Configuration.PreparationDuration,
|
||||||
|
CastleSiegeState.Start => this.Configuration.SiegeDuration,
|
||||||
|
_ => TimeSpan.Zero,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (duration == TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
return TimeSpan.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
var remaining = (this._stateStartedUtc + duration) - nowUtc;
|
||||||
|
return remaining > TimeSpan.Zero ? remaining : TimeSpan.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the guild which currently holds BOTH Crown Switches, so the crown's shield is dropped for it,
|
||||||
|
/// or null. Only meaningful during the siege.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? GetShieldEligibleGuild()
|
||||||
|
{
|
||||||
|
if (!this.IsSiegeRunning)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var holder = this._switchHolders[217];
|
var first = this.GetHeldSwitchGuild(217);
|
||||||
return holder is not null && holder == this._switchHolders[218] ? holder : null;
|
return first is not null && first == this.GetHeldSwitchGuild(218) ? first : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Advances the crown-hold capture. <paramref name="eligibleGuild"/> is the guild with both switches held
|
/// Registers a guild master's intent to take the crown, which is what the crown hold waits for: standing
|
||||||
/// and no defenses left (shield down); <paramref name="masterHolding"/> is whether that guild's master is
|
/// on the crown alone does nothing until its guild master clicked it. Ignored when the guild does not
|
||||||
/// standing on the crown. Captures the throne for the guild once it has held for <paramref name="holdDuration"/>.
|
/// hold both switches, so a click can never arm a hold the guild isn't entitled to.
|
||||||
/// Losing a switch or the master leaving the crown resets the hold (contestable until the siege ends).
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="eligibleGuild">The guild with both switches and no defenses, or null.</param>
|
/// <param name="guildId">The requesting guild master's guild identifier.</param>
|
||||||
|
/// <returns><see langword="true"/> if the request was accepted.</returns>
|
||||||
|
public bool RequestCrownHold(Guid guildId)
|
||||||
|
{
|
||||||
|
if (this.GetShieldEligibleGuild() != guildId || this._occupier == guildId)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._crownHoldRequestedBy = guildId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Advances the crown-hold capture. <paramref name="eligibleGuild"/> is the guild holding both switches
|
||||||
|
/// (shield down) and <paramref name="masterHolding"/> is whether that guild's master stands on the crown.
|
||||||
|
/// The hold only runs after the master requested it via <see cref="RequestCrownHold"/>; it captures the
|
||||||
|
/// throne once it ran for <paramref name="holdDuration"/>. Losing a switch or the master leaving the crown
|
||||||
|
/// resets the hold, and the crown has to be clicked again (contestable until the siege ends).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eligibleGuild">The guild holding both switches, or null.</param>
|
||||||
|
/// <param name="eligibleGuildName">That guild's name, for display.</param>
|
||||||
/// <param name="masterHolding">Whether that guild's master is on the crown.</param>
|
/// <param name="masterHolding">Whether that guild's master is on the crown.</param>
|
||||||
/// <param name="now">The current UTC time.</param>
|
/// <param name="now">The current UTC time.</param>
|
||||||
/// <param name="holdDuration">How long the master must hold to capture.</param>
|
/// <param name="holdDuration">How long the master must hold to capture.</param>
|
||||||
public CrownTickResult TickCrownHold(string? eligibleGuild, bool masterHolding, DateTime now, TimeSpan holdDuration)
|
public CrownTickResult TickCrownHold(Guid? eligibleGuild, string? eligibleGuildName, bool masterHolding, DateTime now, TimeSpan holdDuration)
|
||||||
{
|
{
|
||||||
var shieldDown = this.Phase == CastleSiegePhase.Siege && eligibleGuild is not null;
|
var shieldDown = this.IsSiegeRunning && eligibleGuild is not null;
|
||||||
var shieldChanged = shieldDown != this._lastShieldDown;
|
var shieldChanged = shieldDown != this._lastShieldDown;
|
||||||
this._lastShieldDown = shieldDown;
|
this._lastShieldDown = shieldDown;
|
||||||
|
|
||||||
if (this.Phase != CastleSiegePhase.Siege)
|
if (!this.IsSiegeRunning)
|
||||||
{
|
{
|
||||||
this._crownHoldGuild = null;
|
this.ResetCrownHold();
|
||||||
this._crownHoldStartUtc = null;
|
return new CrownTickResult(false, shieldChanged, CrownEvent.None, null, null);
|
||||||
return new CrownTickResult(false, shieldChanged, CrownEvent.None, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var wasHolding = this._crownHoldGuild is not null;
|
var wasHolding = this._crownHoldGuild is not null;
|
||||||
|
|
||||||
// The guild that already occupies the throne just holds it — no re-registration (avoids a capture loop).
|
// The guild that already occupies the throne just holds it - no re-registration (avoids a capture loop).
|
||||||
// Only a DIFFERENT guild taking both switches can register/capture (contest).
|
// Only a DIFFERENT guild taking both switches can register/capture (contest).
|
||||||
var canCapture = eligibleGuild is not null && eligibleGuild != this._occupier;
|
var canCapture = eligibleGuild is not null
|
||||||
|
&& eligibleGuild != this._occupier
|
||||||
|
&& this._crownHoldRequestedBy == eligibleGuild;
|
||||||
|
|
||||||
if (!canCapture || !masterHolding)
|
if (!canCapture || !masterHolding)
|
||||||
{
|
{
|
||||||
this._crownHoldGuild = null;
|
this.ResetCrownHold();
|
||||||
this._crownHoldStartUtc = null;
|
return new CrownTickResult(shieldDown, shieldChanged, wasHolding ? CrownEvent.HoldReset : CrownEvent.None, null, null);
|
||||||
return new CrownTickResult(shieldDown, shieldChanged, wasHolding ? CrownEvent.HoldReset : CrownEvent.None, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._crownHoldGuild != eligibleGuild || this._crownHoldStartUtc is null)
|
if (this._crownHoldGuild != eligibleGuild || this._crownHoldStartUtc is null)
|
||||||
{
|
{
|
||||||
this._crownHoldGuild = eligibleGuild;
|
this._crownHoldGuild = eligibleGuild;
|
||||||
this._crownHoldStartUtc = now;
|
this._crownHoldStartUtc = now;
|
||||||
return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.HoldStarted, eligibleGuild);
|
return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.HoldStarted, eligibleGuild, eligibleGuildName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (now - this._crownHoldStartUtc.Value >= holdDuration)
|
if (now - this._crownHoldStartUtc.Value >= holdDuration)
|
||||||
{
|
{
|
||||||
this._occupier = eligibleGuild;
|
this._occupier = eligibleGuild;
|
||||||
this._crownHoldGuild = null;
|
this._occupierName = eligibleGuildName;
|
||||||
this._crownHoldStartUtc = null;
|
this.ResetCrownHold();
|
||||||
this._dirty = true;
|
this._dirty = true;
|
||||||
return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.Captured, eligibleGuild);
|
return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.Captured, eligibleGuild, eligibleGuildName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.None, eligibleGuild);
|
return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.None, eligibleGuild, eligibleGuildName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -283,22 +374,27 @@ public class CastleSiegeContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restores persisted state on startup (owner, phase, phase-start, registrations) directly, without
|
/// Restores persisted state on startup (owner, state, state-start, registrations) directly, without
|
||||||
/// firing <see cref="PhaseChanged"/> or marking the state dirty. Battle state stays cleared.
|
/// firing <see cref="StateChanged"/> or marking the state dirty. Battle state stays cleared.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="owner">The persisted owner guild name, or null.</param>
|
/// <param name="ownerGuildId">The persisted owner guild identifier, or null.</param>
|
||||||
/// <param name="phase">The persisted phase.</param>
|
/// <param name="ownerGuildName">The persisted owner guild name, or null.</param>
|
||||||
/// <param name="phaseStartedUtc">When the persisted phase started (UTC), or null to keep the default.</param>
|
/// <param name="state">The persisted state.</param>
|
||||||
/// <param name="registeredGuilds">The persisted registered guild names, or null.</param>
|
/// <param name="stateStartedUtc">When the persisted state started (UTC), or null to keep the default.</param>
|
||||||
public void RestoreState(string? owner, CastleSiegePhase phase, DateTime? phaseStartedUtc, IEnumerable<string>? registeredGuilds)
|
/// <param name="registeredGuilds">The persisted registrations (id to name), or null.</param>
|
||||||
|
public void RestoreState(Guid? ownerGuildId, string? ownerGuildName, CastleSiegeState state, DateTime? stateStartedUtc, IEnumerable<KeyValuePair<Guid, string>>? registeredGuilds)
|
||||||
{
|
{
|
||||||
this.OwnerGuildName = owner;
|
this.OwnerGuildId = ownerGuildId;
|
||||||
this.Phase = phase;
|
this.OwnerGuildName = ownerGuildId is null ? null : ownerGuildName;
|
||||||
this._phaseStartedUtc = phaseStartedUtc ?? this._phaseStartedUtc;
|
this.State = state;
|
||||||
|
this._stateStartedUtc = stateStartedUtc ?? this._stateStartedUtc;
|
||||||
this._registeredGuilds.Clear();
|
this._registeredGuilds.Clear();
|
||||||
if (registeredGuilds is not null)
|
if (registeredGuilds is not null)
|
||||||
{
|
{
|
||||||
this._registeredGuilds.AddRange(registeredGuilds);
|
foreach (var registration in registeredGuilds)
|
||||||
|
{
|
||||||
|
this._registeredGuilds[registration.Key] = registration.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._dirty = false;
|
this._dirty = false;
|
||||||
@@ -317,79 +413,124 @@ public class CastleSiegeContext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Returns who is currently operating a Crown Switch, or <see langword="null"/>.</summary>
|
||||||
|
/// <param name="switchNumber">The Crown Switch NPC number (217 or 218).</param>
|
||||||
|
public CastleSiegeSwitchOperation? GetSwitchOperation(short switchNumber)
|
||||||
|
=> this._switches.TryGetValue(switchNumber, out var operation) ? operation : null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets which guild is currently standing on (holding) a Crown Switch. Called every tick by the plugin
|
/// Starts operating a Crown Switch for a player who clicked it. A switch can only be operated by one
|
||||||
/// based on player positions. Pass <c>null</c> when no registered member stands on it. No-op outside the siege.
|
/// player at a time: while somebody else is on it, the click is refused and the caller is told who holds
|
||||||
|
/// it, which is what the client shows as "another siege team is running the crown switch".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="switchNumber">The Crown Switch NPC number (217 or 218).</param>
|
/// <param name="switchNumber">The Crown Switch NPC number (217 or 218).</param>
|
||||||
/// <param name="guildName">The holding guild's name, or null.</param>
|
/// <param name="guildId">The clicking player's guild identifier.</param>
|
||||||
public void SetSwitchHolder(short switchNumber, string? guildName)
|
/// <param name="guildName">The clicking player's guild name, for display.</param>
|
||||||
|
/// <param name="playerId">The clicking player's object identifier on the map.</param>
|
||||||
|
/// <param name="playerName">The clicking player's name, for display.</param>
|
||||||
|
/// <param name="switchObjectId">The switch NPC's object identifier on the map.</param>
|
||||||
|
/// <param name="now">The current UTC time.</param>
|
||||||
|
/// <returns>The outcome, and the current operation when the switch is taken.</returns>
|
||||||
|
public (CastleSiegeSwitchPush Result, CastleSiegeSwitchOperation? Operation) TryStartSwitchOperation(
|
||||||
|
short switchNumber,
|
||||||
|
Guid guildId,
|
||||||
|
string guildName,
|
||||||
|
ushort playerId,
|
||||||
|
string playerName,
|
||||||
|
ushort switchObjectId,
|
||||||
|
DateTime now)
|
||||||
{
|
{
|
||||||
if (this.Phase == CastleSiegePhase.Siege && this._switchHolders.ContainsKey(switchNumber))
|
if (!this.IsSiegeRunning || !this._switches.ContainsKey(switchNumber))
|
||||||
{
|
{
|
||||||
this._switchHolders[switchNumber] = guildName;
|
return (CastleSiegeSwitchPush.SiegeNotRunning, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._switches[switchNumber] is { } current)
|
||||||
|
{
|
||||||
|
return current.PlayerId == playerId
|
||||||
|
? (CastleSiegeSwitchPush.AlreadyYours, current)
|
||||||
|
: (CastleSiegeSwitchPush.TakenByOther, current);
|
||||||
|
}
|
||||||
|
|
||||||
|
var operation = new CastleSiegeSwitchOperation(guildId, guildName, playerId, playerName, switchObjectId, now);
|
||||||
|
this._switches[switchNumber] = operation;
|
||||||
|
return (CastleSiegeSwitchPush.Started, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to capture the throne for a guild (called when a member registers at the Sinior/Crown NPC).
|
/// Advances one Crown Switch. The operation is dropped as soon as its player is gone from the switch's
|
||||||
/// Succeeds only during the siege when the throne is free, all castle defenses are destroyed, and the
|
/// area, and completes - which makes the switch count for the guild - once it ran <paramref name="pushDuration"/>.
|
||||||
/// guild is currently holding BOTH Crown Switches (a member standing on each).
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="guildName">The capturing guild's name.</param>
|
/// <param name="switchNumber">The Crown Switch NPC number (217 or 218).</param>
|
||||||
/// <returns>Whether it succeeded and a human-readable reason/result message.</returns>
|
/// <param name="operatorPresent">Whether the operating player is still in the switch's area.</param>
|
||||||
public (bool Success, string Reason) TryCaptureThrone(string guildName)
|
/// <param name="now">The current UTC time.</param>
|
||||||
|
/// <param name="pushDuration">How long operating the switch takes.</param>
|
||||||
|
/// <returns>What happened to the switch in this tick, and the operation it happened to.</returns>
|
||||||
|
public (CastleSiegeSwitchEvent Event, CastleSiegeSwitchOperation? Operation) TickSwitch(
|
||||||
|
short switchNumber,
|
||||||
|
bool operatorPresent,
|
||||||
|
DateTime now,
|
||||||
|
TimeSpan pushDuration)
|
||||||
{
|
{
|
||||||
if (this.Phase != CastleSiegePhase.Siege)
|
if (!this._switches.TryGetValue(switchNumber, out var operation) || operation is null)
|
||||||
{
|
{
|
||||||
return (false, "The siege is not running.");
|
return (CastleSiegeSwitchEvent.None, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._occupier is not null)
|
if (!this.IsSiegeRunning || !operatorPresent)
|
||||||
{
|
{
|
||||||
return (false, this._occupier == guildName
|
this._switches[switchNumber] = null;
|
||||||
? "Your guild already holds the throne."
|
return (CastleSiegeSwitchEvent.Released, operation);
|
||||||
: $"The throne is already held by '{this._occupier}'.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._defensesRemaining > 0)
|
if (!operation.IsHeld && now - operation.StartedUtc >= pushDuration)
|
||||||
{
|
{
|
||||||
return (false, $"Destroy the castle defenses first ({this._defensesRemaining} remaining).");
|
operation.MarkHeld();
|
||||||
|
return (CastleSiegeSwitchEvent.Held, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._switchHolders[217] != guildName || this._switchHolders[218] != guildName)
|
return (CastleSiegeSwitchEvent.None, operation);
|
||||||
{
|
|
||||||
return (false, "Your guild must be holding BOTH Crown Switches at once (stand a member on each).");
|
|
||||||
}
|
|
||||||
|
|
||||||
this._occupier = guildName;
|
|
||||||
return (true, "throne captured");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Returns a human-readable status summary for admin display.</summary>
|
/// <summary>Returns a human-readable status summary for admin display.</summary>
|
||||||
public string GetStatusText()
|
public string GetStatusText()
|
||||||
=> $"CS phase={this.Phase}, owner={this.OwnerGuildName ?? "(none)"}, "
|
=> $"CS state={this.State}({(int)this.State}), owner={this.OwnerGuildName ?? "(none)"}, "
|
||||||
+ $"registered={this._registeredGuilds.Count} [{string.Join(", ", this._registeredGuilds)}], "
|
+ $"registered={this._registeredGuilds.Count} [{string.Join(", ", this._registeredGuilds.Values)}], "
|
||||||
+ $"defenses={this._defensesRemaining}, throne={this._occupier ?? "(none)"}, "
|
+ $"defenses={this._defensesRemaining}, throne={this._occupierName ?? "(none)"}, "
|
||||||
+ $"switch217={this._switchHolders[217] ?? "-"}, switch218={this._switchHolders[218] ?? "-"}";
|
+ $"switch217={this.DescribeSwitch(217)}, switch218={this.DescribeSwitch(218)}";
|
||||||
|
|
||||||
|
private Guid? GetHeldSwitchGuild(short switchNumber)
|
||||||
|
=> this._switches[switchNumber] is { IsHeld: true } operation ? operation.GuildId : null;
|
||||||
|
|
||||||
|
private string DescribeSwitch(short switchNumber)
|
||||||
|
=> this._switches[switchNumber] is { } operation
|
||||||
|
? $"{operation.GuildName}/{operation.PlayerName}{(operation.IsHeld ? string.Empty : " (pushing)")}"
|
||||||
|
: "-";
|
||||||
|
|
||||||
|
private void ResetCrownHold()
|
||||||
|
{
|
||||||
|
this._crownHoldGuild = null;
|
||||||
|
this._crownHoldStartUtc = null;
|
||||||
|
this._crownHoldRequestedBy = null;
|
||||||
|
}
|
||||||
|
|
||||||
private void ClearBattleState()
|
private void ClearBattleState()
|
||||||
{
|
{
|
||||||
this._switchHolders[217] = null;
|
this._switches[217] = null;
|
||||||
this._switchHolders[218] = null;
|
this._switches[218] = null;
|
||||||
this._defensesRemaining = 0;
|
this._defensesRemaining = 0;
|
||||||
this._occupier = null;
|
this._occupier = null;
|
||||||
this._crownHoldGuild = null;
|
this._occupierName = null;
|
||||||
this._crownHoldStartUtc = null;
|
this.ResetCrownHold();
|
||||||
this._lastShieldDown = false;
|
this._lastShieldDown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValueTask TransitionAsync(CastleSiegePhase phase, DateTime now)
|
private ValueTask TransitionAsync(CastleSiegeState state, DateTime now)
|
||||||
{
|
{
|
||||||
this.Phase = phase;
|
this.State = state;
|
||||||
this._phaseStartedUtc = now;
|
this._stateStartedUtc = now;
|
||||||
this._dirty = true;
|
this._dirty = true;
|
||||||
this.PhaseChanged?.Invoke(phase);
|
this.StateChanged?.Invoke(state);
|
||||||
return ValueTask.CompletedTask;
|
return ValueTask.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,5 +555,6 @@ public enum CrownEvent
|
|||||||
/// <param name="ShieldDown">Whether the crown shield is currently down (both switches held, defenses cleared).</param>
|
/// <param name="ShieldDown">Whether the crown shield is currently down (both switches held, defenses cleared).</param>
|
||||||
/// <param name="ShieldChanged">Whether the shield state changed this tick (only then should the client be told).</param>
|
/// <param name="ShieldChanged">Whether the shield state changed this tick (only then should the client be told).</param>
|
||||||
/// <param name="Event">The event that occurred this tick.</param>
|
/// <param name="Event">The event that occurred this tick.</param>
|
||||||
/// <param name="Guild">The guild the event refers to, if any.</param>
|
/// <param name="GuildId">The guild the event refers to, if any.</param>
|
||||||
public readonly record struct CrownTickResult(bool ShieldDown, bool ShieldChanged, CrownEvent Event, string? Guild);
|
/// <param name="GuildName">That guild's name, for display and client packets.</param>
|
||||||
|
public readonly record struct CrownTickResult(bool ShieldDown, bool ShieldChanged, CrownEvent Event, Guid? GuildId, string? GuildName);
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public class CastleSiegeGuardsmanTalkPlugIn : IPlayerTalkToNpcPlugIn
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.Phase != CastleSiegePhase.Registration)
|
if (context.State != CastleSiegeState.RegisterGuild)
|
||||||
{
|
{
|
||||||
await ShowAsync(player, "Castle Siege registration is not open right now.").ConfigureAwait(false);
|
await ShowAsync(player, "Castle Siege registration is not open right now.").ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
@@ -61,17 +61,15 @@ public class CastleSiegeGuardsmanTalkPlugIn : IPlayerTalkToNpcPlugIn
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildName = guildStatus.GuildId.ToString();
|
// Registrations are keyed on the guild's persistent id, so a later rename cannot detach them.
|
||||||
if (player.GameContext is IGameServerContext serverContext)
|
if (await CastleSiegeEventPlugIn.GetPersistentGuildAsync(player).ConfigureAwait(false) is not { } guild)
|
||||||
{
|
{
|
||||||
var guild = await serverContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false);
|
await ShowAsync(player, "Your guild could not be resolved. Please try again in a moment.").ConfigureAwait(false);
|
||||||
if (guild?.Name is { Length: > 0 } name)
|
return;
|
||||||
{
|
|
||||||
guildName = name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.RegisteredGuilds.Contains(guildName))
|
var guildName = guild.Name;
|
||||||
|
if (context.IsRegistered(guild.Id))
|
||||||
{
|
{
|
||||||
await ShowAsync(player, $"Your guild '{guildName}' is already registered for the Castle Siege.").ConfigureAwait(false);
|
await ShowAsync(player, $"Your guild '{guildName}' is already registered for the Castle Siege.").ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
@@ -84,7 +82,7 @@ public class CastleSiegeGuardsmanTalkPlugIn : IPlayerTalkToNpcPlugIn
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.RegisterGuild(guildName);
|
context.RegisterGuild(guild.Id, guildName);
|
||||||
await ShowAsync(player, fee > 0
|
await ShowAsync(player, fee > 0
|
||||||
? $"Your guild '{guildName}' is registered for the Castle Siege. ({fee} zen paid)"
|
? $"Your guild '{guildName}' is registered for the Castle Siege. ({fee} zen paid)"
|
||||||
: $"Your guild '{guildName}' is registered for the Castle Siege.").ConfigureAwait(false);
|
: $"Your guild '{guildName}' is registered for the Castle Siege.").ConfigureAwait(false);
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
// <copyright file="CastleSiegePhase.cs" company="MUnique">
|
|
||||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
|
||||||
// </copyright>
|
|
||||||
|
|
||||||
namespace MUnique.OpenMU.GameLogic.CastleSiege;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The phases of a Castle Siege cycle.
|
|
||||||
/// </summary>
|
|
||||||
public enum CastleSiegePhase
|
|
||||||
{
|
|
||||||
/// <summary>Resting phase: castle is (un)owned, waiting for the next registration window.</summary>
|
|
||||||
Ownership,
|
|
||||||
|
|
||||||
/// <summary>Guilds can register to attack.</summary>
|
|
||||||
Registration,
|
|
||||||
|
|
||||||
/// <summary>Registration closed; defenders prepare before the siege starts.</summary>
|
|
||||||
Preparation,
|
|
||||||
|
|
||||||
/// <summary>The siege battle is running.</summary>
|
|
||||||
Siege,
|
|
||||||
|
|
||||||
/// <summary>Siege ended; determining the new owner.</summary>
|
|
||||||
Settlement,
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// <copyright file="CastleSiegeConfiguration.cs" company="MUnique">
|
// <copyright file="CastleSiegeSettings.cs" company="MUnique">
|
||||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
@@ -7,15 +7,31 @@ namespace MUnique.OpenMU.GameLogic.CastleSiege;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configuration for the Castle Siege cycle. Rides on the plugin custom-configuration system (no dedicated
|
/// AdaMu operational settings for the Castle Siege cycle. Rides on the plugin custom-configuration system, so
|
||||||
/// database table). A cycle runs: Ownership -> Registration -> Preparation -> Siege(war) -> Settlement, and
|
/// it is editable in the AdminPanel and needs no dedicated database table.
|
||||||
/// auto-starts when the current day/time matches <see cref="OpenDays"/> + <see cref="RegistrationOpenTimes"/>.
|
/// <para>
|
||||||
/// The AdminPanel-friendly properties (OpenDays checkboxes, minute/second durations) are proxies over the
|
/// This is deliberately separate from <see cref="DataModel.Configuration.CastleSiegeConfiguration"/>, which is
|
||||||
/// runtime fields, which are hidden from the editor to keep the form clean.
|
/// the upstream, database-backed configuration holding the NPC/zone/upgrade definitions and the crown hold
|
||||||
|
/// time. Keeping AdaMu's operational knobs out of that entity means upstream schema changes apply cleanly and
|
||||||
|
/// no hand-editing of the generated persistence code is needed.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// What lives where:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>Castle owner and guild registrations: database (<c>CastleSiegeData</c>, <c>CastleSiegeGuildRegistration</c>).</item>
|
||||||
|
/// <item>NPC/zone/upgrade definitions and crown hold time: database (<c>GameConfiguration.CastleSiegeConfiguration</c>).</item>
|
||||||
|
/// <item>Cycle durations, registration fee, designated server and the current state: here.</item>
|
||||||
|
/// </list>
|
||||||
|
/// </para>
|
||||||
|
/// A cycle runs Idle1 -> RegisterGuild -> Ready -> Start -> End -> EndCycle -> Idle1, and auto-starts when the
|
||||||
|
/// current day/time matches <see cref="OpenDays"/> + <see cref="RegistrationOpenTimes"/>.
|
||||||
|
/// The AdminPanel-friendly properties (OpenDays checkboxes, minute durations) are proxies over the runtime
|
||||||
|
/// fields, which are hidden from the editor to keep the form clean.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CastleSiegeConfiguration
|
public class CastleSiegeSettings
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the days of week on which registration auto-opens (UTC). None = every day (still needs a
|
/// Gets or sets the days of week on which registration auto-opens (UTC). None = every day (still needs a
|
||||||
@@ -42,7 +58,7 @@ public class CastleSiegeConfiguration
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the times of day (UTC) at which a new cycle opens registration.
|
/// Gets or sets the times of day (UTC) at which a new cycle opens registration.
|
||||||
/// Empty = no auto-start (admins start cycles manually via the chat command).
|
/// Empty = no auto-start (admins start cycles manually via the chat command or the AdminPanel).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IList<TimeOnly> RegistrationOpenTimes { get; set; } = new List<TimeOnly>();
|
public IList<TimeOnly> RegistrationOpenTimes { get; set; } = new List<TimeOnly>();
|
||||||
|
|
||||||
@@ -70,17 +86,6 @@ public class CastleSiegeConfiguration
|
|||||||
set => this.SiegeDuration = TimeSpan.FromMinutes(Math.Max(1, value));
|
set => this.SiegeDuration = TimeSpan.FromMinutes(Math.Max(1, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets how long the guild master must hold the Crown to capture the throne, in seconds.
|
|
||||||
/// The client shows a 60-second countdown, so 60 matches the on-screen timer.
|
|
||||||
/// </summary>
|
|
||||||
[JsonIgnore]
|
|
||||||
public int CrownHoldSeconds
|
|
||||||
{
|
|
||||||
get => (int)this.CrownHoldDuration.TotalSeconds;
|
|
||||||
set => this.CrownHoldDuration = TimeSpan.FromSeconds(Math.Max(1, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the registration fee (in zen) a guild master must pay to register the guild
|
/// Gets or sets the registration fee (in zen) a guild master must pay to register the guild
|
||||||
/// for the siege. 0 disables the fee.
|
/// for the siege. 0 disables the fee.
|
||||||
@@ -112,29 +117,23 @@ public class CastleSiegeConfiguration
|
|||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public TimeSpan SiegeDuration { get; set; } = TimeSpan.FromMinutes(10);
|
public TimeSpan SiegeDuration { get; set; } = TimeSpan.FromMinutes(10);
|
||||||
|
|
||||||
/// <summary>Gets or sets how long the guild master must hold the Crown to capture the throne.</summary>
|
/// <summary>
|
||||||
[Browsable(false)]
|
/// Gets or sets how many seconds a player has to operate a Crown Switch before it counts for their guild.
|
||||||
public TimeSpan CrownHoldDuration { get; set; } = TimeSpan.FromSeconds(60);
|
/// The player has to stay in the switch's area for that long, and keeps it until they leave.
|
||||||
|
/// </summary>
|
||||||
|
public int SwitchPushSeconds { get; set; } = 15;
|
||||||
|
|
||||||
// --- Persisted runtime state (hidden from the AdminPanel) ---
|
// --- Persisted cycle bookkeeping (hidden from the AdminPanel) ---
|
||||||
// These ride on the plugin's custom-configuration JSON (stored in PostgreSQL), so the castle owner and the
|
// Only the CURRENT state and when it started ride on the plugin's custom-configuration JSON. The castle
|
||||||
// current cycle survive server restarts. Written by CastleSiegeEventPlugIn; restored on startup.
|
// owner and the guild registrations live in real database tables, so they are not duplicated here.
|
||||||
|
|
||||||
/// <summary>Gets or sets the persisted castle owner guild name (null = unowned).</summary>
|
/// <summary>Gets or sets the persisted current state, so the cycle resumes after a restart.</summary>
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public string? PersistedOwnerGuildName { get; set; }
|
public CastleSiegeState PersistedState { get; set; } = CastleSiegeState.Idle1;
|
||||||
|
|
||||||
/// <summary>Gets or sets the persisted current phase, so the cycle resumes after a restart.</summary>
|
/// <summary>Gets or sets when the persisted state started (UTC), or null if never persisted.</summary>
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public CastleSiegePhase PersistedPhase { get; set; } = CastleSiegePhase.Ownership;
|
public DateTime? PersistedStateStartedUtc { get; set; }
|
||||||
|
|
||||||
/// <summary>Gets or sets when the persisted phase started (UTC), or null if never persisted.</summary>
|
|
||||||
[Browsable(false)]
|
|
||||||
public DateTime? PersistedPhaseStartedUtc { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Gets or sets the persisted registered guild names for the current cycle.</summary>
|
|
||||||
[Browsable(false)]
|
|
||||||
public IList<string> PersistedRegisteredGuilds { get; set; } = new List<string>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if <paramref name="now"/> (UTC) matches a scheduled registration-open day and falls
|
/// Returns true if <paramref name="now"/> (UTC) matches a scheduled registration-open day and falls
|
||||||
20
src/GameLogic/CastleSiege/CastleSiegeSwitchEvent.cs
Normal file
20
src/GameLogic/CastleSiege/CastleSiegeSwitchEvent.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// <copyright file="CastleSiegeSwitchEvent.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.GameLogic.CastleSiege;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What happened to a Crown Switch during one tick.
|
||||||
|
/// </summary>
|
||||||
|
public enum CastleSiegeSwitchEvent
|
||||||
|
{
|
||||||
|
/// <summary>Nothing worth reporting.</summary>
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// <summary>The operation completed, so the switch now counts for the operator's guild.</summary>
|
||||||
|
Held,
|
||||||
|
|
||||||
|
/// <summary>The operator left (or the siege ended), so the switch is free again.</summary>
|
||||||
|
Released,
|
||||||
|
}
|
||||||
55
src/GameLogic/CastleSiege/CastleSiegeSwitchOperation.cs
Normal file
55
src/GameLogic/CastleSiege/CastleSiegeSwitchOperation.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// <copyright file="CastleSiegeSwitchOperation.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.GameLogic.CastleSiege;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// One player operating one Crown Switch. The player starts it by clicking the switch and keeps it by
|
||||||
|
/// staying in its area; the switch counts as held for the guild once the operation has run its time.
|
||||||
|
/// </summary>
|
||||||
|
public class CastleSiegeSwitchOperation
|
||||||
|
{
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="CastleSiegeSwitchOperation"/> class.</summary>
|
||||||
|
/// <param name="guildId">The operating player's guild identifier.</param>
|
||||||
|
/// <param name="guildName">The operating player's guild name, for display.</param>
|
||||||
|
/// <param name="playerId">The operating player's object identifier on the map.</param>
|
||||||
|
/// <param name="playerName">The operating player's name, for display.</param>
|
||||||
|
/// <param name="switchObjectId">The switch NPC's object identifier on the map.</param>
|
||||||
|
/// <param name="startedUtc">When the operation started (UTC).</param>
|
||||||
|
public CastleSiegeSwitchOperation(Guid guildId, string guildName, ushort playerId, string playerName, ushort switchObjectId, DateTime startedUtc)
|
||||||
|
{
|
||||||
|
this.GuildId = guildId;
|
||||||
|
this.GuildName = guildName;
|
||||||
|
this.PlayerId = playerId;
|
||||||
|
this.PlayerName = playerName;
|
||||||
|
this.SwitchObjectId = switchObjectId;
|
||||||
|
this.StartedUtc = startedUtc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the operating player's guild identifier.</summary>
|
||||||
|
public Guid GuildId { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the operating player's guild name.</summary>
|
||||||
|
public string GuildName { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the operating player's object identifier on the map.</summary>
|
||||||
|
public ushort PlayerId { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the operating player's name.</summary>
|
||||||
|
public string PlayerName { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the switch NPC's object identifier on the map, which the client's packets refer to.</summary>
|
||||||
|
public ushort SwitchObjectId { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the point in time (UTC) when the operation started.</summary>
|
||||||
|
public DateTime StartedUtc { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the operation ran its time, so the switch counts for the guild.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsHeld { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Marks the operation as completed, which makes the switch count for the guild.</summary>
|
||||||
|
internal void MarkHeld() => this.IsHeld = true;
|
||||||
|
}
|
||||||
23
src/GameLogic/CastleSiege/CastleSiegeSwitchPush.cs
Normal file
23
src/GameLogic/CastleSiege/CastleSiegeSwitchPush.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// <copyright file="CastleSiegeSwitchPush.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.GameLogic.CastleSiege;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The outcome of a player clicking a Crown Switch.
|
||||||
|
/// </summary>
|
||||||
|
public enum CastleSiegeSwitchPush
|
||||||
|
{
|
||||||
|
/// <summary>The player started operating the switch.</summary>
|
||||||
|
Started,
|
||||||
|
|
||||||
|
/// <summary>The player is already operating this switch.</summary>
|
||||||
|
AlreadyYours,
|
||||||
|
|
||||||
|
/// <summary>Somebody else is operating this switch.</summary>
|
||||||
|
TakenByOther,
|
||||||
|
|
||||||
|
/// <summary>The siege is not running, so the switches do nothing.</summary>
|
||||||
|
SiegeNotRunning,
|
||||||
|
}
|
||||||
92
src/GameLogic/CastleSiege/CastleSiegeSwitchTalkPlugIn.cs
Normal file
92
src/GameLogic/CastleSiege/CastleSiegeSwitchTalkPlugIn.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
// <copyright file="CastleSiegeSwitchTalkPlugIn.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.GameLogic.CastleSiege;
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using MUnique.OpenMU.GameLogic.NPC;
|
||||||
|
using MUnique.OpenMU.GameLogic.PlugIns;
|
||||||
|
using MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;
|
||||||
|
using MUnique.OpenMU.GameLogic.Views;
|
||||||
|
using MUnique.OpenMU.GameLogic.Views.CastleSiege;
|
||||||
|
using MUnique.OpenMU.Interfaces;
|
||||||
|
using MUnique.OpenMU.PlugIns;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles clicking a Crown Switch (NPC 217 / 218) on Valley of Loren. The click starts operating the
|
||||||
|
/// switch, which the client shows as a progress box; the switch counts for the guild once the operation
|
||||||
|
/// ran its time and stays theirs until the operating player leaves the switch's area. Only one player can
|
||||||
|
/// operate a switch at a time - anybody else clicking it is told that another team is on it.
|
||||||
|
/// </summary>
|
||||||
|
[Guid("CA5710A0-7A1B-4C2D-8E3F-000000000217")]
|
||||||
|
[PlugIn]
|
||||||
|
[Display(Name = "Castle Siege Crown Switch", Description = "Operates a Crown Switch (NPC 217/218) during the Castle Siege.")]
|
||||||
|
public class CastleSiegeSwitchTalkPlugIn : IPlayerTalkToNpcPlugIn
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async ValueTask PlayerTalksToNpcAsync(Player player, NonPlayerCharacter npc, NpcTalkEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (!CastleSiegeContext.SwitchNumbers.Contains(npc.Definition.Number))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We drive the switch ourselves, so suppress the default "not implemented" message.
|
||||||
|
eventArgs.HasBeenHandled = true;
|
||||||
|
|
||||||
|
var context = CastleSiegeEventPlugIn.TryGetContext(player.GameContext);
|
||||||
|
if (context is null)
|
||||||
|
{
|
||||||
|
await ShowAsync(player, "Castle Siege is not active on this server.").ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context.IsSiegeRunning)
|
||||||
|
{
|
||||||
|
await ShowAsync(player, "The Crown Switches only work while the siege is running.").ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await CastleSiegeEventPlugIn.GetPersistentGuildAsync(player).ConfigureAwait(false) is not { } guild
|
||||||
|
|| !context.IsRegistered(guild.Id))
|
||||||
|
{
|
||||||
|
await ShowAsync(player, "Only members of a registered guild can operate the Crown Switches.").ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (result, operation) = context.TryStartSwitchOperation(
|
||||||
|
npc.Definition.Number,
|
||||||
|
guild.Id,
|
||||||
|
guild.Name,
|
||||||
|
player.Id,
|
||||||
|
player.Name,
|
||||||
|
npc.Id,
|
||||||
|
DateTime.UtcNow);
|
||||||
|
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case CastleSiegeSwitchPush.Started:
|
||||||
|
// The info packet goes first: it is what makes every client allocate its switch table, which
|
||||||
|
// the "switch released" packet later reads without checking that it exists.
|
||||||
|
await CastleSiegeEventPlugIn.BroadcastSwitchInfoAsync(player.GameContext, npc.Id, operation).ConfigureAwait(false);
|
||||||
|
await player.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.SetCrownSwitchStateAsync(npc.Id, player.Id, 1)).ConfigureAwait(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CastleSiegeSwitchPush.TakenByOther when operation is { } other:
|
||||||
|
// State 2 makes the client name the player who is already on it.
|
||||||
|
await player.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.SetCrownSwitchStateAsync(npc.Id, other.PlayerId, 2)).ConfigureAwait(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CastleSiegeSwitchPush.AlreadyYours:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
await ShowAsync(player, "The Crown Switches only work while the siege is running.").ConfigureAwait(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ValueTask ShowAsync(Player player, string text)
|
||||||
|
=> player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync(text, MessageType.BlueNormal));
|
||||||
|
}
|
||||||
@@ -43,56 +43,58 @@ public class CastleSiegeThroneCaptureTalkPlugIn : IPlayerTalkToNpcPlugIn
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.GuildStatus is not { } guildStatus)
|
if (await CastleSiegeEventPlugIn.GetPersistentGuildAsync(player).ConfigureAwait(false) is not { } guild)
|
||||||
{
|
{
|
||||||
await ShowAsync(player, "Only members of a registered guild can take the throne.").ConfigureAwait(false);
|
await ShowAsync(player, "Only members of a registered guild can take the throne.").ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildName = guildStatus.GuildId.ToString();
|
if (!context.IsRegistered(guild.Id))
|
||||||
if (player.GameContext is IGameServerContext serverContext)
|
|
||||||
{
|
|
||||||
var guild = await serverContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false);
|
|
||||||
if (guild?.Name is { Length: > 0 } name)
|
|
||||||
{
|
|
||||||
guildName = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!context.RegisteredGuilds.Contains(guildName))
|
|
||||||
{
|
{
|
||||||
await ShowAsync(player, "Your guild is not registered for this Castle Siege.").ConfigureAwait(false);
|
await ShowAsync(player, "Your guild is not registered for this Castle Siege.").ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The throne is taken by holding the Crown, not by talking here — give guidance based on the state.
|
// Clicking the Crown as the guild master is what arms the capture: the hold then runs while they
|
||||||
await ShowAsync(player, DescribeThroneStep(context, guildName)).ConfigureAwait(false);
|
// stay on it. Anybody else (or a master who isn't entitled yet) just gets told what is missing.
|
||||||
}
|
var isGuildMaster = player.GuildStatus?.Position == GuildPosition.GuildMaster;
|
||||||
|
if (isGuildMaster && context.RequestCrownHold(guild.Id))
|
||||||
private static string DescribeThroneStep(CastleSiegeContext context, string guildName)
|
|
||||||
{
|
|
||||||
if (context.Phase != CastleSiegePhase.Siege)
|
|
||||||
{
|
{
|
||||||
return "The siege is not running yet.";
|
await ShowAsync(player, "Hold the Crown - do not step away until the seal is registered!").ConfigureAwait(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.DefensesRemaining > 0)
|
await ShowAsync(player, DescribeThroneStep(context, guild.Id, isGuildMaster)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string DescribeThroneStep(CastleSiegeContext context, Guid guildId, bool isGuildMaster)
|
||||||
|
{
|
||||||
|
if (!context.IsSiegeRunning)
|
||||||
{
|
{
|
||||||
return $"Destroy all castle gates first ({context.DefensesRemaining} remaining), then hold both Crown Switches.";
|
return "The siege is not running yet.";
|
||||||
}
|
}
|
||||||
|
|
||||||
var eligible = context.GetShieldEligibleGuild();
|
var eligible = context.GetShieldEligibleGuild();
|
||||||
if (eligible is null)
|
if (eligible is null)
|
||||||
{
|
{
|
||||||
return "All gates are down! Hold BOTH Crown Switches with your guild — the Crown's shield will drop.";
|
return context.DefensesRemaining > 0
|
||||||
|
? $"Hold BOTH Crown Switches with your guild to drop the Crown's shield ({context.DefensesRemaining} castle defenses still standing)."
|
||||||
|
: "Hold BOTH Crown Switches with your guild - the Crown's shield will drop.";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eligible == guildName)
|
if (eligible != guildId)
|
||||||
{
|
{
|
||||||
return "Your guild holds both switches and the shield is down — send your GUILD MASTER to hold the Crown to take the throne!";
|
return "Another guild is holding both switches. Take a switch back to raise their shield.";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"Guild '{eligible}' is holding both switches. Take a switch back to raise their shield.";
|
if (context.OccupierGuildId == guildId)
|
||||||
|
{
|
||||||
|
return "Your guild already holds the throne - keep it until the siege ends.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return isGuildMaster
|
||||||
|
? "Your guild holds both switches, but the Crown cannot be registered right now."
|
||||||
|
: "Your guild holds both switches and the shield is down - your GUILD MASTER has to click the Crown!";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ValueTask ShowAsync(Player player, string text)
|
private static ValueTask ShowAsync(Player player, string text)
|
||||||
|
|||||||
@@ -717,7 +717,7 @@ public class Player : AsyncDisposable, IBucketMapObserver, IAttackable, IAttacke
|
|||||||
// ADAMU-CUSTOM: Castle Siege PvP gate — true while the siege phase runs on Valley of Loren (map 30).
|
// ADAMU-CUSTOM: Castle Siege PvP gate — true while the siege phase runs on Valley of Loren (map 30).
|
||||||
private bool IsCastleSiegeBattleActive()
|
private bool IsCastleSiegeBattleActive()
|
||||||
=> this.CurrentMap?.Definition.Number == 30
|
=> this.CurrentMap?.Definition.Number == 30
|
||||||
&& PlugIns.PeriodicTasks.CastleSiegeEventPlugIn.TryGetContext(this.GameContext)?.Phase == CastleSiege.CastleSiegePhase.Siege;
|
&& PlugIns.PeriodicTasks.CastleSiegeEventPlugIn.TryGetContext(this.GameContext)?.IsSiegeRunning == true;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async ValueTask<HitInfo?> AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0, bool? isFinalStreakHit = null)
|
public async ValueTask<HitInfo?> AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0, bool? isFinalStreakHit = null)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ using MUnique.OpenMU.PlugIns;
|
|||||||
/// <summary>Forces a specific Castle Siege phase. GM only. Usage: /csphase Siege.</summary>
|
/// <summary>Forces a specific Castle Siege phase. GM only. Usage: /csphase Siege.</summary>
|
||||||
[Guid("A1B2C3D4-0003-4E5F-9A0B-CA5710000003")]
|
[Guid("A1B2C3D4-0003-4E5F-9A0B-CA5710000003")]
|
||||||
[PlugIn]
|
[PlugIn]
|
||||||
[Display(Name = "Castle Siege Phase", Description = "GM command: /csphase <Ownership|Registration|Preparation|Siege|Settlement>")]
|
[Display(Name = "Castle Siege Phase", Description = "GM command: /csphase <Idle1|RegisterGuild|Ready|Start|End|EndCycle>")]
|
||||||
[ChatCommandHelp(Command, CharacterStatus.GameMaster)]
|
[ChatCommandHelp(Command, CharacterStatus.GameMaster)]
|
||||||
public class CastleSiegePhaseChatCommandPlugIn : IChatCommandPlugIn
|
public class CastleSiegePhaseChatCommandPlugIn : IChatCommandPlugIn
|
||||||
{
|
{
|
||||||
@@ -30,9 +30,11 @@ public class CastleSiegePhaseChatCommandPlugIn : IChatCommandPlugIn
|
|||||||
public async ValueTask HandleCommandAsync(Player player, string command)
|
public async ValueTask HandleCommandAsync(Player player, string command)
|
||||||
{
|
{
|
||||||
var parts = command.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
var parts = command.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (parts.Length < 2 || !Enum.TryParse<CastleSiegePhase>(parts[1], true, out var phase))
|
// The cycle uses the original Season 6 state names the client knows. AdaMu drives only this subset;
|
||||||
|
// Idle2, RegisterMark, Idle3 and Notify exist for client compatibility but are never entered.
|
||||||
|
if (parts.Length < 2 || !Enum.TryParse<CastleSiegeState>(parts[1], true, out var phase))
|
||||||
{
|
{
|
||||||
await player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync("Usage: /csphase <Ownership|Registration|Preparation|Siege|Settlement>", MessageType.BlueNormal)).ConfigureAwait(false);
|
await player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync("Usage: /csphase <Idle1|RegisterGuild|Ready|Start|End|EndCycle>", MessageType.BlueNormal)).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +45,7 @@ public class CastleSiegePhaseChatCommandPlugIn : IChatCommandPlugIn
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await context.ForcePhaseAsync(phase, DateTime.UtcNow).ConfigureAwait(false);
|
await context.ForceStateAsync(phase, DateTime.UtcNow).ConfigureAwait(false);
|
||||||
await player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync($"Castle Siege: phase set to {phase}.", MessageType.BlueNormal)).ConfigureAwait(false);
|
await player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync($"Castle Siege: phase set to {phase}.", MessageType.BlueNormal)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,22 @@ public class CastleSiegeSetOwnerChatCommandPlugIn : IChatCommandPlugIn
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.SetOwner(string.IsNullOrWhiteSpace(owner) ? null : owner);
|
if (string.IsNullOrWhiteSpace(owner))
|
||||||
await player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync($"Castle Siege owner set to {owner ?? "(none)"}.", MessageType.BlueNormal)).ConfigureAwait(false);
|
{
|
||||||
|
context.SetOwner(null, null);
|
||||||
|
await player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync("Castle Siege owner cleared.", MessageType.BlueNormal)).ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ownership is stored by the guild's persistent id, so the name given here is resolved once.
|
||||||
|
var guildId = await CastleSiegeEventPlugIn.ResolveGuildIdByNameAsync(player.GameContext, owner).ConfigureAwait(false);
|
||||||
|
if (guildId is not { } id)
|
||||||
|
{
|
||||||
|
await player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync($"No guild named '{owner}' was found.", MessageType.BlueNormal)).ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.SetOwner(id, owner);
|
||||||
|
await player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync($"Castle Siege owner set to {owner}.", MessageType.BlueNormal)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using MUnique.OpenMU.DataModel.Configuration;
|
||||||
using MUnique.OpenMU.GameLogic.CastleSiege;
|
using MUnique.OpenMU.GameLogic.CastleSiege;
|
||||||
using MUnique.OpenMU.GameLogic.NPC;
|
using MUnique.OpenMU.GameLogic.NPC;
|
||||||
using MUnique.OpenMU.GameLogic.Views;
|
using MUnique.OpenMU.GameLogic.Views;
|
||||||
@@ -15,65 +16,69 @@ using MUnique.OpenMU.Interfaces;
|
|||||||
using MUnique.OpenMU.Pathfinding;
|
using MUnique.OpenMU.Pathfinding;
|
||||||
using MUnique.OpenMU.Persistence;
|
using MUnique.OpenMU.Persistence;
|
||||||
using MUnique.OpenMU.PlugIns;
|
using MUnique.OpenMU.PlugIns;
|
||||||
|
using CastleSiegeDefinition = MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drives the Castle Siege phase state machine: ticks it every second and carries its configuration.
|
/// Drives the Castle Siege state machine: ticks it every second and carries its operational settings.
|
||||||
/// State is per-<see cref="IGameContext"/> and kept in memory (P1: no persistence).
|
/// <para>
|
||||||
/// When the siege phase starts (P3), warps registered guild members to the Valley of Loren battle map.
|
/// The cycle uses the original Season 6 state numbers the client expects. The castle owner and the guild
|
||||||
|
/// registrations are persisted in real database tables (<see cref="CastleSiegeData"/> and
|
||||||
|
/// <see cref="CastleSiegeGuildRegistration"/>) and keyed by the guild's persistent <see cref="Guid"/>, so a
|
||||||
|
/// guild rename can no longer move castle ownership to the wrong guild. Only the current state and when it
|
||||||
|
/// started ride on the plugin's custom-configuration JSON.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Castle NPCs (gates, statues, catapults, the crown and its switches) are read from
|
||||||
|
/// <see cref="GameConfiguration.CastleSiegeConfiguration"/>, which the CastleSiegeInitializer seeds, instead
|
||||||
|
/// of being hard-coded here.
|
||||||
|
/// </para>
|
||||||
|
/// When the siege starts, registered guild members are warped to the Valley of Loren battle map.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[PlugIn]
|
[PlugIn]
|
||||||
[Display(Name = nameof(CastleSiegeEventPlugIn), Description = "Castle Siege event (phase state machine, scheduling, siege warp).")]
|
[Display(Name = nameof(CastleSiegeEventPlugIn), Description = "Castle Siege event (state machine, scheduling, siege warp).")]
|
||||||
[Guid("6E2C8B41-9A4D-4C2E-9E7B-1F2A3B4C5D60")]
|
[Guid("6E2C8B41-9A4D-4C2E-9E7B-1F2A3B4C5D60")]
|
||||||
public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustomConfiguration<CastleSiegeConfiguration>, ISupportDefaultCustomConfiguration
|
public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustomConfiguration<CastleSiegeSettings>, ISupportDefaultCustomConfiguration
|
||||||
{
|
{
|
||||||
private const ushort ValleyOfLorenMapNumber = 30;
|
private const ushort ValleyOfLorenMapNumber = 30;
|
||||||
private const short CastleGateNumber = 277; // client MONSTER_CASTLE_GATE1 -> renders a real gate + blocks terrain until broken
|
private const short GateTemplateNumber = 131; // BloodCastle "Castle Gate" destructible (has HP) — HP template for the CS gates.
|
||||||
private const short GateTemplateNumber = 131; // BloodCastle "Castle Gate" destructible (has HP) — HP template for 277
|
private const short CrownNumber = 216;
|
||||||
|
private const short CatapultAttackNumber = 221; // client MONSTER_SLINGSHOT_ATTACK
|
||||||
|
private const short CatapultDefenseNumber = 222; // client MONSTER_SLINGSHOT_DEFENSE
|
||||||
private const int SwitchHoldRange = 3;
|
private const int SwitchHoldRange = 3;
|
||||||
|
|
||||||
// Castle defenses spawned at siege start — the 6 REAL castle gates (client g_byGateLocation).
|
|
||||||
// The client renders them closed and blocks the terrain until each is broken. All must fall before the throne.
|
|
||||||
private static readonly (short Number, byte X, byte Y)[] DefenseSpawns =
|
|
||||||
{
|
|
||||||
(CastleGateNumber, 67, 114),
|
|
||||||
(CastleGateNumber, 93, 114),
|
|
||||||
(CastleGateNumber, 119, 114),
|
|
||||||
(CastleGateNumber, 81, 161),
|
|
||||||
(CastleGateNumber, 107, 161),
|
|
||||||
(CastleGateNumber, 93, 204),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Siege weapons spawned at siege start purely for war atmosphere. The client renders monster 221/222 as
|
|
||||||
// catapults (attacker/defender). They are NOT counted as defenses (destroying them doesn't open the throne).
|
|
||||||
private const short CatapultAttackNumber = 221; // client MONSTER_SLINGSHOT_ATTACK
|
|
||||||
private const short CatapultDefenseNumber = 222; // client MONSTER_SLINGSHOT_DEFENSE
|
|
||||||
private static readonly (short Number, byte X, byte Y)[] CatapultSpawns =
|
|
||||||
{
|
|
||||||
(CatapultDefenseNumber, 80, 140),
|
|
||||||
(CatapultDefenseNumber, 110, 140),
|
|
||||||
(CatapultDefenseNumber, 93, 178),
|
|
||||||
(CatapultAttackNumber, 74, 100),
|
|
||||||
(CatapultAttackNumber, 112, 100),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Crown Switch positions on Valley of Loren (from the map init) — held by standing on them: (number, x, y).
|
|
||||||
private static readonly (short SwitchNumber, byte X, byte Y)[] SwitchPositions =
|
|
||||||
{
|
|
||||||
(217, 167, 194),
|
|
||||||
(218, 184, 195),
|
|
||||||
};
|
|
||||||
|
|
||||||
// The Crown (NPC 216) position on Valley of Loren — the guild master holds it here to capture the throne.
|
|
||||||
private static readonly Point CrownPosition = new(176, 212);
|
|
||||||
private const int CrownHoldRange = 4;
|
private const int CrownHoldRange = 4;
|
||||||
|
|
||||||
|
/// <summary>How many ticks (the periodic task runs once per second) between two countdown broadcasts.</summary>
|
||||||
|
private const int SiegeStateBroadcastTicks = 10;
|
||||||
|
|
||||||
|
/// <summary>How many ticks between two castle-flag broadcasts.</summary>
|
||||||
|
private const int CastleFlagBroadcastTicks = 15;
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<IGameContext, CastleSiegeContext> Contexts = new();
|
private static readonly ConcurrentDictionary<IGameContext, CastleSiegeContext> Contexts = new();
|
||||||
|
|
||||||
private string? _cachedFlagOwner;
|
/// <summary>
|
||||||
|
/// The player whose client currently shows the crown registration panel, per game context. The panel is
|
||||||
|
/// opened for exactly one guild master, and it has to be closed for that same player - by the time the
|
||||||
|
/// hold breaks they are usually no longer on the crown, so they can't be found by position any more.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly ConcurrentDictionary<IGameContext, Player> CrownHoldPlayers = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tick counters per game context, used to space out the periodic broadcasts. Counting ticks (instead of
|
||||||
|
/// matching a clock second) keeps a broadcast from being skipped when a tick runs late.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly ConcurrentDictionary<IGameContext, BroadcastCounters> Counters = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps the in-memory guild id (assigned by the guild server, not stable across restarts) to the guild's
|
||||||
|
/// persistent identifier. Populated lazily; a miss costs one database lookup per guild per process.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly ConcurrentDictionary<uint, Guid> PersistentGuildIds = new();
|
||||||
|
|
||||||
|
private Guid? _cachedFlagOwner;
|
||||||
private byte[]? _cachedFlagLogo;
|
private byte[]? _cachedFlagLogo;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public CastleSiegeConfiguration? Configuration { get; set; }
|
public CastleSiegeSettings? Configuration { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Castle Siege context for a game context, if the periodic tick has initialized it.
|
/// Gets the Castle Siege context for a game context, if the periodic tick has initialized it.
|
||||||
@@ -99,41 +104,80 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
|||||||
return serverContext.Id == context.Configuration.CastleSiegeServerId;
|
return serverContext.Id == context.Configuration.CastleSiegeServerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the persistent identifier of the player's guild, or <see langword="null"/> when the player is
|
||||||
|
/// not in a guild or the guild cannot be resolved.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <see cref="Interfaces.Guild"/> deliberately carries no id: the guild server assigns short ids in memory
|
||||||
|
/// only. The persistent <see cref="Guid"/> is therefore resolved through the guild name and cached, which
|
||||||
|
/// avoids adding a method to <see cref="IGuildServer"/> that upstream would keep changing.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="player">The player.</param>
|
||||||
|
public static async ValueTask<(Guid Id, string Name)?> GetPersistentGuildAsync(Player player)
|
||||||
|
{
|
||||||
|
if (player.GuildStatus is not { } guildStatus
|
||||||
|
|| player.GameContext is not IGameServerContext serverContext)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var guild = await serverContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false);
|
||||||
|
if (guild?.Name is not { Length: > 0 } name)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PersistentGuildIds.TryGetValue(guildStatus.GuildId, out var cached))
|
||||||
|
{
|
||||||
|
return (cached, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolved = await ResolveGuildIdByNameAsync(player.GameContext, name).ConfigureAwait(false);
|
||||||
|
if (resolved is not { } id)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PersistentGuildIds[guildStatus.GuildId] = id;
|
||||||
|
return (id, name);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public object CreateDefaultConfig() => new CastleSiegeConfiguration();
|
public object CreateDefaultConfig() => new CastleSiegeSettings();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask ExecuteTaskAsync(GameContext gameContext)
|
public async ValueTask ExecuteTaskAsync(GameContext gameContext)
|
||||||
{
|
{
|
||||||
var context = Contexts.GetOrAdd(gameContext, gc =>
|
var context = Contexts.GetOrAdd(gameContext, gc =>
|
||||||
{
|
{
|
||||||
var config = this.Configuration ?? new CastleSiegeConfiguration();
|
var settings = this.Configuration ?? new CastleSiegeSettings();
|
||||||
var created = new CastleSiegeContext(config);
|
var created = new CastleSiegeContext(settings);
|
||||||
|
|
||||||
// Restore state persisted before the last restart (owner/phase/registrations) BEFORE subscribing,
|
// Restore the cycle bookkeeping BEFORE subscribing, so restoring doesn't announce states or
|
||||||
// so restoring doesn't announce phases or re-spawn defenses.
|
// re-spawn defenses. The owner and registrations are loaded from the database right after.
|
||||||
created.RestoreState(config.PersistedOwnerGuildName, config.PersistedPhase, config.PersistedPhaseStartedUtc, config.PersistedRegisteredGuilds);
|
created.RestoreState(null, null, settings.PersistedState, settings.PersistedStateStartedUtc, null);
|
||||||
|
_ = LoadPersistedStateAsync(gc, created);
|
||||||
|
|
||||||
// Announce phase changes to the whole server and, when the siege begins, warp registered members.
|
created.StateChanged += state => _ = OnStateChangedAsync(gc, created, state);
|
||||||
created.PhaseChanged += phase => _ = OnPhaseChangedAsync(gc, created, phase);
|
|
||||||
|
|
||||||
return created;
|
return created;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Point the context at the current (possibly AdminPanel-edited) config so schedule/durations are live.
|
// Point the context at the current (possibly AdminPanel-edited) settings so schedule/durations are live.
|
||||||
if (this.Configuration is { } liveConfig)
|
if (this.Configuration is { } liveSettings)
|
||||||
{
|
{
|
||||||
context.UpdateConfiguration(liveConfig);
|
context.UpdateConfiguration(liveSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
// With multiple game servers each has its own map instances, so the siege must run on ONE designated
|
// With multiple game servers each has its own map instances, so the siege must run on ONE designated
|
||||||
// server (CastleSiegeServerId). Other servers skip the siege entirely — they only mirror the shared
|
// server (CastleSiegeServerId). Other servers skip the siege entirely — they only mirror the shared
|
||||||
// castle owner from the config so the hunting-map gate + castle flag rewards still work everywhere.
|
// castle owner from the database so the hunting-map gate + castle flag rewards still work everywhere.
|
||||||
if (!IsCastleSiegeServer(gameContext))
|
if (!IsCastleSiegeServer(gameContext))
|
||||||
{
|
{
|
||||||
context.SyncOwnerFromConfig();
|
if (GetCounters(gameContext).NextCastleFlag())
|
||||||
if (DateTime.UtcNow.Second % 15 == 0)
|
|
||||||
{
|
{
|
||||||
|
await LoadPersistedStateAsync(gameContext, context).ConfigureAwait(false);
|
||||||
await this.BroadcastCastleFlagAsync(gameContext, context).ConfigureAwait(false);
|
await this.BroadcastCastleFlagAsync(gameContext, context).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,19 +187,19 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
|||||||
await context.TickAsync(DateTime.UtcNow).ConfigureAwait(false);
|
await context.TickAsync(DateTime.UtcNow).ConfigureAwait(false);
|
||||||
|
|
||||||
// During the siege, evaluate the Crown Switches (held by standing on them) and the throne capture every tick.
|
// During the siege, evaluate the Crown Switches (held by standing on them) and the throne capture every tick.
|
||||||
if (context.Phase == CastleSiegePhase.Siege)
|
if (context.IsSiegeRunning)
|
||||||
{
|
{
|
||||||
await ProcessSiegeTickAsync(gameContext, context).ConfigureAwait(false);
|
await ProcessSiegeTickAsync(gameContext, context).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persist owner/phase/registrations to the database whenever they changed, so they survive a restart.
|
// Persist owner/state/registrations whenever they changed, so they survive a restart.
|
||||||
if (context.ConsumeDirty())
|
if (context.ConsumeDirty())
|
||||||
{
|
{
|
||||||
await this.PersistStateAsync(gameContext, context).ConfigureAwait(false);
|
await this.PersistStateAsync(gameContext, context).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the owner guild's logo painted on the castle flags for anyone on the castle map (any phase).
|
// Keep the owner guild's logo painted on the castle flags for anyone on the castle map (any state).
|
||||||
if (DateTime.UtcNow.Second % 15 == 0)
|
if (GetCounters(gameContext).NextCastleFlag())
|
||||||
{
|
{
|
||||||
await this.BroadcastCastleFlagAsync(gameContext, context).ConfigureAwait(false);
|
await this.BroadcastCastleFlagAsync(gameContext, context).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -171,25 +215,98 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task OnPhaseChangedAsync(IGameContext gameContext, CastleSiegeContext context, CastleSiegePhase phase)
|
/// <summary>Gets the seeded Castle Siege definition, or <see langword="null"/> when it was not initialized.</summary>
|
||||||
|
/// <param name="gameContext">The game context.</param>
|
||||||
|
private static CastleSiegeDefinition? GetDefinition(IGameContext gameContext)
|
||||||
|
=> gameContext.Configuration.CastleSiegeConfiguration;
|
||||||
|
|
||||||
|
/// <summary>Resolves a guild's persistent identifier from its name, or null when there is no such guild.</summary>
|
||||||
|
/// <param name="gameContext">The game context.</param>
|
||||||
|
/// <param name="guildName">The guild name.</param>
|
||||||
|
internal static async ValueTask<Guid?> ResolveGuildIdByNameAsync(IGameContext gameContext, string guildName)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
switch (phase)
|
using var context = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(DataModel.Entities.Guild), false, gameContext.Configuration);
|
||||||
|
var guilds = await context.GetAsync<DataModel.Entities.Guild>().ConfigureAwait(false);
|
||||||
|
return guilds.FirstOrDefault(guild => guild.Name == guildName)?.Id;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
gameContext.LoggerFactory.CreateLogger<CastleSiegeEventPlugIn>()
|
||||||
|
.LogError(ex, "Castle Siege: could not resolve the persistent id of guild '{guildName}'.", guildName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async ValueTask<string?> ResolveGuildNameByIdAsync(IGameContext gameContext, Guid guildId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var context = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(DataModel.Entities.Guild), false, gameContext.Configuration);
|
||||||
|
var guild = await context.GetByIdAsync<DataModel.Entities.Guild>(guildId).ConfigureAwait(false);
|
||||||
|
return guild?.Name;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
gameContext.LoggerFactory.CreateLogger<CastleSiegeEventPlugIn>()
|
||||||
|
.LogError(ex, "Castle Siege: could not resolve the name of guild {guildId}.", guildId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Loads the persisted castle owner and guild registrations from the database into the context.</summary>
|
||||||
|
private static async ValueTask LoadPersistedStateAsync(IGameContext gameContext, CastleSiegeContext context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var dataContext = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(CastleSiegeData), false, gameContext.Configuration);
|
||||||
|
var data = (await dataContext.GetAsync<CastleSiegeData>().ConfigureAwait(false)).FirstOrDefault();
|
||||||
|
|
||||||
|
Guid? ownerId = data?.IsOccupied == true ? data.OwnerGuildId : null;
|
||||||
|
var ownerName = ownerId is { } id ? await ResolveGuildNameByIdAsync(gameContext, id).ConfigureAwait(false) : null;
|
||||||
|
|
||||||
|
using var registrationContext = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(CastleSiegeGuildRegistration), false, gameContext.Configuration);
|
||||||
|
var registrations = await registrationContext.GetAsync<CastleSiegeGuildRegistration>().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var restored = new List<KeyValuePair<Guid, string>>();
|
||||||
|
foreach (var registration in registrations)
|
||||||
{
|
{
|
||||||
case CastleSiegePhase.Registration:
|
var name = await ResolveGuildNameByIdAsync(gameContext, registration.GuildId).ConfigureAwait(false);
|
||||||
|
restored.Add(new KeyValuePair<Guid, string>(registration.GuildId, name ?? registration.GuildId.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.RestoreState(ownerId, ownerName, context.State, context.StateStartedUtc, restored);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
gameContext.LoggerFactory.CreateLogger<CastleSiegeEventPlugIn>()
|
||||||
|
.LogError(ex, "Castle Siege: error while loading the persisted state.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task OnStateChangedAsync(IGameContext gameContext, CastleSiegeContext context, CastleSiegeState state)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case CastleSiegeState.RegisterGuild:
|
||||||
await AnnounceAsync(gameContext, "Castle Siege registration is now open! Guild masters, register at the Guardsman in the Valley of Loren.").ConfigureAwait(false);
|
await AnnounceAsync(gameContext, "Castle Siege registration is now open! Guild masters, register at the Guardsman in the Valley of Loren.").ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
case CastleSiegePhase.Siege:
|
case CastleSiegeState.Start:
|
||||||
await AnnounceAsync(gameContext, "The Castle Siege has begun! Break the castle gates and guardian statues, then hold BOTH Crown Switches to take the throne!").ConfigureAwait(false);
|
await AnnounceAsync(gameContext, "The Castle Siege has begun! Break the castle gates and guardian statues, then hold BOTH Crown Switches to take the throne!").ConfigureAwait(false);
|
||||||
await SpawnCastleDefensesAsync(gameContext, context).ConfigureAwait(false);
|
await SpawnCastleDefensesAsync(gameContext, context).ConfigureAwait(false);
|
||||||
await WarpRegisteredMembersToSiegeAsync(gameContext, context).ConfigureAwait(false);
|
await WarpRegisteredMembersToSiegeAsync(gameContext, context).ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
case CastleSiegePhase.Settlement:
|
case CastleSiegeState.End:
|
||||||
// Stop the on-map countdown for everyone still on the battle map.
|
// Stop the on-map countdown for everyone still on the battle map, and close the panels
|
||||||
|
// the siege opened - the battle state is dropped right after this, so whoever was
|
||||||
|
// operating a switch or holding the crown would keep a dead progress box on screen.
|
||||||
await BroadcastSiegeStateAsync(gameContext, false, 0, 0).ConfigureAwait(false);
|
await BroadcastSiegeStateAsync(gameContext, false, 0, 0).ConfigureAwait(false);
|
||||||
|
await CloseSiegePanelsAsync(gameContext, context).ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
case CastleSiegePhase.Ownership when context.OwnerGuildName is { } owner:
|
case CastleSiegeState.Idle1 when context.OwnerGuildName is { } owner:
|
||||||
await AnnounceAsync(gameContext, $"The Castle Siege has ended. The castle now belongs to the guild '{owner}'!").ConfigureAwait(false);
|
await AnnounceAsync(gameContext, $"The Castle Siege has ended. The castle now belongs to the guild '{owner}'!").ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -199,52 +316,7 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
gameContext.LoggerFactory.CreateLogger<CastleSiegeEventPlugIn>()
|
gameContext.LoggerFactory.CreateLogger<CastleSiegeEventPlugIn>()
|
||||||
.LogError(ex, "Castle Siege: error handling phase change to {phase}.", phase);
|
.LogError(ex, "Castle Siege: error handling state change to {state}.", state);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ValueTask PersistStateAsync(GameContext gameContext, CastleSiegeContext context)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (this.Configuration is not { } config)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snapshot the live context into the persisted config fields.
|
|
||||||
config.PersistedOwnerGuildName = context.OwnerGuildName;
|
|
||||||
config.PersistedPhase = context.Phase;
|
|
||||||
config.PersistedPhaseStartedUtc = context.PhaseStartedUtc;
|
|
||||||
config.PersistedRegisteredGuilds = context.RegisteredGuilds.ToList();
|
|
||||||
|
|
||||||
// Find our plugin-configuration row via the in-memory config graph to get its id.
|
|
||||||
var pluginTypeId = typeof(CastleSiegeEventPlugIn).GUID;
|
|
||||||
var inMemory = gameContext.Configuration.PlugInConfigurations.FirstOrDefault(c => c.TypeId == pluginTypeId);
|
|
||||||
if (inMemory is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load a fresh, change-tracked copy of that row in its own (non-caching) context, rewrite its
|
|
||||||
// custom-configuration JSON, and save — this is what actually persists to PostgreSQL.
|
|
||||||
using var ctx = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(PlugInConfiguration), false, gameContext.Configuration);
|
|
||||||
var row = await ctx.GetByIdAsync<PlugInConfiguration>(inMemory.GetId()).ConfigureAwait(false);
|
|
||||||
if (row is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
row.SetConfiguration(config, gameContext.PlugInManager.CustomConfigReferenceHandler);
|
|
||||||
await ctx.SaveChangesAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
gameContext.LoggerFactory.CreateLogger<CastleSiegeEventPlugIn>()
|
|
||||||
.LogInformation("Castle Siege: persisted state (owner={owner}, phase={phase}).", config.PersistedOwnerGuildName ?? "(none)", config.PersistedPhase);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
gameContext.LoggerFactory.CreateLogger<CastleSiegeEventPlugIn>()
|
|
||||||
.LogError(ex, "Castle Siege: error while persisting state to the database.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,52 +341,36 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spawns the castle defenses from the seeded NPC definitions. Definitions flagged
|
||||||
|
/// <see cref="CastleSiegeNpcDefinition.IsPersistedToDatabase"/> are the breakable defenses (gates and
|
||||||
|
/// guardian statues) and are counted towards the throne; the catapults are pure war atmosphere.
|
||||||
|
/// </summary>
|
||||||
private static async Task SpawnCastleDefensesAsync(IGameContext gameContext, CastleSiegeContext context)
|
private static async Task SpawnCastleDefensesAsync(IGameContext gameContext, CastleSiegeContext context)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (gameContext is not GameContext concrete)
|
if (gameContext is not GameContext concrete
|
||||||
{
|
|| GetDefinition(gameContext) is not { } definition
|
||||||
context.SetDefenseCount(0);
|
|| await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false) is not { } map)
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var map = await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false);
|
|
||||||
if (map is null)
|
|
||||||
{
|
{
|
||||||
context.SetDefenseCount(0);
|
context.SetDefenseCount(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var template = gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == GateTemplateNumber);
|
||||||
var spawned = 0;
|
var spawned = 0;
|
||||||
for (var i = 0; i < DefenseSpawns.Length; i++)
|
var index = 0;
|
||||||
|
foreach (var npc in definition.NpcDefinitions.Where(n => n.IsPersistedToDatabase && n.MonsterDefinition is not null))
|
||||||
{
|
{
|
||||||
var spawn = DefenseSpawns[i];
|
// The stock CS gate/statue definitions have no HP and aren't destructible; make them breakable
|
||||||
var definition = gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == spawn.Number);
|
|
||||||
if (definition is null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The stock CS gate (277) definition has no HP and isn't destructible; make it breakable
|
|
||||||
// by borrowing the HP/attributes of a working destructible (BloodCastle gate 131).
|
// by borrowing the HP/attributes of a working destructible (BloodCastle gate 131).
|
||||||
var template = gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == GateTemplateNumber);
|
EnsureDestructible(npc.MonsterDefinition!, template);
|
||||||
EnsureDestructible(definition, template);
|
|
||||||
|
|
||||||
var spawnArea = new MonsterSpawnArea
|
var monster = await concrete.MapInitializer
|
||||||
{
|
.InitializeSpawnAsync(1000 + index++, map, CreateSpawnArea(npc))
|
||||||
MonsterDefinition = definition,
|
.ConfigureAwait(false);
|
||||||
Quantity = 1,
|
if (monster is AttackableNpcBase attackable)
|
||||||
X1 = spawn.X,
|
|
||||||
X2 = spawn.X,
|
|
||||||
Y1 = spawn.Y,
|
|
||||||
Y2 = spawn.Y,
|
|
||||||
Direction = Direction.South,
|
|
||||||
SpawnTrigger = SpawnTrigger.OnceAtEventStart,
|
|
||||||
};
|
|
||||||
|
|
||||||
var npc = await concrete.MapInitializer.InitializeSpawnAsync(1000 + i, map, spawnArea).ConfigureAwait(false);
|
|
||||||
if (npc is AttackableNpcBase attackable)
|
|
||||||
{
|
{
|
||||||
attackable.Died += (_, _) => context.NotifyDefenseDestroyed();
|
attackable.Died += (_, _) => context.NotifyDefenseDestroyed();
|
||||||
spawned++;
|
spawned++;
|
||||||
@@ -323,29 +379,12 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
|||||||
|
|
||||||
context.SetDefenseCount(spawned);
|
context.SetDefenseCount(spawned);
|
||||||
|
|
||||||
// Spawn the catapults (siege weapons) for war atmosphere — not counted as defenses.
|
index = 0;
|
||||||
for (var i = 0; i < CatapultSpawns.Length; i++)
|
foreach (var npc in definition.NpcDefinitions.Where(IsCatapult))
|
||||||
{
|
{
|
||||||
var spawn = CatapultSpawns[i];
|
await concrete.MapInitializer
|
||||||
var definition = gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == spawn.Number);
|
.InitializeSpawnAsync(2000 + index++, map, CreateSpawnArea(npc))
|
||||||
if (definition is null)
|
.ConfigureAwait(false);
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var spawnArea = new MonsterSpawnArea
|
|
||||||
{
|
|
||||||
MonsterDefinition = definition,
|
|
||||||
Quantity = 1,
|
|
||||||
X1 = spawn.X,
|
|
||||||
X2 = spawn.X,
|
|
||||||
Y1 = spawn.Y,
|
|
||||||
Y2 = spawn.Y,
|
|
||||||
Direction = Direction.South,
|
|
||||||
SpawnTrigger = SpawnTrigger.OnceAtEventStart,
|
|
||||||
};
|
|
||||||
|
|
||||||
await concrete.MapInitializer.InitializeSpawnAsync(2000 + i, map, spawnArea).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -356,54 +395,144 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsCatapult(CastleSiegeNpcDefinition npc)
|
||||||
|
=> npc.MonsterDefinition is { } monster
|
||||||
|
&& (monster.Number == CatapultAttackNumber || monster.Number == CatapultDefenseNumber);
|
||||||
|
|
||||||
|
private static MonsterSpawnArea CreateSpawnArea(CastleSiegeNpcDefinition npc) => new()
|
||||||
|
{
|
||||||
|
MonsterDefinition = npc.MonsterDefinition,
|
||||||
|
Quantity = 1,
|
||||||
|
X1 = npc.SpawnX,
|
||||||
|
X2 = npc.SpawnX,
|
||||||
|
Y1 = npc.SpawnY,
|
||||||
|
Y2 = npc.SpawnY,
|
||||||
|
Direction = npc.Direction,
|
||||||
|
SpawnTrigger = SpawnTrigger.OnceAtEventStart,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static BroadcastCounters GetCounters(IGameContext gameContext)
|
||||||
|
=> Counters.GetOrAdd(gameContext, _ => new BroadcastCounters());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the client panels the siege opened: the crown registration panel of the master who was holding
|
||||||
|
/// it, and the switch progress box of whoever was operating a switch. Called when the siege ends, before
|
||||||
|
/// the battle state is dropped.
|
||||||
|
/// </summary>
|
||||||
|
private static async Task CloseSiegePanelsAsync(IGameContext gameContext, CastleSiegeContext context)
|
||||||
|
{
|
||||||
|
if (CrownHoldPlayers.TryRemove(gameContext, out var holdPlayer))
|
||||||
|
{
|
||||||
|
await holdPlayer.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.SetCrownRegistAsync(2, 0)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false) is not { } map)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var switchNumber in CastleSiegeContext.SwitchNumbers)
|
||||||
|
{
|
||||||
|
if (context.GetSwitchOperation(switchNumber) is { } operation)
|
||||||
|
{
|
||||||
|
await BroadcastSwitchInfoAsync(gameContext, operation.SwitchObjectId, null).ConfigureAwait(false);
|
||||||
|
await CloseSwitchBoxAsync(map, operation).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the switch progress box on the client of the player who was operating it. Object identifiers
|
||||||
|
/// are recycled when a player leaves, so the name is checked too - otherwise a newly connected player
|
||||||
|
/// could inherit the id and get a message box about a switch they never touched.
|
||||||
|
/// </summary>
|
||||||
|
private static async Task CloseSwitchBoxAsync(GameMap map, CastleSiegeSwitchOperation operation)
|
||||||
|
{
|
||||||
|
if (map.GetObject(operation.PlayerId) is Player player && player.Name == operation.PlayerName)
|
||||||
|
{
|
||||||
|
await player.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(
|
||||||
|
p => p.SetCrownSwitchStateAsync(operation.SwitchObjectId, operation.PlayerId, 0)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Point? GetNpcPosition(IGameContext gameContext, short monsterNumber)
|
||||||
|
{
|
||||||
|
var npc = GetDefinition(gameContext)?.NpcDefinitions
|
||||||
|
.FirstOrDefault(n => n.MonsterDefinition?.Number == monsterNumber);
|
||||||
|
return npc is null ? null : new Point(npc.SpawnX, npc.SpawnY);
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task ProcessSiegeTickAsync(IGameContext gameContext, CastleSiegeContext context)
|
private static async Task ProcessSiegeTickAsync(IGameContext gameContext, CastleSiegeContext context)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var map = await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false);
|
if (await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false) is not { } map)
|
||||||
if (map is null)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each Crown Switch is held by whichever registered guild currently has a member standing on it.
|
var now = DateTime.UtcNow;
|
||||||
foreach (var (switchNumber, x, y) in SwitchPositions)
|
var pushDuration = TimeSpan.FromSeconds(Math.Max(1, context.Configuration.SwitchPushSeconds));
|
||||||
|
|
||||||
|
// A Crown Switch belongs to the player who clicked it, for as long as they stay in its area.
|
||||||
|
foreach (var switchNumber in CastleSiegeContext.SwitchNumbers)
|
||||||
{
|
{
|
||||||
string? holder = null;
|
if (context.GetSwitchOperation(switchNumber) is not { } operation)
|
||||||
var nearby = map.GetAttackablesInRange(new Point(x, y), SwitchHoldRange).OfType<Player>();
|
|
||||||
foreach (var player in nearby)
|
|
||||||
{
|
{
|
||||||
var guildName = await GetGuildNameAsync(player).ConfigureAwait(false);
|
continue;
|
||||||
if (guildName is not null && context.RegisteredGuilds.Contains(guildName))
|
|
||||||
{
|
|
||||||
holder = guildName;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
context.SetSwitchHolder(switchNumber, holder);
|
var stillOnIt = GetNpcPosition(gameContext, switchNumber) is { } position
|
||||||
|
&& map.GetAttackablesInRange(position, SwitchHoldRange)
|
||||||
|
.OfType<Player>()
|
||||||
|
.Any(p => p.Id == operation.PlayerId && p.IsAlive);
|
||||||
|
|
||||||
|
var (switchEvent, affected) = context.TickSwitch(switchNumber, stillOnIt, now, pushDuration);
|
||||||
|
if (affected is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (switchEvent == CastleSiegeSwitchEvent.Held)
|
||||||
|
{
|
||||||
|
// Repeat the info so the HUD picks up the names (the client only stores them from the
|
||||||
|
// second packet on, because the first one allocates its table).
|
||||||
|
await BroadcastSwitchInfoAsync(gameContext, affected.SwitchObjectId, affected).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else if (switchEvent == CastleSiegeSwitchEvent.Released)
|
||||||
|
{
|
||||||
|
await BroadcastSwitchInfoAsync(gameContext, affected.SwitchObjectId, null).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// Close the client's progress box of the player who left, if they are still around.
|
||||||
|
await CloseSwitchBoxAsync(map, affected).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
// Crown-hold capture: while one guild holds both switches the crown shield drops for it, and its
|
||||||
|
// master captures the throne by clicking the crown and holding it for the configured time.
|
||||||
// Crown-hold capture: when a guild holds both switches with every gate down, the crown shield drops;
|
|
||||||
// that guild's master then holds the crown for CrownHoldDuration to take the throne (contestable).
|
|
||||||
var eligible = context.GetShieldEligibleGuild();
|
var eligible = context.GetShieldEligibleGuild();
|
||||||
Player? masterPlayer = null;
|
Player? masterPlayer = null;
|
||||||
if (eligible is not null)
|
string? eligibleName = null;
|
||||||
|
if (eligible is { } eligibleId && GetNpcPosition(gameContext, CrownNumber) is { } crownPosition)
|
||||||
{
|
{
|
||||||
foreach (var player in map.GetAttackablesInRange(CrownPosition, CrownHoldRange).OfType<Player>())
|
foreach (var player in map.GetAttackablesInRange(crownPosition, CrownHoldRange).OfType<Player>())
|
||||||
{
|
{
|
||||||
if (player.GuildStatus?.Position == GuildPosition.GuildMaster
|
if (player.GuildStatus?.Position != GuildPosition.GuildMaster)
|
||||||
&& await GetGuildNameAsync(player).ConfigureAwait(false) == eligible)
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await GetPersistentGuildAsync(player).ConfigureAwait(false) is { } guild && guild.Id == eligibleId)
|
||||||
{
|
{
|
||||||
masterPlayer = player;
|
masterPlayer = player;
|
||||||
|
eligibleName = guild.Name;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var crown = context.TickCrownHold(eligible, masterPlayer is not null, now, context.Configuration.CrownHoldDuration);
|
var holdDuration = TimeSpan.FromSeconds(GetDefinition(gameContext)?.CrownHoldTimeSeconds ?? 60);
|
||||||
|
var crown = context.TickCrownHold(eligible, eligibleName, masterPlayer is not null, now, holdDuration);
|
||||||
|
|
||||||
// Shield drop/raise -> everyone on the battle map, but only when it flips (the packet pops a modal).
|
// Shield drop/raise -> everyone on the battle map, but only when it flips (the packet pops a modal).
|
||||||
if (crown.ShieldChanged)
|
if (crown.ShieldChanged)
|
||||||
@@ -414,13 +543,25 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
|||||||
switch (crown.Event)
|
switch (crown.Event)
|
||||||
{
|
{
|
||||||
case CrownEvent.HoldStarted when masterPlayer is not null:
|
case CrownEvent.HoldStarted when masterPlayer is not null:
|
||||||
// The 60-second registration panel is shown ONLY to the master taking the crown.
|
// The registration panel is shown ONLY to the master taking the crown. Remember them:
|
||||||
|
// the hold usually breaks BECAUSE they walked off the crown, and their client still has
|
||||||
|
// the panel open, so the cancel has to reach the player we started it for.
|
||||||
|
CrownHoldPlayers[gameContext] = masterPlayer;
|
||||||
await masterPlayer.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.SetCrownRegistAsync(0, 0)).ConfigureAwait(false);
|
await masterPlayer.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.SetCrownRegistAsync(0, 0)).ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
case CrownEvent.HoldReset when masterPlayer is not null:
|
case CrownEvent.HoldReset:
|
||||||
await masterPlayer.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.SetCrownRegistAsync(2, 0)).ConfigureAwait(false);
|
if (CrownHoldPlayers.TryRemove(gameContext, out var holdPlayer))
|
||||||
|
{
|
||||||
|
await holdPlayer.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.SetCrownRegistAsync(2, 0)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case CrownEvent.Captured when crown.Guild is { } captured:
|
case CrownEvent.Captured when crown.GuildName is { } captured:
|
||||||
|
if (CrownHoldPlayers.TryRemove(gameContext, out var capturingPlayer))
|
||||||
|
{
|
||||||
|
await capturingPlayer.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.SetCrownRegistAsync(1, 0)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
await ForEachOnBattleMapAsync(gameContext, p => p.AnnounceSealCapturedAsync(captured)).ConfigureAwait(false);
|
await ForEachOnBattleMapAsync(gameContext, p => p.AnnounceSealCapturedAsync(captured)).ConfigureAwait(false);
|
||||||
await AnnounceAsync(gameContext, $"Guild '{captured}' has taken the Crown and now holds the throne!").ConfigureAwait(false);
|
await AnnounceAsync(gameContext, $"Guild '{captured}' has taken the Crown and now holds the throne!").ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
@@ -428,9 +569,10 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the client's on-map countdown armed and in sync. Resend every 10s so players who just
|
// Keep the client's on-map countdown armed and in sync. Resend every 10 ticks so players who just
|
||||||
// loaded the battle map pick it up, without visibly resetting the second-counter too often.
|
// loaded the battle map pick it up, without visibly resetting the second-counter too often. This
|
||||||
if ((int)(now - context.PhaseStartedUtc).TotalSeconds % 10 == 0)
|
// counts ticks instead of matching a clock second, which a delayed tick would skip silently.
|
||||||
|
if (GetCounters(gameContext).NextSiegeState())
|
||||||
{
|
{
|
||||||
var remaining = context.GetRemainingSiegeTime(now);
|
var remaining = context.GetRemainingSiegeTime(now);
|
||||||
var totalMinutes = (int)Math.Ceiling(remaining.TotalMinutes);
|
var totalMinutes = (int)Math.Ceiling(remaining.TotalMinutes);
|
||||||
@@ -464,22 +606,155 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
|||||||
});
|
});
|
||||||
|
|
||||||
/// <summary>Invokes the Castle Siege status view for every player currently on the battle map.</summary>
|
/// <summary>Invokes the Castle Siege status view for every player currently on the battle map.</summary>
|
||||||
|
/// <summary>
|
||||||
|
/// Tells everybody on the battle map who is operating a Crown Switch. Besides driving the client's HUD
|
||||||
|
/// list, this is what makes the client allocate its switch table, so it has to be sent before any
|
||||||
|
/// "switch released" packet reaches that client.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="gameContext">The game context.</param>
|
||||||
|
/// <param name="switchObjectId">The switch NPC's object identifier.</param>
|
||||||
|
/// <param name="operation">The operation, or <see langword="null"/> when the switch became free.</param>
|
||||||
|
internal static ValueTask BroadcastSwitchInfoAsync(IGameContext gameContext, ushort switchObjectId, CastleSiegeSwitchOperation? operation)
|
||||||
|
=> ForEachOnBattleMapAsync(
|
||||||
|
gameContext,
|
||||||
|
p => p.SetCrownSwitchInfoAsync(
|
||||||
|
switchObjectId,
|
||||||
|
operation is null ? (byte)0 : (byte)1,
|
||||||
|
(byte)CastleSiegeJoinSide.Attack1,
|
||||||
|
operation?.GuildName ?? string.Empty,
|
||||||
|
operation?.PlayerName ?? string.Empty));
|
||||||
|
|
||||||
private static ValueTask ForEachOnBattleMapAsync(IGameContext gameContext, Func<ICastleSiegeStatusViewPlugIn, ValueTask> action)
|
private static ValueTask ForEachOnBattleMapAsync(IGameContext gameContext, Func<ICastleSiegeStatusViewPlugIn, ValueTask> action)
|
||||||
=> gameContext.ForEachPlayerAsync(player =>
|
=> gameContext.ForEachPlayerAsync(player =>
|
||||||
player.CurrentMap?.Definition.Number == ValleyOfLorenMapNumber
|
player.CurrentMap?.Definition.Number == ValleyOfLorenMapNumber
|
||||||
? player.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(action).AsTask()
|
? player.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(action).AsTask()
|
||||||
: Task.CompletedTask);
|
: Task.CompletedTask);
|
||||||
|
|
||||||
private async ValueTask BroadcastCastleFlagAsync(IGameContext gameContext, CastleSiegeContext context)
|
private static async Task WarpRegisteredMembersToSiegeAsync(IGameContext gameContext, CastleSiegeContext context)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (context.OwnerGuildName is not { Length: > 0 } owner)
|
var map = await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false);
|
||||||
|
if (map?.SafeZoneSpawnGate is not { } gate)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var logo = await this.GetOwnerLogoAsync(gameContext, owner).ConfigureAwait(false);
|
await gameContext.ForEachPlayerAsync(async player =>
|
||||||
|
{
|
||||||
|
if (await GetPersistentGuildAsync(player).ConfigureAwait(false) is { } guild
|
||||||
|
&& context.IsRegistered(guild.Id))
|
||||||
|
{
|
||||||
|
await player.WarpToAsync(gate).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
gameContext.LoggerFactory.CreateLogger<CastleSiegeEventPlugIn>()
|
||||||
|
.LogError(ex, "Castle Siege: error while warping registered members to the battle map.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the castle owner and the guild registrations to their database tables, and the cycle
|
||||||
|
/// bookkeeping (current state and when it started) to the plugin's custom-configuration JSON.
|
||||||
|
/// </summary>
|
||||||
|
private async ValueTask PersistStateAsync(GameContext gameContext, CastleSiegeContext context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await this.PersistOwnerAsync(gameContext, context).ConfigureAwait(false);
|
||||||
|
await PersistRegistrationsAsync(gameContext, context).ConfigureAwait(false);
|
||||||
|
await this.PersistCycleBookkeepingAsync(gameContext, context).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
gameContext.LoggerFactory.CreateLogger<CastleSiegeEventPlugIn>()
|
||||||
|
.LogError(ex, "Castle Siege: error while persisting state to the database.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask PersistOwnerAsync(GameContext gameContext, CastleSiegeContext context)
|
||||||
|
{
|
||||||
|
using var dataContext = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(CastleSiegeData), false, gameContext.Configuration);
|
||||||
|
var data = (await dataContext.GetAsync<CastleSiegeData>().ConfigureAwait(false)).FirstOrDefault()
|
||||||
|
?? dataContext.CreateNew<CastleSiegeData>();
|
||||||
|
|
||||||
|
data.OwnerGuildId = context.OwnerGuildId;
|
||||||
|
data.IsOccupied = context.OwnerGuildId is not null;
|
||||||
|
await dataContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
gameContext.LoggerFactory.CreateLogger<CastleSiegeEventPlugIn>()
|
||||||
|
.LogInformation(
|
||||||
|
"Castle Siege: persisted owner={owner} state={state}.",
|
||||||
|
context.OwnerGuildName ?? "(none)",
|
||||||
|
context.State);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async ValueTask PersistRegistrationsAsync(GameContext gameContext, CastleSiegeContext context)
|
||||||
|
{
|
||||||
|
using var registrationContext = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(CastleSiegeGuildRegistration), false, gameContext.Configuration);
|
||||||
|
var existing = (await registrationContext.GetAsync<CastleSiegeGuildRegistration>().ConfigureAwait(false)).ToList();
|
||||||
|
var wanted = context.RegisteredGuildIds.ToHashSet();
|
||||||
|
|
||||||
|
foreach (var registration in existing)
|
||||||
|
{
|
||||||
|
if (!wanted.Remove(registration.GuildId))
|
||||||
|
{
|
||||||
|
await registrationContext.DeleteAsync(registration).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var guildId in wanted)
|
||||||
|
{
|
||||||
|
var registration = registrationContext.CreateNew<CastleSiegeGuildRegistration>();
|
||||||
|
registration.GuildId = guildId;
|
||||||
|
}
|
||||||
|
|
||||||
|
await registrationContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask PersistCycleBookkeepingAsync(GameContext gameContext, CastleSiegeContext context)
|
||||||
|
{
|
||||||
|
if (this.Configuration is not { } settings)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.PersistedState = context.State;
|
||||||
|
settings.PersistedStateStartedUtc = context.StateStartedUtc;
|
||||||
|
|
||||||
|
// Find our plugin-configuration row via the in-memory config graph to get its id, then rewrite its
|
||||||
|
// custom-configuration JSON in its own (non-caching) context.
|
||||||
|
var pluginTypeId = typeof(CastleSiegeEventPlugIn).GUID;
|
||||||
|
var inMemory = gameContext.Configuration.PlugInConfigurations.FirstOrDefault(c => c.TypeId == pluginTypeId);
|
||||||
|
if (inMemory is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var ctx = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(PlugInConfiguration), false, gameContext.Configuration);
|
||||||
|
var row = await ctx.GetByIdAsync<PlugInConfiguration>(inMemory.GetId()).ConfigureAwait(false);
|
||||||
|
if (row is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
row.SetConfiguration(settings, gameContext.PlugInManager.CustomConfigReferenceHandler);
|
||||||
|
await ctx.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask BroadcastCastleFlagAsync(IGameContext gameContext, CastleSiegeContext context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (context.OwnerGuildId is not { } owner)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var logo = await this.GetOwnerLogoAsync(gameContext, owner, context.OwnerGuildName).ConfigureAwait(false);
|
||||||
if (logo is null)
|
if (logo is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -500,77 +775,58 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask<byte[]?> GetOwnerLogoAsync(IGameContext gameContext, string ownerName)
|
private async ValueTask<byte[]?> GetOwnerLogoAsync(IGameContext gameContext, Guid ownerId, string? ownerName)
|
||||||
{
|
{
|
||||||
if (this._cachedFlagOwner == ownerName && this._cachedFlagLogo is not null)
|
if (this._cachedFlagOwner == ownerId && this._cachedFlagLogo is not null)
|
||||||
{
|
{
|
||||||
return this._cachedFlagLogo;
|
return this._cachedFlagLogo;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameContext is not IGameServerContext serverContext)
|
if (gameContext is not IGameServerContext serverContext || ownerName is not { Length: > 0 })
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildId = await serverContext.GuildServer.GetGuildIdByNameAsync(ownerName).ConfigureAwait(false);
|
var shortGuildId = await serverContext.GuildServer.GetGuildIdByNameAsync(ownerName).ConfigureAwait(false);
|
||||||
if (guildId == 0)
|
if (shortGuildId == 0)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guild = await serverContext.GuildServer.GetGuildAsync(guildId).ConfigureAwait(false);
|
var guild = await serverContext.GuildServer.GetGuildAsync(shortGuildId).ConfigureAwait(false);
|
||||||
if (guild?.Logo is not { Length: > 0 } logo)
|
if (guild?.Logo is not { Length: > 0 } logo)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._cachedFlagOwner = ownerName;
|
this._cachedFlagOwner = ownerId;
|
||||||
this._cachedFlagLogo = logo;
|
this._cachedFlagLogo = logo;
|
||||||
return logo;
|
return logo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task WarpRegisteredMembersToSiegeAsync(IGameContext gameContext, CastleSiegeContext context)
|
/// <summary>
|
||||||
|
/// Counts the ticks between the periodic broadcasts of one game context.
|
||||||
|
/// </summary>
|
||||||
|
private sealed class BroadcastCounters
|
||||||
{
|
{
|
||||||
try
|
private int _siegeState;
|
||||||
|
private int _castleFlag;
|
||||||
|
|
||||||
|
/// <summary>Advances the countdown-broadcast counter and tells whether it is due.</summary>
|
||||||
|
public bool NextSiegeState() => Due(ref this._siegeState, SiegeStateBroadcastTicks);
|
||||||
|
|
||||||
|
/// <summary>Advances the castle-flag-broadcast counter and tells whether it is due.</summary>
|
||||||
|
public bool NextCastleFlag() => Due(ref this._castleFlag, CastleFlagBroadcastTicks);
|
||||||
|
|
||||||
|
private static bool Due(ref int counter, int period)
|
||||||
{
|
{
|
||||||
var map = await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false);
|
if (++counter < period)
|
||||||
if (map?.SafeZoneSpawnGate is not { } gate)
|
|
||||||
{
|
{
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await gameContext.ForEachPlayerAsync(async player =>
|
counter = 0;
|
||||||
{
|
return true;
|
||||||
var guildName = await GetGuildNameAsync(player).ConfigureAwait(false);
|
|
||||||
if (guildName is not null && context.RegisteredGuilds.Contains(guildName))
|
|
||||||
{
|
|
||||||
await player.WarpToAsync(gate).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
gameContext.LoggerFactory.CreateLogger<CastleSiegeEventPlugIn>()
|
|
||||||
.LogError(ex, "Castle Siege: error while warping registered members to the battle map.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async ValueTask<string?> GetGuildNameAsync(Player player)
|
|
||||||
{
|
|
||||||
if (player.GuildStatus is not { } guildStatus)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (player.GameContext is IGameServerContext serverContext)
|
|
||||||
{
|
|
||||||
var guild = await serverContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false);
|
|
||||||
if (guild?.Name is { Length: > 0 } name)
|
|
||||||
{
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return guildStatus.GuildId.ToString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,4 +49,29 @@ public interface ICastleSiegeStatusViewPlugIn : IViewPlugIn
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="guildName">The capturing guild's name (max 8 bytes).</param>
|
/// <param name="guildName">The capturing guild's name (max 8 bytes).</param>
|
||||||
ValueTask AnnounceSealCapturedAsync(string guildName);
|
ValueTask AnnounceSealCapturedAsync(string guildName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends the state of a Crown Switch (C1 B2 14): state 0 = released (the client closes its progress
|
||||||
|
/// box), 1 = this player is operating it (the client opens its hold progress box), 2 = somebody else
|
||||||
|
/// is already operating it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="switchObjectId">The Crown Switch NPC's object identifier.</param>
|
||||||
|
/// <param name="playerObjectId">The operating player's object identifier.</param>
|
||||||
|
/// <param name="state">The switch state (0 released, 1 operated by this player, 2 operated by another).</param>
|
||||||
|
ValueTask SetCrownSwitchStateAsync(ushort switchObjectId, ushort playerObjectId, byte state);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends who is operating a Crown Switch (C1 B2 20), which the client lists on the siege HUD.
|
||||||
|
/// <para>
|
||||||
|
/// This has to reach a client BEFORE any <see cref="SetCrownSwitchStateAsync"/> with state 0: the client
|
||||||
|
/// allocates its switch table when this arrives, and its "switch released" handler reads that table
|
||||||
|
/// without checking whether it exists.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="switchObjectId">The Crown Switch NPC's object identifier.</param>
|
||||||
|
/// <param name="switchState">0 when nobody operates it, 1 while it is operated.</param>
|
||||||
|
/// <param name="joinSide">The operating side (see the castle siege join sides).</param>
|
||||||
|
/// <param name="guildName">The operating guild's name (max 8 bytes), empty when free.</param>
|
||||||
|
/// <param name="playerName">The operating player's name (max 10 bytes), empty when free.</param>
|
||||||
|
ValueTask SetCrownSwitchInfoAsync(ushort switchObjectId, byte switchState, byte joinSide, string guildName, string playerName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,17 @@ public class CastleSiegeStatusViewPlugIn : ICastleSiegeStatusViewPlugIn
|
|||||||
/// <param name="player">The player.</param>
|
/// <param name="player">The player.</param>
|
||||||
public CastleSiegeStatusViewPlugIn(RemotePlayer player) => this._player = player;
|
public CastleSiegeStatusViewPlugIn(RemotePlayer player) => this._player = player;
|
||||||
|
|
||||||
|
private static void WriteName(string name, Span<byte> target)
|
||||||
|
{
|
||||||
|
if (name.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = System.Text.Encoding.UTF8.GetBytes(name);
|
||||||
|
bytes.AsSpan(0, Math.Min(target.Length - 1, bytes.Length)).CopyTo(target);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask SetBattleStateAsync(bool started)
|
public async ValueTask SetBattleStateAsync(bool started)
|
||||||
{
|
{
|
||||||
@@ -153,6 +164,65 @@ public class CastleSiegeStatusViewPlugIn : ICastleSiegeStatusViewPlugIn
|
|||||||
await connection.SendAsync(WritePacket).ConfigureAwait(false);
|
await connection.SendAsync(WritePacket).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async ValueTask SetCrownSwitchStateAsync(ushort switchObjectId, ushort playerObjectId, byte state)
|
||||||
|
{
|
||||||
|
var connection = this._player.Connection;
|
||||||
|
if (connection is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WritePacket()
|
||||||
|
{
|
||||||
|
// C1 09 B2 14 <switchId:2 BE> <playerId:2 BE> <state>
|
||||||
|
var span = connection.Output.GetSpan(9)[..9];
|
||||||
|
span.Clear();
|
||||||
|
span[0] = 0xC1;
|
||||||
|
span[1] = 0x09;
|
||||||
|
span[2] = 0xB2;
|
||||||
|
span[3] = 0x14;
|
||||||
|
span[4] = (byte)(switchObjectId >> 8);
|
||||||
|
span[5] = (byte)switchObjectId;
|
||||||
|
span[6] = (byte)(playerObjectId >> 8);
|
||||||
|
span[7] = (byte)playerObjectId;
|
||||||
|
span[8] = state;
|
||||||
|
return span.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
await connection.SendAsync(WritePacket).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async ValueTask SetCrownSwitchInfoAsync(ushort switchObjectId, byte switchState, byte joinSide, string guildName, string playerName)
|
||||||
|
{
|
||||||
|
var connection = this._player.Connection;
|
||||||
|
if (connection is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WritePacket()
|
||||||
|
{
|
||||||
|
// C1 1B B2 20 <switchId:2 BE> <switchState> <joinSide> <guildName[8]> <playerName[11]>
|
||||||
|
var span = connection.Output.GetSpan(27)[..27];
|
||||||
|
span.Clear();
|
||||||
|
span[0] = 0xC1;
|
||||||
|
span[1] = 0x1B;
|
||||||
|
span[2] = 0xB2;
|
||||||
|
span[3] = 0x20;
|
||||||
|
span[4] = (byte)(switchObjectId >> 8);
|
||||||
|
span[5] = (byte)switchObjectId;
|
||||||
|
span[6] = switchState;
|
||||||
|
span[7] = joinSide;
|
||||||
|
WriteName(guildName, span.Slice(8, 8));
|
||||||
|
WriteName(playerName, span.Slice(16, 10));
|
||||||
|
return span.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
await connection.SendAsync(WritePacket).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask AnnounceSealCapturedAsync(string guildName)
|
public async ValueTask AnnounceSealCapturedAsync(string guildName)
|
||||||
{
|
{
|
||||||
|
|||||||
342
src/Persistence/BasicModel/CastleSiegeConfiguration.Generated.cs
Normal file
342
src/Persistence/BasicModel/CastleSiegeConfiguration.Generated.cs
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
// <copyright file="CastleSiegeConfiguration.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.BasicModel;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.Persistence.Json;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A plain implementation of <see cref="CastleSiegeConfiguration"/>.
|
||||||
|
/// </summary>
|
||||||
|
public partial class CastleSiegeConfiguration : MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration, IIdentifiable, IConvertibleTo<CastleSiegeConfiguration>
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of this instance.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="StateSchedule" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("stateSchedule")]
|
||||||
|
public ICollection<CastleSiegeStateScheduleEntry> RawStateSchedule { get; } = new List<CastleSiegeStateScheduleEntry>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry> StateSchedule
|
||||||
|
{
|
||||||
|
get => base.StateSchedule ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry, CastleSiegeStateScheduleEntry>(this.RawStateSchedule);
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
this.StateSchedule.Clear();
|
||||||
|
foreach (var item in value)
|
||||||
|
{
|
||||||
|
this.StateSchedule.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="NpcDefinitions" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("npcDefinitions")]
|
||||||
|
public ICollection<CastleSiegeNpcDefinition> RawNpcDefinitions { get; } = new List<CastleSiegeNpcDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition> NpcDefinitions
|
||||||
|
{
|
||||||
|
get => base.NpcDefinitions ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition, CastleSiegeNpcDefinition>(this.RawNpcDefinitions);
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
this.NpcDefinitions.Clear();
|
||||||
|
foreach (var item in value)
|
||||||
|
{
|
||||||
|
this.NpcDefinitions.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="GateDefenseUpgrades" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("gateDefenseUpgrades")]
|
||||||
|
public ICollection<CastleSiegeUpgradeDefinition> RawGateDefenseUpgrades { get; } = new List<CastleSiegeUpgradeDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition> GateDefenseUpgrades
|
||||||
|
{
|
||||||
|
get => base.GateDefenseUpgrades ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, CastleSiegeUpgradeDefinition>(this.RawGateDefenseUpgrades);
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
this.GateDefenseUpgrades.Clear();
|
||||||
|
foreach (var item in value)
|
||||||
|
{
|
||||||
|
this.GateDefenseUpgrades.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="GateLifeUpgrades" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("gateLifeUpgrades")]
|
||||||
|
public ICollection<CastleSiegeUpgradeDefinition> RawGateLifeUpgrades { get; } = new List<CastleSiegeUpgradeDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition> GateLifeUpgrades
|
||||||
|
{
|
||||||
|
get => base.GateLifeUpgrades ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, CastleSiegeUpgradeDefinition>(this.RawGateLifeUpgrades);
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
this.GateLifeUpgrades.Clear();
|
||||||
|
foreach (var item in value)
|
||||||
|
{
|
||||||
|
this.GateLifeUpgrades.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="StatueDefenseUpgrades" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("statueDefenseUpgrades")]
|
||||||
|
public ICollection<CastleSiegeUpgradeDefinition> RawStatueDefenseUpgrades { get; } = new List<CastleSiegeUpgradeDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition> StatueDefenseUpgrades
|
||||||
|
{
|
||||||
|
get => base.StatueDefenseUpgrades ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, CastleSiegeUpgradeDefinition>(this.RawStatueDefenseUpgrades);
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
this.StatueDefenseUpgrades.Clear();
|
||||||
|
foreach (var item in value)
|
||||||
|
{
|
||||||
|
this.StatueDefenseUpgrades.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="StatueLifeUpgrades" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("statueLifeUpgrades")]
|
||||||
|
public ICollection<CastleSiegeUpgradeDefinition> RawStatueLifeUpgrades { get; } = new List<CastleSiegeUpgradeDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition> StatueLifeUpgrades
|
||||||
|
{
|
||||||
|
get => base.StatueLifeUpgrades ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, CastleSiegeUpgradeDefinition>(this.RawStatueLifeUpgrades);
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
this.StatueLifeUpgrades.Clear();
|
||||||
|
foreach (var item in value)
|
||||||
|
{
|
||||||
|
this.StatueLifeUpgrades.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="StatueRegenUpgrades" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("statueRegenUpgrades")]
|
||||||
|
public ICollection<CastleSiegeUpgradeDefinition> RawStatueRegenUpgrades { get; } = new List<CastleSiegeUpgradeDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition> StatueRegenUpgrades
|
||||||
|
{
|
||||||
|
get => base.StatueRegenUpgrades ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, CastleSiegeUpgradeDefinition>(this.RawStatueRegenUpgrades);
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
this.StatueRegenUpgrades.Clear();
|
||||||
|
foreach (var item in value)
|
||||||
|
{
|
||||||
|
this.StatueRegenUpgrades.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="AttackMachineZones" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("attackMachineZones")]
|
||||||
|
public ICollection<CastleSiegeZoneDefinition> RawAttackMachineZones { get; } = new List<CastleSiegeZoneDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition> AttackMachineZones
|
||||||
|
{
|
||||||
|
get => base.AttackMachineZones ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition, CastleSiegeZoneDefinition>(this.RawAttackMachineZones);
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
this.AttackMachineZones.Clear();
|
||||||
|
foreach (var item in value)
|
||||||
|
{
|
||||||
|
this.AttackMachineZones.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="DefenseMachineZones" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("defenseMachineZones")]
|
||||||
|
public ICollection<CastleSiegeZoneDefinition> RawDefenseMachineZones { get; } = new List<CastleSiegeZoneDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition> DefenseMachineZones
|
||||||
|
{
|
||||||
|
get => base.DefenseMachineZones ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition, CastleSiegeZoneDefinition>(this.RawDefenseMachineZones);
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
this.DefenseMachineZones.Clear();
|
||||||
|
foreach (var item in value)
|
||||||
|
{
|
||||||
|
this.DefenseMachineZones.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw object of <see cref="CastleSiegeMapDefinition" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("castleSiegeMapDefinition")]
|
||||||
|
public GameMapDefinition RawCastleSiegeMapDefinition
|
||||||
|
{
|
||||||
|
get => base.CastleSiegeMapDefinition as GameMapDefinition;
|
||||||
|
set => base.CastleSiegeMapDefinition = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.GameMapDefinition CastleSiegeMapDefinition
|
||||||
|
{
|
||||||
|
get => base.CastleSiegeMapDefinition;
|
||||||
|
set => base.CastleSiegeMapDefinition = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw object of <see cref="LandOfTrialsMapDefinition" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("landOfTrialsMapDefinition")]
|
||||||
|
public GameMapDefinition RawLandOfTrialsMapDefinition
|
||||||
|
{
|
||||||
|
get => base.LandOfTrialsMapDefinition as GameMapDefinition;
|
||||||
|
set => base.LandOfTrialsMapDefinition = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.GameMapDefinition LandOfTrialsMapDefinition
|
||||||
|
{
|
||||||
|
get => base.LandOfTrialsMapDefinition;
|
||||||
|
set => base.LandOfTrialsMapDefinition = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw object of <see cref="RewardItemDefinition" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("rewardItemDefinition")]
|
||||||
|
public ItemDefinition RawRewardItemDefinition
|
||||||
|
{
|
||||||
|
get => base.RewardItemDefinition as ItemDefinition;
|
||||||
|
set => base.RewardItemDefinition = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.Items.ItemDefinition RewardItemDefinition
|
||||||
|
{
|
||||||
|
get => base.RewardItemDefinition;
|
||||||
|
set => base.RewardItemDefinition = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw object of <see cref="DefenseRespawnArea" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("defenseRespawnArea")]
|
||||||
|
public CastleSiegeZoneDefinition RawDefenseRespawnArea
|
||||||
|
{
|
||||||
|
get => base.DefenseRespawnArea as CastleSiegeZoneDefinition;
|
||||||
|
set => base.DefenseRespawnArea = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition DefenseRespawnArea
|
||||||
|
{
|
||||||
|
get => base.DefenseRespawnArea;
|
||||||
|
set => base.DefenseRespawnArea = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw object of <see cref="AttackRespawnArea" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("attackRespawnArea")]
|
||||||
|
public CastleSiegeZoneDefinition RawAttackRespawnArea
|
||||||
|
{
|
||||||
|
get => base.AttackRespawnArea as CastleSiegeZoneDefinition;
|
||||||
|
set => base.AttackRespawnArea = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition AttackRespawnArea
|
||||||
|
{
|
||||||
|
get => base.AttackRespawnArea;
|
||||||
|
set => base.AttackRespawnArea = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
var clone = new CastleSiegeConfiguration();
|
||||||
|
clone.AssignValuesOf(this, gameConfiguration);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
base.AssignValuesOf(other, gameConfiguration);
|
||||||
|
this.Id = other.GetId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public CastleSiegeConfiguration Convert() => this;
|
||||||
|
}
|
||||||
67
src/Persistence/BasicModel/CastleSiegeData.Generated.cs
Normal file
67
src/Persistence/BasicModel/CastleSiegeData.Generated.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// <copyright file="CastleSiegeData.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.BasicModel;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.Persistence.Json;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A plain implementation of <see cref="CastleSiegeData"/>.
|
||||||
|
/// </summary>
|
||||||
|
public partial class CastleSiegeData : MUnique.OpenMU.DataModel.Entities.CastleSiegeData, IIdentifiable, IConvertibleTo<CastleSiegeData>
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="NpcStates" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("npcStates")]
|
||||||
|
public ICollection<CastleSiegeNpcState> RawNpcStates { get; } = new List<CastleSiegeNpcState>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Entities.CastleSiegeNpcState> NpcStates
|
||||||
|
{
|
||||||
|
get => base.NpcStates ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Entities.CastleSiegeNpcState, CastleSiegeNpcState>(this.RawNpcStates);
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
this.NpcStates.Clear();
|
||||||
|
foreach (var item in value)
|
||||||
|
{
|
||||||
|
this.NpcStates.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public CastleSiegeData Convert() => this;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// <copyright file="CastleSiegeGuildRegistration.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.BasicModel;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.Persistence.Json;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A plain implementation of <see cref="CastleSiegeGuildRegistration"/>.
|
||||||
|
/// </summary>
|
||||||
|
public partial class CastleSiegeGuildRegistration : MUnique.OpenMU.DataModel.Entities.CastleSiegeGuildRegistration, IIdentifiable, IConvertibleTo<CastleSiegeGuildRegistration>
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public CastleSiegeGuildRegistration Convert() => this;
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// <copyright file="CastleSiegeNpcDefinition.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.BasicModel;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.Persistence.Json;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A plain implementation of <see cref="CastleSiegeNpcDefinition"/>.
|
||||||
|
/// </summary>
|
||||||
|
public partial class CastleSiegeNpcDefinition : MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition, IIdentifiable, IConvertibleTo<CastleSiegeNpcDefinition>
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of this instance.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw object of <see cref="MonsterDefinition" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("monsterDefinition")]
|
||||||
|
public MonsterDefinition RawMonsterDefinition
|
||||||
|
{
|
||||||
|
get => base.MonsterDefinition as MonsterDefinition;
|
||||||
|
set => base.MonsterDefinition = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.MonsterDefinition MonsterDefinition
|
||||||
|
{
|
||||||
|
get => base.MonsterDefinition;
|
||||||
|
set => base.MonsterDefinition = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
var clone = new CastleSiegeNpcDefinition();
|
||||||
|
clone.AssignValuesOf(this, gameConfiguration);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
base.AssignValuesOf(other, gameConfiguration);
|
||||||
|
this.Id = other.GetId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public CastleSiegeNpcDefinition Convert() => this;
|
||||||
|
}
|
||||||
46
src/Persistence/BasicModel/CastleSiegeNpcState.Generated.cs
Normal file
46
src/Persistence/BasicModel/CastleSiegeNpcState.Generated.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// <copyright file="CastleSiegeNpcState.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.BasicModel;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.Persistence.Json;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A plain implementation of <see cref="CastleSiegeNpcState"/>.
|
||||||
|
/// </summary>
|
||||||
|
public partial class CastleSiegeNpcState : MUnique.OpenMU.DataModel.Entities.CastleSiegeNpcState, IIdentifiable, IConvertibleTo<CastleSiegeNpcState>
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public CastleSiegeNpcState Convert() => this;
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// <copyright file="CastleSiegeStateScheduleEntry.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.BasicModel;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.Persistence.Json;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A plain implementation of <see cref="CastleSiegeStateScheduleEntry"/>.
|
||||||
|
/// </summary>
|
||||||
|
public partial class CastleSiegeStateScheduleEntry : MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry, IIdentifiable, IConvertibleTo<CastleSiegeStateScheduleEntry>
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of this instance.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
var clone = new CastleSiegeStateScheduleEntry();
|
||||||
|
clone.AssignValuesOf(this, gameConfiguration);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
base.AssignValuesOf(other, gameConfiguration);
|
||||||
|
this.Id = other.GetId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public CastleSiegeStateScheduleEntry Convert() => this;
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// <copyright file="CastleSiegeUpgradeDefinition.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.BasicModel;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.Persistence.Json;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A plain implementation of <see cref="CastleSiegeUpgradeDefinition"/>.
|
||||||
|
/// </summary>
|
||||||
|
public partial class CastleSiegeUpgradeDefinition : MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, IIdentifiable, IConvertibleTo<CastleSiegeUpgradeDefinition>
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of this instance.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
var clone = new CastleSiegeUpgradeDefinition();
|
||||||
|
clone.AssignValuesOf(this, gameConfiguration);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
base.AssignValuesOf(other, gameConfiguration);
|
||||||
|
this.Id = other.GetId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public CastleSiegeUpgradeDefinition Convert() => this;
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// <copyright file="CastleSiegeZoneDefinition.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.BasicModel;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.Persistence.Json;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A plain implementation of <see cref="CastleSiegeZoneDefinition"/>.
|
||||||
|
/// </summary>
|
||||||
|
public partial class CastleSiegeZoneDefinition : MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition, IIdentifiable, IConvertibleTo<CastleSiegeZoneDefinition>
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of this instance.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
var clone = new CastleSiegeZoneDefinition();
|
||||||
|
clone.AssignValuesOf(this, gameConfiguration);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
base.AssignValuesOf(other, gameConfiguration);
|
||||||
|
this.Id = other.GetId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public CastleSiegeZoneDefinition Convert() => this;
|
||||||
|
}
|
||||||
@@ -484,6 +484,24 @@ public partial class GameConfiguration : MUnique.OpenMU.DataModel.Configuration.
|
|||||||
set => base.DuelConfiguration = value;
|
set => base.DuelConfiguration = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw object of <see cref="CastleSiegeConfiguration" />.
|
||||||
|
/// </summary>
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("castleSiegeConfiguration")]
|
||||||
|
public CastleSiegeConfiguration RawCastleSiegeConfiguration
|
||||||
|
{
|
||||||
|
get => base.CastleSiegeConfiguration as CastleSiegeConfiguration;
|
||||||
|
set => base.CastleSiegeConfiguration = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration CastleSiegeConfiguration
|
||||||
|
{
|
||||||
|
get => base.CastleSiegeConfiguration;
|
||||||
|
set => base.CastleSiegeConfiguration = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override MUnique.OpenMU.DataModel.Configuration.GameConfiguration Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
public override MUnique.OpenMU.DataModel.Configuration.GameConfiguration Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,6 +21,16 @@ public class EntityDataContext : ExtendedTypeContext
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal GameConfiguration? CurrentGameConfiguration { get; set; }
|
internal GameConfiguration? CurrentGameConfiguration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the persistent Castle Siege state.
|
||||||
|
/// </summary>
|
||||||
|
internal DbSet<CastleSiegeData> CastleSiegeData => this.Set<CastleSiegeData>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Castle Siege guild registrations.
|
||||||
|
/// </summary>
|
||||||
|
internal DbSet<CastleSiegeGuildRegistration> CastleSiegeGuildRegistrations => this.Set<CastleSiegeGuildRegistration>();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
@@ -58,6 +68,11 @@ public class EntityDataContext : ExtendedTypeContext
|
|||||||
modelBuilder.Entity<Account>().Apply();
|
modelBuilder.Entity<Account>().Apply();
|
||||||
modelBuilder.Entity<Character>().Apply();
|
modelBuilder.Entity<Character>().Apply();
|
||||||
modelBuilder.Entity<CharacterClass>().Apply();
|
modelBuilder.Entity<CharacterClass>().Apply();
|
||||||
|
modelBuilder.Entity<CastleSiegeConfiguration>().Apply();
|
||||||
|
modelBuilder.Entity<CastleSiegeData>().Apply();
|
||||||
|
modelBuilder.Entity<CastleSiegeGuildRegistration>().Apply();
|
||||||
|
modelBuilder.Entity<CastleSiegeNpcDefinition>().Apply();
|
||||||
|
modelBuilder.Entity<CastleSiegeNpcState>().Apply();
|
||||||
modelBuilder.Entity<DropItemGroup>().Apply();
|
modelBuilder.Entity<DropItemGroup>().Apply();
|
||||||
modelBuilder.Entity<ExitGate>().Apply();
|
modelBuilder.Entity<ExitGate>().Apply();
|
||||||
modelBuilder.Entity<GameConfiguration>().Apply();
|
modelBuilder.Entity<GameConfiguration>().Apply();
|
||||||
@@ -98,4 +113,4 @@ public class EntityDataContext : ExtendedTypeContext
|
|||||||
GuildContext.ConfigureModel(modelBuilder);
|
GuildContext.ConfigureModel(modelBuilder);
|
||||||
FriendContext.ConfigureModel(modelBuilder);
|
FriendContext.ConfigureModel(modelBuilder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,55 @@ internal class EntityFrameworkContextBase : IContext
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async ValueTask<bool> SaveChangesAsync(CancellationToken cancellationToken = default)
|
public async ValueTask<bool> SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// A player's entities can be mutated by game logic on a flow that is not serialized against
|
||||||
|
// this save (for example item destruction on an attacker's thread during combat). Such a
|
||||||
|
// concurrent mutation makes change detection throw while it enumerates a tracked collection.
|
||||||
|
// The mutation is a single, quick operation, so a bounded retry lands on a stable moment
|
||||||
|
// instead of failing the whole save - which would otherwise leave the session unpersisted and
|
||||||
|
// roll the player back on relog.
|
||||||
|
const int maxAttempts = 3;
|
||||||
|
var attempt = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
attempt++;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await this.SaveChangesCoreAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (attempt < maxAttempts && IsTransientConcurrencyConflict(ex))
|
||||||
|
{
|
||||||
|
this._logger.LogWarning(ex, "Transient concurrency conflict while saving (attempt {Attempt}/{MaxAttempts}); retrying.", attempt, maxAttempts);
|
||||||
|
await Task.Delay(attempt * 10, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the exception is a transient conflict caused by a concurrent entity mutation
|
||||||
|
/// racing this save, and is therefore worth retrying.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exception">The exception thrown by the save.</param>
|
||||||
|
/// <returns><c>true</c> if the save should be retried.</returns>
|
||||||
|
private static bool IsTransientConcurrencyConflict(Exception exception)
|
||||||
|
{
|
||||||
|
// A concurrent entity mutation racing this save corrupts the change tracker mid-enumeration.
|
||||||
|
// Depending on exactly where change detection was, it surfaces as one of several types - a
|
||||||
|
// modified collection (InvalidOperationException), a transiently-null internal key
|
||||||
|
// (ArgumentNullException/NullReferenceException), or an out-of-range index. All are transient:
|
||||||
|
// the racing mutation is a single quick operation, so a bounded retry lands on a stable moment.
|
||||||
|
// A genuinely persistent error of the same type is not masked - it rethrows once the retries
|
||||||
|
// are exhausted. The deterministic serialization (per-player persistence lock) is the primary
|
||||||
|
// guard; this retry only needs to absorb the rare, bursty sources that lock isn't held for.
|
||||||
|
return exception is DbUpdateConcurrencyException
|
||||||
|
or InvalidOperationException
|
||||||
|
or ArgumentNullException
|
||||||
|
or NullReferenceException
|
||||||
|
or IndexOutOfRangeException
|
||||||
|
or KeyNotFoundException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask<bool> SaveChangesCoreAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using var l = await this._lock.LockAsync();
|
using var l = await this._lock.LockAsync();
|
||||||
|
|
||||||
@@ -252,6 +301,14 @@ internal class EntityFrameworkContextBase : IContext
|
|||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether changes of an entity type are published as configuration changes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entityType">The entity type.</param>
|
||||||
|
/// <returns><see langword="true"/> when the entity belongs to the configuration schema.</returns>
|
||||||
|
internal static bool PublishesConfigurationChanges(IReadOnlyEntityType entityType)
|
||||||
|
=> entityType.GetSchema() == SchemaNames.Configuration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases unmanaged and - optionally - managed resources.
|
/// Releases unmanaged and - optionally - managed resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -343,7 +400,9 @@ internal class EntityFrameworkContextBase : IContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
var changedEntries = this.Context.ChangeTracker.Entries()
|
var changedEntries = this.Context.ChangeTracker.Entries()
|
||||||
.Where(entity => entity.State != EntityState.Unchanged).ToList();
|
.Where(entity => entity.State != EntityState.Unchanged
|
||||||
|
&& PublishesConfigurationChanges(entity.Metadata))
|
||||||
|
.ToList();
|
||||||
foreach (var entry in changedEntries)
|
foreach (var entry in changedEntries)
|
||||||
{
|
{
|
||||||
var (parent, parentCollectionNavigation) = this.GetParentInformation(entry);
|
var (parent, parentCollectionNavigation) = this.GetParentInformation(entry);
|
||||||
@@ -413,4 +472,4 @@ internal class EntityFrameworkContextBase : IContext
|
|||||||
|
|
||||||
return (parent ?? parentId, parentCollectionNavigation);
|
return (parent ?? parentId, parentCollectionNavigation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
// <copyright file="CastleSiegeExtensions.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.EntityFramework.Extensions.ModelBuilder;
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using MUnique.OpenMU.Persistence.EntityFramework.Model;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions for Castle Siege-related <see cref="EntityTypeBuilder"/>s.
|
||||||
|
/// </summary>
|
||||||
|
internal static class CastleSiegeExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the settings for the <see cref="CastleSiegeConfiguration"/> entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder.</param>
|
||||||
|
public static void Apply(this EntityTypeBuilder<CastleSiegeConfiguration> builder)
|
||||||
|
{
|
||||||
|
builder.Property(configuration => configuration.CrownHoldTimeSeconds).HasDefaultValue(30);
|
||||||
|
builder.Property(configuration => configuration.RegisterMinLevel).HasDefaultValue(200);
|
||||||
|
builder.Property(configuration => configuration.RegisterMinMembers).HasDefaultValue(20);
|
||||||
|
builder.Property(configuration => configuration.MaxAttackingGuilds).HasDefaultValue(3);
|
||||||
|
|
||||||
|
builder.HasOne(configuration => configuration.RawCastleSiegeMapDefinition)
|
||||||
|
.WithMany()
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
builder.HasOne(configuration => configuration.RawLandOfTrialsMapDefinition)
|
||||||
|
.WithMany()
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
builder.HasOne(configuration => configuration.RawRewardItemDefinition)
|
||||||
|
.WithMany()
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the settings for the <see cref="CastleSiegeNpcDefinition"/> entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder.</param>
|
||||||
|
public static void Apply(this EntityTypeBuilder<CastleSiegeNpcDefinition> builder)
|
||||||
|
{
|
||||||
|
builder.HasOne(definition => definition.RawMonsterDefinition)
|
||||||
|
.WithMany()
|
||||||
|
.IsRequired()
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
builder.HasIndex(definition => new { definition.MonsterDefinitionId, definition.InstanceId });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the settings for the <see cref="CastleSiegeData"/> entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder.</param>
|
||||||
|
public static void Apply(this EntityTypeBuilder<CastleSiegeData> builder)
|
||||||
|
{
|
||||||
|
builder.HasOne<Guild>()
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(data => data.OwnerGuildId)
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the settings for the <see cref="CastleSiegeNpcState"/> entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder.</param>
|
||||||
|
public static void Apply(this EntityTypeBuilder<CastleSiegeNpcState> builder)
|
||||||
|
{
|
||||||
|
builder.HasIndex(state => new { state.MonsterNumber, state.InstanceId }).IsUnique();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the settings for the <see cref="CastleSiegeGuildRegistration"/> entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder.</param>
|
||||||
|
public static void Apply(this EntityTypeBuilder<CastleSiegeGuildRegistration> builder)
|
||||||
|
{
|
||||||
|
builder.Property(registration => registration.GuildName).HasMaxLength(8).IsRequired();
|
||||||
|
builder.HasIndex(registration => registration.GuildId).IsUnique();
|
||||||
|
builder.HasOne<Guild>()
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(registration => registration.GuildId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,8 +48,15 @@
|
|||||||
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The generator overwrites the checked-in *.Generated.cs files, so it must run against the current data
|
||||||
|
model. Do NOT add the "no build" switch here: it would reuse whatever assemblies happen to lie in the
|
||||||
|
generator's output folder, and a stale copy of the data model silently regenerates the model files
|
||||||
|
without the types added since. The build still succeeds and only fails at runtime, when EF validates
|
||||||
|
the model.
|
||||||
|
-->
|
||||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent" Condition="'$(ci)'!='true'">
|
<Target Name="PreBuild" BeforeTargets="PreBuildEvent" Condition="'$(ci)'!='true'">
|
||||||
<Exec Command="dotnet run --project ../SourceGenerator/MUnique.OpenMU.Persistence.SourceGenerator.csproj -c:$(ConfigurationName) MUnique.OpenMU.Persistence.EntityFramework "$(ProjectDir)Model" --no-build" />
|
<Exec Command="dotnet run --project ../SourceGenerator/MUnique.OpenMU.Persistence.SourceGenerator.csproj -c:$(ConfigurationName) MUnique.OpenMU.Persistence.EntityFramework "$(ProjectDir)Model"" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// <copyright file="20260710205741_AddIsQuestItemFlag.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
|
namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
|
||||||
{
|
{
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class AddIsQuestItemFlag : Migration
|
public partial class AddIsQuestItemFlag : Migration
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
using System;
|
// <copyright file="20260712014203_AddBuff.cs" company="MUnique">
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
|
namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
|
||||||
{
|
{
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class AddBuff : Migration
|
public partial class AddBuff : Migration
|
||||||
{
|
{
|
||||||
@@ -20,7 +24,7 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
|
|||||||
MagicEffectDefinitionId = table.Column<Guid>(type: "uuid", nullable: true),
|
MagicEffectDefinitionId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
MonsterDefinitionId = table.Column<Guid>(type: "uuid", nullable: true),
|
MonsterDefinitionId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
MinimumLevel = table.Column<int>(type: "integer", nullable: true),
|
MinimumLevel = table.Column<int>(type: "integer", nullable: true),
|
||||||
MaximumLevel = table.Column<int>(type: "integer", nullable: true)
|
MaximumLevel = table.Column<int>(type: "integer", nullable: true),
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
|
|||||||
5725
src/Persistence/EntityFramework/Migrations/20260730194321_AddCastleSiege.Designer.cs
generated
Normal file
5725
src/Persistence/EntityFramework/Migrations/20260730194321_AddCastleSiege.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,453 @@
|
|||||||
|
// <copyright file="20260730194321_AddCastleSiege.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddCastleSiege : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<Guid>(
|
||||||
|
name: "CastleSiegeConfigurationId",
|
||||||
|
schema: "config",
|
||||||
|
table: "GameConfiguration",
|
||||||
|
type: "uuid",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CastleSiegeData",
|
||||||
|
schema: "data",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
OwnerGuildId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
IsOccupied = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
|
TaxChaos = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
TaxStore = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
TaxHunt = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
IsHuntZoneEnabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
|
TributeMoney = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CastleSiegeData", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CastleSiegeNpcState",
|
||||||
|
schema: "data",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
CastleSiegeDataId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
MonsterNumber = table.Column<short>(type: "smallint", nullable: false),
|
||||||
|
InstanceId = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
DefenseLevel = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
RegenLevel = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
LifeLevel = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
CurrentHp = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CastleSiegeNpcState", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeNpcState_CastleSiegeData_CastleSiegeDataId",
|
||||||
|
column: x => x.CastleSiegeDataId,
|
||||||
|
principalSchema: "data",
|
||||||
|
principalTable: "CastleSiegeData",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CastleSiegeConfiguration",
|
||||||
|
schema: "config",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
CastleSiegeMapDefinitionId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
LandOfTrialsMapDefinitionId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
RewardItemDefinitionId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
DefenseRespawnAreaId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
AttackRespawnAreaId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
|
CrownHoldTimeSeconds = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
RegisterMinLevel = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
RegisterMinMembers = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
ParticipantRewardMinSeconds = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
MaxAttackingGuilds = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
GuildScoreCastleSiege = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
GuildScoreCastleSiegeMembers = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
GateBuyPrice = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
StatueBuyPrice = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CastleSiegeConfiguration", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_GameMapDefinition_CastleSiegeMapDe~",
|
||||||
|
column: x => x.CastleSiegeMapDefinitionId,
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "GameMapDefinition",
|
||||||
|
principalColumn: "Id");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_GameMapDefinition_LandOfTrialsMapD~",
|
||||||
|
column: x => x.LandOfTrialsMapDefinitionId,
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "GameMapDefinition",
|
||||||
|
principalColumn: "Id");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_ItemDefinition_RewardItemDefinitio~",
|
||||||
|
column: x => x.RewardItemDefinitionId,
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "ItemDefinition",
|
||||||
|
principalColumn: "Id");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CastleSiegeNpcDefinition",
|
||||||
|
schema: "config",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
MonsterDefinitionId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
CastleSiegeConfigurationId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
InstanceId = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
IsPersistedToDatabase = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
|
DefaultSide = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
SpawnX = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
SpawnY = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
Direction = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CastleSiegeNpcDefinition", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeNpcDefinition_CastleSiegeConfiguration_CastleSie~",
|
||||||
|
column: x => x.CastleSiegeConfigurationId,
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "CastleSiegeConfiguration",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeNpcDefinition_MonsterDefinition_MonsterDefinitio~",
|
||||||
|
column: x => x.MonsterDefinitionId,
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "MonsterDefinition",
|
||||||
|
principalColumn: "Id");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CastleSiegeStateScheduleEntry",
|
||||||
|
schema: "config",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
CastleSiegeConfigurationId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
State = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
DayOfWeek = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Hour = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
Minute = table.Column<byte>(type: "smallint", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CastleSiegeStateScheduleEntry", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeStateScheduleEntry_CastleSiegeConfiguration_Cast~",
|
||||||
|
column: x => x.CastleSiegeConfigurationId,
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "CastleSiegeConfiguration",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CastleSiegeUpgradeDefinition",
|
||||||
|
schema: "config",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
CastleSiegeConfigurationId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
CastleSiegeConfigurationId1 = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
CastleSiegeConfigurationId2 = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
CastleSiegeConfigurationId3 = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
CastleSiegeConfigurationId4 = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
Level = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
RequiredJewelOfGuardianCount = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
RequiredZen = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Value = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CastleSiegeUpgradeDefinition", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Castl~",
|
||||||
|
column: x => x.CastleSiegeConfigurationId,
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "CastleSiegeConfiguration",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~1",
|
||||||
|
column: x => x.CastleSiegeConfigurationId1,
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "CastleSiegeConfiguration",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~2",
|
||||||
|
column: x => x.CastleSiegeConfigurationId2,
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "CastleSiegeConfiguration",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~3",
|
||||||
|
column: x => x.CastleSiegeConfigurationId3,
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "CastleSiegeConfiguration",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~4",
|
||||||
|
column: x => x.CastleSiegeConfigurationId4,
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "CastleSiegeConfiguration",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CastleSiegeZoneDefinition",
|
||||||
|
schema: "config",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
CastleSiegeConfigurationId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
CastleSiegeConfigurationId1 = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
X1 = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
Y1 = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
X2 = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
Y2 = table.Column<byte>(type: "smallint", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CastleSiegeZoneDefinition", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeZoneDefinition_CastleSiegeConfiguration_CastleSi~",
|
||||||
|
column: x => x.CastleSiegeConfigurationId,
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "CastleSiegeConfiguration",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeZoneDefinition_CastleSiegeConfiguration_CastleS~1",
|
||||||
|
column: x => x.CastleSiegeConfigurationId1,
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "CastleSiegeConfiguration",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_GameConfiguration_CastleSiegeConfigurationId",
|
||||||
|
schema: "config",
|
||||||
|
table: "GameConfiguration",
|
||||||
|
column: "CastleSiegeConfigurationId",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeConfiguration_AttackRespawnAreaId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
column: "AttackRespawnAreaId",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeConfiguration_CastleSiegeMapDefinitionId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
column: "CastleSiegeMapDefinitionId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeConfiguration_DefenseRespawnAreaId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
column: "DefenseRespawnAreaId",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeConfiguration_LandOfTrialsMapDefinitionId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
column: "LandOfTrialsMapDefinitionId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeConfiguration_RewardItemDefinitionId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
column: "RewardItemDefinitionId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeNpcDefinition_CastleSiegeConfigurationId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeNpcDefinition",
|
||||||
|
column: "CastleSiegeConfigurationId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeNpcDefinition_MonsterDefinitionId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeNpcDefinition",
|
||||||
|
column: "MonsterDefinitionId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeNpcState_CastleSiegeDataId",
|
||||||
|
schema: "data",
|
||||||
|
table: "CastleSiegeNpcState",
|
||||||
|
column: "CastleSiegeDataId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeStateScheduleEntry_CastleSiegeConfigurationId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeStateScheduleEntry",
|
||||||
|
column: "CastleSiegeConfigurationId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeUpgradeDefinition_CastleSiegeConfigurationId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeUpgradeDefinition",
|
||||||
|
column: "CastleSiegeConfigurationId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeUpgradeDefinition_CastleSiegeConfigurationId1",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeUpgradeDefinition",
|
||||||
|
column: "CastleSiegeConfigurationId1");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeUpgradeDefinition_CastleSiegeConfigurationId2",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeUpgradeDefinition",
|
||||||
|
column: "CastleSiegeConfigurationId2");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeUpgradeDefinition_CastleSiegeConfigurationId3",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeUpgradeDefinition",
|
||||||
|
column: "CastleSiegeConfigurationId3");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeUpgradeDefinition_CastleSiegeConfigurationId4",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeUpgradeDefinition",
|
||||||
|
column: "CastleSiegeConfigurationId4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeZoneDefinition_CastleSiegeConfigurationId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeZoneDefinition",
|
||||||
|
column: "CastleSiegeConfigurationId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeZoneDefinition_CastleSiegeConfigurationId1",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeZoneDefinition",
|
||||||
|
column: "CastleSiegeConfigurationId1");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_GameConfiguration_CastleSiegeConfiguration_CastleSiegeConfi~",
|
||||||
|
schema: "config",
|
||||||
|
table: "GameConfiguration",
|
||||||
|
column: "CastleSiegeConfigurationId",
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "CastleSiegeConfiguration",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_CastleSiegeZoneDefinition_AttackRe~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
column: "AttackRespawnAreaId",
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "CastleSiegeZoneDefinition",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_CastleSiegeZoneDefinition_DefenseR~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
column: "DefenseRespawnAreaId",
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "CastleSiegeZoneDefinition",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_GameConfiguration_CastleSiegeConfiguration_CastleSiegeConfi~",
|
||||||
|
schema: "config",
|
||||||
|
table: "GameConfiguration");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_CastleSiegeZoneDefinition_AttackRe~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_CastleSiegeZoneDefinition_DefenseR~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CastleSiegeNpcDefinition",
|
||||||
|
schema: "config");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CastleSiegeNpcState",
|
||||||
|
schema: "data");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CastleSiegeStateScheduleEntry",
|
||||||
|
schema: "config");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CastleSiegeUpgradeDefinition",
|
||||||
|
schema: "config");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CastleSiegeData",
|
||||||
|
schema: "data");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CastleSiegeZoneDefinition",
|
||||||
|
schema: "config");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CastleSiegeConfiguration",
|
||||||
|
schema: "config");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_GameConfiguration_CastleSiegeConfigurationId",
|
||||||
|
schema: "config",
|
||||||
|
table: "GameConfiguration");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "CastleSiegeConfigurationId",
|
||||||
|
schema: "config",
|
||||||
|
table: "GameConfiguration");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5788
src/Persistence/EntityFramework/Migrations/20260801162427_ConfigureCastleSiegePersistence.Designer.cs
generated
Normal file
5788
src/Persistence/EntityFramework/Migrations/20260801162427_ConfigureCastleSiegePersistence.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,343 @@
|
|||||||
|
// <copyright file="20260801162427_ConfigureCastleSiegePersistence.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ConfigureCastleSiegePersistence : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_GameMapDefinition_CastleSiegeMapDe~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_GameMapDefinition_LandOfTrialsMapD~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_ItemDefinition_RewardItemDefinitio~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CastleSiegeNpcDefinition_MonsterDefinition_MonsterDefinitio~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeNpcDefinition");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_CastleSiegeNpcDefinition_MonsterDefinitionId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeNpcDefinition");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(
|
||||||
|
"""
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM config."CastleSiegeNpcDefinition" WHERE "MonsterDefinitionId" IS NULL) THEN
|
||||||
|
RAISE EXCEPTION 'CastleSiegeNpcDefinition contains rows without a MonsterDefinitionId. Repair or remove these rows before applying this migration.';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<Guid>(
|
||||||
|
name: "MonsterDefinitionId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeNpcDefinition",
|
||||||
|
type: "uuid",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(Guid),
|
||||||
|
oldType: "uuid",
|
||||||
|
oldNullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "RegisterMinMembers",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 20,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "integer");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "RegisterMinLevel",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 200,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "integer");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "MaxAttackingGuilds",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 3,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "integer");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "CrownHoldTimeSeconds",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 30,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "integer");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CastleSiegeGuildRegistration",
|
||||||
|
schema: "data",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
GuildId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
GuildName = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: false),
|
||||||
|
Marks = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
RegistrationOrder = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CastleSiegeGuildRegistration", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CastleSiegeGuildRegistration_Guild_GuildId",
|
||||||
|
column: x => x.GuildId,
|
||||||
|
principalSchema: "guild",
|
||||||
|
principalTable: "Guild",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeNpcState_MonsterNumber_InstanceId",
|
||||||
|
schema: "data",
|
||||||
|
table: "CastleSiegeNpcState",
|
||||||
|
columns: new[] { "MonsterNumber", "InstanceId" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeNpcDefinition_MonsterDefinitionId_InstanceId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeNpcDefinition",
|
||||||
|
columns: new[] { "MonsterDefinitionId", "InstanceId" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeData_OwnerGuildId",
|
||||||
|
schema: "data",
|
||||||
|
table: "CastleSiegeData",
|
||||||
|
column: "OwnerGuildId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeGuildRegistration_GuildId",
|
||||||
|
schema: "data",
|
||||||
|
table: "CastleSiegeGuildRegistration",
|
||||||
|
column: "GuildId",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_GameMapDefinition_CastleSiegeMapDe~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
column: "CastleSiegeMapDefinitionId",
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "GameMapDefinition",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_GameMapDefinition_LandOfTrialsMapD~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
column: "LandOfTrialsMapDefinitionId",
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "GameMapDefinition",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_ItemDefinition_RewardItemDefinitio~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
column: "RewardItemDefinitionId",
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "ItemDefinition",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CastleSiegeData_Guild_OwnerGuildId",
|
||||||
|
schema: "data",
|
||||||
|
table: "CastleSiegeData",
|
||||||
|
column: "OwnerGuildId",
|
||||||
|
principalSchema: "guild",
|
||||||
|
principalTable: "Guild",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CastleSiegeNpcDefinition_MonsterDefinition_MonsterDefinitio~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeNpcDefinition",
|
||||||
|
column: "MonsterDefinitionId",
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "MonsterDefinition",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_GameMapDefinition_CastleSiegeMapDe~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_GameMapDefinition_LandOfTrialsMapD~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_ItemDefinition_RewardItemDefinitio~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CastleSiegeData_Guild_OwnerGuildId",
|
||||||
|
schema: "data",
|
||||||
|
table: "CastleSiegeData");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CastleSiegeNpcDefinition_MonsterDefinition_MonsterDefinitio~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeNpcDefinition");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CastleSiegeGuildRegistration",
|
||||||
|
schema: "data");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_CastleSiegeNpcState_MonsterNumber_InstanceId",
|
||||||
|
schema: "data",
|
||||||
|
table: "CastleSiegeNpcState");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_CastleSiegeNpcDefinition_MonsterDefinitionId_InstanceId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeNpcDefinition");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_CastleSiegeData_OwnerGuildId",
|
||||||
|
schema: "data",
|
||||||
|
table: "CastleSiegeData");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<Guid>(
|
||||||
|
name: "MonsterDefinitionId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeNpcDefinition",
|
||||||
|
type: "uuid",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(Guid),
|
||||||
|
oldType: "uuid");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "RegisterMinMembers",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "integer",
|
||||||
|
oldDefaultValue: 20);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "RegisterMinLevel",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "integer",
|
||||||
|
oldDefaultValue: 200);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "MaxAttackingGuilds",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "integer",
|
||||||
|
oldDefaultValue: 3);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "CrownHoldTimeSeconds",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "integer",
|
||||||
|
oldDefaultValue: 30);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CastleSiegeNpcDefinition_MonsterDefinitionId",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeNpcDefinition",
|
||||||
|
column: "MonsterDefinitionId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_GameMapDefinition_CastleSiegeMapDe~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
column: "CastleSiegeMapDefinitionId",
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "GameMapDefinition",
|
||||||
|
principalColumn: "Id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_GameMapDefinition_LandOfTrialsMapD~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
column: "LandOfTrialsMapDefinitionId",
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "GameMapDefinition",
|
||||||
|
principalColumn: "Id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CastleSiegeConfiguration_ItemDefinition_RewardItemDefinitio~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeConfiguration",
|
||||||
|
column: "RewardItemDefinitionId",
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "ItemDefinition",
|
||||||
|
principalColumn: "Id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CastleSiegeNpcDefinition_MonsterDefinition_MonsterDefinitio~",
|
||||||
|
schema: "config",
|
||||||
|
table: "CastleSiegeNpcDefinition",
|
||||||
|
column: "MonsterDefinitionId",
|
||||||
|
principalSchema: "config",
|
||||||
|
principalTable: "MonsterDefinition",
|
||||||
|
principalColumn: "Id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -378,6 +378,329 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
|
|||||||
b.ToTable("Buff", "config");
|
b.ToTable("Buff", "config");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("AttackRespawnAreaId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CastleSiegeMapDefinitionId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int>("CrownHoldTimeSeconds")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasDefaultValue(30);
|
||||||
|
|
||||||
|
b.Property<Guid?>("DefenseRespawnAreaId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<int>("GateBuyPrice")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("GuildScoreCastleSiege")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("GuildScoreCastleSiegeMembers")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<Guid?>("LandOfTrialsMapDefinitionId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int>("MaxAttackingGuilds")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasDefaultValue(3);
|
||||||
|
|
||||||
|
b.Property<int>("ParticipantRewardMinSeconds")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("RegisterMinLevel")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasDefaultValue(200);
|
||||||
|
|
||||||
|
b.Property<int>("RegisterMinMembers")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasDefaultValue(20);
|
||||||
|
|
||||||
|
b.Property<Guid?>("RewardItemDefinitionId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int>("StatueBuyPrice")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AttackRespawnAreaId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("CastleSiegeMapDefinitionId");
|
||||||
|
|
||||||
|
b.HasIndex("DefenseRespawnAreaId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("LandOfTrialsMapDefinitionId");
|
||||||
|
|
||||||
|
b.HasIndex("RewardItemDefinitionId");
|
||||||
|
|
||||||
|
b.ToTable("CastleSiegeConfiguration", "config");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeData", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<bool>("IsHuntZoneEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<bool>("IsOccupied")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<Guid?>("OwnerGuildId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<byte>("TaxChaos")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<int>("TaxHunt")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<byte>("TaxStore")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<long>("TributeMoney")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("OwnerGuildId");
|
||||||
|
|
||||||
|
b.ToTable("CastleSiegeData", "data");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeGuildRegistration", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("GuildId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("GuildName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(8)
|
||||||
|
.HasColumnType("character varying(8)");
|
||||||
|
|
||||||
|
b.Property<int>("Marks")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("RegistrationOrder")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("CastleSiegeGuildRegistration", "data");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeNpcDefinition", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CastleSiegeConfigurationId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<byte>("DefaultSide")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<int>("Direction")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<byte>("InstanceId")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<bool>("IsPersistedToDatabase")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<Guid>("MonsterDefinitionId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<byte>("SpawnX")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<byte>("SpawnY")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CastleSiegeConfigurationId");
|
||||||
|
|
||||||
|
b.HasIndex("MonsterDefinitionId", "InstanceId");
|
||||||
|
|
||||||
|
b.ToTable("CastleSiegeNpcDefinition", "config");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeNpcState", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CastleSiegeDataId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int>("CurrentHp")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<byte>("DefenseLevel")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<byte>("InstanceId")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<byte>("LifeLevel")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<short>("MonsterNumber")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<byte>("RegenLevel")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CastleSiegeDataId");
|
||||||
|
|
||||||
|
b.HasIndex("MonsterNumber", "InstanceId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("CastleSiegeNpcState", "data");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeStateScheduleEntry", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CastleSiegeConfigurationId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int>("DayOfWeek")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<byte>("Hour")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<byte>("Minute")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<byte>("State")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CastleSiegeConfigurationId");
|
||||||
|
|
||||||
|
b.ToTable("CastleSiegeStateScheduleEntry", "config");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeUpgradeDefinition", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CastleSiegeConfigurationId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CastleSiegeConfigurationId1")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CastleSiegeConfigurationId2")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CastleSiegeConfigurationId3")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CastleSiegeConfigurationId4")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<byte>("Level")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<int>("RequiredJewelOfGuardianCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("RequiredZen")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Value")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CastleSiegeConfigurationId");
|
||||||
|
|
||||||
|
b.HasIndex("CastleSiegeConfigurationId1");
|
||||||
|
|
||||||
|
b.HasIndex("CastleSiegeConfigurationId2");
|
||||||
|
|
||||||
|
b.HasIndex("CastleSiegeConfigurationId3");
|
||||||
|
|
||||||
|
b.HasIndex("CastleSiegeConfigurationId4");
|
||||||
|
|
||||||
|
b.ToTable("CastleSiegeUpgradeDefinition", "config");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeZoneDefinition", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CastleSiegeConfigurationId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CastleSiegeConfigurationId1")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<byte>("X1")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<byte>("X2")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<byte>("Y1")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<byte>("Y2")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CastleSiegeConfigurationId");
|
||||||
|
|
||||||
|
b.HasIndex("CastleSiegeConfigurationId1");
|
||||||
|
|
||||||
|
b.ToTable("CastleSiegeZoneDefinition", "config");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b =>
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@@ -1054,6 +1377,9 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
|
|||||||
b.Property<bool>("AreaSkillHitsPlayer")
|
b.Property<bool>("AreaSkillHitsPlayer")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CastleSiegeConfigurationId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<string>("CharacterNameRegex")
|
b.Property<string>("CharacterNameRegex")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
@@ -1145,6 +1471,9 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CastleSiegeConfigurationId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
b.HasIndex("DuelConfigurationId")
|
b.HasIndex("DuelConfigurationId")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
@@ -3638,6 +3967,139 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
|
|||||||
b.Navigation("RawMagicEffectDefinition");
|
b.Navigation("RawMagicEffectDefinition");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeZoneDefinition", "RawAttackRespawnArea")
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", "AttackRespawnAreaId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawCastleSiegeMapDefinition")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CastleSiegeMapDefinitionId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeZoneDefinition", "RawDefenseRespawnArea")
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", "DefenseRespawnAreaId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawLandOfTrialsMapDefinition")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("LandOfTrialsMapDefinitionId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawRewardItemDefinition")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RewardItemDefinitionId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.Navigation("RawAttackRespawnArea");
|
||||||
|
|
||||||
|
b.Navigation("RawCastleSiegeMapDefinition");
|
||||||
|
|
||||||
|
b.Navigation("RawDefenseRespawnArea");
|
||||||
|
|
||||||
|
b.Navigation("RawLandOfTrialsMapDefinition");
|
||||||
|
|
||||||
|
b.Navigation("RawRewardItemDefinition");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeData", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OwnerGuildId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeGuildRegistration", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("GuildId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeNpcDefinition", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null)
|
||||||
|
.WithMany("RawNpcDefinitions")
|
||||||
|
.HasForeignKey("CastleSiegeConfigurationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawMonsterDefinition")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("MonsterDefinitionId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("RawMonsterDefinition");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeNpcState", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeData", null)
|
||||||
|
.WithMany("RawNpcStates")
|
||||||
|
.HasForeignKey("CastleSiegeDataId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeStateScheduleEntry", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null)
|
||||||
|
.WithMany("RawStateSchedule")
|
||||||
|
.HasForeignKey("CastleSiegeConfigurationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeUpgradeDefinition", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null)
|
||||||
|
.WithMany("RawGateDefenseUpgrades")
|
||||||
|
.HasForeignKey("CastleSiegeConfigurationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null)
|
||||||
|
.WithMany("RawGateLifeUpgrades")
|
||||||
|
.HasForeignKey("CastleSiegeConfigurationId1")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~1");
|
||||||
|
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null)
|
||||||
|
.WithMany("RawStatueDefenseUpgrades")
|
||||||
|
.HasForeignKey("CastleSiegeConfigurationId2")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~2");
|
||||||
|
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null)
|
||||||
|
.WithMany("RawStatueLifeUpgrades")
|
||||||
|
.HasForeignKey("CastleSiegeConfigurationId3")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~3");
|
||||||
|
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null)
|
||||||
|
.WithMany("RawStatueRegenUpgrades")
|
||||||
|
.HasForeignKey("CastleSiegeConfigurationId4")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~4");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeZoneDefinition", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null)
|
||||||
|
.WithMany("RawAttackMachineZones")
|
||||||
|
.HasForeignKey("CastleSiegeConfigurationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null)
|
||||||
|
.WithMany("RawDefenseMachineZones")
|
||||||
|
.HasForeignKey("CastleSiegeConfigurationId1")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.HasConstraintName("FK_CastleSiegeZoneDefinition_CastleSiegeConfiguration_CastleS~1");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b =>
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", null)
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", null)
|
||||||
@@ -3887,11 +4349,18 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", b =>
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", b =>
|
||||||
{
|
{
|
||||||
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", "RawCastleSiegeConfiguration")
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", "CastleSiegeConfigurationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelConfiguration", "RawDuelConfiguration")
|
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelConfiguration", "RawDuelConfiguration")
|
||||||
.WithOne()
|
.WithOne()
|
||||||
.HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", "DuelConfigurationId")
|
.HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", "DuelConfigurationId")
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.Navigation("RawCastleSiegeConfiguration");
|
||||||
|
|
||||||
b.Navigation("RawDuelConfiguration");
|
b.Navigation("RawDuelConfiguration");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -5023,6 +5492,32 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
|
|||||||
b.Navigation("RawEquippedItems");
|
b.Navigation("RawEquippedItems");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("RawAttackMachineZones");
|
||||||
|
|
||||||
|
b.Navigation("RawDefenseMachineZones");
|
||||||
|
|
||||||
|
b.Navigation("RawGateDefenseUpgrades");
|
||||||
|
|
||||||
|
b.Navigation("RawGateLifeUpgrades");
|
||||||
|
|
||||||
|
b.Navigation("RawNpcDefinitions");
|
||||||
|
|
||||||
|
b.Navigation("RawStateSchedule");
|
||||||
|
|
||||||
|
b.Navigation("RawStatueDefenseUpgrades");
|
||||||
|
|
||||||
|
b.Navigation("RawStatueLifeUpgrades");
|
||||||
|
|
||||||
|
b.Navigation("RawStatueRegenUpgrades");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeData", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("RawNpcStates");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b =>
|
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("JoinedDropItemGroups");
|
b.Navigation("JoinedDropItemGroups");
|
||||||
|
|||||||
@@ -0,0 +1,276 @@
|
|||||||
|
// <copyright file="CastleSiegeConfiguration.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.EntityFramework.Model;
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using MUnique.OpenMU.Persistence;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Entity Framework Core implementation of <see cref="MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Table(nameof(CastleSiegeConfiguration), Schema = SchemaNames.Configuration)]
|
||||||
|
internal partial class CastleSiegeConfiguration : MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration, IIdentifiable
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of this instance.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="StateSchedule" />.
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<CastleSiegeStateScheduleEntry> RawStateSchedule { get; } = new EntityFramework.List<CastleSiegeStateScheduleEntry>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry> StateSchedule => base.StateSchedule ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry, CastleSiegeStateScheduleEntry>(this.RawStateSchedule);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="NpcDefinitions" />.
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<CastleSiegeNpcDefinition> RawNpcDefinitions { get; } = new EntityFramework.List<CastleSiegeNpcDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition> NpcDefinitions => base.NpcDefinitions ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition, CastleSiegeNpcDefinition>(this.RawNpcDefinitions);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="GateDefenseUpgrades" />.
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<CastleSiegeUpgradeDefinition> RawGateDefenseUpgrades { get; } = new EntityFramework.List<CastleSiegeUpgradeDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition> GateDefenseUpgrades => base.GateDefenseUpgrades ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, CastleSiegeUpgradeDefinition>(this.RawGateDefenseUpgrades);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="GateLifeUpgrades" />.
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<CastleSiegeUpgradeDefinition> RawGateLifeUpgrades { get; } = new EntityFramework.List<CastleSiegeUpgradeDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition> GateLifeUpgrades => base.GateLifeUpgrades ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, CastleSiegeUpgradeDefinition>(this.RawGateLifeUpgrades);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="StatueDefenseUpgrades" />.
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<CastleSiegeUpgradeDefinition> RawStatueDefenseUpgrades { get; } = new EntityFramework.List<CastleSiegeUpgradeDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition> StatueDefenseUpgrades => base.StatueDefenseUpgrades ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, CastleSiegeUpgradeDefinition>(this.RawStatueDefenseUpgrades);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="StatueLifeUpgrades" />.
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<CastleSiegeUpgradeDefinition> RawStatueLifeUpgrades { get; } = new EntityFramework.List<CastleSiegeUpgradeDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition> StatueLifeUpgrades => base.StatueLifeUpgrades ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, CastleSiegeUpgradeDefinition>(this.RawStatueLifeUpgrades);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="StatueRegenUpgrades" />.
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<CastleSiegeUpgradeDefinition> RawStatueRegenUpgrades { get; } = new EntityFramework.List<CastleSiegeUpgradeDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition> StatueRegenUpgrades => base.StatueRegenUpgrades ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, CastleSiegeUpgradeDefinition>(this.RawStatueRegenUpgrades);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="AttackMachineZones" />.
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<CastleSiegeZoneDefinition> RawAttackMachineZones { get; } = new EntityFramework.List<CastleSiegeZoneDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition> AttackMachineZones => base.AttackMachineZones ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition, CastleSiegeZoneDefinition>(this.RawAttackMachineZones);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="DefenseMachineZones" />.
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<CastleSiegeZoneDefinition> RawDefenseMachineZones { get; } = new EntityFramework.List<CastleSiegeZoneDefinition>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition> DefenseMachineZones => base.DefenseMachineZones ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition, CastleSiegeZoneDefinition>(this.RawDefenseMachineZones);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of <see cref="CastleSiegeMapDefinition"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? CastleSiegeMapDefinitionId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw object of <see cref="CastleSiegeMapDefinition" />.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(CastleSiegeMapDefinitionId))]
|
||||||
|
public GameMapDefinition RawCastleSiegeMapDefinition
|
||||||
|
{
|
||||||
|
get => base.CastleSiegeMapDefinition as GameMapDefinition;
|
||||||
|
set => base.CastleSiegeMapDefinition = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.GameMapDefinition CastleSiegeMapDefinition
|
||||||
|
{
|
||||||
|
get => base.CastleSiegeMapDefinition;set
|
||||||
|
{
|
||||||
|
base.CastleSiegeMapDefinition = value;
|
||||||
|
this.CastleSiegeMapDefinitionId = this.RawCastleSiegeMapDefinition?.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of <see cref="LandOfTrialsMapDefinition"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? LandOfTrialsMapDefinitionId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw object of <see cref="LandOfTrialsMapDefinition" />.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(LandOfTrialsMapDefinitionId))]
|
||||||
|
public GameMapDefinition RawLandOfTrialsMapDefinition
|
||||||
|
{
|
||||||
|
get => base.LandOfTrialsMapDefinition as GameMapDefinition;
|
||||||
|
set => base.LandOfTrialsMapDefinition = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.GameMapDefinition LandOfTrialsMapDefinition
|
||||||
|
{
|
||||||
|
get => base.LandOfTrialsMapDefinition;set
|
||||||
|
{
|
||||||
|
base.LandOfTrialsMapDefinition = value;
|
||||||
|
this.LandOfTrialsMapDefinitionId = this.RawLandOfTrialsMapDefinition?.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of <see cref="RewardItemDefinition"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? RewardItemDefinitionId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw object of <see cref="RewardItemDefinition" />.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(RewardItemDefinitionId))]
|
||||||
|
public ItemDefinition RawRewardItemDefinition
|
||||||
|
{
|
||||||
|
get => base.RewardItemDefinition as ItemDefinition;
|
||||||
|
set => base.RewardItemDefinition = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.Items.ItemDefinition RewardItemDefinition
|
||||||
|
{
|
||||||
|
get => base.RewardItemDefinition;set
|
||||||
|
{
|
||||||
|
base.RewardItemDefinition = value;
|
||||||
|
this.RewardItemDefinitionId = this.RawRewardItemDefinition?.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of <see cref="DefenseRespawnArea"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? DefenseRespawnAreaId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw object of <see cref="DefenseRespawnArea" />.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(DefenseRespawnAreaId))]
|
||||||
|
public CastleSiegeZoneDefinition RawDefenseRespawnArea
|
||||||
|
{
|
||||||
|
get => base.DefenseRespawnArea as CastleSiegeZoneDefinition;
|
||||||
|
set => base.DefenseRespawnArea = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition DefenseRespawnArea
|
||||||
|
{
|
||||||
|
get => base.DefenseRespawnArea;set
|
||||||
|
{
|
||||||
|
base.DefenseRespawnArea = value;
|
||||||
|
this.DefenseRespawnAreaId = this.RawDefenseRespawnArea?.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of <see cref="AttackRespawnArea"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? AttackRespawnAreaId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw object of <see cref="AttackRespawnArea" />.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(AttackRespawnAreaId))]
|
||||||
|
public CastleSiegeZoneDefinition RawAttackRespawnArea
|
||||||
|
{
|
||||||
|
get => base.AttackRespawnArea as CastleSiegeZoneDefinition;
|
||||||
|
set => base.AttackRespawnArea = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition AttackRespawnArea
|
||||||
|
{
|
||||||
|
get => base.AttackRespawnArea;set
|
||||||
|
{
|
||||||
|
base.AttackRespawnArea = value;
|
||||||
|
this.AttackRespawnAreaId = this.RawAttackRespawnArea?.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
var clone = new CastleSiegeConfiguration();
|
||||||
|
clone.AssignValuesOf(this, gameConfiguration);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
base.AssignValuesOf(other, gameConfiguration);
|
||||||
|
this.Id = other.GetId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// <copyright file="CastleSiegeData.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.EntityFramework.Model;
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using MUnique.OpenMU.Persistence;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Entity Framework Core implementation of <see cref="MUnique.OpenMU.DataModel.Entities.CastleSiegeData"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Table(nameof(CastleSiegeData), Schema = SchemaNames.AccountData)]
|
||||||
|
internal partial class CastleSiegeData : MUnique.OpenMU.DataModel.Entities.CastleSiegeData, IIdentifiable
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw collection of <see cref="NpcStates" />.
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<CastleSiegeNpcState> RawNpcStates { get; } = new EntityFramework.List<CastleSiegeNpcState>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override ICollection<MUnique.OpenMU.DataModel.Entities.CastleSiegeNpcState> NpcStates => base.NpcStates ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Entities.CastleSiegeNpcState, CastleSiegeNpcState>(this.RawNpcStates);
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// <copyright file="CastleSiegeGuildRegistration.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.EntityFramework.Model;
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using MUnique.OpenMU.Persistence;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Entity Framework Core implementation of <see cref="MUnique.OpenMU.DataModel.Entities.CastleSiegeGuildRegistration"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Table(nameof(CastleSiegeGuildRegistration), Schema = SchemaNames.AccountData)]
|
||||||
|
internal partial class CastleSiegeGuildRegistration : MUnique.OpenMU.DataModel.Entities.CastleSiegeGuildRegistration, IIdentifiable
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
// <copyright file="CastleSiegeNpcDefinition.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.EntityFramework.Model;
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using MUnique.OpenMU.Persistence;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Entity Framework Core implementation of <see cref="MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Table(nameof(CastleSiegeNpcDefinition), Schema = SchemaNames.Configuration)]
|
||||||
|
internal partial class CastleSiegeNpcDefinition : MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition, IIdentifiable
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of this instance.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of <see cref="MonsterDefinition"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? MonsterDefinitionId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw object of <see cref="MonsterDefinition" />.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(MonsterDefinitionId))]
|
||||||
|
public MonsterDefinition RawMonsterDefinition
|
||||||
|
{
|
||||||
|
get => base.MonsterDefinition as MonsterDefinition;
|
||||||
|
set => base.MonsterDefinition = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.MonsterDefinition MonsterDefinition
|
||||||
|
{
|
||||||
|
get => base.MonsterDefinition;set
|
||||||
|
{
|
||||||
|
base.MonsterDefinition = value;
|
||||||
|
this.MonsterDefinitionId = this.RawMonsterDefinition?.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
var clone = new CastleSiegeNpcDefinition();
|
||||||
|
clone.AssignValuesOf(this, gameConfiguration);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
base.AssignValuesOf(other, gameConfiguration);
|
||||||
|
this.Id = other.GetId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// <copyright file="CastleSiegeNpcState.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.EntityFramework.Model;
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using MUnique.OpenMU.Persistence;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Entity Framework Core implementation of <see cref="MUnique.OpenMU.DataModel.Entities.CastleSiegeNpcState"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Table(nameof(CastleSiegeNpcState), Schema = SchemaNames.AccountData)]
|
||||||
|
internal partial class CastleSiegeNpcState : MUnique.OpenMU.DataModel.Entities.CastleSiegeNpcState, IIdentifiable
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// <copyright file="CastleSiegeStateScheduleEntry.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.EntityFramework.Model;
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using MUnique.OpenMU.Persistence;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Entity Framework Core implementation of <see cref="MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Table(nameof(CastleSiegeStateScheduleEntry), Schema = SchemaNames.Configuration)]
|
||||||
|
internal partial class CastleSiegeStateScheduleEntry : MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry, IIdentifiable
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of this instance.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
var clone = new CastleSiegeStateScheduleEntry();
|
||||||
|
clone.AssignValuesOf(this, gameConfiguration);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
base.AssignValuesOf(other, gameConfiguration);
|
||||||
|
this.Id = other.GetId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// <copyright file="CastleSiegeUpgradeDefinition.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.EntityFramework.Model;
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using MUnique.OpenMU.Persistence;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Entity Framework Core implementation of <see cref="MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Table(nameof(CastleSiegeUpgradeDefinition), Schema = SchemaNames.Configuration)]
|
||||||
|
internal partial class CastleSiegeUpgradeDefinition : MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, IIdentifiable
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of this instance.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
var clone = new CastleSiegeUpgradeDefinition();
|
||||||
|
clone.AssignValuesOf(this, gameConfiguration);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
base.AssignValuesOf(other, gameConfiguration);
|
||||||
|
this.Id = other.GetId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// <copyright file="CastleSiegeZoneDefinition.Generated.cs" company="MUnique">
|
||||||
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This source code was auto-generated by a roslyn code generator.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ReSharper disable All
|
||||||
|
|
||||||
|
namespace MUnique.OpenMU.Persistence.EntityFramework.Model;
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using MUnique.OpenMU.Persistence;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Entity Framework Core implementation of <see cref="MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Table(nameof(CastleSiegeZoneDefinition), Schema = SchemaNames.Configuration)]
|
||||||
|
internal partial class CastleSiegeZoneDefinition : MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition, IIdentifiable
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of this instance.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
var clone = new CastleSiegeZoneDefinition();
|
||||||
|
clone.AssignValuesOf(this, gameConfiguration);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
base.AssignValuesOf(other, gameConfiguration);
|
||||||
|
this.Id = other.GetId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var baseObject = obj as IIdentifiable;
|
||||||
|
if (baseObject != null)
|
||||||
|
{
|
||||||
|
return baseObject.Id == this.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -27,6 +27,9 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext
|
|||||||
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Statistics.MiniGameRankingEntry>();
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Statistics.MiniGameRankingEntry>();
|
||||||
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Entities.Account>();
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Entities.Account>();
|
||||||
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Entities.AppearanceData>();
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Entities.AppearanceData>();
|
||||||
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Entities.CastleSiegeData>();
|
||||||
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Entities.CastleSiegeGuildRegistration>();
|
||||||
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Entities.CastleSiegeNpcState>();
|
||||||
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Entities.Character>();
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Entities.Character>();
|
||||||
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Entities.CharacterQuestState>();
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Entities.CharacterQuestState>();
|
||||||
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Entities.Guild>();
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Entities.Guild>();
|
||||||
@@ -41,6 +44,11 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext
|
|||||||
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.AreaSkillSettings>();
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.AreaSkillSettings>();
|
||||||
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.BattleZoneDefinition>();
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.BattleZoneDefinition>();
|
||||||
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.Buff>();
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.Buff>();
|
||||||
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration>();
|
||||||
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition>();
|
||||||
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry>();
|
||||||
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition>();
|
||||||
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition>();
|
||||||
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.CharacterClass>();
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.CharacterClass>();
|
||||||
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.ChatServerDefinition>();
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.ChatServerDefinition>();
|
||||||
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.ChatServerEndpoint>();
|
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.ChatServerEndpoint>();
|
||||||
@@ -118,6 +126,7 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext
|
|||||||
modelBuilder.Entity<Account>().HasMany(entity => entity.RawCharacters).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<Account>().HasMany(entity => entity.RawCharacters).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<Account>().HasMany(entity => entity.RawAttributes).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<Account>().HasMany(entity => entity.RawAttributes).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<AppearanceData>().HasMany(entity => entity.RawEquippedItems).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<AppearanceData>().HasMany(entity => entity.RawEquippedItems).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<CastleSiegeData>().HasMany(entity => entity.RawNpcStates).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<Character>().HasMany(entity => entity.RawAttributes).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<Character>().HasMany(entity => entity.RawAttributes).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<Character>().HasMany(entity => entity.RawLetters).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<Character>().HasMany(entity => entity.RawLetters).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<Character>().HasMany(entity => entity.RawLearnedSkills).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<Character>().HasMany(entity => entity.RawLearnedSkills).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
@@ -131,6 +140,17 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext
|
|||||||
modelBuilder.Entity<BattleZoneDefinition>().HasOne(entity => entity.RawLeftGoal).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<BattleZoneDefinition>().HasOne(entity => entity.RawLeftGoal).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<BattleZoneDefinition>().HasOne(entity => entity.RawRightGoal).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<BattleZoneDefinition>().HasOne(entity => entity.RawRightGoal).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<Buff>().HasOne(entity => entity.RawMagicEffectDefinition).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<Buff>().HasOne(entity => entity.RawMagicEffectDefinition).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<CastleSiegeConfiguration>().HasMany(entity => entity.RawStateSchedule).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<CastleSiegeConfiguration>().HasMany(entity => entity.RawNpcDefinitions).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<CastleSiegeConfiguration>().HasMany(entity => entity.RawGateDefenseUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<CastleSiegeConfiguration>().HasMany(entity => entity.RawGateLifeUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<CastleSiegeConfiguration>().HasMany(entity => entity.RawStatueDefenseUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<CastleSiegeConfiguration>().HasMany(entity => entity.RawStatueLifeUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<CastleSiegeConfiguration>().HasMany(entity => entity.RawStatueRegenUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<CastleSiegeConfiguration>().HasMany(entity => entity.RawAttackMachineZones).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<CastleSiegeConfiguration>().HasMany(entity => entity.RawDefenseMachineZones).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<CastleSiegeConfiguration>().HasOne(entity => entity.RawDefenseRespawnArea).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<CastleSiegeConfiguration>().HasOne(entity => entity.RawAttackRespawnArea).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<CharacterClass>().HasMany(entity => entity.RawStatAttributes).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<CharacterClass>().HasMany(entity => entity.RawStatAttributes).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<CharacterClass>().HasMany(entity => entity.RawAttributeCombinations).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<CharacterClass>().HasMany(entity => entity.RawAttributeCombinations).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<CharacterClass>().HasMany(entity => entity.RawBaseAttributeValues).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<CharacterClass>().HasMany(entity => entity.RawBaseAttributeValues).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
@@ -159,6 +179,7 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext
|
|||||||
modelBuilder.Entity<GameConfiguration>().HasMany(entity => entity.RawGlobalBaseAttributeValues).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<GameConfiguration>().HasMany(entity => entity.RawGlobalBaseAttributeValues).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<GameConfiguration>().HasMany(entity => entity.RawPlugInConfigurations).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<GameConfiguration>().HasMany(entity => entity.RawPlugInConfigurations).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<GameConfiguration>().HasMany(entity => entity.RawMiniGameDefinitions).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<GameConfiguration>().HasMany(entity => entity.RawMiniGameDefinitions).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<GameConfiguration>().HasOne(entity => entity.RawCastleSiegeConfiguration).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<GameMapDefinition>().HasMany(entity => entity.RawMonsterSpawns).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<GameMapDefinition>().HasMany(entity => entity.RawMonsterSpawns).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<GameMapDefinition>().HasMany(entity => entity.RawEnterGates).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<GameMapDefinition>().HasMany(entity => entity.RawEnterGates).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<GameMapDefinition>().HasOne(entity => entity.RawBattleZone).WithOne().OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<GameMapDefinition>().HasOne(entity => entity.RawBattleZone).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|||||||
@@ -243,6 +243,32 @@ internal partial class GameConfiguration : MUnique.OpenMU.DataModel.Configuratio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of <see cref="CastleSiegeConfiguration"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? CastleSiegeConfigurationId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw object of <see cref="CastleSiegeConfiguration" />.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(CastleSiegeConfigurationId))]
|
||||||
|
public CastleSiegeConfiguration RawCastleSiegeConfiguration
|
||||||
|
{
|
||||||
|
get => base.CastleSiegeConfiguration as CastleSiegeConfiguration;
|
||||||
|
set => base.CastleSiegeConfiguration = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[NotMapped]
|
||||||
|
public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration CastleSiegeConfiguration
|
||||||
|
{
|
||||||
|
get => base.CastleSiegeConfiguration;set
|
||||||
|
{
|
||||||
|
base.CastleSiegeConfiguration = value;
|
||||||
|
this.CastleSiegeConfigurationId = this.RawCastleSiegeConfiguration?.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override MUnique.OpenMU.DataModel.Configuration.GameConfiguration Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
public override MUnique.OpenMU.DataModel.Configuration.GameConfiguration Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,6 +44,15 @@ public static class MapsterConfigurator
|
|||||||
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Entities.AppearanceData, MUnique.OpenMU.DataModel.Entities.AppearanceData>()
|
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Entities.AppearanceData, MUnique.OpenMU.DataModel.Entities.AppearanceData>()
|
||||||
.Include<AppearanceData, BasicModel.AppearanceData>();
|
.Include<AppearanceData, BasicModel.AppearanceData>();
|
||||||
|
|
||||||
|
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Entities.CastleSiegeData, MUnique.OpenMU.DataModel.Entities.CastleSiegeData>()
|
||||||
|
.Include<CastleSiegeData, BasicModel.CastleSiegeData>();
|
||||||
|
|
||||||
|
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Entities.CastleSiegeGuildRegistration, MUnique.OpenMU.DataModel.Entities.CastleSiegeGuildRegistration>()
|
||||||
|
.Include<CastleSiegeGuildRegistration, BasicModel.CastleSiegeGuildRegistration>();
|
||||||
|
|
||||||
|
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Entities.CastleSiegeNpcState, MUnique.OpenMU.DataModel.Entities.CastleSiegeNpcState>()
|
||||||
|
.Include<CastleSiegeNpcState, BasicModel.CastleSiegeNpcState>();
|
||||||
|
|
||||||
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Entities.Character, MUnique.OpenMU.DataModel.Entities.Character>()
|
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Entities.Character, MUnique.OpenMU.DataModel.Entities.Character>()
|
||||||
.Include<Character, BasicModel.Character>();
|
.Include<Character, BasicModel.Character>();
|
||||||
|
|
||||||
@@ -86,6 +95,21 @@ public static class MapsterConfigurator
|
|||||||
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Configuration.Buff, MUnique.OpenMU.DataModel.Configuration.Buff>()
|
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Configuration.Buff, MUnique.OpenMU.DataModel.Configuration.Buff>()
|
||||||
.Include<Buff, BasicModel.Buff>();
|
.Include<Buff, BasicModel.Buff>();
|
||||||
|
|
||||||
|
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration, MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration>()
|
||||||
|
.Include<CastleSiegeConfiguration, BasicModel.CastleSiegeConfiguration>();
|
||||||
|
|
||||||
|
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition, MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition>()
|
||||||
|
.Include<CastleSiegeNpcDefinition, BasicModel.CastleSiegeNpcDefinition>();
|
||||||
|
|
||||||
|
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry, MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry>()
|
||||||
|
.Include<CastleSiegeStateScheduleEntry, BasicModel.CastleSiegeStateScheduleEntry>();
|
||||||
|
|
||||||
|
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition>()
|
||||||
|
.Include<CastleSiegeUpgradeDefinition, BasicModel.CastleSiegeUpgradeDefinition>();
|
||||||
|
|
||||||
|
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition, MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition>()
|
||||||
|
.Include<CastleSiegeZoneDefinition, BasicModel.CastleSiegeZoneDefinition>();
|
||||||
|
|
||||||
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Configuration.CharacterClass, MUnique.OpenMU.DataModel.Configuration.CharacterClass>()
|
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Configuration.CharacterClass, MUnique.OpenMU.DataModel.Configuration.CharacterClass>()
|
||||||
.Include<CharacterClass, BasicModel.CharacterClass>();
|
.Include<CharacterClass, BasicModel.CharacterClass>();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
// <copyright file="AddCastleSiegeDataUpdatePlugIn.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.DataModel.Entities;
|
||||||
|
using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Events;
|
||||||
|
using MUnique.OpenMU.PlugIns;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the Castle Siege configuration and persistent state to an existing Season 6 database.
|
||||||
|
/// </summary>
|
||||||
|
[PlugIn]
|
||||||
|
[Display(Name = PlugInName, Description = PlugInDescription)]
|
||||||
|
[Guid("CD201E33-37C9-4C85-95CC-16042B28E974")]
|
||||||
|
public class AddCastleSiegeDataUpdatePlugIn : UpdatePlugInBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The plug-in name.
|
||||||
|
/// </summary>
|
||||||
|
internal const string PlugInName = "Add Castle Siege data";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The plug-in description.
|
||||||
|
/// </summary>
|
||||||
|
internal const string PlugInDescription = "This update adds the Castle Siege configuration and persistent state.";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Name => PlugInName;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Description => PlugInDescription;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override UpdateVersion Version => UpdateVersion.AddCastleSiegeData;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool IsMandatory => true;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override DateTime CreatedAt => new(2026, 07, 28, 20, 0, 0, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
|
||||||
|
{
|
||||||
|
var initializer = new CastleSiegeInitializer(context, gameConfiguration);
|
||||||
|
var configuration = initializer.InitializeConfiguration();
|
||||||
|
if (!(await context.GetAsync<CastleSiegeData>().ConfigureAwait(false)).Any())
|
||||||
|
{
|
||||||
|
initializer.InitializeData(configuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -529,4 +529,14 @@ public enum UpdateVersion
|
|||||||
/// The version of the <see cref="RepairImportedMapWarpsSeason6"/>.
|
/// The version of the <see cref="RepairImportedMapWarpsSeason6"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RepairImportedMapWarpsSeason6 = 104,
|
RepairImportedMapWarpsSeason6 = 104,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The version of the <see cref="AddCastleSiegeDataUpdatePlugIn"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Upstream numbers this update 100. AdaMu already uses 95-104 for its own updates, so it is
|
||||||
|
/// renumbered to 105 here. Never reuse a number that has already shipped: the applied-update
|
||||||
|
/// bookkeeping is keyed on this value, so a collision would skip or re-run updates on live databases.
|
||||||
|
/// </remarks>
|
||||||
|
AddCastleSiegeData = 105,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,228 @@
|
|||||||
|
// <copyright file="CastleSiegeInitializer.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.Events;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
using MUnique.OpenMU.DataModel.Entities;
|
||||||
|
using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the Castle Siege configuration and persistent state.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class CastleSiegeInitializer : InitializerBase
|
||||||
|
{
|
||||||
|
private const short GateMonsterNumber = 277;
|
||||||
|
private const short StatueMonsterNumber = 283;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CastleSiegeInitializer"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context.</param>
|
||||||
|
/// <param name="gameConfiguration">The game configuration.</param>
|
||||||
|
public CastleSiegeInitializer(IContext context, GameConfiguration gameConfiguration)
|
||||||
|
: base(context, gameConfiguration)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
var configuration = this.InitializeConfiguration();
|
||||||
|
this.InitializeData(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the Castle Siege configuration, if it does not exist yet.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The Castle Siege configuration.</returns>
|
||||||
|
internal CastleSiegeConfiguration InitializeConfiguration()
|
||||||
|
{
|
||||||
|
if (this.GameConfiguration.CastleSiegeConfiguration is { } existingConfiguration)
|
||||||
|
{
|
||||||
|
return existingConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
var configuration = this.Context.CreateNew<CastleSiegeConfiguration>();
|
||||||
|
configuration.Enabled = true;
|
||||||
|
configuration.CrownHoldTimeSeconds = 60; // The client's crown registration panel counts down from 60s.
|
||||||
|
configuration.RegisterMinLevel = 200;
|
||||||
|
configuration.RegisterMinMembers = 20;
|
||||||
|
configuration.ParticipantRewardMinSeconds = 60;
|
||||||
|
configuration.MaxAttackingGuilds = 3;
|
||||||
|
configuration.GuildScoreCastleSiege = 0;
|
||||||
|
configuration.GuildScoreCastleSiegeMembers = 0;
|
||||||
|
configuration.GateBuyPrice = 9_500_000;
|
||||||
|
configuration.StatueBuyPrice = 4_500_000;
|
||||||
|
configuration.CastleSiegeMapDefinition = this.GameConfiguration.Maps.Single(map => map.Number == ValleyOfLoren.Number);
|
||||||
|
configuration.LandOfTrialsMapDefinition = this.GameConfiguration.Maps.Single(map => map.Number == LandOfTrials.Number);
|
||||||
|
|
||||||
|
// AdaMu deliberately leaves StateSchedule empty. Upstream drives the cycle from a fixed weekly
|
||||||
|
// schedule; AdaMu drives it from CastleSiegeEventPlugIn (manual /cs commands, AdminPanel durations
|
||||||
|
// and the optional auto-open times in the plugin configuration). Nothing reads StateSchedule here.
|
||||||
|
this.InitializeNpcDefinitions(configuration);
|
||||||
|
this.InitializeUpgradeDefinitions(configuration);
|
||||||
|
this.InitializeMachineZones(configuration);
|
||||||
|
configuration.DefenseRespawnArea = this.CreateZone(74, 144, 115, 154);
|
||||||
|
configuration.AttackRespawnArea = this.CreateZone(35, 11, 144, 48);
|
||||||
|
|
||||||
|
this.GameConfiguration.CastleSiegeConfiguration = configuration;
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the persistent Castle Siege state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="configuration">The Castle Siege configuration.</param>
|
||||||
|
/// <returns>The persistent Castle Siege state.</returns>
|
||||||
|
internal CastleSiegeData InitializeData(CastleSiegeConfiguration configuration)
|
||||||
|
{
|
||||||
|
var data = this.Context.CreateNew<CastleSiegeData>();
|
||||||
|
data.OwnerGuildId = null;
|
||||||
|
data.IsOccupied = false;
|
||||||
|
data.TaxChaos = 0;
|
||||||
|
data.TaxStore = 0;
|
||||||
|
data.TaxHunt = 0;
|
||||||
|
data.IsHuntZoneEnabled = false;
|
||||||
|
data.TributeMoney = 0;
|
||||||
|
|
||||||
|
var gateHitPoints = configuration.GateLifeUpgrades.Single(upgrade => upgrade.Level == 0).Value;
|
||||||
|
var statueHitPoints = configuration.StatueLifeUpgrades.Single(upgrade => upgrade.Level == 0).Value;
|
||||||
|
foreach (var npcDefinition in configuration.NpcDefinitions.Where(definition => definition.IsPersistedToDatabase))
|
||||||
|
{
|
||||||
|
var npcState = this.Context.CreateNew<CastleSiegeNpcState>();
|
||||||
|
npcState.MonsterNumber = npcDefinition.MonsterDefinition!.Number;
|
||||||
|
npcState.InstanceId = npcDefinition.InstanceId;
|
||||||
|
npcState.DefenseLevel = 0;
|
||||||
|
npcState.RegenLevel = 0;
|
||||||
|
npcState.LifeLevel = 0;
|
||||||
|
npcState.CurrentHp = npcState.MonsterNumber switch
|
||||||
|
{
|
||||||
|
GateMonsterNumber => gateHitPoints,
|
||||||
|
StatueMonsterNumber => statueHitPoints,
|
||||||
|
_ => throw new InvalidOperationException($"The persisted Castle Siege NPC monster number {npcState.MonsterNumber} is unsupported."),
|
||||||
|
};
|
||||||
|
data.NpcStates.Add(npcState);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeNpcDefinitions(CastleSiegeConfiguration configuration)
|
||||||
|
{
|
||||||
|
this.AddNpc(configuration, 216, 1, false, CastleSiegeJoinSide.Attack1, 176, 212, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, 217, 1, false, CastleSiegeJoinSide.Attack1, 167, 194, Direction.NorthWest);
|
||||||
|
this.AddNpc(configuration, 218, 1, false, CastleSiegeJoinSide.Attack1, 184, 195, Direction.NorthWest);
|
||||||
|
|
||||||
|
this.AddNpc(configuration, 219, 1, false, CastleSiegeJoinSide.Defense, 93, 208, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, 219, 2, false, CastleSiegeJoinSide.Defense, 81, 165, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, 219, 3, false, CastleSiegeJoinSide.Defense, 107, 165, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, 219, 4, false, CastleSiegeJoinSide.Defense, 67, 118, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, 219, 5, false, CastleSiegeJoinSide.Defense, 93, 118, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, 219, 6, false, CastleSiegeJoinSide.Defense, 119, 118, Direction.SouthWest);
|
||||||
|
|
||||||
|
this.AddNpc(configuration, 221, 1, false, CastleSiegeJoinSide.Attack1, 63, 19, Direction.NorthEast);
|
||||||
|
this.AddNpc(configuration, 221, 2, false, CastleSiegeJoinSide.Attack1, 119, 19, Direction.NorthEast);
|
||||||
|
this.AddNpc(configuration, 222, 1, false, CastleSiegeJoinSide.Defense, 80, 188, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, 222, 2, false, CastleSiegeJoinSide.Defense, 105, 188, Direction.SouthWest);
|
||||||
|
|
||||||
|
this.AddNpc(configuration, GateMonsterNumber, 1, true, CastleSiegeJoinSide.Defense, 93, 204, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, GateMonsterNumber, 2, true, CastleSiegeJoinSide.Defense, 81, 161, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, GateMonsterNumber, 3, true, CastleSiegeJoinSide.Defense, 107, 161, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, GateMonsterNumber, 4, true, CastleSiegeJoinSide.Defense, 67, 114, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, GateMonsterNumber, 5, true, CastleSiegeJoinSide.Defense, 93, 114, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, GateMonsterNumber, 6, true, CastleSiegeJoinSide.Defense, 119, 114, Direction.SouthWest);
|
||||||
|
|
||||||
|
this.AddNpc(configuration, StatueMonsterNumber, 1, true, CastleSiegeJoinSide.Defense, 94, 227, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, StatueMonsterNumber, 2, true, CastleSiegeJoinSide.Defense, 94, 182, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, StatueMonsterNumber, 3, true, CastleSiegeJoinSide.Defense, 82, 130, Direction.SouthWest);
|
||||||
|
this.AddNpc(configuration, StatueMonsterNumber, 4, true, CastleSiegeJoinSide.Defense, 107, 130, Direction.SouthWest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeUpgradeDefinitions(CastleSiegeConfiguration configuration)
|
||||||
|
{
|
||||||
|
this.AddUpgrade(configuration.GateDefenseUpgrades, 0, 0, 0, 100);
|
||||||
|
this.AddUpgrade(configuration.GateDefenseUpgrades, 1, 2, 3_000_000, 180);
|
||||||
|
this.AddUpgrade(configuration.GateDefenseUpgrades, 2, 3, 3_000_000, 300);
|
||||||
|
this.AddUpgrade(configuration.GateDefenseUpgrades, 3, 4, 3_000_000, 520);
|
||||||
|
|
||||||
|
this.AddUpgrade(configuration.StatueDefenseUpgrades, 0, 0, 0, 80);
|
||||||
|
this.AddUpgrade(configuration.StatueDefenseUpgrades, 1, 3, 3_000_000, 180);
|
||||||
|
this.AddUpgrade(configuration.StatueDefenseUpgrades, 2, 5, 3_000_000, 340);
|
||||||
|
this.AddUpgrade(configuration.StatueDefenseUpgrades, 3, 7, 3_000_000, 550);
|
||||||
|
|
||||||
|
this.AddUpgrade(configuration.GateLifeUpgrades, 0, 0, 0, 1_900_000);
|
||||||
|
this.AddUpgrade(configuration.GateLifeUpgrades, 1, 2, 1_000_000, 2_500_000);
|
||||||
|
this.AddUpgrade(configuration.GateLifeUpgrades, 2, 3, 1_000_000, 3_500_000);
|
||||||
|
this.AddUpgrade(configuration.GateLifeUpgrades, 3, 4, 1_000_000, 5_200_000);
|
||||||
|
|
||||||
|
this.AddUpgrade(configuration.StatueLifeUpgrades, 0, 0, 0, 1_500_000);
|
||||||
|
this.AddUpgrade(configuration.StatueLifeUpgrades, 1, 3, 1_000_000, 2_200_000);
|
||||||
|
this.AddUpgrade(configuration.StatueLifeUpgrades, 2, 5, 1_000_000, 3_400_000);
|
||||||
|
this.AddUpgrade(configuration.StatueLifeUpgrades, 3, 7, 1_000_000, 5_000_000);
|
||||||
|
|
||||||
|
this.AddUpgrade(configuration.StatueRegenUpgrades, 0, 0, 0, 0);
|
||||||
|
this.AddUpgrade(configuration.StatueRegenUpgrades, 1, 3, 5_000_000, 1);
|
||||||
|
this.AddUpgrade(configuration.StatueRegenUpgrades, 2, 5, 5_000_000, 2);
|
||||||
|
this.AddUpgrade(configuration.StatueRegenUpgrades, 3, 7, 5_000_000, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeMachineZones(CastleSiegeConfiguration configuration)
|
||||||
|
{
|
||||||
|
configuration.AttackMachineZones.Add(this.CreateZone(62, 103, 72, 112));
|
||||||
|
configuration.AttackMachineZones.Add(this.CreateZone(88, 104, 124, 111));
|
||||||
|
configuration.AttackMachineZones.Add(this.CreateZone(116, 105, 124, 112));
|
||||||
|
configuration.AttackMachineZones.Add(this.CreateZone(73, 86, 105, 103));
|
||||||
|
|
||||||
|
configuration.DefenseMachineZones.Add(this.CreateZone(61, 88, 93, 108));
|
||||||
|
configuration.DefenseMachineZones.Add(this.CreateZone(92, 89, 127, 111));
|
||||||
|
configuration.DefenseMachineZones.Add(this.CreateZone(84, 52, 102, 66));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddNpc(
|
||||||
|
CastleSiegeConfiguration configuration,
|
||||||
|
short monsterNumber,
|
||||||
|
byte instanceId,
|
||||||
|
bool isPersisted,
|
||||||
|
CastleSiegeJoinSide defaultSide,
|
||||||
|
byte spawnX,
|
||||||
|
byte spawnY,
|
||||||
|
Direction direction)
|
||||||
|
{
|
||||||
|
var definition = this.Context.CreateNew<CastleSiegeNpcDefinition>();
|
||||||
|
definition.MonsterDefinition = this.GameConfiguration.Monsters.Single(monster => monster.Number == monsterNumber);
|
||||||
|
definition.InstanceId = instanceId;
|
||||||
|
definition.IsPersistedToDatabase = isPersisted;
|
||||||
|
definition.DefaultSide = defaultSide;
|
||||||
|
definition.SpawnX = spawnX;
|
||||||
|
definition.SpawnY = spawnY;
|
||||||
|
definition.Direction = direction;
|
||||||
|
configuration.NpcDefinitions.Add(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddUpgrade(
|
||||||
|
ICollection<CastleSiegeUpgradeDefinition> target,
|
||||||
|
byte level,
|
||||||
|
int jewelCount,
|
||||||
|
int zen,
|
||||||
|
int value)
|
||||||
|
{
|
||||||
|
var upgrade = this.Context.CreateNew<CastleSiegeUpgradeDefinition>();
|
||||||
|
upgrade.Level = level;
|
||||||
|
upgrade.RequiredJewelOfGuardianCount = jewelCount;
|
||||||
|
upgrade.RequiredZen = zen;
|
||||||
|
upgrade.Value = value;
|
||||||
|
target.Add(upgrade);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CastleSiegeZoneDefinition CreateZone(byte x1, byte y1, byte x2, byte y2)
|
||||||
|
{
|
||||||
|
var zone = this.Context.CreateNew<CastleSiegeZoneDefinition>();
|
||||||
|
zone.X1 = x1;
|
||||||
|
zone.Y1 = y1;
|
||||||
|
zone.X2 = x2;
|
||||||
|
zone.Y2 = y2;
|
||||||
|
return zone;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,6 +89,7 @@ public class GameConfigurationInitializer : GameConfigurationInitializerBase
|
|||||||
new BloodCastleInitializer(this.Context, this.GameConfiguration).Initialize();
|
new BloodCastleInitializer(this.Context, this.GameConfiguration).Initialize();
|
||||||
new ChaosCastleInitializer(this.Context, this.GameConfiguration).Initialize();
|
new ChaosCastleInitializer(this.Context, this.GameConfiguration).Initialize();
|
||||||
new HeykelSavasiInitializer(this.Context, this.GameConfiguration).Initialize();
|
new HeykelSavasiInitializer(this.Context, this.GameConfiguration).Initialize();
|
||||||
|
new CastleSiegeInitializer(this.Context, this.GameConfiguration).Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateJewelMixes()
|
private void CreateJewelMixes()
|
||||||
|
|||||||
@@ -41,8 +41,15 @@
|
|||||||
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The generator overwrites the checked-in *.Generated.cs files, so it must run against the current data
|
||||||
|
model. Do NOT add the "no build" switch here: it would reuse whatever assemblies happen to lie in the
|
||||||
|
generator's output folder, and a stale copy of the data model silently regenerates the model files
|
||||||
|
without the types added since. The build still succeeds and only fails at runtime, when EF validates
|
||||||
|
the model.
|
||||||
|
-->
|
||||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent" Condition="'$(ci)'!='true'">
|
<Target Name="PreBuild" BeforeTargets="PreBuildEvent" Condition="'$(ci)'!='true'">
|
||||||
<Exec Command="dotnet run --project SourceGenerator/MUnique.OpenMU.Persistence.SourceGenerator.csproj -c:$(ConfigurationName) MUnique.OpenMU.Persistence "$(ProjectDir)BasicModel" --no-build" />
|
<Exec Command="dotnet run --project SourceGenerator/MUnique.OpenMU.Persistence.SourceGenerator.csproj -c:$(ConfigurationName) MUnique.OpenMU.Persistence "$(ProjectDir)BasicModel"" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -3904,6 +3904,24 @@ public class PacketStructureTests
|
|||||||
"Packet length mismatch: declared length does not match calculated size");
|
"Packet length mismatch: declared length does not match calculated size");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests the packet size calculation for HeykelSavasiTeamSelect.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void HeykelSavasiTeamSelect_PacketSizeValidation()
|
||||||
|
{
|
||||||
|
// Fixed-length packet validation
|
||||||
|
const int expectedLength = 4;
|
||||||
|
var actualLength = HeykelSavasiTeamSelectRef.Length;
|
||||||
|
|
||||||
|
Assert.That(actualLength, Is.EqualTo(expectedLength),
|
||||||
|
"Packet length mismatch: declared length does not match calculated size");
|
||||||
|
|
||||||
|
// Validate field 'Team' boundary
|
||||||
|
Assert.That(3 + 1, Is.LessThanOrEqualTo(expectedLength),
|
||||||
|
"Field 'Team' exceeds packet boundary");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests the packet size calculation for ChatCommandListRequest.
|
/// Tests the packet size calculation for ChatCommandListRequest.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -6415,10 +6415,102 @@ public class PacketStructureTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests the packet size calculation for ChatCommandInfo.
|
/// Tests the packet size calculation for HeykelSavasiOpenTeamPanel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void ChatCommandInfo_PacketSizeValidation()
|
public void HeykelSavasiOpenTeamPanel_PacketSizeValidation()
|
||||||
|
{
|
||||||
|
// Fixed-length packet validation
|
||||||
|
const int expectedLength = 5;
|
||||||
|
var actualLength = HeykelSavasiOpenTeamPanelRef.Length;
|
||||||
|
|
||||||
|
Assert.That(actualLength, Is.EqualTo(expectedLength),
|
||||||
|
"Packet length mismatch: declared length does not match calculated size");
|
||||||
|
|
||||||
|
// Validate field 'RedCount' boundary
|
||||||
|
Assert.That(3 + 1, Is.LessThanOrEqualTo(expectedLength),
|
||||||
|
"Field 'RedCount' exceeds packet boundary");
|
||||||
|
|
||||||
|
// Validate field 'BlueCount' boundary
|
||||||
|
Assert.That(4 + 1, Is.LessThanOrEqualTo(expectedLength),
|
||||||
|
"Field 'BlueCount' exceeds packet boundary");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests the packet size calculation for HeykelSavasiHudState.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void HeykelSavasiHudState_PacketSizeValidation()
|
||||||
|
{
|
||||||
|
// Fixed-length packet validation
|
||||||
|
const int expectedLength = 11;
|
||||||
|
var actualLength = HeykelSavasiHudStateRef.Length;
|
||||||
|
|
||||||
|
Assert.That(actualLength, Is.EqualTo(expectedLength),
|
||||||
|
"Packet length mismatch: declared length does not match calculated size");
|
||||||
|
|
||||||
|
// Validate field 'Phase' boundary
|
||||||
|
Assert.That(3 + 1, Is.LessThanOrEqualTo(expectedLength),
|
||||||
|
"Field 'Phase' exceeds packet boundary");
|
||||||
|
|
||||||
|
// Validate field 'MyTeam' boundary
|
||||||
|
Assert.That(4 + 1, Is.LessThanOrEqualTo(expectedLength),
|
||||||
|
"Field 'MyTeam' exceeds packet boundary");
|
||||||
|
|
||||||
|
// Validate field 'RedCount' boundary
|
||||||
|
Assert.That(5 + 1, Is.LessThanOrEqualTo(expectedLength),
|
||||||
|
"Field 'RedCount' exceeds packet boundary");
|
||||||
|
|
||||||
|
// Validate field 'BlueCount' boundary
|
||||||
|
Assert.That(6 + 1, Is.LessThanOrEqualTo(expectedLength),
|
||||||
|
"Field 'BlueCount' exceeds packet boundary");
|
||||||
|
|
||||||
|
// Validate field 'RedProgress' boundary
|
||||||
|
Assert.That(7 + 1, Is.LessThanOrEqualTo(expectedLength),
|
||||||
|
"Field 'RedProgress' exceeds packet boundary");
|
||||||
|
|
||||||
|
// Validate field 'BlueProgress' boundary
|
||||||
|
Assert.That(8 + 1, Is.LessThanOrEqualTo(expectedLength),
|
||||||
|
"Field 'BlueProgress' exceeds packet boundary");
|
||||||
|
|
||||||
|
// Validate field 'RemainingSeconds' boundary
|
||||||
|
Assert.That(9 + 2, Is.LessThanOrEqualTo(expectedLength),
|
||||||
|
"Field 'RemainingSeconds' exceeds packet boundary");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests the packet size calculation for HeykelSavasiTeamRoster.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void HeykelSavasiTeamRoster_PacketSizeValidation()
|
||||||
|
{
|
||||||
|
// Basic packet validation
|
||||||
|
// Validate header type and field boundaries
|
||||||
|
|
||||||
|
// Field 'Count' starts at index 3 with size 1
|
||||||
|
Assert.That(3, Is.GreaterThanOrEqualTo(0),
|
||||||
|
"Field 'Count' has invalid negative index");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests the packet size calculation for HeykelSavasiScoreboard.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void HeykelSavasiScoreboard_PacketSizeValidation()
|
||||||
|
{
|
||||||
|
// Basic packet validation
|
||||||
|
// Validate header type and field boundaries
|
||||||
|
|
||||||
|
// Field 'Count' starts at index 3 with size 1
|
||||||
|
Assert.That(3, Is.GreaterThanOrEqualTo(0),
|
||||||
|
"Field 'Count' has invalid negative index");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests the packet size calculation for AvailableChatCommand.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void AvailableChatCommand_PacketSizeValidation()
|
||||||
{
|
{
|
||||||
// Basic packet validation
|
// Basic packet validation
|
||||||
// Validate header type and field boundaries
|
// Validate header type and field boundaries
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// <copyright file="ApplyPendingUpdatesTool.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.Tests;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using MUnique.OpenMU.Interfaces;
|
||||||
|
using MUnique.OpenMU.Persistence.EntityFramework;
|
||||||
|
using MUnique.OpenMU.Persistence.EntityFramework.Json;
|
||||||
|
using MUnique.OpenMU.Persistence.Initialization.Updates;
|
||||||
|
using MUnique.OpenMU.PlugIns;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A manual tool, not a test: applies the pending configuration updates to the database configured in
|
||||||
|
/// <c>ConnectionSettings.xml</c> - the same thing the admin panel's "Updates" page does, for when there is
|
||||||
|
/// no browser at hand. It is <see cref="ExplicitAttribute"/> so it never runs in a normal test pass.
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture]
|
||||||
|
[Explicit("Writes to the configured database. Run it deliberately, not as part of the test suite.")]
|
||||||
|
internal class ApplyPendingUpdatesTool
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Applies every configuration update which is not installed yet.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task ApplyPendingUpdatesAsync()
|
||||||
|
{
|
||||||
|
// The server registers these at startup; without them the configuration JSON cannot be read back.
|
||||||
|
JsonConverterRegistry.RegisterConverter(new LocalizedStringJsonConverter());
|
||||||
|
JsonConverterRegistry.RegisterConverter(new BinaryAsHexJsonConverter());
|
||||||
|
|
||||||
|
var loggerFactory = new NullLoggerFactory();
|
||||||
|
var contextProvider = new PersistenceContextProvider(loggerFactory, null);
|
||||||
|
var plugInManager = new PlugInManager(null, loggerFactory, null, null);
|
||||||
|
plugInManager.DiscoverAndRegisterPlugIns();
|
||||||
|
|
||||||
|
var service = new DataUpdateService(contextProvider, plugInManager);
|
||||||
|
var pending = (await service.DetermineAvailableUpdatesAsync().ConfigureAwait(false)).ToList();
|
||||||
|
TestContext.Out.WriteLine($"Pending updates: {pending.Count}");
|
||||||
|
foreach (var update in pending)
|
||||||
|
{
|
||||||
|
TestContext.Out.WriteLine($" {(int)update.Version} - {update.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pending.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var progress = new Progress<(UpdateVersion CurrentUpdatingVersion, bool IsCompleted)>(
|
||||||
|
p => TestContext.Out.WriteLine($" applying {(int)p.CurrentUpdatingVersion} completed={p.IsCompleted}"));
|
||||||
|
await service.ApplyUpdatesAsync(pending, progress).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var left = await service.DetermineAvailableUpdatesAsync().ConfigureAwait(false);
|
||||||
|
Assert.That(left, Is.Empty, "all updates should be installed now");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ namespace MUnique.OpenMU.Persistence.Initialization.Tests;
|
|||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using MUnique.OpenMU.DataModel;
|
using MUnique.OpenMU.DataModel;
|
||||||
using MUnique.OpenMU.DataModel.Configuration;
|
using MUnique.OpenMU.DataModel.Configuration;
|
||||||
|
using MUnique.OpenMU.DataModel.Entities;
|
||||||
using MUnique.OpenMU.GameLogic;
|
using MUnique.OpenMU.GameLogic;
|
||||||
using MUnique.OpenMU.Persistence.EntityFramework;
|
using MUnique.OpenMU.Persistence.EntityFramework;
|
||||||
using MUnique.OpenMU.Persistence.Initialization.Updates;
|
using MUnique.OpenMU.Persistence.Initialization.Updates;
|
||||||
@@ -91,6 +92,48 @@ internal class TestInitializationWithEfCore
|
|||||||
Assert.That(groups[0].PossibleItems.Single().Number, Is.EqualTo((short)14));
|
Assert.That(groups[0].PossibleItems.Single().Number, Is.EqualTo((short)14));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the Castle Siege update writes its configuration into an existing Season 6 database, and
|
||||||
|
/// that applying it twice does not duplicate anything. This is the update which existing servers run to
|
||||||
|
/// get the castle: without it there is no Castle Siege configuration for the event to read.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task TestSeason6CastleSiegeUpdatePlugInAsync()
|
||||||
|
{
|
||||||
|
var contextProvider = new InMemoryPersistenceContextProvider();
|
||||||
|
var dataInitialization = new VersionSeasonSix.DataInitialization(contextProvider, new NullLoggerFactory());
|
||||||
|
await dataInitialization.CreateInitialDataAsync(1, true).ConfigureAwait(false);
|
||||||
|
|
||||||
|
using var context = contextProvider.CreateNewContext();
|
||||||
|
var gameConfiguration = (await context.GetAsync<GameConfiguration>().ConfigureAwait(false)).First();
|
||||||
|
gameConfiguration.CastleSiegeConfiguration = null;
|
||||||
|
|
||||||
|
var update = new AddCastleSiegeDataUpdatePlugIn();
|
||||||
|
await update.ApplyUpdateAsync(context, gameConfiguration).ConfigureAwait(false);
|
||||||
|
await update.ApplyUpdateAsync(context, gameConfiguration).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var castleSiege = gameConfiguration.CastleSiegeConfiguration;
|
||||||
|
Assert.That(castleSiege, Is.Not.Null);
|
||||||
|
Assert.That(castleSiege!.Enabled, Is.True);
|
||||||
|
|
||||||
|
// The client's crown registration panel counts down from 60 seconds, so the server has to match it.
|
||||||
|
Assert.That(castleSiege.CrownHoldTimeSeconds, Is.EqualTo(60));
|
||||||
|
|
||||||
|
// Both Crown Switches, the crown and the throne have to be there, or the siege cannot be finished.
|
||||||
|
var npcNumbers = castleSiege.NpcDefinitions.Select(n => n.MonsterDefinition?.Number).ToList();
|
||||||
|
Assert.That(npcNumbers, Does.Contain((short)217), "Crown Switch 1");
|
||||||
|
Assert.That(npcNumbers, Does.Contain((short)218), "Crown Switch 2");
|
||||||
|
Assert.That(npcNumbers, Does.Contain((short)216), "Crown");
|
||||||
|
|
||||||
|
// Applying it twice must not double the NPC definitions.
|
||||||
|
Assert.That(npcNumbers.Count(n => n == 217), Is.EqualTo(1));
|
||||||
|
Assert.That(npcNumbers.Count(n => n == 218), Is.EqualTo(1));
|
||||||
|
|
||||||
|
var data = (await context.GetAsync<CastleSiegeData>().ConfigureAwait(false)).ToList();
|
||||||
|
Assert.That(data, Has.Count.EqualTo(1), "exactly one castle state row");
|
||||||
|
Assert.That(data[0].IsOccupied, Is.False, "a fresh castle has no owner");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests the data initialization using the in-memory persistence.
|
/// Tests the data initialization using the in-memory persistence.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// <copyright file="TypedContextModelTests.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.Tests;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using MUnique.OpenMU.Persistence.EntityFramework;
|
||||||
|
using MUnique.OpenMU.PlugIns;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the typed contexts can build their entity model. A typed context keeps only the edited type
|
||||||
|
/// (plus its aggregate) and ignores every other type, so a type which is only mapped in the full context
|
||||||
|
/// slips through the build and blows up at runtime instead. The startup reads the plugin configurations
|
||||||
|
/// through such a context before anything else, so a broken model there means the server doesn't start.
|
||||||
|
/// No database is needed: building the model already runs the EF model validation.
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture]
|
||||||
|
internal class TypedContextModelTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Builds the model of the typed context which the startup uses to read the plugin configurations.
|
||||||
|
/// A failing connection is fine here (there may be no database); a failing model is not.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void PlugInConfigurationContextBuildsModel()
|
||||||
|
{
|
||||||
|
var provider = new PersistenceContextProvider(new NullLoggerFactory(), null);
|
||||||
|
using var context = provider.CreateNewTypedContext(typeof(PlugInConfiguration), false);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = context.GetAsync<PlugInConfiguration>().AsTask().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AssertNoModelError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertNoModelError(Exception exception)
|
||||||
|
{
|
||||||
|
for (var ex = exception; ex is not null; ex = ex.InnerException!)
|
||||||
|
{
|
||||||
|
if (ex is InvalidOperationException && ex.Message.Contains("requires a primary key"))
|
||||||
|
{
|
||||||
|
Assert.Fail($"The entity model of the typed context is broken: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ex.InnerException is null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,143 +4,208 @@
|
|||||||
|
|
||||||
namespace MUnique.OpenMU.Tests.CastleSiege;
|
namespace MUnique.OpenMU.Tests.CastleSiege;
|
||||||
|
|
||||||
|
using MUnique.OpenMU.DataModel.Configuration;
|
||||||
using MUnique.OpenMU.GameLogic.CastleSiege;
|
using MUnique.OpenMU.GameLogic.CastleSiege;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests for the Castle Siege phase state machine (time-driven, injected clock).
|
/// Tests for the Castle Siege state machine (time-driven, injected clock). The cycle runs through the
|
||||||
|
/// original Season 6 state numbers the client knows: Idle1 -> RegisterGuild -> Ready -> Start -> End
|
||||||
|
/// -> EndCycle -> Idle1. Guilds are identified by their persistent id, not by their (renameable) name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class CastleSiegeContextTest
|
public class CastleSiegeContextTest
|
||||||
{
|
{
|
||||||
private static readonly DateTime T0 = new(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc);
|
private static readonly DateTime T0 = new(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc);
|
||||||
|
private static readonly Guid GuildA = new("11111111-1111-1111-1111-111111111111");
|
||||||
|
private static readonly Guid GuildB = new("22222222-2222-2222-2222-222222222222");
|
||||||
|
|
||||||
/// <summary>Tests that a fresh context starts in the ownership (resting) phase.</summary>
|
/// <summary>Tests that a fresh context rests in the idle state.</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void StartsInOwnership()
|
public void StartsInIdle()
|
||||||
{
|
{
|
||||||
var ctx = new CastleSiegeContext(Config());
|
var ctx = new CastleSiegeContext(Config());
|
||||||
Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Ownership));
|
Assert.That(ctx.State, Is.EqualTo(CastleSiegeState.Idle1));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests that force-starting moves the state machine into registration.</summary>
|
/// <summary>Tests that force-starting moves the state machine into guild registration.</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public async Task ForceStartMovesToRegistrationAsync()
|
public async Task ForceStartMovesToRegistrationAsync()
|
||||||
{
|
{
|
||||||
var ctx = new CastleSiegeContext(Config());
|
var ctx = new CastleSiegeContext(Config());
|
||||||
await ctx.ForceStartRegistrationAsync(T0);
|
await ctx.ForceStartRegistrationAsync(T0);
|
||||||
Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Registration));
|
Assert.That(ctx.State, Is.EqualTo(CastleSiegeState.RegisterGuild));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests that registration advances to preparation once its duration elapses.</summary>
|
/// <summary>Tests that registration advances to the preparation state once its duration elapses.</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public async Task RegistrationAdvancesToPreparationAfterDurationAsync()
|
public async Task RegistrationAdvancesToReadyAfterDurationAsync()
|
||||||
{
|
{
|
||||||
var ctx = new CastleSiegeContext(Config());
|
var ctx = new CastleSiegeContext(Config());
|
||||||
await ctx.ForceStartRegistrationAsync(T0);
|
await ctx.ForceStartRegistrationAsync(T0);
|
||||||
await ctx.TickAsync(T0.AddMinutes(4)); // still within registration
|
await ctx.TickAsync(T0.AddMinutes(4)); // still within registration
|
||||||
Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Registration));
|
Assert.That(ctx.State, Is.EqualTo(CastleSiegeState.RegisterGuild));
|
||||||
await ctx.TickAsync(T0.AddMinutes(5)); // registration duration elapsed
|
await ctx.TickAsync(T0.AddMinutes(5)); // registration duration elapsed
|
||||||
Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Preparation));
|
Assert.That(ctx.State, Is.EqualTo(CastleSiegeState.Ready));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests a full cycle: registration -> preparation -> siege -> settlement -> ownership.</summary>
|
/// <summary>Tests a full cycle: register -> ready -> start -> end -> end cycle -> idle.</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public async Task FullCycleReturnsToOwnershipAsync()
|
public async Task FullCycleReturnsToIdleAsync()
|
||||||
{
|
{
|
||||||
var ctx = new CastleSiegeContext(Config());
|
var ctx = new CastleSiegeContext(Config());
|
||||||
await ctx.ForceStartRegistrationAsync(T0);
|
await ctx.ForceStartRegistrationAsync(T0);
|
||||||
await ctx.TickAsync(T0.AddMinutes(5)); // -> Preparation
|
await ctx.TickAsync(T0.AddMinutes(5)); // -> Ready
|
||||||
await ctx.TickAsync(T0.AddMinutes(7)); // +2 prep -> Siege
|
await ctx.TickAsync(T0.AddMinutes(7)); // +2 preparation -> Start
|
||||||
Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Siege));
|
Assert.That(ctx.State, Is.EqualTo(CastleSiegeState.Start));
|
||||||
await ctx.TickAsync(T0.AddMinutes(17)); // +10 siege -> Settlement
|
await ctx.TickAsync(T0.AddMinutes(17)); // +10 siege -> End
|
||||||
await ctx.TickAsync(T0.AddMinutes(17)); // Settlement -> Ownership
|
await ctx.TickAsync(T0.AddMinutes(17)); // End -> EndCycle
|
||||||
Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Ownership));
|
await ctx.TickAsync(T0.AddMinutes(17)); // EndCycle -> Idle1
|
||||||
|
Assert.That(ctx.State, Is.EqualTo(CastleSiegeState.Idle1));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests that guilds can be registered (by name) during the registration phase.</summary>
|
/// <summary>Tests that guilds are collected by id during the registration state.</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public async Task RegisterGuildCollectsNamesDuringRegistrationAsync()
|
public async Task RegisterGuildCollectsGuildsDuringRegistrationAsync()
|
||||||
{
|
{
|
||||||
var ctx = new CastleSiegeContext(Config());
|
var ctx = new CastleSiegeContext(Config());
|
||||||
await ctx.ForceStartRegistrationAsync(T0);
|
await ctx.ForceStartRegistrationAsync(T0);
|
||||||
ctx.RegisterGuild("Attackers");
|
ctx.RegisterGuild(GuildA, "Attackers");
|
||||||
Assert.That(ctx.RegisteredGuilds, Does.Contain("Attackers"));
|
|
||||||
|
Assert.That(ctx.IsRegistered(GuildA), Is.True);
|
||||||
|
Assert.That(ctx.RegisteredGuildNames, Does.Contain("Attackers"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests the full siege objective chain: destroy defenses, then hold both switches to capture the throne.</summary>
|
/// <summary>
|
||||||
|
/// Tests the full siege objective chain: operate both Crown Switches, which drops the crown's shield,
|
||||||
|
/// then hold the crown to take the throne, which becomes the castle ownership when the siege ends.
|
||||||
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public async Task FullSiegeObjectiveChainToOwnershipAsync()
|
public async Task FullSiegeObjectiveChainToOwnershipAsync()
|
||||||
{
|
{
|
||||||
var ctx = new CastleSiegeContext(Config());
|
var ctx = new CastleSiegeContext(Config());
|
||||||
await ctx.ForceStartRegistrationAsync(T0);
|
await ctx.ForceStartRegistrationAsync(T0);
|
||||||
await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0);
|
await ctx.ForceStateAsync(CastleSiegeState.Start, T0);
|
||||||
ctx.SetDefenseCount(2);
|
var hold = TimeSpan.FromSeconds(60);
|
||||||
|
|
||||||
// Both switches held but defenses still up -> no capture.
|
// A switch which is still being operated does not count yet.
|
||||||
ctx.SetSwitchHolder(217, "Attackers");
|
StartSwitch(ctx, 217, GuildA, 1);
|
||||||
ctx.SetSwitchHolder(218, "Attackers");
|
StartSwitch(ctx, 218, GuildA, 2);
|
||||||
Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.False);
|
Assert.That(ctx.GetShieldEligibleGuild(), Is.Null);
|
||||||
|
|
||||||
ctx.NotifyDefenseDestroyed();
|
CompleteSwitch(ctx, 217);
|
||||||
ctx.NotifyDefenseDestroyed();
|
Assert.That(ctx.GetShieldEligibleGuild(), Is.Null, "one completed switch is not enough");
|
||||||
|
|
||||||
// Only one switch held -> still no capture.
|
CompleteSwitch(ctx, 218);
|
||||||
ctx.SetSwitchHolder(218, null);
|
Assert.That(ctx.GetShieldEligibleGuild(), Is.EqualTo(GuildA));
|
||||||
Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.False);
|
|
||||||
|
|
||||||
// Both switches held by the same guild + defenses down -> capture at the Sinior/Crown.
|
// The crown only starts counting after the guild master clicked it.
|
||||||
ctx.SetSwitchHolder(218, "Attackers");
|
Assert.That(ctx.TickCrownHold(GuildA, "Attackers", true, T0, hold).Event, Is.EqualTo(CrownEvent.None));
|
||||||
Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.True);
|
Assert.That(ctx.RequestCrownHold(GuildA), Is.True);
|
||||||
Assert.That(ctx.OccupierGuildName, Is.EqualTo("Attackers"));
|
ctx.TickCrownHold(GuildA, "Attackers", true, T0, hold);
|
||||||
|
Assert.That(ctx.TickCrownHold(GuildA, "Attackers", true, T0.AddSeconds(60), hold).Event, Is.EqualTo(CrownEvent.Captured));
|
||||||
|
Assert.That(ctx.OccupierGuildId, Is.EqualTo(GuildA));
|
||||||
|
|
||||||
await ctx.TickAsync(T0.AddMinutes(20)); // Siege -> Settlement
|
await ctx.TickAsync(T0.AddMinutes(20)); // siege time is up: Start -> End
|
||||||
await ctx.TickAsync(T0.AddMinutes(20)); // Settlement -> Ownership
|
await ctx.TickAsync(T0.AddMinutes(20)); // End hands the castle to the guild on the throne
|
||||||
|
Assert.That(ctx.OwnerGuildId, Is.EqualTo(GuildA));
|
||||||
Assert.That(ctx.OwnerGuildName, Is.EqualTo("Attackers"));
|
Assert.That(ctx.OwnerGuildName, Is.EqualTo("Attackers"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests that two different guilds each holding one switch cannot capture the throne.</summary>
|
/// <summary>Tests that two different guilds each holding one switch keep the crown's shield up.</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public async Task ThroneRequiresBothSwitchesBySameGuildAsync()
|
public async Task ShieldRequiresBothSwitchesBySameGuildAsync()
|
||||||
{
|
{
|
||||||
var ctx = new CastleSiegeContext(Config());
|
var ctx = new CastleSiegeContext(Config());
|
||||||
await ctx.ForceStartRegistrationAsync(T0);
|
await ctx.ForceStartRegistrationAsync(T0);
|
||||||
await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0);
|
await ctx.ForceStateAsync(CastleSiegeState.Start, T0);
|
||||||
ctx.SetDefenseCount(0);
|
StartSwitch(ctx, 217, GuildA, 1);
|
||||||
ctx.SetSwitchHolder(217, "A");
|
StartSwitch(ctx, 218, GuildB, 2);
|
||||||
ctx.SetSwitchHolder(218, "B");
|
CompleteSwitch(ctx, 217);
|
||||||
Assert.That(ctx.TryCaptureThrone("A").Success, Is.False);
|
CompleteSwitch(ctx, 218);
|
||||||
Assert.That(ctx.TryCaptureThrone("B").Success, Is.False);
|
|
||||||
|
Assert.That(ctx.GetShieldEligibleGuild(), Is.Null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests that capturing the throne outside the siege phase is a no-op.</summary>
|
/// <summary>Tests that the switches don't work at all outside the running siege.</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void ThroneCaptureOutsideSiegeIsNoOp()
|
public void SwitchesDoNothingOutsideSiege()
|
||||||
{
|
{
|
||||||
var ctx = new CastleSiegeContext(Config());
|
var ctx = new CastleSiegeContext(Config());
|
||||||
ctx.SetSwitchHolder(217, "A");
|
var (result, _) = ctx.TryStartSwitchOperation(217, GuildA, "A", 1, "player", 100, T0);
|
||||||
ctx.SetSwitchHolder(218, "A");
|
|
||||||
Assert.That(ctx.TryCaptureThrone("A").Success, Is.False);
|
Assert.That(result, Is.EqualTo(CastleSiegeSwitchPush.SiegeNotRunning));
|
||||||
Assert.That(ctx.OccupierGuildName, Is.Null);
|
Assert.That(ctx.GetSwitchOperation(217), Is.Null);
|
||||||
|
Assert.That(ctx.GetShieldEligibleGuild(), Is.Null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests that restoring persisted state sets phase/owner/registrations without raising PhaseChanged.</summary>
|
/// <summary>Tests that a switch belongs to the first player who clicked it, until they leave its area.</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void RestoreStateSetsStateWithoutFiringPhaseChanged()
|
public async Task SwitchIsTakenByOnePlayerUntilTheyLeaveAsync()
|
||||||
{
|
{
|
||||||
var ctx = new CastleSiegeContext(Config());
|
var ctx = new CastleSiegeContext(Config());
|
||||||
var phaseChangedFired = false;
|
await ctx.ForceStartRegistrationAsync(T0);
|
||||||
ctx.PhaseChanged += _ => phaseChangedFired = true;
|
await ctx.ForceStateAsync(CastleSiegeState.Start, T0);
|
||||||
|
var push = TimeSpan.FromSeconds(15);
|
||||||
|
|
||||||
ctx.RestoreState("Winners", CastleSiegePhase.Ownership, T0, new[] { "Winners", "Losers" });
|
var (first, _) = ctx.TryStartSwitchOperation(217, GuildA, "A", 1, "first", 100, T0);
|
||||||
|
Assert.That(first, Is.EqualTo(CastleSiegeSwitchPush.Started));
|
||||||
|
|
||||||
Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Ownership));
|
// Somebody else clicking it is refused and learns who is on it.
|
||||||
|
var (second, blocking) = ctx.TryStartSwitchOperation(217, GuildB, "B", 2, "second", 100, T0.AddSeconds(1));
|
||||||
|
Assert.That(second, Is.EqualTo(CastleSiegeSwitchPush.TakenByOther));
|
||||||
|
Assert.That(blocking?.PlayerId, Is.EqualTo(1));
|
||||||
|
|
||||||
|
// It only counts once the push ran its time.
|
||||||
|
Assert.That(ctx.TickSwitch(217, true, T0.AddSeconds(14), push).Event, Is.EqualTo(CastleSiegeSwitchEvent.None));
|
||||||
|
Assert.That(ctx.GetSwitchOperation(217)!.IsHeld, Is.False);
|
||||||
|
Assert.That(ctx.TickSwitch(217, true, T0.AddSeconds(15), push).Event, Is.EqualTo(CastleSiegeSwitchEvent.Held));
|
||||||
|
Assert.That(ctx.GetSwitchOperation(217)!.IsHeld, Is.True);
|
||||||
|
|
||||||
|
// Leaving the area frees it for everybody.
|
||||||
|
var (released, freed) = ctx.TickSwitch(217, false, T0.AddSeconds(20), push);
|
||||||
|
Assert.That(released, Is.EqualTo(CastleSiegeSwitchEvent.Released));
|
||||||
|
Assert.That(freed?.PlayerId, Is.EqualTo(1));
|
||||||
|
Assert.That(ctx.GetSwitchOperation(217), Is.Null);
|
||||||
|
|
||||||
|
var (afterRelease, _) = ctx.TryStartSwitchOperation(217, GuildB, "B", 2, "second", 100, T0.AddSeconds(21));
|
||||||
|
Assert.That(afterRelease, Is.EqualTo(CastleSiegeSwitchPush.Started));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Tests that losing a switch while the crown is being held drops the guild's eligibility.</summary>
|
||||||
|
[Test]
|
||||||
|
public async Task LosingASwitchRaisesTheShieldAgainAsync()
|
||||||
|
{
|
||||||
|
var ctx = await SiegeWithSwitchesHeldAsync(GuildA);
|
||||||
|
Assert.That(ctx.GetShieldEligibleGuild(), Is.EqualTo(GuildA));
|
||||||
|
|
||||||
|
ctx.TickSwitch(218, false, T0.AddSeconds(20), TimeSpan.FromSeconds(15));
|
||||||
|
Assert.That(ctx.GetShieldEligibleGuild(), Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Tests that restoring persisted state sets state/owner/registrations without raising StateChanged.</summary>
|
||||||
|
[Test]
|
||||||
|
public void RestoreStateSetsStateWithoutFiringStateChanged()
|
||||||
|
{
|
||||||
|
var ctx = new CastleSiegeContext(Config());
|
||||||
|
var stateChangedFired = false;
|
||||||
|
ctx.StateChanged += _ => stateChangedFired = true;
|
||||||
|
|
||||||
|
ctx.RestoreState(
|
||||||
|
GuildA,
|
||||||
|
"Winners",
|
||||||
|
CastleSiegeState.Idle1,
|
||||||
|
T0,
|
||||||
|
new[] { new KeyValuePair<Guid, string>(GuildA, "Winners"), new KeyValuePair<Guid, string>(GuildB, "Losers") });
|
||||||
|
|
||||||
|
Assert.That(ctx.State, Is.EqualTo(CastleSiegeState.Idle1));
|
||||||
|
Assert.That(ctx.OwnerGuildId, Is.EqualTo(GuildA));
|
||||||
Assert.That(ctx.OwnerGuildName, Is.EqualTo("Winners"));
|
Assert.That(ctx.OwnerGuildName, Is.EqualTo("Winners"));
|
||||||
Assert.That(ctx.RegisteredGuilds, Is.EquivalentTo(new[] { "Winners", "Losers" }));
|
Assert.That(ctx.RegisteredGuildNames, Is.EquivalentTo(new[] { "Winners", "Losers" }));
|
||||||
Assert.That(phaseChangedFired, Is.False);
|
Assert.That(stateChangedFired, Is.False);
|
||||||
Assert.That(ctx.ConsumeDirty(), Is.False, "restore must not mark the state dirty");
|
Assert.That(ctx.ConsumeDirty(), Is.False, "restore must not mark the state dirty");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests that the weekly auto-schedule (stored in config) only fires on a matching day/time window.</summary>
|
/// <summary>Tests that the weekly auto-schedule (stored in the settings) only fires on a matching day/time window.</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void ScheduleFiresOnMatchingDayAndTimeWindow()
|
public void ScheduleFiresOnMatchingDayAndTimeWindow()
|
||||||
{
|
{
|
||||||
@@ -149,7 +214,7 @@ public class CastleSiegeContextTest
|
|||||||
var config = ctx.Configuration;
|
var config = ctx.Configuration;
|
||||||
|
|
||||||
var sunday = new DateTime(2026, 1, 4, 20, 0, 2, DateTimeKind.Utc); // a Sunday, +2s into the window
|
var sunday = new DateTime(2026, 1, 4, 20, 0, 2, DateTimeKind.Utc); // a Sunday, +2s into the window
|
||||||
var sundayLate = new DateTime(2026, 1, 4, 20, 0, 30, DateTimeKind.Utc); // past the 5s window
|
var sundayLate = new DateTime(2026, 1, 4, 20, 0, 30, DateTimeKind.Utc); // past the window
|
||||||
var monday = new DateTime(2026, 1, 5, 20, 0, 2, DateTimeKind.Utc); // wrong day
|
var monday = new DateTime(2026, 1, 5, 20, 0, 2, DateTimeKind.Utc); // wrong day
|
||||||
|
|
||||||
Assert.That(config.IsRegistrationOpenTime(sunday), Is.True);
|
Assert.That(config.IsRegistrationOpenTime(sunday), Is.True);
|
||||||
@@ -168,26 +233,26 @@ public class CastleSiegeContextTest
|
|||||||
var ctx = new CastleSiegeContext(Config());
|
var ctx = new CastleSiegeContext(Config());
|
||||||
Assert.That(ctx.ConsumeDirty(), Is.False, "a fresh context has nothing to persist");
|
Assert.That(ctx.ConsumeDirty(), Is.False, "a fresh context has nothing to persist");
|
||||||
|
|
||||||
await ctx.ForceStartRegistrationAsync(T0); // phase transition -> dirty
|
await ctx.ForceStartRegistrationAsync(T0); // state transition -> dirty
|
||||||
Assert.That(ctx.ConsumeDirty(), Is.True);
|
Assert.That(ctx.ConsumeDirty(), Is.True);
|
||||||
Assert.That(ctx.ConsumeDirty(), Is.False, "ConsumeDirty resets the flag");
|
Assert.That(ctx.ConsumeDirty(), Is.False, "ConsumeDirty resets the flag");
|
||||||
|
|
||||||
ctx.RegisterGuild("Attackers"); // registration -> dirty
|
ctx.RegisterGuild(GuildA, "Attackers"); // registration -> dirty
|
||||||
Assert.That(ctx.ConsumeDirty(), Is.True);
|
Assert.That(ctx.ConsumeDirty(), Is.True);
|
||||||
|
|
||||||
ctx.SetOwner("Attackers"); // owner change -> dirty
|
ctx.SetOwner(GuildA, "Attackers"); // owner change -> dirty
|
||||||
Assert.That(ctx.ConsumeDirty(), Is.True);
|
Assert.That(ctx.ConsumeDirty(), Is.True);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests that the remaining siege time counts down during the siege and is zero otherwise.</summary>
|
/// <summary>Tests that the remaining siege time counts down during the siege and is zero otherwise.</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public async Task RemainingSiegeTimeReflectsSiegePhaseAsync()
|
public async Task RemainingSiegeTimeReflectsSiegeStateAsync()
|
||||||
{
|
{
|
||||||
var ctx = new CastleSiegeContext(Config()); // 10 minute siege duration
|
var ctx = new CastleSiegeContext(Config()); // 10 minute siege duration
|
||||||
Assert.That(ctx.GetRemainingSiegeTime(T0), Is.EqualTo(TimeSpan.Zero), "no siege running -> zero");
|
Assert.That(ctx.GetRemainingSiegeTime(T0), Is.EqualTo(TimeSpan.Zero), "no siege running -> zero");
|
||||||
|
|
||||||
await ctx.ForceStartRegistrationAsync(T0);
|
await ctx.ForceStartRegistrationAsync(T0);
|
||||||
await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0);
|
await ctx.ForceStateAsync(CastleSiegeState.Start, T0);
|
||||||
|
|
||||||
Assert.That(ctx.GetRemainingSiegeTime(T0.AddMinutes(3)), Is.EqualTo(TimeSpan.FromMinutes(7)));
|
Assert.That(ctx.GetRemainingSiegeTime(T0.AddMinutes(3)), Is.EqualTo(TimeSpan.FromMinutes(7)));
|
||||||
Assert.That(ctx.GetRemainingSiegeTime(T0.AddMinutes(15)), Is.EqualTo(TimeSpan.Zero), "past the end -> clamped to zero");
|
Assert.That(ctx.GetRemainingSiegeTime(T0.AddMinutes(15)), Is.EqualTo(TimeSpan.Zero), "past the end -> clamped to zero");
|
||||||
@@ -197,75 +262,90 @@ public class CastleSiegeContextTest
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task CrownHoldCapturesAfterHoldDurationAsync()
|
public async Task CrownHoldCapturesAfterHoldDurationAsync()
|
||||||
{
|
{
|
||||||
var ctx = new CastleSiegeContext(Config());
|
var ctx = await SiegeWithSwitchesHeldAsync(GuildA);
|
||||||
await ctx.ForceStartRegistrationAsync(T0);
|
|
||||||
await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0);
|
|
||||||
ctx.SetDefenseCount(0);
|
|
||||||
ctx.SetSwitchHolder(217, "Attackers");
|
|
||||||
ctx.SetSwitchHolder(218, "Attackers");
|
|
||||||
Assert.That(ctx.GetShieldEligibleGuild(), Is.EqualTo("Attackers"));
|
|
||||||
|
|
||||||
var hold = TimeSpan.FromSeconds(60);
|
var hold = TimeSpan.FromSeconds(60);
|
||||||
Assert.That(ctx.TickCrownHold("Attackers", true, T0, hold).Event, Is.EqualTo(CrownEvent.HoldStarted));
|
|
||||||
Assert.That(ctx.TickCrownHold("Attackers", true, T0.AddSeconds(30), hold).Event, Is.EqualTo(CrownEvent.None));
|
|
||||||
Assert.That(ctx.OccupierGuildName, Is.Null);
|
|
||||||
|
|
||||||
var captured = ctx.TickCrownHold("Attackers", true, T0.AddSeconds(60), hold);
|
Assert.That(ctx.GetShieldEligibleGuild(), Is.EqualTo(GuildA));
|
||||||
|
Assert.That(ctx.RequestCrownHold(GuildA), Is.True);
|
||||||
|
Assert.That(ctx.TickCrownHold(GuildA, "Attackers", true, T0, hold).Event, Is.EqualTo(CrownEvent.HoldStarted));
|
||||||
|
Assert.That(ctx.TickCrownHold(GuildA, "Attackers", true, T0.AddSeconds(30), hold).Event, Is.EqualTo(CrownEvent.None));
|
||||||
|
Assert.That(ctx.OccupierGuildId, Is.Null);
|
||||||
|
|
||||||
|
var captured = ctx.TickCrownHold(GuildA, "Attackers", true, T0.AddSeconds(60), hold);
|
||||||
Assert.That(captured.Event, Is.EqualTo(CrownEvent.Captured));
|
Assert.That(captured.Event, Is.EqualTo(CrownEvent.Captured));
|
||||||
Assert.That(captured.ShieldDown, Is.True);
|
Assert.That(captured.ShieldDown, Is.True);
|
||||||
Assert.That(ctx.OccupierGuildName, Is.EqualTo("Attackers"));
|
Assert.That(ctx.OccupierGuildId, Is.EqualTo(GuildA));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests that losing a switch mid-hold resets the crown-hold progress (contestable).</summary>
|
/// <summary>Tests that losing a switch mid-hold resets the crown-hold progress (contestable).</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public async Task CrownHoldResetsWhenSwitchLostAsync()
|
public async Task CrownHoldResetsWhenSwitchLostAsync()
|
||||||
{
|
{
|
||||||
var ctx = new CastleSiegeContext(Config());
|
var ctx = await SiegeWithSwitchesHeldAsync(GuildA);
|
||||||
await ctx.ForceStartRegistrationAsync(T0);
|
var hold = TimeSpan.FromSeconds(60);
|
||||||
await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0);
|
|
||||||
ctx.SetDefenseCount(0);
|
|
||||||
ctx.SetSwitchHolder(217, "A");
|
|
||||||
ctx.SetSwitchHolder(218, "A");
|
|
||||||
|
|
||||||
Assert.That(ctx.TickCrownHold("A", true, T0, TimeSpan.FromSeconds(60)).Event, Is.EqualTo(CrownEvent.HoldStarted));
|
Assert.That(ctx.RequestCrownHold(GuildA), Is.True);
|
||||||
|
Assert.That(ctx.TickCrownHold(GuildA, "A", true, T0, hold).Event, Is.EqualTo(CrownEvent.HoldStarted));
|
||||||
|
|
||||||
ctx.SetSwitchHolder(218, null); // lost a switch -> no longer eligible
|
ctx.TickSwitch(218, false, T0.AddSeconds(5), TimeSpan.FromSeconds(15)); // lost a switch -> no longer eligible
|
||||||
var reset = ctx.TickCrownHold(ctx.GetShieldEligibleGuild(), false, T0.AddSeconds(10), TimeSpan.FromSeconds(60));
|
var reset = ctx.TickCrownHold(ctx.GetShieldEligibleGuild(), null, false, T0.AddSeconds(10), hold);
|
||||||
Assert.That(reset.ShieldDown, Is.False);
|
Assert.That(reset.ShieldDown, Is.False);
|
||||||
Assert.That(reset.Event, Is.EqualTo(CrownEvent.HoldReset));
|
Assert.That(reset.Event, Is.EqualTo(CrownEvent.HoldReset));
|
||||||
Assert.That(ctx.OccupierGuildName, Is.Null);
|
Assert.That(ctx.OccupierGuildId, Is.Null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests that the occupier can't re-capture its own throne, but a different guild can contest it.</summary>
|
/// <summary>Tests that the occupier can't re-capture its own throne, but a different guild can contest it.</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public async Task OccupierDoesNotRecaptureButAnotherGuildCanContestAsync()
|
public async Task OccupierDoesNotRecaptureButAnotherGuildCanContestAsync()
|
||||||
{
|
{
|
||||||
var ctx = new CastleSiegeContext(Config());
|
var ctx = await SiegeWithSwitchesHeldAsync(GuildA);
|
||||||
await ctx.ForceStartRegistrationAsync(T0);
|
|
||||||
await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0);
|
|
||||||
ctx.SetDefenseCount(0);
|
|
||||||
var hold = TimeSpan.FromSeconds(60);
|
var hold = TimeSpan.FromSeconds(60);
|
||||||
|
var push = TimeSpan.FromSeconds(15);
|
||||||
|
|
||||||
ctx.SetSwitchHolder(217, "A");
|
ctx.RequestCrownHold(GuildA);
|
||||||
ctx.SetSwitchHolder(218, "A");
|
ctx.TickCrownHold(GuildA, "A", true, T0, hold);
|
||||||
ctx.TickCrownHold("A", true, T0, hold);
|
Assert.That(ctx.TickCrownHold(GuildA, "A", true, T0.AddSeconds(60), hold).Event, Is.EqualTo(CrownEvent.Captured));
|
||||||
Assert.That(ctx.TickCrownHold("A", true, T0.AddSeconds(60), hold).Event, Is.EqualTo(CrownEvent.Captured));
|
Assert.That(ctx.OccupierGuildId, Is.EqualTo(GuildA));
|
||||||
Assert.That(ctx.OccupierGuildName, Is.EqualTo("A"));
|
|
||||||
|
|
||||||
// A keeps holding — no re-registration loop, but the shield stays down (they hold it).
|
// A keeps holding - no re-registration loop, but the shield stays down (they hold it).
|
||||||
var after = ctx.TickCrownHold("A", true, T0.AddSeconds(61), hold);
|
Assert.That(ctx.RequestCrownHold(GuildA), Is.False, "the occupier cannot re-register its own throne");
|
||||||
|
var after = ctx.TickCrownHold(GuildA, "A", true, T0.AddSeconds(61), hold);
|
||||||
Assert.That(after.Event, Is.EqualTo(CrownEvent.None));
|
Assert.That(after.Event, Is.EqualTo(CrownEvent.None));
|
||||||
Assert.That(after.ShieldDown, Is.True);
|
Assert.That(after.ShieldDown, Is.True);
|
||||||
|
|
||||||
// B takes both switches and can contest/capture.
|
// B takes both switches and can contest/capture.
|
||||||
ctx.SetSwitchHolder(217, "B");
|
ctx.TickSwitch(217, false, T0.AddSeconds(61), push);
|
||||||
ctx.SetSwitchHolder(218, "B");
|
ctx.TickSwitch(218, false, T0.AddSeconds(61), push);
|
||||||
Assert.That(ctx.TickCrownHold("B", true, T0.AddSeconds(62), hold).Event, Is.EqualTo(CrownEvent.HoldStarted));
|
StartSwitch(ctx, 217, GuildB, 3);
|
||||||
Assert.That(ctx.TickCrownHold("B", true, T0.AddSeconds(122), hold).Event, Is.EqualTo(CrownEvent.Captured));
|
StartSwitch(ctx, 218, GuildB, 4);
|
||||||
Assert.That(ctx.OccupierGuildName, Is.EqualTo("B"));
|
CompleteSwitch(ctx, 217);
|
||||||
|
CompleteSwitch(ctx, 218);
|
||||||
|
|
||||||
|
Assert.That(ctx.RequestCrownHold(GuildB), Is.True);
|
||||||
|
Assert.That(ctx.TickCrownHold(GuildB, "B", true, T0.AddSeconds(62), hold).Event, Is.EqualTo(CrownEvent.HoldStarted));
|
||||||
|
Assert.That(ctx.TickCrownHold(GuildB, "B", true, T0.AddSeconds(122), hold).Event, Is.EqualTo(CrownEvent.Captured));
|
||||||
|
Assert.That(ctx.OccupierGuildId, Is.EqualTo(GuildB));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CastleSiegeConfiguration Config() => new()
|
private static async Task<CastleSiegeContext> SiegeWithSwitchesHeldAsync(Guid guildId)
|
||||||
|
{
|
||||||
|
var ctx = new CastleSiegeContext(Config());
|
||||||
|
await ctx.ForceStartRegistrationAsync(T0);
|
||||||
|
await ctx.ForceStateAsync(CastleSiegeState.Start, T0);
|
||||||
|
StartSwitch(ctx, 217, guildId, 1);
|
||||||
|
StartSwitch(ctx, 218, guildId, 2);
|
||||||
|
CompleteSwitch(ctx, 217);
|
||||||
|
CompleteSwitch(ctx, 218);
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StartSwitch(CastleSiegeContext ctx, short switchNumber, Guid guildId, ushort playerId)
|
||||||
|
=> ctx.TryStartSwitchOperation(switchNumber, guildId, guildId.ToString()[..4], playerId, $"p{playerId}", (ushort)switchNumber, T0);
|
||||||
|
|
||||||
|
private static void CompleteSwitch(CastleSiegeContext ctx, short switchNumber)
|
||||||
|
=> ctx.TickSwitch(switchNumber, true, T0.AddSeconds(30), TimeSpan.FromSeconds(15));
|
||||||
|
|
||||||
|
private static CastleSiegeSettings Config() => new()
|
||||||
{
|
{
|
||||||
RegistrationDuration = TimeSpan.FromMinutes(5),
|
RegistrationDuration = TimeSpan.FromMinutes(5),
|
||||||
PreparationDuration = TimeSpan.FromMinutes(2),
|
PreparationDuration = TimeSpan.FromMinutes(2),
|
||||||
|
|||||||
Reference in New Issue
Block a user