Skip to content

Add Summoner Curse Skills and Lightning Shock Skill Fix#738

Open
ze-dom wants to merge 7 commits intoMUnique:masterfrom
ze-dom:lightning_shock
Open

Add Summoner Curse Skills and Lightning Shock Skill Fix#738
ze-dom wants to merge 7 commits intoMUnique:masterfrom
ze-dom:lightning_shock

Conversation

@ze-dom
Copy link
Copy Markdown
Contributor

@ze-dom ze-dom commented Mar 26, 2026

To-do

  • UpdatePlugins (after discussion)
  • Comment changes with source references

Developments

  • Summoner's Explosion, Requiem and Pollution skills (book) and their magic effects
  • Summoner's Lightning Shock skill completion
  • Added Skill.SkipElementalModifierto bypass element resistance checks

Comment on lines -284 to -286
var hitChance = attackRound < areaSkillSettings.MinimumNumberOfHitsPerTarget
? 1.0
: Math.Min(areaSkillSettings.HitChancePerDistanceMultiplier, Math.Pow(areaSkillSettings.HitChancePerDistanceMultiplier, player.GetDistanceTo(target)));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure this is not accurate for Flame and Twister. Those may sometimes not hit at all. Will check it another time. For now I've kept it as it was.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, it's hard to replicate the exact same logic on server side. Flame and Twisters hits were originally calculated on the client side, when rendering each frame(!), depending on the actual (random) animation. So, increasing the frame rate from 25 to 60 fps led to a higher chance of hits 😄

currentTarget = nextTarget;
}

await movableTarget.MoveAsync(currentTarget).ConfigureAwait(false);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await observable.ForEachWorldObserverAsync<IObjectMovedPlugIn>(p => p.ObjectMovedAsync(walkSupporter, MoveType.Instant), true).ConfigureAwait(false);
}
}
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
return false;
}
var skipModifier = skillEntry.Skill.SkipElementalModifier;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only the early seasons skills (S1, S2) use the elemental resistance checks:
emu, zTeamS6.3

return;
}

var multiplier = magicEffectDefinition.Number == ExplosionMagicEffectNumber ? attacker.Attributes[Stats.BleedingDamageMultiplier] : 0.6f;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emu, zTeamS6.3
Because the FireTomeMastery (Explosion skill) is a passive, we need to hardcode 0.6f here, otherwise you can use Requiem skill and also get the FireTomeMastery multiplier bonus.

await this._attackAction.AttackAsync(player, extraTargetId, PollutionSkillId, point, rotation).ConfigureAwait(false);
}
});
}
Copy link
Copy Markdown
Contributor Author

@ze-dom ze-dom Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

skill.SkipElementalModifier = true;
skill.MagicEffectDef = this.CreateEffect(ElementalType.Ice, MagicEffectNumber.Iced, Stats.IsIced, 2);
return;
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isStunnedPowerUpDefinition.Boost = this.Context.CreateNew<PowerUpDefinitionValue>();
isStunnedPowerUpDefinition.Boost.ConstantValue.Value = 1;
}
} No newline at end of file
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't add any duration because from what I've seen so far the effect is not default to any skill, so it's more called in an ad hoc fashion.

@ggmammoth
Copy link
Copy Markdown

I want to implement a stun mechanic with a percentage chance and a fixed duration.
When a skill or attack is executed, I generate a random value between 0 and 1 and compare it to a predefined stun chance (for example, 20%). If the random value is within that chance, I apply a stun effect to the target for a specific duration (for example, 3 seconds).

// stun chance (example: 20%)
double stunChance = 0.20;

// generate random number between 0 and 1
double roll = Random.Shared.NextDouble();

// check if stun should be applied
if (roll <= stunChance)
{
// apply stun effect for 3 seconds
target.ApplyMagicEffect(
MagicEffectNumber.Stunned,
TimeSpan.FromSeconds(3));
}

