fix: Fixes ignoring mobs, champions, party crash, slayer rarity, boat speedhack, etc (#1222)

* Movie ignore mobiles to Mobile class
* Allow necromancer familiars to ignore mobiles
* ChampionSpawn should not quietly fail when creating new spawn
* Fix client party crash bug: 2 people partied, the leader logs out, other client is hung
* Fix spellbooks creating with magery/meditation only and creating magery multiple times
* Fix BaseRunicTool.GetRandomSlayer() creating more undead slayers than intended
* Do not allow players to dismount each other whilst mounted
* Fix AOS onwards damage increase tooltip and wrong formula in AOS
* Fix monsters killing other monster revenants + animate dead should not attack player pets + other animates
* Fix fire steed having loot pack added twice
* Fix oaks spawn not able to create Unicorns + Kirins via Activator.CreateInstace() due to constructor having name as parametr
* Fix ignoreMobiles flag not propagated to client for non-players.
* Fix harrower tents not leeching from players
* Fix boats speedhacking around the map
* Fix bless, agility etc. not renewing duration
* Fix reveal always worked
* Fix empty constructor for steeds so they don't throw now.
This commit is contained in:
mark1145 2022-10-31 22:16:50 -07:00 committed by GitHub
parent 910e06767b
commit 053dbcbad0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 871 additions and 454 deletions

View file

@ -329,6 +329,7 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
private int m_WarmodeChanges; private int m_WarmodeChanges;
private bool _warmodeSpamValue; private bool _warmodeSpamValue;
private IWeapon m_Weapon; private IWeapon m_Weapon;
private bool _ignoreMobiles;
private bool m_YellowHealthbar; private bool m_YellowHealthbar;
private List<StatMod> _statMods; private List<StatMod> _statMods;
@ -405,6 +406,20 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
public virtual bool NewGuildDisplay => false; public virtual bool NewGuildDisplay => false;
[CommandProperty(AccessLevel.GameMaster)]
public virtual bool IgnoreMobiles
{
get => _ignoreMobiles;
set
{
if (_ignoreMobiles != value)
{
_ignoreMobiles = value;
Delta(MobileDelta.Flags);
}
}
}
public List<Mobile> Stabled { get; private set; } public List<Mobile> Stabled { get; private set; }
[CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)]
@ -6974,6 +6989,11 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
flags |= 0x08; flags |= 0x08;
} }
if (IgnoreMobiles)
{
flags |= 0x10;
}
if (m_Warmode) if (m_Warmode)
{ {
flags |= 0x40; flags |= 0x40;

View file

@ -94,7 +94,9 @@ namespace Server.Accounting
_totalGameTime = reader.ReadTimeSpan(); _totalGameTime = reader.ReadTimeSpan();
if (version > 1) if (version > 1)
{
_email = reader.ReadString(); _email = reader.ReadString();
}
Timer.StartTimer(AfterDeserialization); Timer.StartTimer(AfterDeserialization);
} }

View file

@ -20,11 +20,14 @@ using Server.Gumps;
using Server.Items; using Server.Items;
using Server.Mobiles; using Server.Mobiles;
using Server.Regions; using Server.Regions;
using Server.Logging;
using Server.Utilities;
namespace Server.Engines.CannedEvil namespace Server.Engines.CannedEvil
{ {
public class ChampionSpawn : Item public class ChampionSpawn : Item
{ {
private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChampionSpawn));
private bool m_Active; private bool m_Active;
private ChampionSpawnType m_Type; private ChampionSpawnType m_Type;
private List<Mobile> m_Creatures; private List<Mobile> m_Creatures;
@ -597,11 +600,11 @@ namespace Server.Engines.CannedEvil
{ {
if (gainedPath) if (gainedPath)
{ {
m.SendLocalizedMessage(1054032); // You have gained a path in Valor! pm.SendLocalizedMessage(1054032); // You have gained a path in Valor!
} }
else else
{ {
m.SendLocalizedMessage(1054030); // You have gained in Valor! pm.SendLocalizedMessage(1054030); // You have gained in Valor!
} }
// No delay on Valor gains // No delay on Valor gains
@ -682,10 +685,18 @@ namespace Server.Engines.CannedEvil
try try
{ {
Champion = Activator.CreateInstance(ChampionSpawnInfo.GetInfo(m_Type).Champion) as Mobile; Champion = ChampionSpawnInfo.GetInfo(m_Type).Champion.CreateInstance<Mobile>();
} }
catch (Exception e) catch (Exception e)
{ Console.WriteLine($"Exception creating champion {m_Type}: {e}"); } {
logger.Error(
e,
"Failed to spawn champion \"{MobileType}\" at {Location} ({Map}).",
m_Type,
Location,
Map
);
}
if (Champion != null) if (Champion != null)
{ {
@ -851,12 +862,21 @@ namespace Server.Engines.CannedEvil
public Mobile Spawn(params Type[] types) public Mobile Spawn(params Type[] types)
{ {
var type = types[Utility.Random(types.Length)];
try try
{ {
return Activator.CreateInstance(types[Utility.Random(types.Length)]) as Mobile; return type.CreateInstance<Mobile>();
} }
catch catch (Exception e)
{ {
logger.Error(
e,
"Failed to spawn minion \"{Type}\" for champion {ChampionSpawnType} at {Location} ({Map}).",
type,
m_Type,
Location,
Map
);
return null; return null;
} }
} }

View file

@ -106,9 +106,15 @@ namespace Server.Engines.CannedEvil
} }
} }
} }
catch catch (Exception e)
{ {
logger.Warning($"Failed to generate champion spawn {entry.m_ChampType.FullName} at {entry.m_SignLocation} ({entry.m_Map})"); logger.Error(
e,
"Failed to generate champion \"{Type}\" at {Location} ({Map}).",
entry.m_ChampType.FullName,
entry.m_SignLocation,
entry.m_Map
);
} }
} }
} }

View file

