223 lines
9.6 KiB
C#
223 lines
9.6 KiB
C#
// <copyright file="EnterMiniGameAction.cs" company="MUnique">
|
|
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
|
// </copyright>
|
|
|
|
namespace MUnique.OpenMU.GameLogic.PlayerActions.MiniGames;
|
|
|
|
using MUnique.OpenMU.DataModel.Configuration.Items;
|
|
using MUnique.OpenMU.GameLogic.Attributes;
|
|
using MUnique.OpenMU.GameLogic.GuildWar;
|
|
using MUnique.OpenMU.GameLogic.MiniGames;
|
|
using MUnique.OpenMU.GameLogic.PlayerActions.PlayerStore;
|
|
using MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;
|
|
using MUnique.OpenMU.GameLogic.Views.Inventory;
|
|
|
|
/// <summary>
|
|
/// Player action which implements entering a mini game.
|
|
/// </summary>
|
|
public class EnterMiniGameAction
|
|
{
|
|
private const int UndefinedTicketSlot = 0xFF;
|
|
|
|
/// <summary>
|
|
/// Tries to enter the mini game event of the specified type and level with the specified ticket item.
|
|
/// </summary>
|
|
/// <param name="player">The player.</param>
|
|
/// <param name="miniGameType">The mini game type.</param>
|
|
/// <param name="gameLevel">The mini game level.</param>
|
|
/// <param name="gameTicketInventoryIndex">The inventory index of the ticket.</param>
|
|
public async ValueTask TryEnterMiniGameAsync(Player player, MiniGameType miniGameType, int gameLevel, byte gameTicketInventoryIndex)
|
|
{
|
|
if (player.SelectedCharacter?.CharacterClass is null)
|
|
{
|
|
throw new InvalidOperationException("Character not selected or initialized.");
|
|
}
|
|
|
|
var miniGameDefinition = player.GameContext.Configuration.MiniGameDefinitions
|
|
.FirstOrDefault(def => def.Type == miniGameType && def.GameLevel == gameLevel);
|
|
if (miniGameDefinition is null
|
|
|| (miniGameDefinition.RequiresMasterClass && !player.SelectedCharacter.CharacterClass.IsMasterClass)
|
|
|| player.CurrentMiniGame is not null)
|
|
{
|
|
await player.InvokeViewPlugInAsync<IShowMiniGameEnterResultPlugIn>(p => p.ShowResultAsync(miniGameType, EnterResult.Failed)).ConfigureAwait(false);
|
|
return;
|
|
}
|
|
|
|
var isSpecialCharacter = player.SelectedCharacter.IsSpecialCharacter();
|
|
var characterLevel = player.Attributes![Stats.Level];
|
|
var minLevel = isSpecialCharacter ? miniGameDefinition.MinimumSpecialCharacterLevel : miniGameDefinition.MinimumCharacterLevel;
|
|
var maxLevel = isSpecialCharacter ? miniGameDefinition.MaximumSpecialCharacterLevel : miniGameDefinition.MaximumCharacterLevel;
|
|
var requiresMasterLevel = miniGameDefinition.RequiresMasterClass;
|
|
if (characterLevel < minLevel || (requiresMasterLevel && player.SelectedCharacter?.CharacterClass?.IsMasterClass is not true))
|
|
{
|
|
await player.InvokeViewPlugInAsync<IShowMiniGameEnterResultPlugIn>(p => p.ShowResultAsync(miniGameType, EnterResult.CharacterLevelTooLow)).ConfigureAwait(false);
|
|
return;
|
|
}
|
|
|
|
if (characterLevel > maxLevel)
|
|
{
|
|
await player.InvokeViewPlugInAsync<IShowMiniGameEnterResultPlugIn>(p => p.ShowResultAsync(miniGameType, EnterResult.CharacterLevelTooHigh)).ConfigureAwait(false);
|
|
return;
|
|
}
|
|
|
|
if (!this.CheckTicketItem(miniGameDefinition, player, gameTicketInventoryIndex, out var ticketItem))
|
|
{
|
|
await player.InvokeViewPlugInAsync<IShowMiniGameEnterResultPlugIn>(p => p.ShowResultAsync(miniGameType, EnterResult.Failed)).ConfigureAwait(false);
|
|
return;
|
|
}
|
|
|
|
if (!this.CheckEntranceFee(miniGameDefinition, player, out var entranceFee))
|
|
{
|
|
await player.InvokeViewPlugInAsync<IShowMiniGameEnterResultPlugIn>(p => p.ShowResultAsync(miniGameType, EnterResult.NotEnoughMoney)).ConfigureAwait(false);
|
|
return;
|
|
}
|
|
|
|
if (!this.CheckPlayerKillState(miniGameDefinition, player))
|
|
{
|
|
await player.InvokeViewPlugInAsync<IShowMiniGameEnterResultPlugIn>(p => p.ShowResultAsync(miniGameType, EnterResult.PlayerKillerCantEnter)).ConfigureAwait(false);
|
|
return;
|
|
}
|
|
|
|
if (player.GuildWarContext is { State: GuildWarState.Started or GuildWarState.Requested })
|
|
{
|
|
await player.InvokeViewPlugInAsync<IShowMiniGameEnterResultPlugIn>(p => p.ShowResultAsync(miniGameType, EnterResult.Failed)).ConfigureAwait(false);
|
|
return;
|
|
}
|
|
|
|
// TODO: Check Duel state when duels are implemented
|
|
if (miniGameDefinition.MapCreationPolicy == MiniGameMapCreationPolicy.Shared)
|
|
{
|
|
var miniGameStrategy = player.GameContext.PlugInManager.GetStrategy<MiniGameType, IPeriodicMiniGameStartPlugIn>(miniGameDefinition.Type);
|
|
if (miniGameStrategy is not null
|
|
&& await miniGameStrategy.GetDurationUntilNextStartAsync(player.GameContext, miniGameDefinition).ConfigureAwait(false) != TimeSpan.Zero)
|
|
{
|
|
await player.InvokeViewPlugInAsync<IShowMiniGameEnterResultPlugIn>(p => p.ShowResultAsync(miniGameType, EnterResult.NotOpen)).ConfigureAwait(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
var entrance = miniGameDefinition.Entrance ?? throw new InvalidOperationException("mini game entrance not defined");
|
|
var miniGame = await player.GameContext.GetMiniGameAsync(miniGameDefinition, player).ConfigureAwait(false);
|
|
|
|
// Snapshot before entering: an event which disallows parties (Chaos Castle) kicks the
|
|
// entering player out of its party below, losing the knowledge of who was going to follow.
|
|
var partyBots = Bots.BotMiniGameHandler.SnapshotPartyBots(player);
|
|
|
|
var enterResult = await miniGame.TryEnterAsync(player).ConfigureAwait(false);
|
|
if (enterResult == EnterResult.Success)
|
|
{
|
|
if (entranceFee > 0)
|
|
{
|
|
player.TryRemoveMoney(entranceFee);
|
|
}
|
|
|
|
await this.ConsumeTicketItemAsync(ticketItem, player).ConfigureAwait(false);
|
|
|
|
if (!miniGameDefinition.AllowParty && player.Party is { } party)
|
|
{
|
|
await party.KickMySelfAsync(player).ConfigureAwait(false);
|
|
}
|
|
|
|
await player.MagicEffectList.ClearEffectsAfterDeathAsync().ConfigureAwait(false);
|
|
|
|
if (player.ShopStorage?.StoreOpen ?? false)
|
|
{
|
|
var storeCloseAction = new CloseStoreAction();
|
|
await storeCloseAction.CloseStoreAsync(player).ConfigureAwait(false);
|
|
}
|
|
|
|
await player.RemoveSummonAsync().ConfigureAwait(false);
|
|
await player.MagicEffectList.ClearEffectsAfterDeathAsync().ConfigureAwait(false);
|
|
await player.WarpToAsync(entrance).ConfigureAwait(false);
|
|
|
|
// The bots of the entering party leader follow them in (each checked against the same
|
|
// entry restrictions, no ticket needed - the leader's own ticket legitimizes the visit).
|
|
Bots.BotMiniGameHandler.BringPartyBotsAlong(player, partyBots, miniGameDefinition, miniGame);
|
|
}
|
|
else
|
|
{
|
|
await player.InvokeViewPlugInAsync<IShowMiniGameEnterResultPlugIn>(p => p.ShowResultAsync(miniGameType, enterResult)).ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
private bool CheckPlayerKillState(MiniGameDefinition miniGameDefinition, Player player)
|
|
{
|
|
if (miniGameDefinition.ArePlayerKillersAllowedToEnter)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return player.SelectedCharacter?.State < HeroState.PlayerKiller1stStage;
|
|
}
|
|
|
|
private async ValueTask ConsumeTicketItemAsync(Item? ticketItem, Player player)
|
|
{
|
|
if (ticketItem is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ticketItem.Durability > 0)
|
|
{
|
|
ticketItem.Durability -= 1;
|
|
}
|
|
|
|
if (ticketItem.Durability == 0)
|
|
{
|
|
if (player.Inventory is { } inventory)
|
|
{
|
|
await inventory.RemoveItemAsync(ticketItem).ConfigureAwait(false);
|
|
}
|
|
|
|
await player.DestroyInventoryItemAsync(ticketItem).ConfigureAwait(false);
|
|
}
|
|
else
|
|
{
|
|
await player.InvokeViewPlugInAsync<IItemDurabilityChangedPlugIn>(p => p.ItemDurabilityChangedAsync(ticketItem, false)).ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
private bool CheckTicketItem(MiniGameDefinition miniGameDefinition, Player player, byte gameTicketInventoryIndex, out Item? ticketItem)
|
|
{
|
|
ticketItem = null;
|
|
|
|
if (miniGameDefinition.TicketItem is not { } ticketItemDefinition)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (gameTicketInventoryIndex != UndefinedTicketSlot)
|
|
{
|
|
ticketItem = player.Inventory?.GetItem(gameTicketInventoryIndex);
|
|
if (this.IsValidTicket(ticketItem, ticketItemDefinition, miniGameDefinition))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
foreach (var candidateItem in player.Inventory?.FindItemsByDefinition(ticketItemDefinition) ?? Enumerable.Empty<Item>())
|
|
{
|
|
if (this.IsValidTicket(candidateItem, ticketItemDefinition, miniGameDefinition))
|
|
{
|
|
ticketItem = candidateItem;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool IsValidTicket(Item? ticketItem, ItemDefinition requiredItemDefinition, MiniGameDefinition miniGameDefinition)
|
|
{
|
|
return ticketItem is not null
|
|
&& requiredItemDefinition.Equals(ticketItem.Definition)
|
|
&& ticketItem.Durability > 0
|
|
&& ticketItem.Level == miniGameDefinition.TicketItemLevel;
|
|
}
|
|
|
|
private bool CheckEntranceFee(MiniGameDefinition miniGameDefinition, Player player, out int entranceFee)
|
|
{
|
|
entranceFee = Math.Max(miniGameDefinition.EntranceFee, 0);
|
|
return entranceFee <= player.Money;
|
|
}
|
|
} |