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 bool _warmodeSpamValue;
private IWeapon m_Weapon;
private bool _ignoreMobiles;
private bool m_YellowHealthbar;
private List<StatMod> _statMods;
@ -405,6 +406,20 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
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; }
[CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)]
@ -6974,6 +6989,11 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
flags |= 0x08;
}
if (IgnoreMobiles)
{
flags |= 0x10;
}
if (m_Warmode)
{
flags |= 0x40;

View file

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

View file

@ -20,11 +20,14 @@ using Server.Gumps;
using Server.Items;
using Server.Mobiles;
using Server.Regions;
using Server.Logging;
using Server.Utilities;
namespace Server.Engines.CannedEvil
{
public class ChampionSpawn : Item
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChampionSpawn));
private bool m_Active;
private ChampionSpawnType m_Type;
private List<Mobile> m_Creatures;
@ -597,11 +600,11 @@ namespace Server.Engines.CannedEvil
{
if (gainedPath)
{
m.SendLocalizedMessage(1054032); // You have gained a path in Valor!
pm.SendLocalizedMessage(1054032); // You have gained a path in Valor!
}
else
{
m.SendLocalizedMessage(1054030); // You have gained in Valor!
pm.SendLocalizedMessage(1054030); // You have gained in Valor!
}
// No delay on Valor gains
@ -682,10 +685,18 @@ namespace Server.Engines.CannedEvil
try
{
Champion = Activator.CreateInstance(ChampionSpawnInfo.GetInfo(m_Type).Champion) as Mobile;
Champion = ChampionSpawnInfo.GetInfo(m_Type).Champion.CreateInstance<Mobile>();
}
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)
{
@ -851,12 +862,21 @@ namespace Server.Engines.CannedEvil
public Mobile Spawn(params Type[] types)
{
var type = types[Utility.Random(types.Length)];
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;
}
}

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 override string DefaultName => "a dark steed";
[Constructible]
public UnholySteed()
: base("a dark steed", 0x74, 0x3EA7, AIType.AI_Melee, FightMode.Aggressor)
public UnholySteed() : base(0x74, 0x3EA7, AIType.AI_Melee, FightMode.Aggressor)
{
SetStr(496, 525);
SetDex(86, 105);

View file

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

View file

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

View file

@ -195,7 +195,7 @@ namespace Server.Engines.PartySystem
m.Party = this;
Span<byte> memberList =
stackalloc byte[PartyPackets.GetPartyMemberListPacketLength(this)].InitializePacket();
stackalloc byte[PartyPackets.GetPartyMemberListPacketLength(Count)].InitializePacket();
Span<byte> attrsPacket = stackalloc byte[OutgoingMobilePackets.MobileAttributesPacketLength].InitializePacket();
for (var i = 0; i < Members.Count; ++i)
@ -216,12 +216,7 @@ namespace Server.Engines.PartySystem
}
}
public void OnAccept(Mobile from)
{
OnAccept(from, false);
}
public void OnAccept(Mobile from, bool force)
public void OnAccept(Mobile from, bool force = false)
{
var ourFaction = Faction.Find(Leader);
var theirFaction = Faction.Find(from);
@ -289,28 +284,33 @@ namespace Server.Engines.PartySystem
return;
}
Span<byte> removeMember =
stackalloc byte[PartyPackets.GetPartyRemoveMemberPacketLength(this)].InitializePacket();
var removed = false;
for (var i = 0; i < Members.Count; i++)
{
if (Members[i].Mobile == m)
{
Members.RemoveAt(i);
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.
removed = true;
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)
{
SendToAll(1005450); // The last person has left the party...

View file

@ -23,7 +23,7 @@ namespace Server.Engines.PartySystem
public static class PartyPackets
{
[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)
{
@ -32,15 +32,16 @@ namespace Server.Engines.PartySystem
return;
}
var length = GetPartyMemberListPacketLength(p);
var count = p.Count;
var length = GetPartyMemberListPacketLength(count);
var writer = new SpanWriter(buffer);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)length);
writer.Write((ushort)0x06); // Sub-packet
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);
}
@ -53,14 +54,14 @@ namespace Server.Engines.PartySystem
return;
}
Span<byte> buffer = stackalloc byte[GetPartyMemberListPacketLength(p)];
Span<byte> buffer = stackalloc byte[GetPartyMemberListPacketLength(p.Count)];
CreatePartyMemberList(buffer, p);
ns.Send(buffer);
}
[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)
{
@ -69,7 +70,7 @@ namespace Server.Engines.PartySystem
return;
}
Span<byte> buffer = stackalloc byte[GetPartyRemoveMemberPacketLength(p)];
Span<byte> buffer = stackalloc byte[GetPartyRemoveMemberPacketLength(p?.Count ?? 0)];
CreatePartyRemoveMember(buffer, m, p);
ns.Send(buffer);
@ -82,14 +83,14 @@ namespace Server.Engines.PartySystem
return;
}
var length = GetPartyRemoveMemberPacketLength(p);
var count = p?.Count ?? 0;
var length = GetPartyRemoveMemberPacketLength(count);
var writer = new SpanWriter(buffer);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)length);
writer.Write((ushort)0x06); // Sub-packet
writer.Write((byte)0x02); // Command
var count = p?.Count ?? 0;
writer.Write((byte)count);
writer.Write(removed);