@ -4,9 +4,10 @@ namespace Server.Mobiles
{ {
public class UnholySteed : BaseMount public class UnholySteed : BaseMount
{ {
public override string DefaultName => "a dark steed";
[Constructible] [Constructible]
public UnholySteed() public UnholySteed() : base(0x74, 0x3EA7, AIType.AI_Melee, FightMode.Aggressor)
: base("a dark steed", 0x74, 0x3EA7, AIType.AI_Melee, FightMode.Aggressor)
{ {
SetStr(496, 525); SetStr(496, 525);
SetDex(86, 105); SetDex(86, 105);

View file

@ -4,9 +4,9 @@ namespace Server.Mobiles
{ {
public class HolySteed : BaseMount public class HolySteed : BaseMount
{ {
public override string DefaultName => "a silver steed";
[Constructible] [Constructible]
public HolySteed() public HolySteed() : base(0x75, 0x3EA8, AIType.AI_Melee, FightMode.Aggressor)
: base("a silver steed", 0x75, 0x3EA8, AIType.AI_Melee, FightMode.Aggressor)
{ {
SetStr(496, 525); SetStr(496, 525);
SetDex(86, 105); SetDex(86, 105);

View file

@ -8,9 +8,10 @@ namespace Server.Factions
public const int GoldPrice = 3000; public const int GoldPrice = 3000;
private Faction m_Faction; private Faction m_Faction;
public override string DefaultName => "a war horse";
[Constructible] [Constructible]
public FactionWarHorse(Faction faction = null) public FactionWarHorse(Faction faction = null) : base(0xE2, 0x3EA0, AIType.AI_Melee, FightMode.Aggressor)
: base("a war horse", 0xE2, 0x3EA0, AIType.AI_Melee, FightMode.Aggressor)
{ {
BaseSoundID = 0xA8; BaseSoundID = 0xA8;

View file

@ -195,7 +195,7 @@ namespace Server.Engines.PartySystem
m.Party = this; m.Party = this;
Span<byte> memberList = Span<byte> memberList =
stackalloc byte[PartyPackets.GetPartyMemberListPacketLength(this)].InitializePacket(); stackalloc byte[PartyPackets.GetPartyMemberListPacketLength(Count)].InitializePacket();
Span<byte> attrsPacket = stackalloc byte[OutgoingMobilePackets.MobileAttributesPacketLength].InitializePacket(); Span<byte> attrsPacket = stackalloc byte[OutgoingMobilePackets.MobileAttributesPacketLength].InitializePacket();
for (var i = 0; i < Members.Count; ++i) for (var i = 0; i < Members.Count; ++i)
@ -216,12 +216,7 @@ namespace Server.Engines.PartySystem
} }
} }
public void OnAccept(Mobile from) public void OnAccept(Mobile from, bool force = false)
{
OnAccept(from, false);
}
public void OnAccept(Mobile from, bool force)
{ {
var ourFaction = Faction.Find(Leader); var ourFaction = Faction.Find(Leader);
var theirFaction = Faction.Find(from); var theirFaction = Faction.Find(from);
@ -289,28 +284,33 @@ namespace Server.Engines.PartySystem
return; return;
} }
Span<byte> removeMember = var removed = false;
stackalloc byte[PartyPackets.GetPartyRemoveMemberPacketLength(this)].InitializePacket();
for (var i = 0; i < Members.Count; i++) for (var i = 0; i < Members.Count; i++)
{ {
if (Members[i].Mobile == m) if (Members[i].Mobile == m)
{ {
Members.RemoveAt(i); Members.RemoveAt(i);
removed = true;
m.NetState.SendPartyRemoveMember(m.Serial);
m.Party = null;
m.SendLocalizedMessage(1005451); // You have been removed from the party.
PartyPackets.CreatePartyRemoveMember(removeMember, m.Serial, this);
SendToAll(removeMember);
SendToAll(1005452); // A player has been removed from your party.
break; break;
} }
} }
if (removed)
{
Span<byte> removeMember =
stackalloc byte[PartyPackets.GetPartyRemoveMemberPacketLength(Count)].InitializePacket();
// Send empty party to the player removed
m.NetState.SendPartyRemoveMember(m.Serial);
m.Party = null;
m.SendLocalizedMessage(1005451); // You have been removed from the party.
PartyPackets.CreatePartyRemoveMember(removeMember, m.Serial, this);
SendToAll(removeMember);
SendToAll(1005452); // A player has been removed from your party.
}
if (Members.Count == 1) if (Members.Count == 1)
{ {
SendToAll(1005450); // The last person has left the party... SendToAll(1005450); // The last person has left the party...

View file

@ -23,7 +23,7 @@ namespace Server.Engines.PartySystem
public static class PartyPackets public static class PartyPackets
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetPartyMemberListPacketLength(Party p) => 7 + p.Count * 4; public static int GetPartyMemberListPacketLength(int partyCount) => 7 + partyCount * 4;
public static void CreatePartyMemberList(Span<byte> buffer, Party p) public static void CreatePartyMemberList(Span<byte> buffer, Party p)
{ {
@ -32,15 +32,16 @@ namespace Server.Engines.PartySystem
return; return;
} }
var length = GetPartyMemberListPacketLength(p); var count = p.Count;
var length = GetPartyMemberListPacketLength(count);
var writer = new SpanWriter(buffer); var writer = new SpanWriter(buffer);
writer.Write((byte)0xBF); // Packet ID writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)length); writer.Write((ushort)length);
writer.Write((ushort)0x06); // Sub-packet writer.Write((ushort)0x06); // Sub-packet
writer.Write((byte)0x01); // Command writer.Write((byte)0x01); // Command
writer.Write((byte)p.Count); writer.Write((byte)count);
for (var i = 0; i < p.Count; ++i) for (var i = 0; i < count; ++i)
{ {
writer.Write(p[i].Mobile.Serial); writer.Write(p[i].Mobile.Serial);
} }
@ -53,14 +54,14 @@ namespace Server.Engines.PartySystem
return; return;
} }
Span<byte> buffer = stackalloc byte[GetPartyMemberListPacketLength(p)]; Span<byte> buffer = stackalloc byte[GetPartyMemberListPacketLength(p.Count)];
CreatePartyMemberList(buffer, p); CreatePartyMemberList(buffer, p);
ns.Send(buffer); ns.Send(buffer);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetPartyRemoveMemberPacketLength(Party p) => 11 + (p?.Count * 4 ?? 0); public static int GetPartyRemoveMemberPacketLength(int partyCount) => 11 + partyCount * 4;
public static void SendPartyRemoveMember(this NetState ns, Serial m, Party p = null) public static void SendPartyRemoveMember(this NetState ns, Serial m, Party p = null)
{ {
@ -69,7 +70,7 @@ namespace Server.Engines.PartySystem
return; return;
} }
Span<byte> buffer = stackalloc byte[GetPartyRemoveMemberPacketLength(p)]; Span<byte> buffer = stackalloc byte[GetPartyRemoveMemberPacketLength(p?.Count ?? 0)];
CreatePartyRemoveMember(buffer, m, p); CreatePartyRemoveMember(buffer, m, p);
ns.Send(buffer); ns.Send(buffer);
@ -82,14 +83,14 @@ namespace Server.Engines.PartySystem
return; return;
} }
var length = GetPartyRemoveMemberPacketLength(p); var count = p?.Count ?? 0;
var length = GetPartyRemoveMemberPacketLength(count);
var writer = new SpanWriter(buffer); var writer = new SpanWriter(buffer);
writer.Write((byte)0xBF); // Packet ID writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)length); writer.Write((ushort)length);
writer.Write((ushort)0x06); // Sub-packet writer.Write((ushort)0x06); // Sub-packet
writer.Write((byte)0x02); // Command writer.Write((byte)0x02); // Command
var count = p?.Count ?? 0;
writer.Write((byte)count); writer.Write((byte)count);
writer.Write(removed); writer.Write(removed);

View file

@ -218,13 +218,13 @@ namespace Server
m_Timer = new InternalTimer(this); m_Timer = new InternalTimer(this);
m_Timer.Start(); m_Timer.Start();
source.m_hontime = Core.Now + TimeSpan.FromMinutes(40); source._honorTime = Core.Now + TimeSpan.FromMinutes(40);
Timer.StartTimer( Timer.StartTimer(
TimeSpan.FromMinutes(40), TimeSpan.FromMinutes(40),
() => () =>
{ {
if (source.m_hontime < Core.Now && source.SentHonorContext != null) if (source._honorTime < Core.Now && source.SentHonorContext != null)
{ {
Cancel(); Cancel();
} }

View file

@ -109,6 +109,7 @@ public static class ValorVirtue
// Your challenge is heard by the Champion of this region! Beware its wrath! // Your challenge is heard by the Champion of this region! Beware its wrath!
from.SendLocalizedMessage(1054037); from.SendLocalizedMessage(1054037);
idol.Spawn.Start(); idol.Spawn.Start();
idol.Spawn.ActivatedByValor = true;
// Uncomment to not allow advancing level after starting with valor. // Uncomment to not allow advancing level after starting with valor.
// idol.Spawn.HasBeenAdvanced = true; // idol.Spawn.HasBeenAdvanced = true;
} }

View file

@ -207,9 +207,10 @@ namespace Server.Items
private static void ApplySkillBonus(AosSkillBonuses attrs, int min, int max, int index, int low, int high) private static void ApplySkillBonus(AosSkillBonuses attrs, int min, int max, int index, int low, int high)
{ {
var isSpellBook = attrs.Owner is Spellbook;
var possibleSkills = var possibleSkills =
new List<SkillName>(attrs.Owner is Spellbook ? m_PossibleSpellbookSkills : m_PossibleBonusSkills); new List<SkillName>(isSpellBook ? m_PossibleSpellbookSkills : m_PossibleBonusSkills);
var count = Core.SE ? possibleSkills.Count : possibleSkills.Count - 2; var count = Core.SE || isSpellBook ? possibleSkills.Count : possibleSkills.Count - 2;
SkillName sk; SkillName sk;
bool found; bool found;
@ -234,20 +235,30 @@ namespace Server.Items
switch (res) switch (res)
{ {
case ResistanceType.Physical: case ResistanceType.Physical:
ar.PhysicalBonus += Scale(min, max, low, high); {
break; ar.PhysicalBonus += Scale(min, max, low, high);
break;
}
case ResistanceType.Fire: case ResistanceType.Fire:
ar.FireBonus += Scale(min, max, low, high); {
break; ar.FireBonus += Scale(min, max, low, high);
break;
}
case ResistanceType.Cold: case ResistanceType.Cold:
ar.ColdBonus += Scale(min, max, low, high); {
break; ar.ColdBonus += Scale(min, max, low, high);
break;
}
case ResistanceType.Poison: case ResistanceType.Poison:
ar.PoisonBonus += Scale(min, max, low, high); {
break; ar.PoisonBonus += Scale(min, max, low, high);
break;
}
case ResistanceType.Energy: case ResistanceType.Energy:
ar.EnergyBonus += Scale(min, max, low, high); {
break; ar.EnergyBonus += Scale(min, max, low, high);
break;
}
} }
} }
@ -332,20 +343,30 @@ namespace Server.Items
switch (Utility.Random(5)) switch (Utility.Random(5))
{ {
case 0: case 0:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitPhysicalArea, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitPhysicalArea, 2, 50, 2);
break;
}
case 1: case 1:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitFireArea, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitFireArea, 2, 50, 2);
break;
}
case 2: case 2:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitColdArea, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitColdArea, 2, 50, 2);
break;
}
case 3: case 3:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitPoisonArea, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitPoisonArea, 2, 50, 2);
break;
}
case 4: case 4:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitEnergyArea, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitEnergyArea, 2, 50, 2);
break;
}
} }
break; break;
@ -355,17 +376,25 @@ namespace Server.Items
switch (Utility.Random(4)) switch (Utility.Random(4))
{ {
case 0: case 0:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitMagicArrow, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitMagicArrow, 2, 50, 2);
break;
}
case 1: case 1:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitHarm, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitHarm, 2, 50, 2);
break;
}
case 2: case 2:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitFireball, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitFireball, 2, 50, 2);
break;
}
case 3: case 3:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLightning, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLightning, 2, 50, 2);
break;
}
} }
break; break;
@ -375,81 +404,129 @@ namespace Server.Items
switch (Utility.Random(2)) switch (Utility.Random(2))
{ {
case 0: case 0:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.UseBestSkill, 1, 1); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.UseBestSkill, 1, 1);
break;
}
case 1: case 1:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.MageWeapon, 1, 10); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.MageWeapon, 1, 10);
break;
}
} }
break; break;
} }
case 3: case 3:
ApplyAttribute(primary, min, max, AosAttribute.WeaponDamage, 1, 50); {
break; ApplyAttribute(primary, min, max, AosAttribute.WeaponDamage, 1, 50);
break;
}
case 4: case 4:
ApplyAttribute(primary, min, max, AosAttribute.DefendChance, 1, 15); {
break; ApplyAttribute(primary, min, max, AosAttribute.DefendChance, 1, 15);
break;
}
case 5: case 5:
ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1); {
break; ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1);
break;
}
case 6: case 6:
ApplyAttribute(primary, min, max, AosAttribute.AttackChance, 1, 15); {
break; ApplyAttribute(primary, min, max, AosAttribute.AttackChance, 1, 15);
break;
}
case 7: case 7:
ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100); {
break; ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100);
break;
}
case 8: case 8:
ApplyAttribute(primary, min, max, AosAttribute.WeaponSpeed, 5, 30, 5); {
break; ApplyAttribute(primary, min, max, AosAttribute.WeaponSpeed, 5, 30, 5);
break;
}
case 9: case 9:
ApplyAttribute(primary, min, max, AosAttribute.SpellChanneling, 1, 1); {
break; ApplyAttribute(primary, min, max, AosAttribute.SpellChanneling, 1, 1);
break;
}
case 10: case 10:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitDispel, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitDispel, 2, 50, 2);
break;
}
case 11: case 11:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLeechHits, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLeechHits, 2, 50, 2);
break;
}
case 12: case 12:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLowerAttack, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLowerAttack, 2, 50, 2);
break;
}
case 13: case 13:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLowerDefend, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLowerDefend, 2, 50, 2);
break;
}
case 14: case 14:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLeechMana, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLeechMana, 2, 50, 2);
break;
}
case 15: case 15:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLeechStam, 2, 50, 2); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLeechStam, 2, 50, 2);
break;
}
case 16: case 16:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.LowerStatReq, 10, 100, 10); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.LowerStatReq, 10, 100, 10);
break;
}
case 17: case 17:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistPhysicalBonus, 1, 15); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistPhysicalBonus, 1, 15);
break;
}
case 18: case 18:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistFireBonus, 1, 15); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistFireBonus, 1, 15);
break;
}
case 19: case 19:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistColdBonus, 1, 15); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistColdBonus, 1, 15);
break;
}
case 20: case 20:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistPoisonBonus, 1, 15); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistPoisonBonus, 1, 15);
break;
}
case 21: case 21:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistEnergyBonus, 1, 15); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistEnergyBonus, 1, 15);
break;
}
case 22: case 22:
ApplyAttribute(secondary, min, max, AosWeaponAttribute.DurabilityBonus, 10, 100, 10); {
break; ApplyAttribute(secondary, min, max, AosWeaponAttribute.DurabilityBonus, 10, 100, 10);
break;
}
case 23: case 23:
weapon.Slayer = GetRandomSlayer(); {
break; weapon.Slayer = GetRandomSlayer();
break;
}
case 24: case 24:
GetElementalDamages(weapon); {
break; GetElementalDamages(weapon);
break;
}
} }
} }
} }
@ -529,14 +606,14 @@ namespace Server.Items
return SlayerName.None; return SlayerName.None;
} }
// -1 To Exclude the Fey Slayer which appears ONLY on a certain artifact. // 10% chance of a super - skip fey
var group = groups[Utility.Random(groups.Length - 1)]; if (Utility.RandomDouble() < 0.10)
{
return groups[Utility.Random(groups.Length - 1)].Super.Name;
}
// 10% chance for a super slayer // Minor slayer - skip fey and undead
var entry = group.Entries.Length == 0 || Utility.Random(10) == 0 return groups[Utility.Random(groups.Length - 2)].Entries.RandomElement().Name;
? group.Super : group.Entries.RandomElement();
return entry.Name;
} }
public void ApplyAttributesTo(BaseArmor armor) public void ApplyAttributesTo(BaseArmor armor)
@ -611,87 +688,135 @@ namespace Server.Items
{ {
/* Begin Shields */ /* Begin Shields */
case 0: case 0:
ApplyAttribute(primary, min, max, AosAttribute.SpellChanneling, 1, 1); {
break; ApplyAttribute(primary, min, max, AosAttribute.SpellChanneling, 1, 1);
break;
}
case 1: case 1:
ApplyAttribute(primary, min, max, AosAttribute.DefendChance, 1, 15); {
break; ApplyAttribute(primary, min, max, AosAttribute.DefendChance, 1, 15);
break;
}
case 2: case 2:
if (Core.ML)
{ {
ApplyAttribute(primary, min, max, AosAttribute.ReflectPhysical, 1, 15); if (Core.ML)
} {
else ApplyAttribute(primary, min, max, AosAttribute.ReflectPhysical, 1, 15);
{ }
ApplyAttribute(primary, min, max, AosAttribute.AttackChance, 1, 15); else
} {
ApplyAttribute(primary, min, max, AosAttribute.AttackChance, 1, 15);
}
break; break;
}
case 3: case 3:
ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1); {
break; ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1);
break;
}
/* Begin Armor */ /* Begin Armor */
case 4: case 4:
ApplyAttribute(secondary, min, max, AosArmorAttribute.LowerStatReq, 10, 100, 10); {
break; ApplyAttribute(secondary, min, max, AosArmorAttribute.LowerStatReq, 10, 100, 10);
break;
}
case 5: case 5:
ApplyAttribute(secondary, min, max, AosArmorAttribute.SelfRepair, 1, 5); {
break; ApplyAttribute(secondary, min, max, AosArmorAttribute.SelfRepair, 1, 5);
break;
}
case 6: case 6:
ApplyAttribute(secondary, min, max, AosArmorAttribute.DurabilityBonus, 10, 100, 10); {
break; ApplyAttribute(secondary, min, max, AosArmorAttribute.DurabilityBonus, 10, 100, 10);
break;
}
/* End Shields */ /* End Shields */
case 7: case 7:
ApplyAttribute(secondary, min, max, AosArmorAttribute.MageArmor, 1, 1); {
break; ApplyAttribute(secondary, min, max, AosArmorAttribute.MageArmor, 1, 1);
break;
}
case 8: case 8:
ApplyAttribute(primary, min, max, AosAttribute.RegenHits, 1, 2); {
break; ApplyAttribute(primary, min, max, AosAttribute.RegenHits, 1, 2);
break;
}
case 9: case 9:
ApplyAttribute(primary, min, max, AosAttribute.RegenStam, 1, 3); {
break; ApplyAttribute(primary, min, max, AosAttribute.RegenStam, 1, 3);
break;
}
case 10: case 10:
ApplyAttribute(primary, min, max, AosAttribute.RegenMana, 1, 2); {
break; ApplyAttribute(primary, min, max, AosAttribute.RegenMana, 1, 2);
break;
}
case 11: case 11:
ApplyAttribute(primary, min, max, AosAttribute.NightSight, 1, 1); {
break; ApplyAttribute(primary, min, max, AosAttribute.NightSight, 1, 1);
break;
}
case 12: case 12:
ApplyAttribute(primary, min, max, AosAttribute.BonusHits, 1, 5); {
break; ApplyAttribute(primary, min, max, AosAttribute.BonusHits, 1, 5);
break;
}
case 13: case 13:
ApplyAttribute(primary, min, max, AosAttribute.BonusStam, 1, 8); {
break; ApplyAttribute(primary, min, max, AosAttribute.BonusStam, 1, 8);
break;
}
case 14: case 14:
ApplyAttribute(primary, min, max, AosAttribute.BonusMana, 1, 8); {
break; ApplyAttribute(primary, min, max, AosAttribute.BonusMana, 1, 8);
break;
}
case 15: case 15:
ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8); {
break; ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8);
break;
}
case 16: case 16:
ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20); {
break; ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20);
break;
}
case 17: case 17:
ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100); {
break; ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100);
break;
}
case 18: case 18:
ApplyAttribute(primary, min, max, AosAttribute.ReflectPhysical, 1, 15); {
break; ApplyAttribute(primary, min, max, AosAttribute.ReflectPhysical, 1, 15);
break;
}
case 19: case 19:
ApplyResistance(armor, min, max, ResistanceType.Physical, 1, 15); {
break; ApplyResistance(armor, min, max, ResistanceType.Physical, 1, 15);
break;
}
case 20: case 20:
ApplyResistance(armor, min, max, ResistanceType.Fire, 1, 15); {
break; ApplyResistance(armor, min, max, ResistanceType.Fire, 1, 15);
break;
}
case 21: case 21:
ApplyResistance(armor, min, max, ResistanceType.Cold, 1, 15); {
break; ApplyResistance(armor, min, max, ResistanceType.Cold, 1, 15);
break;
}
case 22: case 22:
ApplyResistance(armor, min, max, ResistanceType.Poison, 1, 15); {
break; ApplyResistance(armor, min, max, ResistanceType.Poison, 1, 15);
break;
}
case 23: case 23:
ApplyResistance(armor, min, max, ResistanceType.Energy, 1, 15); {
break; ApplyResistance(armor, min, max, ResistanceType.Energy, 1, 15);
break;
}
/* End Armor */ /* End Armor */
} }
} }
@ -728,62 +853,100 @@ namespace Server.Items
switch (random) switch (random)
{ {
case 0: case 0:
ApplyAttribute(primary, min, max, AosAttribute.ReflectPhysical, 1, 15); {
break; ApplyAttribute(primary, min, max, AosAttribute.ReflectPhysical, 1, 15);
break;
}
case 1: case 1:
ApplyAttribute(primary, min, max, AosAttribute.RegenHits, 1, 2); {
break; ApplyAttribute(primary, min, max, AosAttribute.RegenHits, 1, 2);
break;
}
case 2: case 2:
ApplyAttribute(primary, min, max, AosAttribute.RegenStam, 1, 3); {
break; ApplyAttribute(primary, min, max, AosAttribute.RegenStam, 1, 3);
break;
}
case 3: case 3:
ApplyAttribute(primary, min, max, AosAttribute.RegenMana, 1, 2); {
break; ApplyAttribute(primary, min, max, AosAttribute.RegenMana, 1, 2);
break;
}
case 4: case 4:
ApplyAttribute(primary, min, max, AosAttribute.NightSight, 1, 1); {
break; ApplyAttribute(primary, min, max, AosAttribute.NightSight, 1, 1);
break;
}
case 5: case 5:
ApplyAttribute(primary, min, max, AosAttribute.BonusHits, 1, 5); {
break; ApplyAttribute(primary, min, max, AosAttribute.BonusHits, 1, 5);
break;
}
case 6: case 6:
ApplyAttribute(primary, min, max, AosAttribute.BonusStam, 1, 8); {
break; ApplyAttribute(primary, min, max, AosAttribute.BonusStam, 1, 8);
break;
}
case 7: case 7:
ApplyAttribute(primary, min, max, AosAttribute.BonusMana, 1, 8); {
break; ApplyAttribute(primary, min, max, AosAttribute.BonusMana, 1, 8);
break;
}
case 8: case 8:
ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8); {
break; ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8);
break;
}
case 9: case 9:
ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20); {
break; ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20);
break;
}
case 10: case 10:
ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100); {
break; ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100);
break;
}
case 11: case 11:
ApplyAttribute(secondary, min, max, AosArmorAttribute.LowerStatReq, 10, 100, 10); {
break; ApplyAttribute(secondary, min, max, AosArmorAttribute.LowerStatReq, 10, 100, 10);
break;
}
case 12: case 12:
ApplyAttribute(secondary, min, max, AosArmorAttribute.SelfRepair, 1, 5); {
break; ApplyAttribute(secondary, min, max, AosArmorAttribute.SelfRepair, 1, 5);
break;
}
case 13: case 13:
ApplyAttribute(secondary, min, max, AosArmorAttribute.DurabilityBonus, 10, 100, 10); {
break; ApplyAttribute(secondary, min, max, AosArmorAttribute.DurabilityBonus, 10, 100, 10);
break;
}
case 14: case 14:
ApplyAttribute(resists, min, max, AosElementAttribute.Physical, 1, 15); {
break; ApplyAttribute(resists, min, max, AosElementAttribute.Physical, 1, 15);
break;
}
case 15: case 15:
ApplyAttribute(resists, min, max, AosElementAttribute.Fire, 1, 15); {
break; ApplyAttribute(resists, min, max, AosElementAttribute.Fire, 1, 15);
break;
}
case 16: case 16:
ApplyAttribute(resists, min, max, AosElementAttribute.Cold, 1, 15); {
break; ApplyAttribute(resists, min, max, AosElementAttribute.Cold, 1, 15);
break;
}
case 17: case 17:
ApplyAttribute(resists, min, max, AosElementAttribute.Poison, 1, 15); {
break; ApplyAttribute(resists, min, max, AosElementAttribute.Poison, 1, 15);
break;
}
case 18: case 18:
ApplyAttribute(resists, min, max, AosElementAttribute.Energy, 1, 15); {
break; ApplyAttribute(resists, min, max, AosElementAttribute.Energy, 1, 15);
break;
}
} }
} }
} }
@ -819,77 +982,125 @@ namespace Server.Items
switch (random) switch (random)
{ {
case 0: case 0:
ApplyAttribute(resists, min, max, AosElementAttribute.Physical, 1, 15); {
break; ApplyAttribute(resists, min, max, AosElementAttribute.Physical, 1, 15);
break;
}
case 1: case 1:
ApplyAttribute(resists, min, max, AosElementAttribute.Fire, 1, 15); {
break; ApplyAttribute(resists, min, max, AosElementAttribute.Fire, 1, 15);
break;
}
case 2: case 2:
ApplyAttribute(resists, min, max, AosElementAttribute.Cold, 1, 15); {
break; ApplyAttribute(resists, min, max, AosElementAttribute.Cold, 1, 15);
break;
}
case 3: case 3:
ApplyAttribute(resists, min, max, AosElementAttribute.Poison, 1, 15); {
break; ApplyAttribute(resists, min, max, AosElementAttribute.Poison, 1, 15);
break;
}
case 4: case 4:
ApplyAttribute(resists, min, max, AosElementAttribute.Energy, 1, 15); {
break; ApplyAttribute(resists, min, max, AosElementAttribute.Energy, 1, 15);
break;
}
case 5: case 5:
ApplyAttribute(primary, min, max, AosAttribute.WeaponDamage, 1, 25); {
break; ApplyAttribute(primary, min, max, AosAttribute.WeaponDamage, 1, 25);
break;
}
case 6: case 6:
ApplyAttribute(primary, min, max, AosAttribute.DefendChance, 1, 15); {
break; ApplyAttribute(primary, min, max, AosAttribute.DefendChance, 1, 15);
break;
}
case 7: case 7:
ApplyAttribute(primary, min, max, AosAttribute.AttackChance, 1, 15); {
break; ApplyAttribute(primary, min, max, AosAttribute.AttackChance, 1, 15);
break;
}
case 8: case 8:
ApplyAttribute(primary, min, max, AosAttribute.BonusStr, 1, 8); {
break; ApplyAttribute(primary, min, max, AosAttribute.BonusStr, 1, 8);
break;
}
case 9: case 9:
ApplyAttribute(primary, min, max, AosAttribute.BonusDex, 1, 8); {
break; ApplyAttribute(primary, min, max, AosAttribute.BonusDex, 1, 8);
break;
}
case 10: case 10:
ApplyAttribute(primary, min, max, AosAttribute.BonusInt, 1, 8); {
break; ApplyAttribute(primary, min, max, AosAttribute.BonusInt, 1, 8);
break;
}
case 11: case 11:
ApplyAttribute(primary, min, max, AosAttribute.EnhancePotions, 5, 25, 5); {
break; ApplyAttribute(primary, min, max, AosAttribute.EnhancePotions, 5, 25, 5);
break;
}
case 12: case 12:
ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1); {
break; ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1);
break;
}
case 13: case 13:
ApplyAttribute(primary, min, max, AosAttribute.CastRecovery, 1, 3); {
break; ApplyAttribute(primary, min, max, AosAttribute.CastRecovery, 1, 3);
break;
}
case 14: case 14:
ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8); {
break; ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8);
break;
}
case 15: case 15:
ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20); {
break; ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20);
break;
}
case 16: case 16:
ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100); {
break; ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100);
break;
}
case 17: case 17:
ApplyAttribute(primary, min, max, AosAttribute.SpellDamage, 1, 12); {
break; ApplyAttribute(primary, min, max, AosAttribute.SpellDamage, 1, 12);
break;
}
case 18: case 18:
ApplyAttribute(primary, min, max, AosAttribute.NightSight, 1, 1); {
break; ApplyAttribute(primary, min, max, AosAttribute.NightSight, 1, 1);
break;
}
case 19: case 19:
ApplySkillBonus(skills, min, max, 0, 1, 15); {
break; ApplySkillBonus(skills, min, max, 0, 1, 15);
break;
}
case 20: case 20:
ApplySkillBonus(skills, min, max, 1, 1, 15); {
break; ApplySkillBonus(skills, min, max, 1, 1, 15);
break;
}
case 21: case 21:
ApplySkillBonus(skills, min, max, 2, 1, 15); {
break; ApplySkillBonus(skills, min, max, 2, 1, 15);
break;
}
case 22: case 22:
ApplySkillBonus(skills, min, max, 3, 1, 15); {
break; ApplySkillBonus(skills, min, max, 3, 1, 15);
break;
}
case 23: case 23:
ApplySkillBonus(skills, min, max, 4, 1, 15); {
break; ApplySkillBonus(skills, min, max, 4, 1, 15);
break;
}
} }
} }
} }
@ -938,41 +1149,65 @@ namespace Server.Items
break; break;
} }
case 4: case 4:
ApplyAttribute(primary, min, max, AosAttribute.BonusMana, 1, 8); {
break; ApplyAttribute(primary, min, max, AosAttribute.BonusMana, 1, 8);
break;
}
case 5: case 5:
ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1); {
break; ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1);
break;
}
case 6: case 6:
ApplyAttribute(primary, min, max, AosAttribute.CastRecovery, 1, 3); {
break; ApplyAttribute(primary, min, max, AosAttribute.CastRecovery, 1, 3);
break;
}
case 7: case 7:
ApplyAttribute(primary, min, max, AosAttribute.SpellDamage, 1, 12); {
break; ApplyAttribute(primary, min, max, AosAttribute.SpellDamage, 1, 12);
break;
}
case 8: case 8:
ApplySkillBonus(skills, min, max, 0, 1, 15); {
break; ApplySkillBonus(skills, min, max, 0, 1, 15);
break;
}
case 9: case 9:
ApplySkillBonus(skills, min, max, 1, 1, 15); {
break; ApplySkillBonus(skills, min, max, 1, 1, 15);
break;
}
case 10: case 10:
ApplySkillBonus(skills, min, max, 2, 1, 15); {
break; ApplySkillBonus(skills, min, max, 2, 1, 15);
break;
}
case 11: case 11:
ApplySkillBonus(skills, min, max, 3, 1, 15); {
break; ApplySkillBonus(skills, min, max, 3, 1, 15);
break;
}
case 12: case 12:
ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20); {
break; ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20);
break;
}
case 13: case 13:
ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8); {
break; ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8);
break;
}
case 14: case 14:
ApplyAttribute(primary, min, max, AosAttribute.RegenMana, 1, 2); {
break; ApplyAttribute(primary, min, max, AosAttribute.RegenMana, 1, 2);
break;
}
case 15: case 15:
spellbook.Slayer = GetRandomSlayer(); {
break; spellbook.Slayer = GetRandomSlayer();
break;
}
} }
} }
} }

