Skip to content

Commit

Permalink
Merge pull request #466 from bernatvadell/feat/bc-start-time
Browse files Browse the repository at this point in the history
Implement Blood Castle start time system
  • Loading branch information
sven-n authored Aug 21, 2024
2 parents 5fad28a + aa4e4c2 commit b9cec6d
Show file tree
Hide file tree
Showing 13 changed files with 280 additions and 251 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// <copyright file="StartBloodCastleEventChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;

using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;
using MUnique.OpenMU.PlugIns;

/// <summary>
/// A chat command plugin which handles the startds command.
/// </summary>
[Guid("7177533A-F147-407E-97B0-C4D8E1AC1AF4")]
[PlugIn(nameof(StartBloodCastleEventChatCommandPlugIn), "Handles the chat command '/startbc'. Starts the blood castle event at the next possible time.")]
[ChatCommandHelp(Command, "Starts the blood castle event at the next possible time.", CharacterStatus.GameMaster)]
public class StartBloodCastleEventChatCommandPlugIn : IChatCommandPlugIn
{
private const string Command = "/startbc";

/// <inheritdoc />
public string Key => Command;

/// <inheritdoc/>
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;

/// <inheritdoc />
public async ValueTask HandleCommandAsync(Player player, string command)
{
var bloodCastle = player.GameContext.PlugInManager.GetStrategy<MiniGameType, IPeriodicMiniGameStartPlugIn>(MiniGameType.BloodCastle);
bloodCastle?.ForceStart();
}
}
23 changes: 23 additions & 0 deletions src/GameLogic/PlugIns/PeriodicTasks/BloodCastleGameServerState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// <copyright file="BloodCastleGameServerState.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;

