Skip to content
2 changes: 1 addition & 1 deletion src/GameLogic/AttackableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ private static async ValueTask ApplyMagicEffectAsync(this IAttackable target, IA
await target.MagicEffectList.AddEffectAsync(magicEffect).ConfigureAwait(false);
if (target is ISupportWalk walkSupporter
&& walkSupporter.IsWalking
&& magicEffectDefinition.PowerUpDefinitions.Any(e => e.TargetAttribute == Stats.IsFrozen || e.TargetAttribute == Stats.IsStunned))
&& magicEffectDefinition.PowerUpDefinitions.Any(e => e.TargetAttribute == Stats.IsFrozen || e.TargetAttribute == Stats.IsStunned || e.TargetAttribute == Stats.IsAsleep))
{
await walkSupporter.StopWalkingAsync().ConfigureAwait(false);

Expand Down
7 changes: 6 additions & 1 deletion src/GameLogic/Attributes/Stats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,12 @@ public class Stats
/// <summary>
/// Gets the attribute definition, which defines if a player has stun effect applied.
/// </summary>
public static AttributeDefinition IsStunned { get; } = new (new Guid("22C86BAF-7F27-478D-8075-E4465C2859DD"), "Is stunned", "The player is poisoned and loses health");
public static AttributeDefinition IsStunned { get; } = new (new Guid("22C86BAF-7F27-478D-8075-E4465C2859DD"), "Is stunned", "The player is stunned and can't move.");

/// <summary>
/// Gets the attribute definition, which defines if a player has asleep effect applied.
/// </summary>
public static AttributeDefinition IsAsleep { get; } = new(new Guid("0518F532-7A8F-4491-8A23-98B620608CB3"), "Is asleep", "The player is asleep and can't move until hit.");

/// <summary>
/// Gets the ice resistance attribute definition. Value range from 0 to 1.
Expand Down
23 changes: 21 additions & 2 deletions src/GameLogic/MagicEffectsList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ namespace MUnique.OpenMU.GameLogic;
using System.Collections;
using Nito.AsyncEx;
using MUnique.OpenMU.GameLogic.Views.World;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.AttributeSystem;

/// <summary>
/// The list of magic effects of a player instance. Automatically applies the power-ups of the effects to the player.
/// </summary>
public class MagicEffectsList : AsyncDisposable
{
private const byte InvisibleEffectStartIndex = 200;
private readonly BitArray _contains = new (0x100);
private readonly BitArray _contains = new(0x100);
private readonly IAttackable _owner;
private readonly AsyncLock _addLock = new ();
private readonly AsyncLock _addLock = new();

/// <summary>
/// Initializes a new instance of the <see cref="MagicEffectsList"/> class.
Expand Down Expand Up @@ -93,6 +95,23 @@ public async ValueTask ClearAllEffectsAsync()
}
}

/// <summary>
/// Clear the effects that produce a specific stat.
/// </summary>
/// <param name="stat">The stat produced by effect</param>
public async ValueTask ClearAllEffectsProducingSpecificStatAsync(AttributeDefinition stat)
{
var effects = this.ActiveEffects.Values.ToArray();

foreach (var effect in effects)
{
if (effect.PowerUpElements.Any(p => p.Target == stat))
{
await effect.DisposeAsync().ConfigureAwait(false);
}
}
}

/// <summary>
/// Clears the effects after death of the player.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/GameLogic/NPC/AttackableNpcBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ public int Health
return null;
}

if (this.Attributes[Stats.IsAsleep] > 0)
{
await this.MagicEffectList.ClearAllEffectsProducingSpecificStatAsync(Stats.IsAsleep).ConfigureAwait(false);
}

var hitInfo = await attacker.CalculateDamageAsync(this, skill, isCombo, damageFactor).ConfigureAwait(false);
await this.HitAsync(hitInfo, attacker, skill?.Skill).ConfigureAwait(false);
if (hitInfo.HealthDamage > 0)
Expand Down
2 changes: 1 addition & 1 deletion src/GameLogic/NPC/BasicMonsterIntelligence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ private async ValueTask TickAsync()
return;
}