View file

@ -35,11 +35,10 @@ namespace Server.Mobiles
{ {
public class GrizzledMare : HellSteed public class GrizzledMare : HellSteed
{ {
private static readonly string m_Myname = "a grizzled mare"; public override string DefaultName => "a grizzled mare";
[Constructible] [Constructible]
public GrizzledMare() public GrizzledMare()
: base(m_Myname)
{ {
} }
@ -49,18 +48,11 @@ namespace Server.Mobiles
public override bool DeleteOnRelease => true; public override bool DeleteOnRelease => true;
public virtual void OnAfterDeserialize_Callback()
{
SetStats(this);
Name = m_Myname;
}
public override void Serialize(IGenericWriter writer) public override void Serialize(IGenericWriter writer)
{ {
base.Serialize(writer); base.Serialize(writer);
writer.Write(1); // version writer.Write(0); // version
} }
public override void Deserialize(IGenericReader reader) public override void Deserialize(IGenericReader reader)
@ -68,11 +60,6 @@ namespace Server.Mobiles
base.Deserialize(reader); base.Deserialize(reader);
var version = reader.ReadInt(); var version = reader.ReadInt();
if (version < 1)
{
Timer.StartTimer(TimeSpan.FromSeconds(0), OnAfterDeserialize_Callback);
}
} }
} }
} }