/// <summary>
/// The state of a game server state for a blood castle event.
/// </summary>
public class BloodCastleGameServerState : PeriodicTaskGameServerState
{
/// <summary>
/// Initializes a new instance of the <see cref="BloodCastleGameServerState"/> class.
/// </summary>
/// <param name="context">The context.</param>
public BloodCastleGameServerState(IGameContext context)
: base(context)
{
}

/// <inheritdoc />
public override string Description => "Blood Castle";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// <copyright file="BloodCastleStartConfiguration.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;

/// <summary>
/// The blood castle start configuration.
/// </summary>
public class BloodCastleStartConfiguration : MiniGameStartConfiguration
{
/// <summary>
/// Gets the default configuration for blood castle.
/// </summary>
public static BloodCastleStartConfiguration Default =>
new()
{
PreStartMessageDelay = TimeSpan.Zero,
EntranceOpenedMessage = "Blood Castle entrance is open and closes in {0} minute(s).",
EntranceClosedMessage = "Blood Castle entrance closed.",
TaskDuration = TimeSpan.FromMinutes(20),
Timetable = GenerateTimeSequence(TimeSpan.FromMinutes(120)).ToList(),
};
}
32 changes: 32 additions & 0 deletions src/GameLogic/PlugIns/PeriodicTasks/BloodCastleStartPlugIn.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// <copyright file="BloodCastleStartPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;

using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.MiniGames;
using MUnique.OpenMU.PlugIns;

/// <summary>
/// This plugin enables the start of the blood castle.
/// </summary>
[PlugIn(nameof(BloodCastleStartPlugIn), "Blood Castle event")]
[Guid("95E68C14-AD87-4B3C-AF46-45B8F1C3BC2A")]
public sealed class BloodCastleStartPlugIn : MiniGameStartBasePlugIn<BloodCastleStartConfiguration, BloodCastleGameServerState>
{
/// <inheritdoc />
public override MiniGameType Key => MiniGameType.BloodCastle;

/// <inheritdoc />
public override object CreateDefaultConfig()
{
return BloodCastleStartConfiguration.Default;
}

/// <inheritdoc />
protected override BloodCastleGameServerState CreateState(IGameContext gameContext)
{
return new BloodCastleGameServerState(gameContext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;
/// <summary>
/// The chaos castle start configuration.
/// </summary>
public class ChaosCastleStartConfiguration : PeriodicTaskConfiguration
public class ChaosCastleStartConfiguration : MiniGameStartConfiguration
{
/// <summary>
/// Gets the default configuration for chaos castle.
Expand All @@ -21,14 +21,4 @@ public class ChaosCastleStartConfiguration : PeriodicTaskConfiguration
TaskDuration = TimeSpan.FromMinutes(15),
Timetable = PeriodicTaskConfiguration.GenerateTimeSequence(TimeSpan.FromMinutes(60)).ToList(),
};

/// <summary>
/// Gets or sets the entrance opened message.
/// </summary>
public string? EntranceOpenedMessage { get; set; }

/// <summary>
/// Gets or sets the entrance closed message.
/// </summary>
public string? EntranceClosedMessage { get; set; }
}
116 changes: 4 additions & 112 deletions src/GameLogic/PlugIns/PeriodicTasks/ChaosCastleStartPlugIn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,128 +13,20 @@ namespace MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;
/// </summary>
[PlugIn(nameof(ChaosCastleStartPlugIn), "Chaos Castle event")]
[Guid("3AD96A70-ED24-4979-80B8-169E461E548F")]
public sealed class ChaosCastleStartPlugIn : PeriodicTaskBasePlugIn<ChaosCastleStartConfiguration, ChaosCastleGameServerState>, ISupportDefaultCustomConfiguration, IPeriodicMiniGameStartPlugIn
public sealed class ChaosCastleStartPlugIn : MiniGameStartBasePlugIn<ChaosCastleStartConfiguration, ChaosCastleGameServerState>
{
/// <summary>
/// Gets the key under which the strategy is getting registered.
/// </summary>
public MiniGameType Key => MiniGameType.ChaosCastle;

/// <inheritdoc />
public object CreateDefaultConfig()
{
return ChaosCastleStartConfiguration.Default;
}

/// <inheritdoc />
public async ValueTask<TimeSpan?> GetDurationUntilNextStartAsync(IGameContext gameContext, MiniGameDefinition miniGameDefinition)
{
var state = this.GetStateByGameContext(gameContext);
if (state.State == PeriodicTaskState.Prepared)
{
// That's not totally correct, but should be sufficient.
return this.Configuration?.PreStartMessageDelay;
}

if (state.State == PeriodicTaskState.Started
&& await state.Context.GetMiniGameAsync(miniGameDefinition, null!).ConfigureAwait(false) is { State: MiniGameState.Open })
{
return TimeSpan.Zero;
}

var timeNow = new TimeOnly(DateTime.UtcNow.TimeOfDay.Ticks);
var nextRun = this.Configuration?.Timetable.Where(time => time > timeNow).Order().FirstOrDefault();
return nextRun - timeNow;
}

/// <inheritdoc />
public async ValueTask<MiniGameContext?> GetMiniGameContextAsync(IGameContext gameContext, MiniGameDefinition miniGameDefinition)
{
var state = this.GetStateByGameContext(gameContext);
if (state.State == PeriodicTaskState.Started)
{
return await state.Context.GetMiniGameAsync(miniGameDefinition, null!).ConfigureAwait(false);
}

return null;
}
public override MiniGameType Key => MiniGameType.ChaosCastle;

/// <inheritdoc />
protected override ValueTask OnPrepareEventAsync(ChaosCastleGameServerState state)
public override object CreateDefaultConfig()
{
// nothing to do
return ValueTask.CompletedTask;
return ChaosCastleStartConfiguration.Default;
}

/// <inheritdoc />
protected override ChaosCastleGameServerState CreateState(IGameContext gameContext)
{
return new ChaosCastleGameServerState(gameContext);
}

/// <inheritdoc />
protected override ValueTask OnPreparedAsync(ChaosCastleGameServerState state)
{
// We keep it simple and don't send a message here.
return ValueTask.CompletedTask;
}

/// <inheritdoc />
protected override async ValueTask OnStartedAsync(ChaosCastleGameServerState state)
{
var chaosCastleDefinitions = state.Context.Configuration.MiniGameDefinitions
.Where(d => d is { Type: MiniGameType.ChaosCastle, MapCreationPolicy: MiniGameMapCreationPolicy.Shared });
var enterDuration = TimeSpan.Zero;
foreach (var chaosCastleDefinition in chaosCastleDefinitions)
{
// we're causing that the event context gets created.
_ = await state.Context.GetMiniGameAsync(chaosCastleDefinition, null!).ConfigureAwait(false);
enterDuration = chaosCastleDefinition.EnterDuration;
}

_ = this.SendOpenedNotificationsAsync(state, enterDuration);
}

/// <inheritdoc />
protected override ValueTask OnFinishedAsync(ChaosCastleGameServerState state)
{
// nothing to do; The mini game will clean up itself.
return ValueTask.CompletedTask;
}

private async Task SendOpenedNotificationsAsync(ChaosCastleGameServerState state, TimeSpan enterDuration)
{
try
{
if (enterDuration <= TimeSpan.Zero)
{
return;
}

for (var remainingMinutes = (int)enterDuration.TotalMinutes; remainingMinutes > 0; remainingMinutes--)
{
if (this.Configuration?.EntranceOpenedMessage is { } openMessage)
{
await state.Context.SendGlobalNotificationAsync(string.Format(openMessage, remainingMinutes)).ConfigureAwait(false);
}

await Task.Delay(TimeSpan.FromMinutes(1)).ConfigureAwait(false);
}

var remainingSeconds = TimeSpan.FromSeconds(enterDuration.Seconds);
if (remainingSeconds > TimeSpan.Zero)
{
await Task.Delay(remainingSeconds).ConfigureAwait(false);
}

if (this.Configuration?.EntranceClosedMessage is { } closedMessage)
{
await state.Context.SendGlobalNotificationAsync(closedMessage).ConfigureAwait(false);
}
}
catch (Exception ex)
{
state.Context.LoggerFactory.CreateLogger<ChaosCastleStartPlugIn>().LogError(ex, "Error when sending the notifications");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;
/// <summary>
/// The devil square start configuration.
/// </summary>
public class DevilSquareStartConfiguration : PeriodicTaskConfiguration
public class DevilSquareStartConfiguration : MiniGameStartConfiguration
{
/// <summary>
/// Gets the default configuration for devil square.
Expand All @@ -21,14 +21,4 @@ public class DevilSquareStartConfiguration : PeriodicTaskConfiguration
TaskDuration = TimeSpan.FromMinutes(25),
Timetable = PeriodicTaskConfiguration.GenerateTimeSequence(TimeSpan.FromMinutes(240)).ToList(),
};

/// <summary>
/// Gets or sets the entrance opened message.
/// </summary>
public string? EntranceOpenedMessage { get; set; }

/// <summary>
/// Gets or sets the entrance closed message.
/// </summary>
public string? EntranceClosedMessage { get; set; }
}
Loading

0 comments on commit b9cec6d

Please sign in to comment.