baseline: OpenMU upstream b5a0961 (fresh source)
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
// <copyright file="ElfSoldierBuffRequestAction.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.Quests;
|
||||
|
||||
using MUnique.OpenMU.AttributeSystem;
|
||||
using MUnique.OpenMU.DataModel.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Action of requesting the elf soldier buff.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Instead of hard-coding all this stuff, we could define something like a 'RequestableBuff' in the MonsterDefinition.
|
||||
/// </remarks>
|
||||
public class ElfSoldierBuffRequestAction
|
||||
{
|
||||
private static readonly short ElfSoldierNumber = 257;
|
||||
|
||||
private static readonly MagicEffectDefinition BuffEffect = new SoldierBuffMagicEffectDefinition
|
||||
{
|
||||
InformObservers = true,
|
||||
Name = "Elf Soldier Buff",
|
||||
Number = 3,
|
||||
StopByDeath = true,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Requests the buff and adds it to the <see cref="Player.MagicEffectList"/> when the player is allowed to get it.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public async ValueTask RequestBuffAsync(Player player)
|
||||
{
|
||||
if (player.OpenedNpc is null
|
||||
|| player.OpenedNpc.Definition.NpcWindow != NpcWindow.NpcDialog
|
||||
|| player.OpenedNpc.Definition.Number != ElfSoldierNumber)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.Level > 220)
|
||||
{
|
||||
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.ElfSoldierStrongEnoughMessage)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await player.MagicEffectList.AddEffectAsync(new MagicEffect(
|
||||
TimeSpan.FromMinutes(60),
|
||||
BuffEffect,
|
||||
new MagicEffect.ElementWithTarget(new ConstantElement(50 + (player.Level / 5), AggregateType.AddFinal), Stats.DefenseFinal),
|
||||
new MagicEffect.ElementWithTarget(new ConstantElement(45 + (player.Level / 3)), Stats.GreaterDamageBonus))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private sealed class SoldierBuffMagicEffectDefinition : MagicEffectDefinition
|
||||
{
|
||||
public SoldierBuffMagicEffectDefinition()
|
||||
{
|
||||
this.PowerUpDefinitions = new List<PowerUpDefinition>(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
68
src/GameLogic/PlayerActions/Quests/PlayerQuestExtensions.cs
Normal file
68
src/GameLogic/PlayerActions/Quests/PlayerQuestExtensions.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
// <copyright file="PlayerQuestExtensions.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.Quests;
|
||||
|
||||
using MUnique.OpenMU.DataModel.Configuration.Quests;
|
||||
|
||||
/// <summary>
|
||||
/// Quest related extension methods.
|
||||
/// </summary>
|
||||
public static class PlayerQuestExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the state of the quest.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
/// <param name="group">The group of the quest.</param>
|
||||
/// <param name="number">The number of the quest.</param>
|
||||
/// <returns>The quest state, if found; Otherwise, <c>null</c>.</returns>
|
||||
public static CharacterQuestState? GetQuestState(this Player player, short group, short number)
|
||||
{
|
||||
return player.SelectedCharacter?.QuestStates.FirstOrDefault(state => state.Group == group && (state.ActiveQuest?.Number == number || state.ActiveQuest?.StartingNumber == number));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the quest of a group.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
/// <param name="group">The group of the quest.</param>
|
||||
/// <returns>The quest state, if found; Otherwise, <c>null</c>.</returns>
|
||||
public static CharacterQuestState? GetQuestState(this Player player, short group)
|
||||
{
|
||||
return player.SelectedCharacter?.QuestStates.FirstOrDefault(state => state.Group == group);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the quest definition for the specified group and number depending on the players character class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
/// <param name="group">The group.</param>
|
||||
/// <param name="number">The number.</param>
|
||||
/// <returns>The quest definition for the specified group and number depending on the players character class.</returns>
|
||||
public static QuestDefinition? GetQuest(this Player player, short group, short number)
|
||||
{
|
||||
var possibleQuestsOfGroup = player.OpenedNpc?.Definition.Quests
|
||||
.Where(q => q.Group == group)
|
||||
.Where(q => q.QualifiedCharacter is null || Equals(q.QualifiedCharacter, player.SelectedCharacter?.CharacterClass))
|
||||
.OrderBy(q => q.Number);
|
||||
|
||||
return possibleQuestsOfGroup?.FirstOrDefault(q => q.StartingNumber == number || q.Number == number);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available quests of the currently opened NPC depending on the players character class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
/// <returns>The available quests of the currently opened NPC depending on the players character class.</returns>
|
||||
public static IEnumerable<QuestDefinition> GetAvailableQuestsOfOpenedNpc(this Player player)
|
||||
{
|
||||
return player.OpenedNpc?.Definition.Quests
|
||||
.Where(q => q.QualifiedCharacter is null
|
||||
|| Equals(q.QualifiedCharacter, player.SelectedCharacter?.CharacterClass))
|
||||
.Where(q => q.MinimumCharacterLevel <= player.Level
|
||||
&& (q.MaximumCharacterLevel == default || q.MaximumCharacterLevel >= player.Level))
|
||||
?? Enumerable.Empty<QuestDefinition>();
|
||||
}
|
||||
}
|
||||
32
src/GameLogic/PlayerActions/Quests/QuestCancelAction.cs
Normal file
32
src/GameLogic/PlayerActions/Quests/QuestCancelAction.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
// <copyright file="QuestCancelAction.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.Quests;
|
||||
|
||||
using MUnique.OpenMU.GameLogic.Views.Quest;
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the specified quest for the player.
|
||||
/// </summary>
|
||||
public class QuestCancelAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Cancels the quest for the player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
/// <param name="group">The group of the quest.</param>
|
||||
/// <param name="number">The number of the quest.</param>
|
||||
public async ValueTask CancelQuestAsync(Player player, short group, short number)
|
||||
{
|
||||
var questState = player.GetQuestState(group, number);
|
||||
if (questState?.ActiveQuest is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var quest = questState.ActiveQuest;
|
||||
await questState.ClearAsync(player.PersistenceContext).ConfigureAwait(false);
|
||||
await player.InvokeViewPlugInAsync<IQuestCancelledPlugIn>(p => p.QuestCancelledAsync(quest)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
31
src/GameLogic/PlayerActions/Quests/QuestClientAction.cs
Normal file
31
src/GameLogic/PlayerActions/Quests/QuestClientAction.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// <copyright file="QuestClientAction.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.Quests;
|
||||
|
||||
/// <summary>
|
||||
/// Is called when the player completed the an action (e.g. the tutorial).
|
||||
/// The server checks if the specified quest is currently in progress.
|
||||
/// If the quest got a Condition(condition type 0x10) for this flag,
|
||||
/// the condition is flagged as fulfilled.
|
||||
/// </summary>
|
||||
public class QuestClientAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Clients the action.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
/// <param name="group">The group.</param>
|
||||
/// <param name="number">The number.</param>
|
||||
public void ClientAction(Player player, short group, short number)
|
||||
{
|
||||
var questState = player.SelectedCharacter?.QuestStates.FirstOrDefault(s => s.Group == group && s.ActiveQuest?.Number == number && s.ActiveQuest.RequiresClientAction);
|
||||
if (questState is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
questState.ClientActionPerformed = true;
|
||||
}
|
||||
}
|
||||
197
src/GameLogic/PlayerActions/Quests/QuestCompletionAction.cs
Normal file
197
src/GameLogic/PlayerActions/Quests/QuestCompletionAction.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
// <copyright file="QuestCompletionAction.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.Quests;
|
||||
|
||||
using MUnique.OpenMU.AttributeSystem;
|
||||
using MUnique.OpenMU.DataModel.Configuration.Quests;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Views.Character;
|
||||
using MUnique.OpenMU.GameLogic.Views.Inventory;
|
||||
using MUnique.OpenMU.GameLogic.Views.Quest;
|
||||
using MUnique.OpenMU.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// A player action which implements the completion of a quest.
|
||||
/// </summary>
|
||||
public class QuestCompletionAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to completes the quest of the given group and number for the specified player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
/// <param name="group">The group.</param>
|
||||
/// <param name="number">The number.</param>
|
||||
public async ValueTask CompleteQuestAsync(Player player, short group, short number)
|
||||
{
|
||||
using var loggerScope = player.Logger.BeginScope(this.GetType());
|
||||
var questState = player.GetQuestState(group, number);
|
||||
var activeQuest = questState?.ActiveQuest;
|
||||
if (activeQuest is null)
|
||||
{
|
||||
player.Logger.LogDebug("Failed, no active quest");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var requiredItem in activeQuest.RequiredItems)
|
||||
{
|
||||
var requiredLevel = requiredItem.DropItemGroup?.ItemLevel;
|
||||
var itemCount = player.Inventory?.Items
|
||||
.Count(i => Equals(i.Definition, requiredItem.Item)
|
||||
&& (requiredLevel is null || requiredLevel == i.Level));
|
||||
|
||||
if (requiredItem.MinimumNumber <= itemCount)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
player.Logger.LogDebug("Failed, required item not found: {0}", requiredItem.Item!.GetNameForLevel(requiredLevel ?? 0));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var requiredKills in activeQuest.RequiredMonsterKills)
|
||||
{
|
||||
var currentKillCount = questState!.RequirementStates.FirstOrDefault(r => object.Equals(r.Requirement, requiredKills))?.KillCount ?? 0;
|
||||
if (currentKillCount >= requiredKills.MinimumNumber)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
player.Logger.LogDebug("Failed, required kills of monster {0}: {1}/{2};", requiredKills.Monster?.Designation, currentKillCount, requiredKills.MinimumNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeQuest.RequiresClientAction && !questState!.ClientActionPerformed)
|
||||
{
|
||||
player.Logger.LogDebug("Failed, client action not performed.");
|
||||
return;
|
||||
}
|
||||
|
||||
questState!.LastFinishedQuest = activeQuest;
|
||||
foreach (var requiredItem in activeQuest.RequiredItems)
|
||||
{
|
||||
var requiredLevel = requiredItem.DropItemGroup?.ItemLevel;
|
||||
var items = player.Inventory!.Items
|
||||
.Where(item => Equals(item.Definition, requiredItem.Item)
|
||||
&& (requiredLevel is null || requiredLevel == item.Level))
|
||||
.Take(requiredItem.MinimumNumber)
|
||||
.ToList();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
await player.DestroyInventoryItemAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var reward in activeQuest.Rewards)
|
||||
{
|
||||
await AddRewardAsync(player, reward, activeQuest).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await questState.ClearAsync(player.PersistenceContext).ConfigureAwait(false);
|
||||
await player.InvokeViewPlugInAsync<IQuestCompletionResponsePlugIn>(p => p.QuestCompletedAsync(activeQuest)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async ValueTask AddRewardAsync(Player player, QuestReward reward, QuestDefinition quest)
|
||||
{
|
||||
switch (reward.RewardType)
|
||||
{
|
||||
case QuestRewardType.Attribute:
|
||||
var attribute = player.SelectedCharacter!.Attributes.FirstOrDefault(a => object.Equals(a.Definition, reward.AttributeReward));
|
||||
if (attribute is null)
|
||||
{
|
||||
attribute = player.PersistenceContext.CreateNew<StatAttribute>(reward.AttributeReward, 0);
|
||||
player.SelectedCharacter.Attributes.Add(attribute);
|
||||
}
|
||||
|
||||
attribute.Value += reward.Value;
|
||||
|
||||
// Compensate level-up points when doing a quest at a later level.
|
||||
if (attribute.Definition == Stats.PointsPerLevelUp)
|
||||
{
|
||||
var playerLevel = (int)player.Attributes![Stats.Level];
|
||||
player.SelectedCharacter.LevelUpPoints += (playerLevel - quest.MinimumCharacterLevel) * reward.Value;
|
||||
}
|
||||
|
||||
await player.InvokeViewPlugInAsync<ILegacyQuestRewardPlugIn>(p => p.ShowAsync(player, QuestRewardType.Attribute, reward.Value, attribute.Definition)).ConfigureAwait(false);
|
||||
break;
|
||||
case QuestRewardType.Item:
|
||||
var item = player.PersistenceContext.CreateNew<Item>();
|
||||
item.AssignValues(reward.ItemReward ?? throw new InvalidOperationException($"Reward {reward.GetId()} is defined as item reward, but has no item assigned"));
|
||||
if (await player.Inventory!.AddItemAsync(item).ConfigureAwait(false))
|
||||
{
|
||||
await player.InvokeViewPlugInAsync<IItemAppearPlugIn>(p => p.ItemAppearAsync(item)).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
player.CurrentMap?.AddAsync(new DroppedItem(item, player.Position, player.CurrentMap, player, player.GetAsEnumerable()));
|
||||
}
|
||||
|
||||
break;
|
||||
case QuestRewardType.LevelUpPoints:
|
||||
player.SelectedCharacter!.LevelUpPoints += reward.Value;
|
||||
await player.InvokeViewPlugInAsync<ILegacyQuestRewardPlugIn>(p => p.ShowAsync(player, QuestRewardType.LevelUpPoints, reward.Value, null)).ConfigureAwait(false);
|
||||
break;
|
||||
case QuestRewardType.CharacterEvolutionFirstToSecond:
|
||||
player.SelectedCharacter!.CharacterClass = player.SelectedCharacter.CharacterClass?.NextGenerationClass
|
||||
?? throw new InvalidOperationException($"Current character class has no next generation");
|
||||
await player.ForEachWorldObserverAsync<ILegacyQuestRewardPlugIn>(
|
||||
p => p.ShowAsync(
|
||||
player,
|
||||
QuestRewardType.CharacterEvolutionFirstToSecond,
|
||||
reward.Value,
|
||||
null),
|
||||
true).ConfigureAwait(false);
|
||||
break;
|
||||
case QuestRewardType.CharacterEvolutionSecondToThird:
|
||||
player.SelectedCharacter!.CharacterClass = player.SelectedCharacter.CharacterClass?.NextGenerationClass
|
||||
?? throw new InvalidOperationException($"Current character class has no next generation");
|
||||
await player.ForEachWorldObserverAsync<ILegacyQuestRewardPlugIn>(
|
||||
p => p.ShowAsync(
|
||||
player,
|
||||
QuestRewardType.CharacterEvolutionSecondToThird,
|
||||
reward.Value,
|
||||
null),
|
||||
true).ConfigureAwait(false);
|
||||
await player.InvokeViewPlugInAsync<IUpdateMasterStatsPlugIn>(p => p.SendMasterStatsAsync()).ConfigureAwait(false);
|
||||
break;
|
||||
case QuestRewardType.Experience:
|
||||
await player.AddExperienceAsync(reward.Value, null).ConfigureAwait(false);
|
||||
break;
|
||||
case QuestRewardType.Money:
|
||||
player.TryAddMoney(reward.Value);
|
||||
await player.InvokeViewPlugInAsync<IUpdateMoneyPlugIn>(p => p.UpdateMoneyAsync()).ConfigureAwait(false);
|
||||
break;
|
||||
case QuestRewardType.GensAttribution:
|
||||
// not yet implemented.
|
||||
break;
|
||||
case QuestRewardType.Skill:
|
||||
if (reward.SkillReward is not { } skill)
|
||||
{
|
||||
player.Logger.LogError("Reward has no skill defined.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.SkillList is not { } skillList)
|
||||
{
|
||||
player.Logger.LogError("Can't reward the skill; SkillList is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!skillList.ContainsSkill(skill.Number.ToUnsigned()))
|
||||
{
|
||||
await skillList.AddLearnedSkillAsync(skill).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
player.Logger.LogWarning($"Skill {skill} is already learned.");
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
player.Logger.LogWarning("Unknown reward type: {0}", reward.RewardType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/GameLogic/PlayerActions/Quests/QuestExtensions.cs
Normal file
30
src/GameLogic/PlayerActions/Quests/QuestExtensions.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// <copyright file="QuestExtensions.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.Quests;
|
||||
|
||||
using MUnique.OpenMU.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions regarding quests.
|
||||
/// </summary>
|
||||
public static class QuestExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears the quest state for the specified player.
|
||||
/// </summary>
|
||||
/// <param name="questState">State of the quest.</param>
|
||||
/// <param name="persistenceContext">The persistence context of the player.</param>
|
||||
public static async ValueTask ClearAsync(this CharacterQuestState questState, IContext persistenceContext)
|
||||
{
|
||||
questState.ActiveQuest = null;
|
||||
questState.ClientActionPerformed = false;
|
||||
foreach (var requirementState in questState.RequirementStates)
|
||||
{
|
||||
await persistenceContext.DeleteAsync(requirementState).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
questState.RequirementStates.Clear();
|
||||
}
|
||||
}
|
||||
56
src/GameLogic/PlayerActions/Quests/QuestSelectAction.cs
Normal file
56
src/GameLogic/PlayerActions/Quests/QuestSelectAction.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
// <copyright file="QuestSelectAction.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.Quests;
|
||||
|
||||
using MUnique.OpenMU.DataModel.Configuration.Quests;
|
||||
using MUnique.OpenMU.GameLogic.Views.Quest;
|
||||
|
||||
/// <summary>
|
||||
/// A player action which implements the selecting of a quest in the quest list.
|
||||
/// </summary>
|
||||
public class QuestSelectAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to select the quest of the given group and number for the specified player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
/// <param name="group">The <see cref="QuestDefinition.Group"/>.</param>
|
||||
/// <param name="number">The <see cref="QuestDefinition.StartingNumber"/>.</param>
|
||||
public async ValueTask SelectQuestAsync(Player player, short group, short number)
|
||||
{
|
||||
using var loggerScope = player.Logger.BeginScope(this.GetType());
|
||||
var quest = player.GetQuest(group, number);
|
||||
if (quest is null)
|
||||
{
|
||||
player.Logger.LogDebug("Failed, quest not found");
|
||||
return;
|
||||
}
|
||||
|
||||
var questState = player.SelectedCharacter?.QuestStates.FirstOrDefault(q => q.Group == group);
|
||||
if (questState?.ActiveQuest != null)
|
||||
{
|
||||
player.Logger.LogDebug("There is already an active quest of this group.");
|
||||
await player.InvokeViewPlugInAsync<IQuestProgressPlugIn>(p => p.ShowQuestProgressAsync(questState.ActiveQuest, false)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (quest.Equals(questState?.LastFinishedQuest) && !quest.Repeatable)
|
||||
{
|
||||
player.Logger.LogDebug("The quest is not repeatable.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (quest.MinimumCharacterLevel > player.Level || (quest.MaximumCharacterLevel > 0 && quest.MaximumCharacterLevel < player.Level))
|
||||
{
|
||||
player.Logger.LogDebug("Failed, character level {0} not in allowed range {1} to {2}.", player.Level, quest.MinimumCharacterLevel, quest.MaximumCharacterLevel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (quest.StartingNumber == number && quest.Number != number)
|
||||
{
|
||||
await player.InvokeViewPlugInAsync<IQuestStepInfoPlugIn>(p => p.ShowQuestStepInfoAsync(quest.Group, quest.StartingNumber)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/GameLogic/PlayerActions/Quests/QuestStartAction.cs
Normal file
75
src/GameLogic/PlayerActions/Quests/QuestStartAction.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
// <copyright file="QuestStartAction.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.Quests;
|
||||
|
||||
using MUnique.OpenMU.GameLogic.Views.Inventory;
|
||||
using MUnique.OpenMU.GameLogic.Views.Quest;
|
||||
|
||||
/// <summary>
|
||||
/// A player action which implements the starting of a quest.
|
||||
/// </summary>
|
||||
public class QuestStartAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to start the quest of the given group and number for the specified player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
/// <param name="group">The group.</param>
|
||||
/// <param name="number">The number.</param>
|
||||
public async ValueTask StartQuestAsync(Player player, short group, short number)
|
||||
{
|
||||
using var loggerScope = player.Logger.BeginScope(this.GetType());
|
||||
var quest = player.GetQuest(group, number);
|
||||
if (quest is null)
|
||||
{
|
||||
player.Logger.LogWarning("Failed, quest not found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (quest.MinimumCharacterLevel > player.Level || (quest.MaximumCharacterLevel > 0 && quest.MaximumCharacterLevel < player.Level))
|
||||
{
|
||||
player.Logger.LogDebug("Failed, character level {0} not in allowed range {1} to {2}.", player.Level, quest.MinimumCharacterLevel, quest.MaximumCharacterLevel);
|
||||
return;
|
||||
}
|
||||
|
||||
var questState = player.SelectedCharacter!.QuestStates.FirstOrDefault(q => q.Group == group);
|
||||
if (questState is null)
|
||||
{
|
||||
questState = player.PersistenceContext.CreateNew<CharacterQuestState>();
|
||||
questState.Group = group;
|
||||
player.SelectedCharacter.QuestStates.Add(questState);
|
||||
}
|
||||
|
||||
if (questState.ActiveQuest != null)
|
||||
{
|
||||
player.Logger.LogDebug("There is already an active quest of this group.");
|
||||
await player.InvokeViewPlugInAsync<IQuestProgressPlugIn>(p => p.ShowQuestProgressAsync(questState.ActiveQuest, false)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Equals(questState.LastFinishedQuest, quest) && !quest.Repeatable)
|
||||
{
|
||||
player.Logger.LogDebug("The quest is not repeatable.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (quest.RequiredStartMoney > 0)
|
||||
{
|
||||
if (player.TryRemoveMoney(quest.RequiredStartMoney))
|
||||
{
|
||||
await player.InvokeViewPlugInAsync<IUpdateMoneyPlugIn>(p => p.UpdateMoneyAsync()).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.NotEnoughMoneyToProceed)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await questState.ClearAsync(player.PersistenceContext).ConfigureAwait(false);
|
||||
questState.ActiveQuest = quest;
|
||||
await player.InvokeViewPlugInAsync<IQuestStartedPlugIn>(p => p.QuestStartedAsync(quest)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user