Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/GameLogic/AttackableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public static async ValueTask<HitInfo> CalculateDamageAsync(this IAttacker attac
else
{
var defenseAttribute = defender.GetDefenseAttribute(attacker);
defense = (int)(defender.Attributes[defenseAttribute] * defender.Attributes[Stats.DefenseDecrement]);
defense = (int)((defender.Attributes[defenseAttribute] + defender.Attributes[Stats.GreaterDefenseBonus]) * defender.Attributes[Stats.DefenseDecrement]);
if (defense < 0)
{
defense = 0;
Expand Down Expand Up @@ -378,7 +378,7 @@ public static async ValueTask ApplyRegenerationAsync(this IAttackable target, Pl
if (regeneration != null)
{
var regenerationValue = player.Attributes.CreateElement(powerUpDefinition);
var value = skillEntry.Level == 0 ? regenerationValue.Value : regenerationValue.Value + skillEntry.CalculateValue();
var value = regenerationValue.Value + (skillEntry.Level == 0 ? 0 : regenerationValue.Value * skillEntry.CalculateValue() / 100);

@ze-dom ze-dom Jul 16, 2026

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.

zTeamS6.3, emu

Image

Recovery is the only other Regeneration type skill and has no master skill successor, so we are good.

target.Attributes[regeneration.CurrentAttribute] = Math.Min(
target.Attributes[regeneration.CurrentAttribute] + value,
target.Attributes[regeneration.MaximumAttribute]);
Expand Down
4 changes: 2 additions & 2 deletions src/GameLogic/Attributes/MonsterAttributeHolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public class MonsterAttributeHolder : IAttributeSystem
new Dictionary<AttributeDefinition, Func<AttackableNpcBase, float>>
{
{ Stats.CurrentHealth, m => m.Health },
{ Stats.DefensePvm, m => m.Attributes.GetValueOfAttribute(Stats.DefenseBase) + ((m as Monster)?.SummonedBy?.Attributes?[Stats.SummonedMonsterDefenseIncrease] ?? 0) },
{ Stats.DefensePvp, m => m.Attributes.GetValueOfAttribute(Stats.DefenseBase) + ((m as Monster)?.SummonedBy?.Attributes?[Stats.SummonedMonsterDefenseIncrease] ?? 0) },
{ Stats.DefensePvm, m => m.Attributes.GetValueOfAttribute(Stats.DefenseBase) * (1 + ((m as Monster)?.SummonedBy?.Attributes?[Stats.SummonedMonsterDefenseIncrease] ?? 0)) },
{ Stats.DefensePvp, m => m.Attributes.GetValueOfAttribute(Stats.DefenseBase) * (1 + ((m as Monster)?.SummonedBy?.Attributes?[Stats.SummonedMonsterDefenseIncrease] ?? 0)) },

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.

{ Stats.DamageReceiveDecrement, m => 1.0f },
{ Stats.AttackDamageIncrease, m => 1.0f },
{ Stats.MovementSpeedFactor, m => 1.0f },
Expand Down
17 changes: 14 additions & 3 deletions src/GameLogic/Attributes/Stats.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <copyright file="Stats.cs" company="MUnique">
// <copyright file="Stats.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

Expand Down Expand Up @@ -524,6 +524,12 @@ public class Stats
/// </summary>
public static AttributeDefinition CrossBowMasteryBonusDamage { get; } = new(new Guid("B3AF7F51-6D6B-42FB-B8FF-689D03890F3E"), "Cross Bow Mastery PvP Bonus Damage (MST)", string.Empty);

/// <summary>
/// Gets the extra projectiles attribute definition.
/// </summary>
/// <remarks>Can be increased by equipping certain (cross)bows and/or unlocking triple shot mastery.</remarks>
public static AttributeDefinition ExtraProjectiles { get; } = new(new Guid("B9E4F3C2-A1D5-4B8E-7F2C-6D3A9E1F5C8B"), "Extra Projectiles", "The number of extra projectiles shot with triple shot skill.");

/// <summary>
/// Gets elf's melee attack mode attribute definition.
/// </summary>
Expand Down Expand Up @@ -554,6 +560,11 @@ public class Stats
/// </summary>
public static AttributeDefinition ArcheryMaxDmg { get; } = new(new Guid("EC807B7C-4004-4D13-BED2-326E13F8EFEB"), "Archery Maximum Damage", "The elf's maximum archery damage, which is added to projectile weapon attacks.");

/// <summary>
/// Gets the elf's greater defense buff bonus defense attribute definition.
/// </summary>
public static AttributeDefinition GreaterDefenseBonus { get; } = new(new Guid("5A7C3E9B-D1F4-4B82-8A6D-2F3E1C9B7A4D"), "Greater Defense Bonus", string.Empty);

/// <summary>
/// Gets the elf's greater damage buff bonus damage attribute definition.
/// </summary>
Expand Down Expand Up @@ -729,7 +740,7 @@ public class Stats
public static AttributeDefinition SummonedMonsterHealthIncrease { get; } = new(new Guid("7B0625C8-DA1A-4A5D-BCA5-26AACDA0BDC6"), "Summoned Monster Health Increase %", string.Empty);

/// <summary>
/// Gets the summoned monster defense increase, absolute.
/// Gets the summoned monster defense increase, percentage.
/// </summary>
public static AttributeDefinition SummonedMonsterDefenseIncrease { get; } = new(new Guid("0D55CFCA-751F-4E66-B327-635576A9A0B3"), "Summoned Monster Defense Increase", string.Empty);

Expand Down Expand Up @@ -775,7 +786,7 @@ public class Stats
/// <see cref="AggregateType.Multiplicate"/> values include:
/// <see cref="DefenseIncreaseWithEquippedShield"/>.
/// <see cref="AggregateType.AddFinal"/> values include:
/// Greater defense buff; MST bonus defense with shield (shield strengthener); MST dark horse strengthener; Jack O'Lantern Cry bonus (halved); Berserker defense reduction.
/// MST bonus defense with shield (shield strengthener); MST dark horse strengthener; Jack O'Lantern Cry bonus (halved); Berserker defense reduction.
/// </remarks>
public static AttributeDefinition DefenseFinal { get; } = new(new Guid("0888AD48-0CC8-47CA-B6A3-99F3771AA5FC"), "Final Defense", string.Empty);

Expand Down
28 changes: 7 additions & 21 deletions src/GameLogic/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1606,36 +1606,26 @@ void AddSkillPowersToResult(ICollection<PowerUpDefinition> powerUps, ref (Attrib
var durationExtended = false;
foreach (var powerUpDef in powerUps)
{
IElement powerUp;
IElement powerUp = this.Attributes!.CreateElement(powerUpDef);
if (skillEntry.Level > 0)
{
powerUp = this.Attributes!.CreateElement(powerUpDef);

foreach (var masterSkillEntry in GetMasterSkillEntries(skillEntry))
{
var extendsDuration = masterSkillEntry.Skill?.MasterDefinition?.ExtendsDuration ?? false;
if (extendsDuration && !durationExtended)
{
durationElement = new CombinedElement(durationElement, new ConstantElement(masterSkillEntry.CalculateValue()));
}
else if (extendsDuration)
{
continue;
}
else

if (masterSkillEntry.Skill?.MasterDefinition?.TargetAttribute is not null)
{
// Apply either for all, or just for the specified TargetAttribute of the master skill
powerUp = AppedMasterSkillPowerUp(masterSkillEntry, powerUpDef, powerUp);
}
}

// After the first iteration all possible duration extensions have been applied
durationExtended = true;
}
else
{
powerUp = this.Attributes!.CreateElement(powerUpDef);
}

result[i] = (powerUpDef.TargetAttribute!, powerUp);
i++;
Expand All @@ -1654,15 +1644,12 @@ IEnumerable<SkillEntry> GetMasterSkillEntries(SkillEntry masterSkillEntry)

IElement AppedMasterSkillPowerUp(SkillEntry masterSkillEntry, PowerUpDefinition powerUpDef, IElement powerUp)
{
if (masterSkillEntry.Skill?.MasterDefinition is not { } masterSkillDefinition)
{
return powerUp;
}
var masterSkillDefinition = masterSkillEntry.Skill!.MasterDefinition!;

if (masterSkillDefinition.TargetAttribute is { } masterSkillTargetAttribute
&& masterSkillTargetAttribute == powerUpDef.TargetAttribute)
if (masterSkillDefinition.TargetAttribute == powerUpDef.TargetAttribute
&& masterSkillDefinition.Aggregation == powerUp.AggregateType)
{
var additionalValue = new SimpleElement(masterSkillEntry.CalculateValue(), masterSkillEntry.Skill.MasterDefinition?.Aggregation ?? powerUp.AggregateType);
var additionalValue = new SimpleElement(masterSkillEntry.CalculateValue(), masterSkillDefinition.Aggregation);
powerUp = new CombinedElement(powerUp, additionalValue);
}

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.

Matching by both TargetAttribute and AggregateType now allows us to have two power ups with the same TargetAttribute, but different AggregateType, which is the case for the GreaterDamage and GreaterDefense magic effects.


Expand Down Expand Up @@ -1730,7 +1717,6 @@ public async ValueTask CreateSummonedMonsterAsync(MonsterDefinition definition)
area.MaximumHealthOverride = (int)monster.Attributes[Stats.MaximumHealth];
area.MaximumHealthOverride += (int)(monster.Attributes[Stats.MaximumHealth] * this.Attributes?[Stats.SummonedMonsterHealthIncrease] ?? 0);

// todo: Stats.SummonedMonsterDefenseIncrease
this.Summon = (monster, intelligence);
monster.Initialize();
await gameMap.AddAsync(monster).ConfigureAwait(false);
Expand Down
6 changes: 3 additions & 3 deletions src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,17 +237,17 @@ private async ValueTask PerformAutomaticHitsAsync(Player player, ushort extraTar
var minAttacks = areaSkillSettings.MinimumNumberOfHitsPerAttack == 0 ? maxAttacks : areaSkillSettings.MinimumNumberOfHitsPerAttack;
var currentDelay = TimeSpan.Zero;

// Order targets by distance to process nearest targets first
var orderedTargets = targets.ToList();
FrustumBasedTargetFilter? filter = null;
var projectileCount = 1;
var attackRounds = areaSkillSettings.MaximumNumberOfHitsPerTarget;

if (areaSkillSettings is { UseFrustumFilter: true, ProjectileCount: > 1 })
{
// Order targets by distance to process nearest targets first
orderedTargets.Sort((a, b) => player.GetDistanceTo(a).CompareTo(player.GetDistanceTo(b)));
filter = FrustumFilters.GetOrAdd(areaSkillSettings, static s => new FrustumBasedTargetFilter(s.FrustumStartWidth, s.FrustumEndWidth, s.FrustumDistance, s.ProjectileCount));
projectileCount = areaSkillSettings.ProjectileCount;
projectileCount = areaSkillSettings.ProjectileCount + (int)(player.Attributes?[Stats.ExtraProjectiles] ?? 0);
attackRounds = 1; // One attack round per projectile

extraTarget = orderedTargets.FirstOrDefault(t => t.Id == extraTargetId);
Expand All @@ -256,7 +256,7 @@ private async ValueTask PerformAutomaticHitsAsync(Player player, ushort extraTar
// In this case we just calculate the angle on server side, so that lags
// or desynced positions may not have such a big impact
var angle = (float)player.Position.GetAngleDegreeTo(extraTarget.Position);
rotation = (byte)((angle / 360.0f) * 256.0f);
rotation = (byte)(angle / 360.0f * 256.0f);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/GameLogic/SkillList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ public sealed class SkillList : ISkillList, IDisposable
private const ushort DurabilityReduction1FistMasterSkillId = 578;
private const short TwistingSlashMasterySkillId = 332;
private const short RagefulBlowMasterySkillId = 333;
private const short TripleShotMasterySkillId = 418;

private readonly short[] _castedSkillsWithPassiveBoost = [TwistingSlashMasterySkillId, RagefulBlowMasterySkillId];
private readonly short[] _castedSkillsWithPassiveBoost = [TwistingSlashMasterySkillId, RagefulBlowMasterySkillId, TripleShotMasterySkillId];

private readonly IDictionary<ushort, SkillEntry> _availableSkills;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ public override void Initialize()
magicEffect.StopByDeath = true;
magicEffect.Duration = this.Context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Duration.ConstantValue.Value = 60; // 60 Seconds

// The buff gives 3 + (energy / 7) damage
var powerUpDefinition = this.Context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(powerUpDefinition);
powerUpDefinition.TargetAttribute = Stats.GreaterDamageBonus.GetPersistent(this.GameConfiguration);

// The buff gives 3 + (energy / 7) damage
powerUpDefinition.Boost = this.Context.CreateNew<PowerUpDefinitionValue>();
powerUpDefinition.Boost.ConstantValue.Value = 3f;
powerUpDefinition.Boost.ConstantValue.AggregateType = AggregateType.AddRaw;
Expand All @@ -50,5 +50,13 @@ public override void Initialize()
boostPerEnergy.InputOperator = InputOperator.Multiply;
boostPerEnergy.InputOperand = 1f / 7f; // one damage per 7 energy
powerUpDefinition.Boost.RelatedValues.Add(boostPerEnergy);

// Placeholder for AttackIncreaseStr and AttackIncreaseMastery master skills
var powerUpDefinition2 = this.Context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(powerUpDefinition2);
powerUpDefinition2.TargetAttribute = Stats.GreaterDamageBonus.GetPersistent(this.GameConfiguration);
powerUpDefinition2.Boost = this.Context.CreateNew<PowerUpDefinitionValue>();
powerUpDefinition2.Boost.ConstantValue.Value = 1f;
powerUpDefinition2.Boost.ConstantValue.AggregateType = AggregateType.Multiplicate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,23 @@ public override void Initialize()
// The buff gives 2 + (energy / 8) defense
var powerUpDefinition = this.Context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(powerUpDefinition);
powerUpDefinition.TargetAttribute = Stats.DefenseFinal.GetPersistent(this.GameConfiguration);
powerUpDefinition.TargetAttribute = Stats.GreaterDefenseBonus.GetPersistent(this.GameConfiguration);
powerUpDefinition.Boost = this.Context.CreateNew<PowerUpDefinitionValue>();
powerUpDefinition.Boost.ConstantValue.Value = 2f;
powerUpDefinition.Boost.ConstantValue.AggregateType = AggregateType.AddFinal;
powerUpDefinition.Boost.ConstantValue.AggregateType = AggregateType.AddRaw;

var boostPerEnergy = this.Context.CreateNew<AttributeRelationship>();
boostPerEnergy.InputAttribute = Stats.TotalEnergy.GetPersistent(this.GameConfiguration);
boostPerEnergy.InputOperator = InputOperator.Multiply;
boostPerEnergy.InputOperand = 1f / 8f; // one defense per 8 energy
powerUpDefinition.Boost.RelatedValues.Add(boostPerEnergy);

// Placeholder for DefenseIncreaseStr and DefenseIncreaseMastery master skills
var powerUpDefinition2 = this.Context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(powerUpDefinition2);
powerUpDefinition2.TargetAttribute = Stats.GreaterDefenseBonus.GetPersistent(this.GameConfiguration);
powerUpDefinition2.Boost = this.Context.CreateNew<PowerUpDefinitionValue>();
powerUpDefinition2.Boost.ConstantValue.Value = 1f;
powerUpDefinition2.Boost.ConstantValue.AggregateType = AggregateType.Multiplicate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,11 @@ protected void CreateWeapon(
{
item.BasePowerUpAttributes.Add(this.CreateItemBasePowerUpDefinition(Stats.AmmunitionConsumptionRate, 1, AggregateType.AddRaw));
item.BasePowerUpAttributes.Add(this.CreateItemBasePowerUpDefinition(slot == 0 ? Stats.IsCrossBowEquipped : Stats.IsBowEquipped, 1, AggregateType.AddRaw));

if (number == 18 || number == 19 || number == 22 || number == 23 || number == 24) // Divine Crossbow of Archangel, Great Reign Crossbow, Albatross Bow, Stinger Bow, Air Lyn Bow
{
item.BasePowerUpAttributes.Add(this.CreateItemBasePowerUpDefinition(Stats.ExtraProjectiles, 1, AggregateType.AddRaw));
}

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.

}

if (group < (int)ItemGroups.Bows && width == 2)
Expand Down
Loading