Regens refactoring & add tier 2 chars default movement speeds - #888
Regens refactoring & add tier 2 chars default movement speeds#888ze-dom wants to merge 8 commits into
Conversation
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.DefenseDecrement, 1, tempInnovDefDec, InputOperator.Add, AggregateType.Multiplicate)); | ||
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.DefenseDecrement, -1, Stats.InnovationDefDecrement)); | ||
|
|
||
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.HealthRecoveryMultiplier, 0.03f, Stats.IsRecovering)); |
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.DefenseDecrement, -1, Stats.InnovationDefDecrement)); | ||
|
|
||
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.HealthRecoveryMultiplier, 0.03f, Stats.IsRecovering)); | ||
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.ManaRecoveryMultiplier, 0.03f, Stats.IsRecovering)); |
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.DefenseDecrement, 1, tempInnovDefDec, InputOperator.Add, AggregateType.Multiplicate)); | ||
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.DefenseDecrement, -1, Stats.InnovationDefDecrement)); | ||
|
|
||
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.HealthRecoveryMultiplier, 0.03f, Stats.IsResting)); |
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.DefenseDecrement, -1, Stats.InnovationDefDecrement)); | ||
|
|
||
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.HealthRecoveryMultiplier, 0.03f, Stats.IsResting)); | ||
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.ManaRecoveryMultiplier, 0.03f, Stats.IsResting)); |
|
|
||
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.HealthRecoveryMultiplier, 0.03f, Stats.IsResting)); | ||
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.ManaRecoveryMultiplier, 0.03f, Stats.IsResting)); | ||
| attributeRelationships.Add(this.CreateAttributeRelationship(Stats.AbilityRecoveryAbsolute, 3, Stats.IsInSafezone)); |
| player.Pose = animation switch | ||
| { | ||
| 0x80 => CharacterPose.Sitting, | ||
| 0x81 => CharacterPose.Leaning, | ||
| 0x82 => CharacterPose.Hanging, | ||
| _ => default, | ||
| }; | ||
|
|
||
| if (player.Pose > CharacterPose.Standing) | ||
| { | ||
| player.Attributes?.SetStatAttribute(Stats.IsResting, 1.0f); | ||
| } | ||
|
|
There was a problem hiding this comment.
| this.ItemPowerUpFactory = new ItemPowerUpFactory(loggerFactory.CreateLogger<ItemPowerUpFactory>()); | ||
| this.PartyManager = new PartyManager(configuration.MaximumPartySize, loggerFactory.CreateLogger<Party>()); | ||
| this._recoverTimer = new Timer(this.RecoverTimerElapsed, null, this.Configuration.RecoveryInterval, this.Configuration.RecoveryInterval); | ||
| this._restingRecoveryInterval = (int)Math.Round(this.Configuration.RecoveryInterval * (1 + (2f / 3))); // Originally, resting recovery interval is 5s |
|
Nice work on the regen rework — the behavior it produces looks right to me. My concern is with where the numbers live: What's hardcoded
Also worth deciding explicitly: since the resting cycle keeps mana at multiplier 1, mana regenerates on both cycles (~1.53x while sitting). That may well be intended, but right now it's neither configurable nor obvious from the code. Suggestion: rate-based regeneration + everything else as attributes1. Make regeneration rate-based instead of tick-based. Give each public class Regeneration
{
// ...
public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(3);
public AttributeDefinition? EnabledAttribute { get; init; }
}
private static Regeneration HealthRegeneration { get; } =
new(HealthRecoveryMultiplier, MaximumHealth, CurrentHealth, HealthRecoveryAbsolute)
{ Interval = TimeSpan.FromSeconds(7) };var factor = (float)((now - this._lastRegeneration[r]) / r.Interval);
attributes[r.CurrentAttribute] = Math.Min(
attributes[r.CurrentAttribute] + (((max * multiplier) + absolute) * factor),
max);
2. Gate conditions as attributes instead of branches. With the optional // ShieldRecoveryActive = Maximum(IsInSafezone, ShieldRecoveryEverywhere)
this.CreateAttributeRelationship(Stats.ShieldRecoveryActive, 1, Stats.IsInSafezone, InputOperator.Maximum, Stats.ShieldRecoveryEverywhere)and the loop body is just 3. Replace the hard steps with a linear ramp driven by a duration attribute. The attribute system already gives us the cap for free — New input attributes, written by code: // Stats.cs — the cap lives on the definition
public static AttributeDefinition ShieldRecoveryRampFactor { get; } =
new(new Guid("..."), "Shield recovery ramp factor", "Rises linearly with the uninterrupted shield recovery duration.")
{
MaximumValue = 3,
};// CharacterClassInitialization — factor = 1 + (duration / 15), capped at 3
baseAttributeValues.Add(this.CreateConstValueAttribute(1, Stats.ShieldRecoveryRampFactor));
attributeRelationships.Add(this.CreateAttributeRelationship(Stats.ShieldRecoveryRampFactor, 1f / 15f, Stats.ShieldRecoveryDuration));
attributeRelationships.Add(this.CreateAttributeRelationship(
Stats.ShieldRecoveryMultiplier, 1, Stats.ShieldRecoveryRampFactor, InputOperator.Multiply, AggregateType.Multiplicate));The resting bonus takes the same shape (and there's already precedent one line above it, attributeRelationships.Add(this.CreateAttributeRelationship(Stats.RestingRecoveryFactor, 1f / 30f, Stats.RestingDuration));
attributeRelationships.Add(this.CreateAttributeRelationship(
Stats.HealthRecoveryMultiplier, 1, Stats.RestingRecoveryFactor, InputOperator.Multiply, AggregateType.Multiplicate));That deletes the Mapping the current numbers: either keep the delay (const base 4. One small plug-in keeps the durations current — no game rules inside it: [PlugIn]
[Guid("...")]
public class RegenerationDurationPlugIn : IPeriodicTaskPlugIn, IAttackableMovedPlugIn, IAttackableGotHitPlugIntick: One caveat: I wouldn't make the durations persisted stat attributes the way 5. Give character.Pose = value;
this.Attributes?.SetStatAttribute(Stats.IsResting, value > CharacterPose.Standing ? 1f : 0f);which fixes the never-cleared case and lets Net effect
foreach (var r in Stats.IntervalRegenerationAttributes)
{
if (r.EnabledAttribute is { } flag && attributes[flag] < 1) { continue; }
// current += ((max * multiplier) + absolute) * elapsedFactor(r)
}Everything the PR currently hardcodes lands in one of two configurable places: the As a bonus, once the durations are real attributes other systems can consume them — an item option that raises the ramp cap, or a skill that scales with resting time — all without touching Generated by Claude Code |
| this.ItemPowerUpFactory = new ItemPowerUpFactory(loggerFactory.CreateLogger<ItemPowerUpFactory>()); | ||
| this.PartyManager = new PartyManager(configuration.MaximumPartySize, loggerFactory.CreateLogger<Party>()); | ||
| this._recoverTimer = new Timer(this.RecoverTimerElapsed, null, this.Configuration.RecoveryInterval, this.Configuration.RecoveryInterval); | ||
| this._restingRecoveryInterval = (int)Math.Round(this.Configuration.RecoveryInterval * 5f / 3); // Originally, resting recovery interval is 5s |
| var secondsSinceLastShieldRegenerate = (int)Math.Round(DateTime.UtcNow.Subtract(this._lastShieldRegenerateStop).TotalSeconds); | ||
| switch (secondsSinceLastShieldRegenerate) | ||
| { | ||
| case >= 25: | ||
| multiplier = 3; | ||
| break; | ||
| case >= 15: | ||
| multiplier = 2.5f; | ||
| break; | ||
| case >= 10: | ||
| multiplier = 2; | ||
| break; | ||
| default: | ||
| continue; | ||
| } |
| } | ||
|
|
||
| // Originally, health is recovered every 7s | ||
| multiplier = 3f / 7f; |
| else | ||
| { | ||
| // Originally, mana and ability are recovered every 3s (the default recovery interval). | ||
| multiplier = 1; |
| baseAttributeValues.Add(this.CreateConstValueAttribute(100, Stats.ShieldRecoveryMultiplier)); | ||
| baseAttributeValues.Add(this.CreateConstValueAttribute(1f / 75000, Stats.ShieldRecoveryMultiplier, AggregateType.Multiplicate)); // 1 / (30 * 100 * 25) |
| result.BaseAttributeValues.Add(this.CreateConstValueAttribute(48.5f, Stats.MaximumHealth)); | ||
| result.BaseAttributeValues.Add(this.CreateConstValueAttribute(2, Stats.SkillMultiplier)); | ||
| result.BaseAttributeValues.Add(this.CreateConstValueAttribute(1.0f / 33f, Stats.AbilityRecoveryMultiplier)); | ||
| result.BaseAttributeValues.Add(this.CreateConstValueAttribute(0.03f, Stats.AbilityRecoveryMultiplier)); |
|
|
||
| definition.PossibleOptions.Add(this.CreateOption(ItemGroups.Helm, Stats.DefenseRatePvp, 10, AggregateType.AddRaw, ItemOptionDefinitionNumbers.GuardianOption1)); | ||
| definition.PossibleOptions.Add(this.CreateOption(ItemGroups.Helm, Stats.ShieldRecoveryMultiplier, 20, AggregateType.AddRaw, ItemOptionDefinitionNumbers.GuardianOption2)); // 20 absolute, need test | ||
| definition.PossibleOptions.Add(this.CreateOption(ItemGroups.Helm, Stats.ShieldRecoveryMultiplier, 20, AggregateType.AddRaw, ItemOptionDefinitionNumbers.GuardianOption2)); |
| this.AddPassiveMasterSkillDefinition(SkillNumber.PoisonResistanceInc, Stats.PoisonResistance, AggregateType.AddRaw, Formula120, Formula120, 2, 1); | ||
| this.AddPassiveMasterSkillDefinition(SkillNumber.DurabilityReduction2, Stats.ItemDurationIncrease, AggregateType.Multiplicate, Formula1204, 3, 1, SkillNumber.DurabilityReduction1); | ||
| this.AddMasterSkillDefinition(SkillNumber.SdRecoverySpeedInc, SkillNumber.MaximumSDincrease, SkillNumber.Undefined, 1, 3, SkillNumber.Undefined, 20, Formula120); | ||
| this.AddPassiveMasterSkillDefinition(SkillNumber.SdRecoverySpeedInc, Stats.ShieldRecoveryMultiplier, AggregateType.Multiplicate, $"1 + {FormulaRecoveryIncrease120}", Formula120, 3, 1, SkillNumber.MaximumSDincrease, SkillNumber.Undefined, 20); |
| private void AddCommonBaseAttributeValues(ICollection<ConstValueAttribute> baseAttributeValues, bool isMaster) | ||
| { | ||
| baseAttributeValues.Add(this.CreateConstValueAttribute(1.0f / 27.5f, Stats.ManaRecoveryMultiplier)); | ||
| baseAttributeValues.Add(this.CreateConstValueAttribute(0.037f, Stats.ManaRecoveryMultiplier)); |
To-do
Developments
Bugfixes