View file

@ -23,6 +23,8 @@ public class Dismount : WeaponAbility
return; return;
} }
ClearCurrentAbility(attacker);
if (defender is ChaosDragoon or ChaosDragoonElite) if (defender is ChaosDragoon or ChaosDragoonElite)
{ {
return; return;
@ -33,11 +35,10 @@ public class Dismount : WeaponAbility
if (attacker.Weapon is not Lance || !defender.Mounted && !defender.Flying && defender.Weapon is not Lance) if (attacker.Weapon is not Lance || !defender.Mounted && !defender.Flying && defender.Weapon is not Lance)
{ {
attacker.SendLocalizedMessage(1061283); // You cannot perform that attack while mounted! attacker.SendLocalizedMessage(1061283); // You cannot perform that attack while mounted!
return;
} }
} }
ClearCurrentAbility(attacker);
var mount = defender.Mount; var mount = defender.Mount;
if (mount == null && !AnimalForm.UnderTransformation(defender)) if (mount == null && !AnimalForm.UnderTransformation(defender))

View file

@ -18,6 +18,13 @@ namespace Server.Items
if (index >= 1 && index < WeaponAbility.Abilities.Length) if (index >= 1 && index < WeaponAbility.Abilities.Length)
{ {
if (m.Mounted && index == 6) //dismount
{
WeaponAbility.ClearCurrentAbility(m);
m.SendLocalizedMessage(1061283); // You cannot perform that attack while mounted!
return;
}
WeaponAbility.SetCurrentAbility(m, WeaponAbility.Abilities[index]); WeaponAbility.SetCurrentAbility(m, WeaponAbility.Abilities[index]);
} }
else else