this.CreateSkill(SkillNumber.Pollution, "Pollution", CharacterClasses.AllSummoners, DamageType.Curse, 80, 6, 15, 120, energyRequirement: 115, elementalModifier: ElementalType.Lightning); // Book of Lagle's skill
this.CreateSkill(SkillNumber.LightningShock, "Lightning Shock", CharacterClasses.AllSummoners, DamageType.Wizardry, 95, 6, 7, 115, energyRequirement: 823, elementalModifier: ElementalType.Lightning);
this.CreateSkill(SkillNumber.Explosion223, "Explosion", CharacterClasses.AllSummoners, DamageType.Curse, 40, 6, 5, 90, energyRequirement: 100, elementalModifier: ElementalType.Fire, skillType: SkillType.AreaSkillAutomaticHits); // Book of Samut's skill
this.AddAreaSkillSettings(SkillNumber.Explosion223, false, 0, 0, 0, effectRange: 2);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.CreateSkill(SkillNumber.Requiem, "Requiem", CharacterClasses.AllSummoners, DamageType.Curse, 65, 6, 10, 110, energyRequirement: 99, elementalModifier: ElementalType.Wind, skillType: SkillType.AreaSkillAutomaticHits); // Book of Neil's skill
this.AddAreaSkillSettings(SkillNumber.Requiem, false, 0, 0, 0, effectRange: 2);
this.CreateSkill(SkillNumber.Pollution, "Pollution", CharacterClasses.AllSummoners, DamageType.Curse, 80, 6, 15, 120, energyRequirement: 115, elementalModifier: ElementalType.Lightning, skillType: SkillType.AreaSkillAutomaticHits); // Book of Lagle's skill
this.AddAreaSkillSettings(SkillNumber.Pollution, false, 0, 0, 0, minimumHitsPerAttack: 4, maximumHitsPerAttack: 8, effectRange: 3);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.CreateSkill(SkillNumber.Pollution, "Pollution", CharacterClasses.AllSummoners, DamageType.Curse, 80, 6, 15, 120, energyRequirement: 115, elementalModifier: ElementalType.Lightning, skillType: SkillType.AreaSkillAutomaticHits); // Book of Lagle's skill
this.AddAreaSkillSettings(SkillNumber.Pollution, false, 0, 0, 0, minimumHitsPerAttack: 4, maximumHitsPerAttack: 8, effectRange: 3);
this.CreateSkill(SkillNumber.LightningShock, "Lightning Shock", CharacterClasses.AllSummoners, DamageType.Wizardry, 95, 6, 7, 115, energyRequirement: 823, elementalModifier: ElementalType.Lightning, skillType: SkillType.AreaSkillAutomaticHits);
this.AddAreaSkillSettings(SkillNumber.LightningShock, false, 0, 0, 0, minimumHitsPerAttack: 5, maximumHitsPerAttack: 12, useTargetAreaFilter: true, targetAreaDiameter: 14);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.AddMasterSkillDefinition(SkillNumber.LightningTomeMastery, SkillNumber.LightningTomeStren, SkillNumber.Undefined, 2, 3, SkillNumber.Undefined, 20, Formula181);
this.AddPassiveMasterSkillDefinition(SkillNumber.FireTomeMastery, Stats.BleedingDamageMultiplier, AggregateType.AddRaw, $"{Formula181} / 100", 3, 2, SkillNumber.FireTomeStrengthener);
this.AddPassiveMasterSkillDefinition(SkillNumber.WindTomeMastery, Stats.StunChance, AggregateType.AddRaw, $"{Formula120} / 100", 3, 2, SkillNumber.WindTomeStrengthener);
this.AddPassiveMasterSkillDefinition(SkillNumber.LightningTomeMastery, Stats.PollutionMoveTargetChance, AggregateType.AddRaw, $"{Formula181} / 100", 3, 2, SkillNumber.LightningTomeStren);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.AddMasterSkillDefinition(SkillNumber.WindTomeMastery, SkillNumber.WindTomeStrengthener, SkillNumber.Undefined, 2, 3, SkillNumber.Undefined, 20, Formula120);
this.AddMasterSkillDefinition(SkillNumber.LightningTomeMastery, SkillNumber.LightningTomeStren, SkillNumber.Undefined, 2, 3, SkillNumber.Undefined, 20, Formula181);
this.AddPassiveMasterSkillDefinition(SkillNumber.FireTomeMastery, Stats.BleedingDamageMultiplier, AggregateType.AddRaw, $"{Formula181} / 100", 3, 2, SkillNumber.FireTomeStrengthener);
this.AddPassiveMasterSkillDefinition(SkillNumber.WindTomeMastery, Stats.StunChance, AggregateType.AddRaw, $"{Formula120} / 100", 3, 2, SkillNumber.WindTomeStrengthener);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.AddMasterSkillDefinition(SkillNumber.FireTomeMastery, SkillNumber.FireTomeStrengthener, SkillNumber.Undefined, 2, 3, SkillNumber.Undefined, 20, Formula181);
this.AddMasterSkillDefinition(SkillNumber.WindTomeMastery, SkillNumber.WindTomeStrengthener, SkillNumber.Undefined, 2, 3, SkillNumber.Undefined, 20, Formula120);
this.AddMasterSkillDefinition(SkillNumber.LightningTomeMastery, SkillNumber.LightningTomeStren, SkillNumber.Undefined, 2, 3, SkillNumber.Undefined, 20, Formula181);
this.AddPassiveMasterSkillDefinition(SkillNumber.FireTomeMastery, Stats.BleedingDamageMultiplier, AggregateType.AddRaw, $"{Formula181} / 100", 3, 2, SkillNumber.FireTomeStrengthener);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magicEffect.Duration = this.Context.CreateNew<PowerUpDefinitionValue>();

// Damage is applied 5 times for every second
magicEffect.Duration.ConstantValue.Value = 5;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magicEffect.Duration = this.Context.CreateNew<PowerUpDefinitionValue>();

// Damage is applied 5 times for every second
magicEffect.Duration.ConstantValue.Value = 5;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.Attacker = attacker;
this.Owner = owner;
this._damage = damage;
this._damageTimer = new Timer(1000);
Copy link
Copy Markdown
Contributor Author