if (this.Monster.Attributes[Stats.IsStunned] > 0)
if (this.Monster.Attributes[Stats.IsStunned] > 0 || this.Monster.Attributes[Stats.IsAsleep] > 0)
{
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/GameLogic/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1162,7 +1162,7 @@ public async ValueTask WalkToAsync(Point target, Memory<WalkingStep> steps)
return;
}

if (attributes[Stats.IsFrozen] > 0 || attributes[Stats.IsStunned] > 0)
if (attributes[Stats.IsFrozen] > 0 || attributes[Stats.IsStunned] > 0 || attributes[Stats.IsAsleep] > 0)
{
return;
}
Expand Down
6 changes: 6 additions & 0 deletions src/GameLogic/PlayerActions/HitAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ public async ValueTask HitAsync(Player player, IAttackable target, byte attackAn
return;
}

if (attributes[Stats.IsAsleep] > 0)
{
player.Logger.LogWarning($"Probably Hacker - player {player} is attacking in asleep state");
return;
}

if (player.IsAtSafezone())
{
player.Logger.LogWarning($"Probably Hacker - player {player} is attacking from safezone");
Expand Down
6 changes: 6 additions & 0 deletions src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ private async ValueTask PerformAutomaticHitsAsync(Player player, ushort extraTar
return;
}

if (attributes[Stats.IsAsleep] > 0)
{
player.Logger.LogWarning("Probably Hacker - player {player} is attacking in sleep state", player);
return;
}

if (player.IsAtSafezone())
{
player.Logger.LogWarning("Probably Hacker - player {player} is attacking from safezone", player);
Expand Down
8 changes: 7 additions & 1 deletion src/GameLogic/PlayerActions/Skills/TargetedSkillAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace MUnique.OpenMU.GameLogic.PlayerActions.Skills;
/// </summary>
public class TargetedSkillAction
{
private static readonly Dictionary<short, short> SummonSkillToMonsterMapping = new ()
private static readonly Dictionary<short, short> SummonSkillToMonsterMapping = new()
{
{ 30, 26 }, // Goblin
{ 31, 32 }, // Stone Golem
Expand Down Expand Up @@ -51,6 +51,12 @@ public async ValueTask PerformSkillAsync(Player player, IAttackable target, usho
return;
}

if (attributes[Stats.IsAsleep] > 0)
{
player.Logger.LogWarning($"Probably Hacker - player {player} is attacking in asleep state");
return;
}

var skillEntry = player.SkillList?.GetSkill(skillId);
var skill = skillEntry?.Skill;
if (skill is null || skill.SkillType == SkillType.PassiveBoost)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ private void ApplyElementalModifier(ElementalType elementalModifier, Skill skill
return;
}

if ((SkillNumber)skill.Number is SkillNumber.Sleep)
{
skill.MagicEffectDef = this.CreateEffect(ElementalType.Undefined, MagicEffectNumber.Sleep, Stats.IsAsleep, 5);
return;
}

switch (elementalModifier)
{
case ElementalType.Ice:
Expand Down
88 changes: 88 additions & 0 deletions src/Persistence/Initialization/Updates/FixSleepSkillUpdate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// <copyright file="FixDrainLifeSkillUpdate.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.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;

/// <summary>
/// This adds the items required to enter the kalima map.
/// </summary>
[PlugIn(PlugInName, PlugInDescription)]
[Guid("A8827A3C-7F52-47CF-9EA5-562A9C06B986")]
public class FixSleepSkillUpdate : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Sleep Skill";

/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "Updates the Sleep skill definition to correct it and implement the new stat.";

/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixSleepSkillSeason6;

/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;

/// <inheritdoc />
public override string Name => PlugInName;

/// <inheritdoc />
public override string Description => PlugInDescription;

/// <inheritdoc />
public override bool IsMandatory => false;

/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 09, 01, 18, 0, 0, DateTimeKind.Utc);

/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var sleepAttribute = gameConfiguration.Attributes.FirstOrDefault(a => a.Id == Stats.IsAsleep.Id);

if (sleepAttribute == null)
{
var attr = context.CreateNew<AttributeSystem.AttributeDefinition>();
attr.Id = Stats.IsAsleep.Id;
attr.Designation = Stats.IsAsleep.Designation;
attr.Description = Stats.IsAsleep.Description;
gameConfiguration.Attributes.Add(attr);
}

var magicEffectSleep = gameConfiguration.MagicEffects.FirstOrDefault(e => e.Number == (short)MagicEffectNumber.Sleep);

if (magicEffectSleep == null)
{
magicEffectSleep = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(magicEffectSleep);
magicEffectSleep.Name = "Sleep";
magicEffectSleep.InformObservers = true;
magicEffectSleep.Number = (short)MagicEffectNumber.Sleep;
magicEffectSleep.StopByDeath = true;
magicEffectSleep.SubType = 255;
magicEffectSleep.Duration = context.CreateNew<PowerUpDefinitionValue>();
magicEffectSleep.Duration.ConstantValue.Value = 5;
var powerUp = context.CreateNew<PowerUpDefinition>();
magicEffectSleep.PowerUpDefinitions.Add(powerUp);
var boost = context.CreateNew<PowerUpDefinitionValue>();
powerUp.Boost = boost;
boost.ConstantValue.Value = 1;
powerUp.TargetAttribute = Stats.IsAsleep.GetPersistent(gameConfiguration);
}

var sleepSkill = gameConfiguration.Skills.First(x => x.Number == (short)SkillNumber.Sleep);
sleepSkill.SkillType = SkillType.Buff;
sleepSkill.MagicEffectDef = magicEffectSleep;
}
}
5 changes: 5 additions & 0 deletions src/Persistence/Initialization/Updates/UpdateVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,9 @@ public enum UpdateVersion
/// The version of the <see cref="AddItemDropGroupForJewelsUpdateSeason6"/>.
/// </summary>
AddItemDropGroupForJewelsSeason6 = 30,

/// <summary>
/// The version of the <see cref="FixSleepSkillUpdate"/>.
/// </summary>
FixSleepSkillSeason6 = 31,
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ public override void Initialize()
this.CreateSkill(SkillNumber.ChainLightning, "Chain Lightning", CharacterClasses.AllSummoners, DamageType.Curse, 70, 6, manaConsumption: 85, energyRequirement: 245, skillType: SkillType.AreaSkillExplicitTarget, skillTarget: SkillTarget.Explicit);
this.CreateSkill(SkillNumber.DamageReflection, "Damage Reflection", CharacterClasses.AllSummoners, distance: 5, abilityConsumption: 10, manaConsumption: 40, energyRequirement: 375);
this.CreateSkill(SkillNumber.Berserker, "Berserker", CharacterClasses.AllSummoners, DamageType.Curse, distance: 5, abilityConsumption: 50, manaConsumption: 100, energyRequirement: 620);
this.CreateSkill(SkillNumber.Sleep, "Sleep", CharacterClasses.AllSummoners, distance: 6, abilityConsumption: 3, manaConsumption: 20, energyRequirement: 180);
this.CreateSkill(SkillNumber.Sleep, "Sleep", CharacterClasses.AllSummoners, distance: 6, abilityConsumption: 3, manaConsumption: 20, energyRequirement: 180, skillType: SkillType.Buff);
this.CreateSkill(SkillNumber.Weakness, "Weakness", CharacterClasses.AllSummoners, distance: 6, abilityConsumption: 15, manaConsumption: 50, energyRequirement: 663);
this.CreateSkill(SkillNumber.Innovation, "Innovation", CharacterClasses.AllSummoners, distance: 6, abilityConsumption: 15, manaConsumption: 70, energyRequirement: 912);
this.CreateSkill(SkillNumber.Explosion223, "Explosion", CharacterClasses.AllSummoners, DamageType.Curse, 40, 6, 5, 90, energyRequirement: 100, elementalModifier: ElementalType.Fire);
Expand Down Expand Up @@ -226,7 +226,7 @@ public override void Initialize()
this.CreateSkill(SkillNumber.MonsterAttackSdInc, "Monster Attack SD Inc", CharacterClasses.AllMastersExceptFistMaster, damage: 11, skillType: SkillType.PassiveBoost);
this.CreateSkill(SkillNumber.MonsterAttackLifeInc, "Monster Attack Life Inc", CharacterClasses.AllMastersExceptFistMaster, damage: 6, skillType: SkillType.PassiveBoost);
this.CreateSkill(SkillNumber.SwellLifeProficiency, "Swell Life Proficiency", CharacterClasses.BladeMaster, damage: 7, abilityConsumption: 28, manaConsumption: 26, levelRequirement: 120);
this.CreateSkill(SkillNumber.MinimumAttackPowerInc, "Minimum Attack Power Inc", CharacterClasses.BladeMaster | CharacterClasses.DuelMaster |CharacterClasses.LordEmperor, DamageType.Physical, 22, skillType: SkillType.PassiveBoost);
this.CreateSkill(SkillNumber.MinimumAttackPowerInc, "Minimum Attack Power Inc", CharacterClasses.BladeMaster | CharacterClasses.DuelMaster | CharacterClasses.LordEmperor, DamageType.Physical, 22, skillType: SkillType.PassiveBoost);
this.CreateSkill(SkillNumber.MonsterAttackManaInc, "Monster Attack Mana Inc", CharacterClasses.AllMastersExceptFistMaster, damage: 6, skillType: SkillType.PassiveBoost);
this.CreateSkill(SkillNumber.PvPAttackRate, "PvP Attack Rate", CharacterClasses.AllMastersExceptFistMaster, damage: 14, skillType: SkillType.PassiveBoost);

Expand Down Expand Up @@ -427,7 +427,7 @@ private void InitializeNextSeasonMasterSkills()
this.CreateSkill(SkillNumber.WingofStormAbsPowUp, "Wing of Storm Abs PowUp", CharacterClasses.BladeMaster, damage: 1, skillType: SkillType.PassiveBoost);
this.CreateSkill(SkillNumber.WingofStormDefPowUp, "Wing of Storm Def PowUp", CharacterClasses.BladeMaster, damage: 17, skillType: SkillType.PassiveBoost);
this.CreateSkill(SkillNumber.IronDefense, "Iron Defense", CharacterClasses.AllMasters, damage: 1);
this.CreateSkill(SkillNumber.WingofStormAttPowUp, "Wing of Storm Att PowUp", CharacterClasses.BladeMaster, damage: 17, skillType: SkillType.PassiveBoost);
this.CreateSkill(SkillNumber.WingofStormAttPowUp, "Wing of Storm Att PowUp", CharacterClasses.BladeMaster, damage: 17, skillType: SkillType.PassiveBoost);
this.CreateSkill(SkillNumber.DeathStabProficiency, "Death Stab Proficiency", CharacterClasses.BladeMaster, DamageType.Physical, 7, 2, 26, 30, 160, elementalModifier: ElementalType.Wind);
this.CreateSkill(SkillNumber.StrikeofDestrProf, "Strike of Destr Prof", CharacterClasses.BladeMaster, DamageType.Physical, 7, 5, 24, 30, 100, elementalModifier: ElementalType.Ice);
this.CreateSkill(SkillNumber.MaximumAgIncrease, "Maximum AG Increase", CharacterClasses.AllMastersExceptFistMaster, damage: 8, skillType: SkillType.PassiveBoost);
Expand Down Expand Up @@ -649,7 +649,7 @@ private void InitializeMasterSkillData()
this.AddMasterSkillDefinition(SkillNumber.SpearMastery, SkillNumber.SpearStrengthener, SkillNumber.Undefined, 3, 3, SkillNumber.Undefined, 20, Formula120);

this.AddMasterSkillDefinition(SkillNumber.SwellLifeStrengt, SkillNumber.SwellLife, SkillNumber.Undefined, 3, 4, SkillNumber.SwellLife, 20, Formula181);
this.AddPassiveMasterSkillDefinition(SkillNumber.ManaReduction, Stats.ManaUsageReduction, AggregateType.AddRaw, Formula722Value, Formula722, 4, 3);
this.AddPassiveMasterSkillDefinition(SkillNumber.ManaReduction, Stats.ManaUsageReduction, AggregateType.AddRaw, Formula722Value, Formula722, 4, 3);
this.AddPassiveMasterSkillDefinition(SkillNumber.MonsterAttackSdInc, Stats.ShieldAfterMonsterKillMultiplier, AggregateType.AddFinal, Formula914, 4, 3);
this.AddPassiveMasterSkillDefinition(SkillNumber.MonsterAttackLifeInc, Stats.HealthAfterMonsterKillMultiplier, AggregateType.AddFinal, Formula4319, 4, 3);
this.AddMasterSkillDefinition(SkillNumber.SwellLifeProficiency, SkillNumber.SwellLifeStrengt, SkillNumber.Undefined, 3, 5, SkillNumber.SwellLife, 20, Formula181);
Expand Down Expand Up @@ -755,7 +755,7 @@ private void InitializeMasterSkillData()
this.AddPassiveMasterSkillDefinition(SkillNumber.ScepterMastery, Stats.ScepterBonusBaseDamage, AggregateType.AddRaw, Formula1154, 3, 3, SkillNumber.ScepterStrengthener); // todo pvp
this.AddPassiveMasterSkillDefinition(SkillNumber.ShieldMastery, Stats.BonusDefenseWithShield, AggregateType.AddRaw, Formula1204, 3, 3, SkillNumber.ShieldStrengthenerLordEmperor);
this.AddPassiveMasterSkillDefinition(SkillNumber.CommandAttackInc, Stats.BonusDefenseWithScepterCmdDiv, AggregateType.AddRaw, $"1 / ({Formula3822})", Formula3822, 3, 3);
this.AddPassiveMasterSkillDefinition(SkillNumber.DarkSpiritStr3, Stats.RavenExcDamageChanceBonus, AggregateType.AddRaw, $"{Formula120} / 100", Formula120,5, 3, SkillNumber.DarkSpiritStr2);
this.AddPassiveMasterSkillDefinition(SkillNumber.DarkSpiritStr3, Stats.RavenExcDamageChanceBonus, AggregateType.AddRaw, $"{Formula120} / 100", Formula120, 5, 3, SkillNumber.DarkSpiritStr2);
this.AddPassiveMasterSkillDefinition(SkillNumber.PetDurabilityStr, Stats.PetDurationIncrease, AggregateType.Multiplicate, Formula1204, 5, 3);

// RF
Expand All @@ -764,7 +764,7 @@ private void InitializeMasterSkillData()
this.AddPassiveMasterSkillDefinition(SkillNumber.IncreaseMaximumSd, Stats.MaximumShield, AggregateType.AddRaw, Formula30704, 2, 1);
this.AddPassiveMasterSkillDefinition(SkillNumber.IncreaseManaRecoveryRate, Stats.ManaRecoveryMultiplier, AggregateType.AddRaw, FormulaRecoveryIncrease181, Formula181, 2, 1, SkillNumber.Undefined, SkillNumber.Undefined, 20);
this.AddPassiveMasterSkillDefinition(SkillNumber.IncreasePoisonResistance, Stats.PoisonResistance, AggregateType.AddRaw, Formula120Value, Formula120, 2, 1);
this.AddPassiveMasterSkillDefinition(SkillNumber.DurabilityReduction2FistMaster, Stats.ItemDurationIncrease, AggregateType.Multiplicate,Formula1204, 3, 1, SkillNumber.DurabilityReduction1FistMaster);
this.AddPassiveMasterSkillDefinition(SkillNumber.DurabilityReduction2FistMaster, Stats.ItemDurationIncrease, AggregateType.Multiplicate, Formula1204, 3, 1, SkillNumber.DurabilityReduction1FistMaster);
this.AddPassiveMasterSkillDefinition(SkillNumber.IncreaseSdRecoveryRate, Stats.ShieldRecoveryMultiplier, AggregateType.AddRaw, FormulaRecoveryIncrease120, Formula120, 3, 1, SkillNumber.IncreaseMaximumSd, SkillNumber.Undefined, 20);
this.AddPassiveMasterSkillDefinition(SkillNumber.IncreaseHpRecoveryRate, Stats.HealthRecoveryMultiplier, AggregateType.AddRaw, FormulaRecoveryIncrease120, Formula120, 3, 1, SkillNumber.IncreaseManaRecoveryRate, SkillNumber.Undefined, 20);
this.AddPassiveMasterSkillDefinition(SkillNumber.IncreaseLightningResistance, Stats.LightningResistance, AggregateType.AddRaw, Formula120Value, Formula120, 3, 1, requiredSkill1: SkillNumber.IncreasePoisonResistance);
Expand Down