fix: Removes scale speed by dex for monsters, fixes NPC movement/thinking speed (#1019)

* Removes scaled speed by dex for monsters
* Changes scaled speed by dex for pets (HS+ expansion) to be 400ms -> 100ms between 50 -> 200 dex
* Removes speed changes that were broken for various mobs
* Combines "Legacy" speed and renames the class to `NPCSpeeds`
* Creates speed "classes" (slow, medium, fast, very fast)
* Introduces a new overridable property `SpeedClass`. Register a new speed class using `NPCSpeeds.Register()` and then set the speed class to bulk/mass apply speeds.

Order of speed determination:
1. SpeedMod
2. SpeedClass property (not null)
3. SpeedClass entry in Data\npc-speeds.json for that type
4. "Fast" (200ms Active, 400ms Passive)
This commit is contained in:
Kamron Batman 2022-05-15 10:01:14 -07:00 • committed by GitHub
parent 6ec84b3c01
commit eee8494558
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 477 additions and 472 deletions

View file

@ -338,12 +338,7 @@ namespace Server.Mobiles
FightMode = mode;
if (LegacySpeedInfo.Enabled && LegacySpeedInfo.GetSpeeds(GetType(), out var activeSpeed, out var passiveSpeed))
{
ActiveSpeed = activeSpeed;
PassiveSpeed = passiveSpeed;
CurrentSpeed = passiveSpeed;
}
ResetSpeeds();
m_Team = 0;
@ -879,6 +874,8 @@ namespace Server.Mobiles
public virtual bool ReturnsToHome =>
SeeksHome && Home != Point3D.Zero && !m_ReturnQueued && !Controlled && !Summoned;
public virtual bool ScaleSpeedByDex => NPCSpeeds.ScaleSpeedByDex && !IsMonster;
// used for deleting untamed creatures [in houses]
[CommandProperty(AccessLevel.GameMaster)]
public bool RemoveIfUntamed { get; set; }
@ -1363,7 +1360,8 @@ namespace Server.Mobiles
public override void OnRawDexChange(int oldValue)
{
if (oldValue != RawDex && ActiveSpeed <= 0 && PassiveSpeed <= 0)
// This only really happens for pets or when a GM modifies a mob.
if (oldValue != RawDex && ScaleSpeedByDex)
{
ResetSpeeds();
}
@ -1627,23 +1625,35 @@ namespace Server.Mobiles
switch (sc)
{
case ScaleType.Red:
corpse.AddCarvedItem(new RedScales(scales), from);
break;
{
corpse.AddCarvedItem(new RedScales(scales), from);
break;
}
case ScaleType.Yellow:
corpse.AddCarvedItem(new YellowScales(scales), from);
break;
{
corpse.AddCarvedItem(new YellowScales(scales), from);
break;
}
case ScaleType.Black:
corpse.AddCarvedItem(new BlackScales(scales), from);
break;
{
corpse.AddCarvedItem(new BlackScales(scales), from);
break;
}
case ScaleType.Green:
corpse.AddCarvedItem(new GreenScales(scales), from);
break;
{
corpse.AddCarvedItem(new GreenScales(scales), from);
break;
}
case ScaleType.White:
corpse.AddCarvedItem(new WhiteScales(scales), from);
break;
{
corpse.AddCarvedItem(new WhiteScales(scales), from);
break;
}
case ScaleType.Blue:
corpse.AddCarvedItem(new BlueScales(scales), from);
break;
{
corpse.AddCarvedItem(new BlueScales(scales), from);
break;
}
case ScaleType.All:
{
corpse.AddCarvedItem(new RedScales(scales), from);
@ -2567,18 +2577,23 @@ namespace Server.Mobiles
return false; // not idling, but don't want to enter idle state
}
m_IdleReleaseTime = Core.Now + TimeSpan.FromSeconds(Utility.RandomMinMax(15, 25));
var idleSeconds = Utility.RandomMinMax(NPCSpeeds.MinIdleSeconds, NPCSpeeds.MaxIdleSeconds);
m_IdleReleaseTime = Core.Now + TimeSpan.FromSeconds(idleSeconds);
if (Body.IsHuman)
{
switch (Utility.Random(2))
{
case 0:
CheckedAnimate(5, 5, 1, true, true, 1);
break;
{
CheckedAnimate(5, 5, 1, true, true, 1);
break;
}
case 1:
CheckedAnimate(6, 5, 1, true, false, 1);
break;
{
CheckedAnimate(6, 5, 1, true, false, 1);
break;
}
}
}
else if (Body.IsAnimal)
@ -2586,14 +2601,20 @@ namespace Server.Mobiles
switch (Utility.Random(3))
{
case 0:
CheckedAnimate(3, 3, 1, true, false, 1);
break;
{
CheckedAnimate(3, 3, 1, true, false, 1);
break;
}
case 1:
CheckedAnimate(9, 5, 1, true, false, 1);
break;
{
CheckedAnimate(9, 5, 1, true, false, 1);
break;
}
case 2:
CheckedAnimate(10, 5, 1, true, false, 1);
break;
{
CheckedAnimate(10, 5, 1, true, false, 1);
break;
}
}
}
else if (Body.IsMonster)
@ -2601,11 +2622,15 @@ namespace Server.Mobiles
switch (Utility.Random(2))
{
case 0:
CheckedAnimate(17, 5, 1, true, false, 1);
break;
{
CheckedAnimate(17, 5, 1, true, false, 1);
break;
}
case 1:
CheckedAnimate(18, 5, 1, true, false, 1);
break;
{
CheckedAnimate(18, 5, 1, true, false, 1);
break;
}
}
}
@ -2862,17 +2887,25 @@ namespace Server.Mobiles
switch (Utility.Random(4))
{
case 0:
PackItem(new CocoaButter());
break;
{
PackItem(new CocoaButter());
break;
}
case 1:
PackItem(new CocoaLiquor());
break;
{
PackItem(new CocoaLiquor());
break;
}
case 2:
PackItem(new SackOfSugar());
break;
{
PackItem(new SackOfSugar());
break;
}
case 3:
PackItem(new Vanilla());
break;
{
PackItem(new Vanilla());
break;
}
}
}
}
@ -3323,11 +3356,6 @@ namespace Server.Mobiles
Controlled = false;
ControlTarget = null;
ControlOrder = OrderType.None;
Guild = null;
ResetSpeeds();
Delta(MobileDelta.Noto);
}
else
{
@ -3351,19 +3379,20 @@ namespace Server.Mobiles
Controlled = true;
ControlTarget = null;
ControlOrder = OrderType.Come;
Guild = null;
if (m_DeleteTimer != null)
{
m_DeleteTimer.Stop();
m_DeleteTimer = null;
}
ResetSpeeds(true);
Delta(MobileDelta.Noto);
}
Guild = null;
ResetSpeeds();
Delta(MobileDelta.Noto);
InvalidateProperties();
return true;
@ -3953,20 +3982,30 @@ namespace Server.Mobiles
switch (Utility.Random(5))
{
case 0:
physDamage += BreathChaosDamage;
break;
{
physDamage += BreathChaosDamage;
break;
}
case 1:
fireDamage += BreathChaosDamage;
break;
{
fireDamage += BreathChaosDamage;
break;
}
case 2:
coldDamage += BreathChaosDamage;
break;
{
coldDamage += BreathChaosDamage;
break;
}
case 3:
poisDamage += BreathChaosDamage;
break;
{
poisDamage += BreathChaosDamage;
break;
}
case 4:
nrgyDamage += BreathChaosDamage;
break;
{
nrgyDamage += BreathChaosDamage;
break;
}
}
}
@ -4620,20 +4659,30 @@ namespace Server.Mobiles
switch (type)
{
case ResistanceType.Physical:
PhysicalDamage = val;
break;
{
PhysicalDamage = val;
break;
}
case ResistanceType.Fire:
FireDamage = val;
break;
{
FireDamage = val;
break;
}
case ResistanceType.Cold:
ColdDamage = val;
break;
{
ColdDamage = val;
break;
}
case ResistanceType.Poison:
PoisonDamage = val;
break;
{
PoisonDamage = val;
break;
}
case ResistanceType.Energy:
EnergyDamage = val;
break;
{
EnergyDamage = val;
break;
}
}
}
@ -4647,20 +4696,30 @@ namespace Server.Mobiles
switch (type)
{
case ResistanceType.Physical:
m_PhysicalResistance = val;
break;
{
m_PhysicalResistance = val;
break;
}
case ResistanceType.Fire:
m_FireResistance = val;
break;
{
m_FireResistance = val;
break;
}
case ResistanceType.Cold:
m_ColdResistance = val;
break;
{
m_ColdResistance = val;
break;
}
case ResistanceType.Poison:
m_PoisonResistance = val;
break;
{
m_PoisonResistance = val;
break;
}
case ResistanceType.Energy:
m_EnergyResistance = val;
break;
{
m_EnergyResistance = val;
break;
}
}
UpdateResistances();
@ -4794,9 +4853,17 @@ namespace Server.Mobiles
}
}
public virtual void ResetSpeeds(bool currentUseActive = false)
// If this needs to be serialized, recommend creating a hash or registry id. Don't serialize strings.
public virtual string SpeedClass => null;
public virtual void GetSpeeds(out double activeSpeed, out double passiveSpeed)
{
SpeedInfo.GetSpeeds(this, out var activeSpeed, out var passiveSpeed);
NPCSpeeds.GetSpeeds(this, out activeSpeed, out passiveSpeed);
}
public void ResetSpeeds(bool currentUseActive = false)
{
GetSpeeds(out var activeSpeed, out var passiveSpeed);
ActiveSpeed = activeSpeed;
PassiveSpeed = passiveSpeed;