View file

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

View file

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

View file

@ -35,11 +35,10 @@ namespace Server.Mobiles
{
public class GrizzledMare : HellSteed
{
private static readonly string m_Myname = "a grizzled mare";
public override string DefaultName => "a grizzled mare";
[Constructible]
public GrizzledMare()
: base(m_Myname)
{
}
@ -49,18 +48,11 @@ namespace Server.Mobiles
public override bool DeleteOnRelease => true;
public virtual void OnAfterDeserialize_Callback()
{
SetStats(this);
Name = m_Myname;
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(1); // version
writer.Write(0); // version
}
public override void Deserialize(IGenericReader reader)
@ -68,11 +60,6 @@ namespace Server.Mobiles
base.Deserialize(reader);
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;
}
ClearCurrentAbility(attacker);
if (defender is ChaosDragoon or ChaosDragoonElite)
{
return;
@ -33,11 +35,10 @@ public class Dismount : WeaponAbility
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!
return;
}
}
ClearCurrentAbility(attacker);
var mount = defender.Mount;
if (mount == null && !AnimalForm.UnderTransformation(defender))

View file

@ -18,6 +18,13 @@ namespace Server.Items
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]);
}
else

View file

@ -487,14 +487,7 @@ namespace Server.Items
if (Quality == WeaponQuality.Exceptional)
{
if (Attributes.WeaponDamage > 35)
{
Attributes.WeaponDamage -= 20;
}
else
{
Attributes.WeaponDamage = 15;
}
Attributes.WeaponDamage = 35;
if (Core.ML)
{
@ -2385,27 +2378,8 @@ namespace Server.Items
};
}
public virtual int GetDamageBonus()
{
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;
}
// Note: AOS quality/damage bonuses removed since they are incorporated into the crafting already
public virtual int GetDamageBonus() => VirtualDamageBonus;
public virtual double ScaleDamageAOS(Mobile attacker, double damage, bool checkSkills)
{
@ -2477,10 +2451,9 @@ namespace Server.Items
damageBonus = 100;
}
var totalBonus = strengthBonus + anatomyBonus + tacticsBonus + lumberBonus +
(GetDamageBonus() + damageBonus) / 100.0;
var totalBonus = strengthBonus + anatomyBonus + tacticsBonus + lumberBonus + damageBonus + GetDamageBonus();
return damage + (int)(damage * totalBonus);
return damage + damage * totalBonus / 100.0;
}
public virtual int ComputeDamageAOS(Mobile attacker, Mobile defender) =>

View file

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

View file

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

View file

@ -2633,8 +2633,8 @@ public abstract class BaseAI
var bc = m as BaseCreature;
var pm = m as PlayerMobile;
// Monster don't attack it's own summon
if (Core.AOS && bc != null && bc.Summoned && bc.SummonMaster == m_Mobile)
// 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 || (!bc.SummonMaster.Player && IsHostile(bc.SummonMaster))))
{
continue;
}
@ -2658,6 +2658,18 @@ public abstract class BaseAI
{
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.
@ -2726,7 +2738,7 @@ public abstract class BaseAI
newFocusMob = m;
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.
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;
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
) : base(aiType, fightMode, rangePerception, rangeFight)
{
Name = name;
Body = bodyID;
InternalItem = new MountItem(this, itemID);

View file

@ -6,8 +6,10 @@ namespace Server.Mobiles
{
public class Beetle : BaseMount
{
public override string DefaultName => "a giant beetle";
[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);
SetDex(100);

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{
public class DesertOstard : BaseMount
{
public override string DefaultName => "a desert ostard";
[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;

View file

@ -5,8 +5,10 @@ namespace Server.Mobiles
{
public class FireSteed : BaseMount
{
public override string DefaultName => "a fire steed";
[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;
@ -37,6 +39,9 @@ namespace Server.Mobiles
Tamable = true;
ControlSlots = 2;
MinTameSkill = 106.0;
PackItem(new SulfurousAsh(Utility.RandomMinMax(151, 300)));
PackItem(new Ruby(Utility.RandomMinMax(16, 30)));
}
public FireSteed(Serial serial) : base(serial)
@ -52,8 +57,6 @@ namespace Server.Mobiles
public override void GenerateLoot()
{
PackItem(new SulfurousAsh(Utility.RandomMinMax(151, 300)));
PackItem(new Ruby(Utility.RandomMinMax(16, 30)));
}
public override void Serialize(IGenericWriter writer)

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{
public class ForestOstard : BaseMount
{
public override string DefaultName => "a forest ostard";
[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;

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{
public class FrenziedOstard : BaseMount
{
public override string DefaultName => "a frenzied ostard";
[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;

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{
public class HellSteed : BaseMount
{
public override string DefaultName => "a frenzied ostard";
[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);
}

View file

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

View file

@ -16,8 +16,10 @@ namespace Server.Mobiles
0xCC, 0x3EA2
};
public override string DefaultName => "a horse";
[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);

View file

@ -6,8 +6,10 @@ namespace Server.Mobiles
{
public class Kirin : BaseMount
{
public override string DefaultName => "a ki-rin";
[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;

View file

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

View file

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

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{
public class RidableLlama : BaseMount
{
public override string DefaultName => "a ridable llama";
[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;

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{
public class Ridgeback : BaseMount
{
public override string DefaultName => "a ridgeback";
[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;

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{
public class SavageRidgeback : BaseMount
{
public override string DefaultName => "a savage ridgeback";
[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;

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{
public class ScaledSwampDragon : BaseMount
{
public override string DefaultName => "a swamp dragon";
[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);
SetDex(66, 85);

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{
public class SeaHorse : BaseMount
{
public override string DefaultName => "a sea horse";
[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);
Skills.MagicResist.Base = 25.0 + Utility.RandomDouble() * 5.0;

View file

@ -2,8 +2,10 @@ namespace Server.Mobiles
{
public class SilverSteed : BaseMount
{
public override string DefaultName => "a silver steed";
[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);
Skills.MagicResist.Base = 25.0 + Utility.RandomDouble() * 5.0;

View file

@ -3,7 +3,7 @@ namespace Server.Mobiles
public class SkeletalMount : BaseMount
{
[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);
SetDex(46, 55);

View file

@ -4,6 +4,8 @@ namespace Server.Mobiles
{
public class SwampDragon : BaseMount
{
public override string DefaultName => "a swamp dragon";
private string _bardingCraftedBy;
private bool m_BardingExceptional;
private int m_BardingHP;
@ -11,7 +13,7 @@ namespace Server.Mobiles
private bool m_HasBarding;
[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;

View file

@ -6,8 +6,10 @@ namespace Server.Mobiles
{
public class Unicorn : BaseMount
{
public override string DefaultName => "a unicorn";
[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;

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ namespace Server.Mobiles
public class CuSidhe : BaseMount
{
[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;

View file

@ -5,7 +5,7 @@ namespace Server.Mobiles
public class RedDeath : SkeletalMount
{
[Constructible]
public RedDeath() : base("Red Death")
public RedDeath() : base()
{
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 bool GivesMLMinorArtifact => true;

View file

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

View file

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

View file

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

View file

@ -146,12 +146,17 @@ namespace Server.Mobiles
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;
}
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;
}

View file

@ -67,11 +67,12 @@ namespace Server.Multis
private DateTime m_DecayTime;
private Direction m_Facing;
private TimerExecutionToken _moveTimerToken;
private string m_ShipName;
private TimerExecutionToken _turnTimerToken;
private TurnTimer _turnTimer;
private MoveTimer _moveTimer;
public BaseBoat() : base(0x0)
{
@ -125,7 +126,7 @@ namespace Server.Multis
public Direction Moving { get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public bool IsMoving => _moveTimerToken.Running;
public bool IsMoving => _moveTimer?.Running == true;
[CommandProperty(AccessLevel.GameMaster)]
public int Speed { get; set; }
@ -459,8 +460,8 @@ namespace Server.Multis
Hold?.Delete();
PPlank?.Delete();
SPlank?.Delete();
_turnTimerToken.Cancel();
_moveTimerToken.Cancel();
_turnTimer?.Stop();
_moveTimer?.Stop();
Boats.Remove(this);
}
@ -1091,8 +1092,10 @@ namespace Server.Multis
Speed = FastSpeed;
Order = single ? BoatOrder.Single : BoatOrder.Course;
_moveTimerToken.Cancel();
Timer.StartTimer(FastInterval, FastInterval, StopBoat, out _moveTimerToken);
if (!SafelyStartMoveTimer(FastInterval, single))
{
return false;
}
if (message)
{
@ -1279,11 +1282,27 @@ namespace Server.Multis
if (Order != BoatOrder.Move)
{
_moveTimerToken.Cancel();
_turnTimer?.Stop();
}
_turnTimerToken.Cancel();
Timer.StartTimer(TimeSpan.FromMilliseconds(500), () => Turn(offset), out _turnTimerToken);
_turnTimer ??= new TurnTimer(offset, this, TimeSpan.FromMilliseconds(500), message);
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)
{
@ -1295,7 +1314,7 @@ namespace Server.Multis
public bool Turn(int offset, bool message = true)
{
_turnTimerToken.Cancel();
_turnTimer?.Stop();
if (CheckDecay())
{
@ -1347,12 +1366,51 @@ namespace Server.Multis
m_ClientSpeed = clientSpeed;
Order = BoatOrder.Move;
_moveTimerToken.Cancel();
Timer.StartTimer(interval, single ? 1 : 0, StopBoat, out _moveTimerToken);
SafelyStartMoveTimer(interval, single);
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)
{
if (CheckDecay())
@ -1360,7 +1418,7 @@ namespace Server.Multis
return false;
}
if (!_moveTimerToken.Running)
if (_moveTimer?.Running == false)
{
if (message)
{
@ -1373,7 +1431,7 @@ namespace Server.Multis
Moving = Direction.North;
Speed = 0;
m_ClientSpeed = 0;
_moveTimerToken.Cancel();
_moveTimer?.Stop();
if (message)
{
@ -1891,7 +1949,7 @@ namespace Server.Multis
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)
{
_current = current;
@ -2047,6 +2105,45 @@ namespace Server.Multis
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 readonly BaseBoat m_Boat;

View file

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

View file

@ -77,17 +77,38 @@ namespace Server.Spells.Necromancy
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)
{
if (!(Caster is BaseCreature && targ is BaseCreature) &&
targ != Caster && m != targ &&
SpellHelper.ValidIndirectTarget(Caster, targ) &&
Caster.CanBeHarmful(targ, false))
if (targ == Caster || m == targ || !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();

View file

@ -49,33 +49,39 @@ namespace Server.Spells.Necromancy
using var pool = PooledRefQueue<Mobile>.Create();
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);
foreach (var m in eable)
foreach (var targ in eable)
{
if (Caster == m || !Caster.InLOS(m) || (!isMonster && !SpellHelper.ValidIndirectTarget(Caster, m)) ||
!Caster.CanBeHarmful(m, false))
if (targ == Caster
|| !Caster.InLOS(targ)
|| !isMonster && !SpellHelper.ValidIndirectTarget(Caster, targ)
|| !Caster.CanBeHarmful(targ, false))
{
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;
}
}
pool.Enqueue(m);
pool.Enqueue(targ);
}
eable.Free();

View file

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