View file

@ -487,14 +487,7 @@ namespace Server.Items
if (Quality == WeaponQuality.Exceptional) if (Quality == WeaponQuality.Exceptional)
{ {
if (Attributes.WeaponDamage > 35) Attributes.WeaponDamage = 35;
{
Attributes.WeaponDamage -= 20;
}
else
{
Attributes.WeaponDamage = 15;
}
if (Core.ML) if (Core.ML)
{ {
@ -2385,27 +2378,8 @@ namespace Server.Items
}; };
} }
public virtual int GetDamageBonus() // Note: AOS quality/damage bonuses removed since they are incorporated into the crafting already
{ public virtual int GetDamageBonus() => VirtualDamageBonus;
var quality = m_Quality switch
{
WeaponQuality.Low => -20,
WeaponQuality.Exceptional => 20,
_ => 0
};
var damageLevel = m_DamageLevel switch
{
WeaponDamageLevel.Ruin => 15,
WeaponDamageLevel.Might => 20,
WeaponDamageLevel.Force => 25,
WeaponDamageLevel.Power => 30,
WeaponDamageLevel.Vanq => 35,
_ => 0
};
return VirtualDamageBonus + quality + damageLevel;
}
public virtual double ScaleDamageAOS(Mobile attacker, double damage, bool checkSkills) public virtual double ScaleDamageAOS(Mobile attacker, double damage, bool checkSkills)
{ {
@ -2477,10 +2451,9 @@ namespace Server.Items
damageBonus = 100; damageBonus = 100;
} }
var totalBonus = strengthBonus + anatomyBonus + tacticsBonus + lumberBonus + var totalBonus = strengthBonus + anatomyBonus + tacticsBonus + lumberBonus + damageBonus + GetDamageBonus();
(GetDamageBonus() + damageBonus) / 100.0;
return damage + (int)(damage * totalBonus); return damage + damage * totalBonus / 100.0;
} }
public virtual int ComputeDamageAOS(Mobile attacker, Mobile defender) => public virtual int ComputeDamageAOS(Mobile attacker, Mobile defender) =>

View file

@ -402,11 +402,13 @@ namespace Server.Items
Groups = new[] Groups = new[]
{ {
humanoid, humanoid,
undead,
elemental, elemental,
abyss, abyss,
arachnid, arachnid,
reptilian, reptilian,
// Skip undead for minor slayers
undead,
// Skip fey for random slayer
fey fey
}; };

View file

@ -105,7 +105,7 @@ namespace Server
var key = cols[0].ToLowerInvariant(); var key = cols[0].ToLowerInvariant();
var value = cols[1].Trim('"'); var value = cols[1].Trim('"');
if (key == "type" && value != "profession") if (key == "type" && !value.InsensitiveEquals("profession"))
{ {
break; break;
} }

View file

@ -2633,8 +2633,8 @@ public abstract class BaseAI
var bc = m as BaseCreature; var bc = m as BaseCreature;
var pm = m as PlayerMobile; var pm = m as PlayerMobile;
// Monster don't attack it's own summon // Monster don't attack it's own summon or the summon of another monster
if (Core.AOS && bc != null && bc.Summoned && bc.SummonMaster == m_Mobile) if (Core.AOS && bc != null && bc.Summoned && (bc.SummonMaster == m_Mobile || (!bc.SummonMaster.Player && IsHostile(bc.SummonMaster))))
{ {
continue; continue;
} }
@ -2658,6 +2658,18 @@ public abstract class BaseAI
{ {
continue; continue;
} }
// Animated creatures cannot attack other animated creatures
if (m_Mobile.IsAnimatedDead && bc?.IsAnimatedDead == true)
{
continue;
}
// Animated creatures cannot attack pets of other players
if (m_Mobile.IsAnimatedDead && bc?.Controlled == true)
{
continue;
}
} }
// If we only want faction friends, make sure it's one. // If we only want faction friends, make sure it's one.
@ -2726,7 +2738,7 @@ public abstract class BaseAI
newFocusMob = m; newFocusMob = m;
val = theirVal; val = theirVal;
} }
// The summon is targeted when nothing else around. Otherwise this monster enters idle mode. // The summon is targeted when nothing else around. Otherwise this monster enters idle mode.
// Do a check for this edge case so players cannot abuse by casting EVs offscreen to kill an idle monster. // Do a check for this edge case so players cannot abuse by casting EVs offscreen to kill an idle monster.
else if (Core.AOS && theirVal > enemySummonVal && m_Mobile.InLOS(m) && bc?.Summoned == true && bc?.Controlled != true) else if (Core.AOS && theirVal > enemySummonVal && m_Mobile.InLOS(m) && bc?.Summoned == true && bc?.Controlled != true)
{ {

View file

@ -11,11 +11,10 @@ namespace Server.Mobiles
private Mobile m_Rider; private Mobile m_Rider;
public BaseMount( public BaseMount(
string name, int bodyID, int itemID, AIType aiType, FightMode fightMode = FightMode.Closest, int bodyID, int itemID, AIType aiType, FightMode fightMode = FightMode.Closest,
int rangePerception = 10, int rangeFight = 1 int rangePerception = 10, int rangeFight = 1
) : base(aiType, fightMode, rangePerception, rangeFight) ) : base(aiType, fightMode, rangePerception, rangeFight)
{ {
Name = name;
Body = bodyID; Body = bodyID;
InternalItem = new MountItem(this, itemID); InternalItem = new MountItem(this, itemID);

View file

@ -6,8 +6,10 @@ namespace Server.Mobiles
{ {
public class Beetle : BaseMount public class Beetle : BaseMount
{ {
public override string DefaultName => "a giant beetle";
[Constructible] [Constructible]
public Beetle(string name = "a giant beetle") : base(name, 0x317, 0x3EBC, AIType.AI_Melee) public Beetle() : base( 0x317, 0x3EBC, AIType.AI_Melee)
{ {
SetStr(300); SetStr(300);
SetDex(100); SetDex(100);

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{ {
public class DesertOstard : BaseMount public class DesertOstard : BaseMount
{ {
public override string DefaultName => "a desert ostard";
[Constructible] [Constructible]
public DesertOstard(string name = "a desert ostard") : base(name, 0xD2, 0x3EA3, AIType.AI_Animal, FightMode.Aggressor) public DesertOstard() : base(0xD2, 0x3EA3, AIType.AI_Animal, FightMode.Aggressor)
{ {
BaseSoundID = 0x270; BaseSoundID = 0x270;

View file

@ -5,8 +5,10 @@ namespace Server.Mobiles
{ {
public class FireSteed : BaseMount public class FireSteed : BaseMount
{ {
public override string DefaultName => "a fire steed";
[Constructible] [Constructible]
public FireSteed(string name = "a fire steed") : base(name, 0xBE, 0x3E9E, AIType.AI_Melee) public FireSteed() : base(0xBE, 0x3E9E, AIType.AI_Melee)
{ {
BaseSoundID = 0xA8; BaseSoundID = 0xA8;
@ -37,6 +39,9 @@ namespace Server.Mobiles
Tamable = true; Tamable = true;
ControlSlots = 2; ControlSlots = 2;
MinTameSkill = 106.0; MinTameSkill = 106.0;
PackItem(new SulfurousAsh(Utility.RandomMinMax(151, 300)));
PackItem(new Ruby(Utility.RandomMinMax(16, 30)));
} }
public FireSteed(Serial serial) : base(serial) public FireSteed(Serial serial) : base(serial)
@ -52,8 +57,6 @@ namespace Server.Mobiles
public override void GenerateLoot() public override void GenerateLoot()
{ {
PackItem(new SulfurousAsh(Utility.RandomMinMax(151, 300)));
PackItem(new Ruby(Utility.RandomMinMax(16, 30)));
} }
public override void Serialize(IGenericWriter writer) public override void Serialize(IGenericWriter writer)

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{ {
public class ForestOstard : BaseMount public class ForestOstard : BaseMount
{ {
public override string DefaultName => "a forest ostard";
[Constructible] [Constructible]
public ForestOstard(string name = "a forest ostard") : base(name, 0xDB, 0x3EA5, AIType.AI_Animal, FightMode.Aggressor) public ForestOstard() : base(0xDB, 0x3EA5, AIType.AI_Animal, FightMode.Aggressor)
{ {
Hue = Utility.RandomSlimeHue() | 0x8000; Hue = Utility.RandomSlimeHue() | 0x8000;

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{ {
public class FrenziedOstard : BaseMount public class FrenziedOstard : BaseMount
{ {
public override string DefaultName => "a frenzied ostard";
[Constructible] [Constructible]
public FrenziedOstard(string name = "a frenzied ostard") : base(name, 0xDA, 0x3EA4, AIType.AI_Melee) public FrenziedOstard() : base(0xDA, 0x3EA4, AIType.AI_Melee)
{ {
Hue = Race.Human.RandomHairHue() | 0x8000; Hue = Race.Human.RandomHairHue() | 0x8000;

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{ {
public class HellSteed : BaseMount public class HellSteed : BaseMount
{ {
public override string DefaultName => "a frenzied ostard";
[Constructible] [Constructible]
public HellSteed(string name = "a hellsteed") : base(name, 793, 0x3EBB, AIType.AI_Animal, FightMode.Aggressor) public HellSteed() : base(793, 0x3EBB, AIType.AI_Animal, FightMode.Aggressor)
{ {
SetStats(this); SetStats(this);
} }

View file

@ -6,8 +6,10 @@ namespace Server.Mobiles;
public class Hiryu : BaseMount public class Hiryu : BaseMount
{ {
public override string DefaultName => "a hiryu";
[Constructible] [Constructible]
public Hiryu() : base("a hiryu", 243, 0x3E94, AIType.AI_Melee) public Hiryu() : base(243, 0x3E94, AIType.AI_Melee)
{ {
Hue = GetHue(); Hue = GetHue();

View file

@ -16,8 +16,10 @@ namespace Server.Mobiles
0xCC, 0x3EA2 0xCC, 0x3EA2
}; };
public override string DefaultName => "a horse";
[Constructible] [Constructible]
public Horse(string name = "a horse") : base(name, 0xE2, 0x3EA0, AIType.AI_Animal, FightMode.Aggressor) public Horse() : base(0xE2, 0x3EA0, AIType.AI_Animal, FightMode.Aggressor)
{ {
var random = Utility.Random(4); var random = Utility.Random(4);

View file

@ -6,8 +6,10 @@ namespace Server.Mobiles
{ {
public class Kirin : BaseMount public class Kirin : BaseMount
{ {
public override string DefaultName => "a ki-rin";
[Constructible] [Constructible]
public Kirin(string name = "a ki-rin") : base(name, 132, 0x3EAD, AIType.AI_Mage, FightMode.Evil) public Kirin() : base(132, 0x3EAD, AIType.AI_Mage, FightMode.Evil)
{ {
BaseSoundID = 0x3C5; BaseSoundID = 0x3C5;

View file

@ -6,8 +6,10 @@ namespace Server.Mobiles;
public class LesserHiryu : BaseMount public class LesserHiryu : BaseMount
{ {
public override string DefaultName => "a lesser hiryu";
[Constructible] [Constructible]
public LesserHiryu() : base("a lesser hiryu", 243, 0x3E94, AIType.AI_Melee) public LesserHiryu() : base(243, 0x3E94, AIType.AI_Melee)
{ {
Hue = GetHue(); Hue = GetHue();

View file

@ -4,13 +4,10 @@ namespace Server.Mobiles
{ {
public class Nightmare : BaseMount public class Nightmare : BaseMount
{ {
public override string DefaultName => "a nightmare";
[Constructible] [Constructible]
public Nightmare(string name = "a nightmare") : base( public Nightmare() : base(0x74, 0x3EA7, AIType.AI_Mage)
name,
0x74,
0x3EA7,
AIType.AI_Mage
)
{ {
BaseSoundID = Core.AOS ? 0xA8 : 0x16A; BaseSoundID = Core.AOS ? 0xA8 : 0x16A;

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{ {
public class RidableLlama : BaseMount public class RidableLlama : BaseMount
{ {
public override string DefaultName => "a ridable llama";
[Constructible] [Constructible]
public RidableLlama(string name = "a ridable llama") : base(name, 0xDC, 0x3EA6, AIType.AI_Animal, FightMode.Aggressor public RidableLlama() : base(0xDC, 0x3EA6, AIType.AI_Animal, FightMode.Aggressor
) )
{ {
BaseSoundID = 0x3F3; BaseSoundID = 0x3F3;

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{ {
public class Ridgeback : BaseMount public class Ridgeback : BaseMount
{ {
public override string DefaultName => "a ridgeback";
[Constructible] [Constructible]
public Ridgeback(string name = "a ridgeback") : base(name, 187, 0x3EBA, AIType.AI_Animal, FightMode.Aggressor) public Ridgeback() : base(187, 0x3EBA, AIType.AI_Animal, FightMode.Aggressor)
{ {
BaseSoundID = 0x3F3; BaseSoundID = 0x3F3;

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{ {
public class SavageRidgeback : BaseMount public class SavageRidgeback : BaseMount
{ {
public override string DefaultName => "a savage ridgeback";
[Constructible] [Constructible]
public SavageRidgeback(string name = "a savage ridgeback") : base(name, 188, 0x3EB8, AIType.AI_Melee, FightMode.Aggressor) public SavageRidgeback() : base(188, 0x3EB8, AIType.AI_Melee, FightMode.Aggressor)
{ {
BaseSoundID = 0x3F3; BaseSoundID = 0x3F3;

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{ {
public class ScaledSwampDragon : BaseMount public class ScaledSwampDragon : BaseMount
{ {
public override string DefaultName => "a swamp dragon";
[Constructible] [Constructible]
public ScaledSwampDragon(string name = "a swamp dragon") : base(name, 0x31F, 0x3EBE, AIType.AI_Melee, FightMode.Aggressor) public ScaledSwampDragon() : base(0x31F, 0x3EBE, AIType.AI_Melee, FightMode.Aggressor)
{ {
SetStr(201, 300); SetStr(201, 300);
SetDex(66, 85); SetDex(66, 85);

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{ {
public class SeaHorse : BaseMount public class SeaHorse : BaseMount
{ {
public override string DefaultName => "a sea horse";
[Constructible] [Constructible]
public SeaHorse(string name = "a sea horse") : base(name, 0x90, 0x3EB3, AIType.AI_Animal, FightMode.Aggressor) public SeaHorse() : base(0x90, 0x3EB3, AIType.AI_Animal, FightMode.Aggressor)
{ {
InitStats(Utility.Random(50, 30), Utility.Random(50, 30), 10); InitStats(Utility.Random(50, 30), Utility.Random(50, 30), 10);
Skills.MagicResist.Base = 25.0 + Utility.RandomDouble() * 5.0; Skills.MagicResist.Base = 25.0 + Utility.RandomDouble() * 5.0;

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{ {
public class SilverSteed : BaseMount public class SilverSteed : BaseMount
{ {
public override string DefaultName => "a silver steed";
[Constructible] [Constructible]
public SilverSteed(string name = "a silver steed") : base(name, 0x75, 0x3EA8, AIType.AI_Animal, FightMode.Aggressor) public SilverSteed() : base(0x75, 0x3EA8, AIType.AI_Animal, FightMode.Aggressor)
{ {
InitStats(Utility.Random(50, 30), Utility.Random(50, 30), 10); InitStats(Utility.Random(50, 30), Utility.Random(50, 30), 10);
Skills.MagicResist.Base = 25.0 + Utility.RandomDouble() * 5.0; Skills.MagicResist.Base = 25.0 + Utility.RandomDouble() * 5.0;

View file

@ -3,7 +3,7 @@ namespace Server.Mobiles
public class SkeletalMount : BaseMount public class SkeletalMount : BaseMount
{ {
[Constructible] [Constructible]
public SkeletalMount(string name = null) : base(name, 793, 0x3EBB, AIType.AI_Animal, FightMode.Aggressor) public SkeletalMount() : base(793, 0x3EBB, AIType.AI_Animal, FightMode.Aggressor)
{ {
SetStr(91, 100); SetStr(91, 100);
SetDex(46, 55); SetDex(46, 55);

View file

@ -4,6 +4,8 @@ namespace Server.Mobiles
{ {
public class SwampDragon : BaseMount public class SwampDragon : BaseMount
{ {
public override string DefaultName => "a swamp dragon";
private string _bardingCraftedBy; private string _bardingCraftedBy;
private bool m_BardingExceptional; private bool m_BardingExceptional;
private int m_BardingHP; private int m_BardingHP;
@ -11,7 +13,7 @@ namespace Server.Mobiles
private bool m_HasBarding; private bool m_HasBarding;
[Constructible] [Constructible]
public SwampDragon(string name = "a swamp dragon") : base(name, 0x31A, 0x3EBD, AIType.AI_Melee, FightMode.Aggressor) public SwampDragon() : base(0x31A, 0x3EBD, AIType.AI_Melee, FightMode.Aggressor)
{ {
BaseSoundID = 0x16A; BaseSoundID = 0x16A;

View file

@ -6,8 +6,10 @@ namespace Server.Mobiles
{ {
public class Unicorn : BaseMount public class Unicorn : BaseMount
{ {
public override string DefaultName => "a unicorn";
[Constructible] [Constructible]
public Unicorn(string name = "a unicorn") : base(name, 0x7A, 0x3EB4, AIType.AI_Mage, FightMode.Evil) public Unicorn() : base(0x7A, 0x3EB4, AIType.AI_Mage, FightMode.Evil)
{ {
BaseSoundID = 0x4BC; BaseSoundID = 0x4BC;

View file

@ -2,11 +2,12 @@ namespace Server.Mobiles
{ {
public abstract class BaseWarHorse : BaseMount public abstract class BaseWarHorse : BaseMount
{ {
public override string DefaultName => "a war horse";
public BaseWarHorse( public BaseWarHorse(
int bodyID, int itemID, AIType aiType = AIType.AI_Melee, FightMode fightMode = FightMode.Aggressor, int bodyID, int itemID, AIType aiType = AIType.AI_Melee, FightMode fightMode = FightMode.Aggressor,
int rangePerception = 10, int rangeFight = 1 int rangePerception = 10, int rangeFight = 1
) : base( ) : base(
"a war horse",
bodyID, bodyID,
itemID, itemID,
aiType, aiType,

View file

@ -20,6 +20,7 @@ namespace Server.Mobiles
public override bool BardImmune => true; public override bool BardImmune => true;
public override Poison PoisonImmune => Poison.Lethal; public override Poison PoisonImmune => Poison.Lethal;
public override bool Commandable => false; public override bool Commandable => false;
public override bool IgnoreMobiles => true;
public override bool PlayerRangeSensitive => false; public override bool PlayerRangeSensitive => false;

View file

@ -73,6 +73,8 @@ namespace Server.Mobiles
public override bool BardImmune => true; public override bool BardImmune => true;
public override Poison PoisonImmune => Poison.Lethal; public override Poison PoisonImmune => Poison.Lethal;
public override bool IgnoreMobiles => true;
public override void DisplayPaperdollTo(Mobile to) public override void DisplayPaperdollTo(Mobile to)
{ {
// Do nothing // Do nothing

View file

@ -5,7 +5,7 @@ namespace Server.Mobiles
public class CuSidhe : BaseMount public class CuSidhe : BaseMount
{ {
[Constructible] [Constructible]
public CuSidhe(string name = null) : base(name, 277, 0x3E91, AIType.AI_Animal, FightMode.Aggressor) public CuSidhe() : base(277, 0x3E91, AIType.AI_Animal, FightMode.Aggressor)
{ {
var chance = Utility.RandomDouble() * 23301; var chance = Utility.RandomDouble() * 23301;

View file

@ -5,7 +5,7 @@ namespace Server.Mobiles
public class RedDeath : SkeletalMount public class RedDeath : SkeletalMount
{ {
[Constructible] [Constructible]
public RedDeath() : base("Red Death") public RedDeath() : base()
{ {
IsParagon = true; IsParagon = true;
@ -55,6 +55,8 @@ namespace Server.Mobiles
{ {
} }
public override string DefaultName => "Red Death";
public override string CorpseName => "a Red Death corpse"; public override string CorpseName => "a Red Death corpse";
public override bool GivesMLMinorArtifact => true; public override bool GivesMLMinorArtifact => true;

View file

@ -4,8 +4,10 @@ namespace Server.Mobiles
{ {
public class Reptalon : BaseMount public class Reptalon : BaseMount
{ {
public override string DefaultName => "a reptalon";
[Constructible] [Constructible]
public Reptalon() : base("a reptalon", 0x114, 0x3E90, AIType.AI_Melee) public Reptalon() : base(0x114, 0x3E90, AIType.AI_Melee)
{ {
BaseSoundID = 0x16A; BaseSoundID = 0x16A;

View file

@ -6,8 +6,10 @@ namespace Server.Mobiles
[Forge] [Forge]
public class FireBeetle : BaseMount public class FireBeetle : BaseMount
{ {
public override string DefaultName => "a fire beetle";
[Constructible] [Constructible]
public FireBeetle() : base("a fire beetle", 0xA9, 0x3E95, AIType.AI_Melee) public FireBeetle() : base(0xA9, 0x3E95, AIType.AI_Melee)
{ {
SetStam(100); SetStam(100);
SetStr(300); SetStr(300);

View file

@ -165,9 +165,7 @@ namespace Server.Mobiles
private int m_HairModID = -1, m_HairModHue; private int m_HairModID = -1, m_HairModHue;
public DateTime m_hontime; public DateTime _honorTime;
private bool m_IgnoreMobiles; // IgnoreMobiles should be moved to Server.Mobiles
private Mobile m_InsuranceAward; private Mobile m_InsuranceAward;
private int m_InsuranceBonus; private int m_InsuranceBonus;
@ -420,20 +418,6 @@ namespace Server.Mobiles
set; set;
} }
[CommandProperty(AccessLevel.GameMaster)]
public bool IgnoreMobiles // IgnoreMobiles should be moved to Server.Mobiles
{
get => m_IgnoreMobiles;
set
{
if (m_IgnoreMobiles != value)
{
m_IgnoreMobiles = value;
Delta(MobileDelta.Flags);
}
}
}
[CommandProperty(AccessLevel.GameMaster)] [CommandProperty(AccessLevel.GameMaster)]
public NpcGuild NpcGuild { get; set; } public NpcGuild NpcGuild { get; set; }
@ -952,18 +936,6 @@ namespace Server.Mobiles
return true; return true;
} }
public override int GetPacketFlags(bool stygianAbyss)
{
var flags = base.GetPacketFlags(stygianAbyss);
if (m_IgnoreMobiles)
{
flags |= 0x10;
}
return flags;
}
public bool GetFlag(PlayerFlag flag) => (Flags & flag) != 0; public bool GetFlag(PlayerFlag flag) => (Flags & flag) != 0;
public void SetFlag(PlayerFlag flag, bool value) public void SetFlag(PlayerFlag flag, bool value)
@ -2336,7 +2308,7 @@ namespace Server.Mobiles
pm.DuelPlayer.Eliminated) || base.OnMoveOver(m); pm.DuelPlayer.Eliminated) || base.OnMoveOver(m);
public override bool CheckShove(Mobile shoved) => public override bool CheckShove(Mobile shoved) =>
m_IgnoreMobiles || TransformationSpellHelper.UnderTransformation(shoved, typeof(WraithFormSpell)) || IgnoreMobiles || shoved.IgnoreMobiles || TransformationSpellHelper.UnderTransformation(shoved, typeof(WraithFormSpell)) ||
base.CheckShove(shoved); base.CheckShove(shoved);
protected override void OnMapChange(Map oldMap) protected override void OnMapChange(Map oldMap)
@ -3228,7 +3200,7 @@ namespace Server.Mobiles
if (AccessLevel > AccessLevel.Player) if (AccessLevel > AccessLevel.Player)
{ {
m_IgnoreMobiles = true; IgnoreMobiles = true;
} }
var list = Stabled; var list = Stabled;

View file

@ -146,12 +146,17 @@ namespace Server.Mobiles
foreach (var m in eable) foreach (var m in eable)
{ {
if (m == m_Owner || !(m_Owner.CanBeHarmful(m) || m.Player && m.Alive)) if (m == m_Owner || !m_Owner.CanBeHarmful(m))
{ {
continue; continue;
} }
if (m is not BaseCreature bc || !(bc.Controlled || bc.Summoned || bc.Team != m_Owner.Team)) if (m.Player && !m.Alive)
{
continue;
}
if (m is BaseCreature bc && !(bc.Controlled || bc.IsAnimatedDead || bc.Summoned || bc.Team != m_Owner.Team))
{ {
continue; continue;
} }

View file

@ -67,11 +67,12 @@ namespace Server.Multis
private DateTime m_DecayTime; private DateTime m_DecayTime;
private Direction m_Facing; private Direction m_Facing;
private TimerExecutionToken _moveTimerToken;
private string m_ShipName; private string m_ShipName;
private TimerExecutionToken _turnTimerToken;
private TurnTimer _turnTimer;
private MoveTimer _moveTimer;
public BaseBoat() : base(0x0) public BaseBoat() : base(0x0)
{ {
@ -125,7 +126,7 @@ namespace Server.Multis
public Direction Moving { get; set; } public Direction Moving { get; set; }
[CommandProperty(AccessLevel.GameMaster)] [CommandProperty(AccessLevel.GameMaster)]
public bool IsMoving => _moveTimerToken.Running; public bool IsMoving => _moveTimer?.Running == true;
[CommandProperty(AccessLevel.GameMaster)] [CommandProperty(AccessLevel.GameMaster)]
public int Speed { get; set; } public int Speed { get; set; }
@ -459,8 +460,8 @@ namespace Server.Multis
Hold?.Delete(); Hold?.Delete();
PPlank?.Delete(); PPlank?.Delete();
SPlank?.Delete(); SPlank?.Delete();
_turnTimerToken.Cancel(); _turnTimer?.Stop();
_moveTimerToken.Cancel(); _moveTimer?.Stop();
Boats.Remove(this); Boats.Remove(this);
} }
@ -1091,8 +1092,10 @@ namespace Server.Multis
Speed = FastSpeed; Speed = FastSpeed;
Order = single ? BoatOrder.Single : BoatOrder.Course; Order = single ? BoatOrder.Single : BoatOrder.Course;
_moveTimerToken.Cancel(); if (!SafelyStartMoveTimer(FastInterval, single))
Timer.StartTimer(FastInterval, FastInterval, StopBoat, out _moveTimerToken); {
return false;
}
if (message) if (message)
{ {
@ -1279,11 +1282,27 @@ namespace Server.Multis
if (Order != BoatOrder.Move) if (Order != BoatOrder.Move)
{ {
_moveTimerToken.Cancel(); _turnTimer?.Stop();
} }
_turnTimerToken.Cancel(); _turnTimer ??= new TurnTimer(offset, this, TimeSpan.FromMilliseconds(500), message);
Timer.StartTimer(TimeSpan.FromMilliseconds(500), () => Turn(offset), out _turnTimerToken);
if (_turnTimer.Running && _turnTimer._turn == offset)
{
// Dont let them spin around in circles like crazy
return true;
}
// Have they issued a new command too soon? Ignore it if so
var turnTimerNext = (_turnTimer.Next - Core.Now).TotalMilliseconds;
if (turnTimerNext is < 0 and > -250)
{
return true;
}
_turnTimer._turn = offset;
_turnTimer.Stop();
_turnTimer.Start();
if (message) if (message)
{ {
@ -1295,7 +1314,7 @@ namespace Server.Multis
public bool Turn(int offset, bool message = true) public bool Turn(int offset, bool message = true)
{ {
_turnTimerToken.Cancel(); _turnTimer?.Stop();
if (CheckDecay()) if (CheckDecay())
{ {
@ -1347,12 +1366,51 @@ namespace Server.Multis
m_ClientSpeed = clientSpeed; m_ClientSpeed = clientSpeed;
Order = BoatOrder.Move; Order = BoatOrder.Move;
_moveTimerToken.Cancel(); SafelyStartMoveTimer(interval, single);
Timer.StartTimer(interval, single ? 1 : 0, StopBoat, out _moveTimerToken);
return true; return true;
} }
private bool SafelyStartMoveTimer(TimeSpan interval, bool single)
{
var singleNum = single ? 1 : 0;
if (_moveTimer != null)
{
// Do not allow them to travel faster simply by respamming the command
if (_moveTimer.Running && _moveTimer.Interval == interval && _moveTimer.Single == singleNum)
{
return false;
}
// Have they issued a new command too soon? Ignore it if so
var moverTimerNext = (_moveTimer.Next - Core.Now).TotalMilliseconds;
if (moverTimerNext is < 0 and > -500)
{
return false;
}
// Changing the interval or flipping between commands: "Forward" and "Forward One"
TimeSpan delay = TimeSpan.Zero;
if (interval.TotalMilliseconds > Math.Abs(moverTimerNext))
{
var addedDelay = (int)(interval.TotalMilliseconds - Math.Abs(moverTimerNext));
delay = TimeSpan.FromMilliseconds(addedDelay);
}
_moveTimer.Stop();
_moveTimer.Delay = delay;
_moveTimer.Interval = interval;
}
else
{
_moveTimer = new MoveTimer(this, interval, interval, singleNum);
}
_moveTimer.Start();
return true;
}
public bool StopMove(bool message) public bool StopMove(bool message)
{ {
if (CheckDecay()) if (CheckDecay())
@ -1360,7 +1418,7 @@ namespace Server.Multis
return false; return false;
} }
if (!_moveTimerToken.Running) if (_moveTimer?.Running == false)
{ {
if (message) if (message)
{ {
@ -1373,7 +1431,7 @@ namespace Server.Multis
Moving = Direction.North; Moving = Direction.North;
Speed = 0; Speed = 0;
m_ClientSpeed = 0; m_ClientSpeed = 0;
_moveTimerToken.Cancel(); _moveTimer?.Stop();
if (message) if (message)
{ {
@ -1891,7 +1949,7 @@ namespace Server.Multis
continue; continue;
} }
// TODO: Remove visible check and use something better, like check for spawners, or other things in the ocean we shouldn't pick up on accident // TODO: Remove visible check and use something better, like check for spawners, or other things in the ocean we shouldn't pick up on accident
if (_boat.Contains(item) && item.Visible && item.Z >= _boat.Z) if (_boat.Contains(item) && item.Visible && item.Z >= _boat.Z)
{ {
_current = current; _current = current;
@ -2047,6 +2105,45 @@ namespace Server.Multis
EventSink.WorldSave += UpdateAllComponents; EventSink.WorldSave += UpdateAllComponents;
} }
private class TurnTimer : Timer
{
internal int _turn;
private bool _message;
private BaseBoat _boat;
public TurnTimer(int turn, BaseBoat boat, TimeSpan delay, bool message = true) : base(delay)
{
_turn = turn;
_message = message;
_boat = boat;
}
protected override void OnTick()
{
_boat?.Turn(_turn, _message);
Stop();
}
}
private class MoveTimer : Timer
{
public TimeSpan BoatMoveDelay;
public TimeSpan BoatMoveInterval;
public int Single;
private BaseBoat _boat;
public MoveTimer(BaseBoat boat, TimeSpan delay, TimeSpan interval, int single = 0) : base(delay, interval, single)
{
BoatMoveInterval = interval;
BoatMoveDelay = delay;
Single = single;
_boat = boat;
}
protected override void OnTick()
{
_boat?.StopBoat();
}
}
private class DecayTimer : Timer private class DecayTimer : Timer
{ {
private readonly BaseBoat m_Boat; private readonly BaseBoat m_Boat;

View file

@ -312,7 +312,7 @@ namespace Server.Spells
return true; return true;
} }
if (mod == null || mod.Offset < offset) if (mod == null || mod.Offset <= offset)
{ {
target.AddStatMod(new StatMod(type, name, offset, duration)); target.AddStatMod(new StatMod(type, name, offset, duration));
return true; return true;

View file

@ -77,17 +77,38 @@ namespace Server.Spells.Necromancy
pool.Enqueue(m); pool.Enqueue(m);
} }
var eable = m.GetMobilesInRange(2); var cbc = Caster as BaseCreature;
var isMonster = cbc?.Controlled == false && (cbc.IsAnimatedDead || !cbc.Summoned);
var eable = m.GetMobilesInRange(2);
foreach (Mobile targ in eable) foreach (Mobile targ in eable)
{ {
if (!(Caster is BaseCreature && targ is BaseCreature) && if (targ == Caster || m == targ || !SpellHelper.ValidIndirectTarget(Caster, targ)
targ != Caster && m != targ && || !Caster.CanBeHarmful(targ, false))
SpellHelper.ValidIndirectTarget(Caster, targ) &&
Caster.CanBeHarmful(targ, false))
{ {
pool.Enqueue(targ); continue;
} }
if (isMonster && targ.Player)
{
continue;
}
// Animate dead casting poison strike shouldn't hit: familiars or player or pets
if (targ is BaseCreature bc)
{
if (bc.IsAnimatedDead)
{
continue;
}
if (isMonster && (bc.Controlled || bc.Summoned || bc.Team == cbc.Team || bc.IsNecroFamiliar))
{
continue;
}
}
pool.Enqueue(targ);
} }
eable.Free(); eable.Free();

View file

@ -49,33 +49,39 @@ namespace Server.Spells.Necromancy
using var pool = PooledRefQueue<Mobile>.Create(); using var pool = PooledRefQueue<Mobile>.Create();
var cbc = Caster as BaseCreature; var cbc = Caster as BaseCreature;
var isMonster = cbc?.Controlled == false && !cbc.Summoned; var isMonster = cbc?.Controlled == false && (cbc.IsAnimatedDead || !cbc.Summoned);
var eable = Caster.GetMobilesInRange(Core.ML ? 4 : 5); var eable = Caster.GetMobilesInRange(Core.ML ? 4 : 5);
foreach (var m in eable) foreach (var targ in eable)
{ {
if (Caster == m || !Caster.InLOS(m) || (!isMonster && !SpellHelper.ValidIndirectTarget(Caster, m)) || if (targ == Caster
!Caster.CanBeHarmful(m, false)) || !Caster.InLOS(targ)
|| !isMonster && !SpellHelper.ValidIndirectTarget(Caster, targ)
|| !Caster.CanBeHarmful(targ, false))
{ {
continue; continue;
} }
if (isMonster) if (isMonster && targ.Player)
{ {
if (m is BaseCreature bc) continue;
}
// Animate dead casting poison strike shouldn't hit: familiars or player or pets
if (targ is BaseCreature bc)
{
if (bc.IsAnimatedDead)
{ {
if (!bc.Controlled && !bc.Summoned && bc.Team == cbc.Team) continue;
{
continue;
}
} }
else if (!m.Player)
if (isMonster && (bc.Controlled || bc.Summoned || bc.Team == cbc.Team || bc.IsNecroFamiliar))
{ {
continue; continue;
} }
} }
pool.Enqueue(m); pool.Enqueue(targ);
} }
eable.Free(); eable.Free();

View file

@ -36,10 +36,14 @@ namespace Server.Spells.Sixth
foreach (var m in eable) foreach (var m in eable)
{ {
if (m is ShadowKnight && if (m is ShadowKnight && (m.X != p.X || m.Y != p.Y))
(m.X != p.X || m.Y != p.Y || !m.Hidden || m.AccessLevel != AccessLevel.Player && {
Caster.AccessLevel <= m.AccessLevel || continue;
!CheckDifficulty(Caster, m))) }
if (!m.Hidden
|| m.AccessLevel > AccessLevel.Player && Caster.AccessLevel <= m.AccessLevel
|| !CheckDifficulty(Caster, m))
{ {
continue; continue;
} }