@ze-dom ze-dom Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emu, zTeamS6.3
While poison is every 3s, this one seems to be every second. Check the skill videos at muonlinefanz.com (they are cut short, but you can see the 2nd dmg coming up).

@ze-dom
Copy link
Copy Markdown
Contributor Author

ze-dom commented Mar 27, 2026

I want to implement a stun mechanic with a percentage chance and a fixed duration. When a skill or attack is executed, I generate a random value between 0 and 1 and compare it to a predefined stun chance (for example, 20%). If the random value is within that chance, I apply a stun effect to the target for a specific duration (for example, 3 seconds).

// stun chance (example: 20%) double stunChance = 0.20;

// generate random number between 0 and 1 double roll = Random.Shared.NextDouble();

// check if stun should be applied if (roll <= stunChance) { // apply stun effect for 3 seconds target.ApplyMagicEffect( MagicEffectNumber.Stunned, TimeSpan.FromSeconds(3)); }

Check this file. I do that there:
https://github.com/MUnique/OpenMU/pull/738/changes#diff-48e019d7ab8cfc38baac49d2275d7d992eca9f414cd658ec05c5c71603e242d7

@ze-dom ze-dom marked this pull request as ready for review March 30, 2026 15:24
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces significant enhancements to the Summoner class by implementing several key skills and refining existing mechanics. It adds new functionality for handling elemental modifiers, introduces a bleeding damage system, and updates skill configurations to ensure proper behavior and balance. These changes aim to improve the gameplay experience for the Summoner class by providing more robust and accurate skill mechanics.

Highlights

  • Summoner Skill Implementation: Implemented the Summoner's Explosion, Requiem, and Pollution skills, including their respective magic effects and mechanics.
  • Lightning Shock Skill Fix: Completed the implementation of the Summoner's Lightning Shock skill.
  • Elemental Modifier Bypass: Added the SkipElementalModifier property to skills to allow bypassing element resistance checks for specific skills.
  • Bleeding Damage Mechanic: Introduced a new bleeding damage mechanic, including a dedicated magic effect and damage multiplier attributes.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the Summoner's book skills (Explosion, Requiem, Pollution) and introduces a bleeding effect mechanic. It extends AreaSkillSettings with new properties, adds corresponding attributes to the Stats class, and implements stun and push-back logic through new skill plugins. Feedback highlights the need to wrap a fire-and-forget task in a try-catch block to ensure application stability, replace placeholder GUIDs in the new plugins, and define magic numbers as named constants to improve code maintainability.

Comment on lines +59 to +66
_ = Task.Run(async () =>
{
for (int i = 1; i <= 5; i++)
{
await Task.Delay(1000).ConfigureAwait(false);
await this._attackAction.AttackAsync(player, extraTargetId, PollutionSkillId, point, rotation).ConfigureAwait(false);
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This fire-and-forget task (_ = Task.Run(...)) can be dangerous. Any unhandled exception within the async lambda will be swallowed and could crash the application. It's crucial to wrap the content of the task in a try-catch block to handle potential exceptions and log them.

            _ = Task.Run(async () =>
            {
                try
                {
                    for (int i = 1; i <= 5; i++)
                    {
                        await Task.Delay(1000).ConfigureAwait(false);
                        await this._attackAction.AttackAsync(player, extraTargetId, PollutionSkillId, point, rotation).ConfigureAwait(false);
                    }
                }
                catch (Exception ex)
                {
                    player.Logger.LogError(ex, "Error during pollution skill execution.");
                }
            });

/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.PollutionSkillPlugIn_Name), Description = nameof(PlugInResources.PollutionSkillPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("A2B3C4D5-E6F7-4812-3456-7890ABCDEF12")]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This GUID A2B3C4D5-E6F7-4812-3456-7890ABCDEF12 appears to be a placeholder. Please generate a new, unique GUID for this plugin to avoid potential conflicts.

/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.RequiemSkillPlugIn_Name), Description = nameof(PlugInResources.RequiemSkillPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("A1B2C3D4-E5F6-7890-ABCD-EF1234567890")]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This GUID A1B2C3D4-E5F6-7890-ABCD-EF1234567890 appears to be a placeholder. Please generate a new, unique GUID for this plugin to avoid potential conflicts.

return;
}

var multiplier = magicEffectDefinition.Number == ExplosionMagicEffectNumber ? attacker.Attributes[Stats.BleedingDamageMultiplier] : 0.6f;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The magic number 0.6f for the bleeding damage multiplier should be defined as a named constant to improve readability and maintainability. For example, you could add private const float RequiemBleedingDamageMultiplier = 0.6f; at the top of the AttackableExtensions class and use it here. This would make it clear what this value represents.

}
else if (attackCount >= minAttacks)
{
hitChance = 0.5;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The magic number 0.5 for the hit chance should be defined as a named constant to improve readability and maintainability. For example, private const double ReducedHitChanceAfterMinimumHits = 0.5;.

}
else if (attackCount >= minAttacks)
{
hitChance = 0.5;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E.g. Lightning Shock: emu, zTeamS6.3
It's also used for several other skills in the sources.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants