Merge branch 'main' into Refactor/MagerySpell.cs
This commit is contained in:
commit
4648947f38
48 changed files with 1519 additions and 1665 deletions
24
.github/workflows/code_quality.yml
vendored
24
.github/workflows/code_quality.yml
vendored
|
|
@ -1,24 +0,0 @@
|
|||
name: Qodana
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches: # Specify your branches here
|
||||
- main # The 'main' branch
|
||||
|
||||
jobs:
|
||||
qodana:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
checks: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit
|
||||
fetch-depth: 0 # a full history is required for pull request analysis
|
||||
- name: 'Qodana Scan'
|
||||
uses: JetBrains/qodana-action@v2025.1
|
||||
env:
|
||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||
|
|
@ -294,13 +294,17 @@ public ref struct SpanReader
|
|||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Read(Span<byte> bytes)
|
||||
public int Read(Span<byte> bytes)
|
||||
{
|
||||
if (bytes.Length < Length)
|
||||
if (bytes.Length == 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(bytes));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return _buffer.TryCopyTo(bytes);
|
||||
var bytesWritten = Math.Min(bytes.Length, Remaining);
|
||||
_buffer.Slice(Position, bytesWritten).CopyTo(bytes);
|
||||
|
||||
Position += bytesWritten;
|
||||
return bytesWritten;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -792,6 +792,7 @@ public class Item : IHued, IComparable<Item>, ISpawnable, IObjectPropertyListEnt
|
|||
public virtual void GetProperties(IPropertyList list)
|
||||
{
|
||||
AddNameProperties(list);
|
||||
AppendChildNameProperties(list);
|
||||
}
|
||||
|
||||
[IgnoreDupe]
|
||||
|
|
@ -1943,8 +1944,6 @@ public class Item : IHued, IComparable<Item>, ISpawnable, IObjectPropertyListEnt
|
|||
{
|
||||
AddQuestItemProperty(list);
|
||||
}
|
||||
|
||||
AppendChildNameProperties(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -661,6 +661,7 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
}
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.Administrator)]
|
||||
public long NextActionTime { get; set; }
|
||||
|
||||
public long NextActionMessage { get; set; }
|
||||
|
|
@ -675,6 +676,7 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
public virtual bool CanRegenStam => Alive;
|
||||
public virtual bool CanRegenMana => Alive;
|
||||
|
||||
[CommandProperty(AccessLevel.Administrator)]
|
||||
public long NextSkillTime { get; set; }
|
||||
|
||||
public List<AggressorInfo> Aggressors { get; private set; }
|
||||
|
|
|
|||
|
|
@ -343,7 +343,28 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
|
|||
return null;
|
||||
}
|
||||
|
||||
public SecureTradeContainer FindTradeContainer(Mobile m) => FindTrade(m)?.From.Container;
|
||||
public SecureTradeContainer FindTradeContainer(Mobile m)
|
||||
{
|
||||
for (var i = 0; i < Trades.Count; ++i)
|
||||
{
|
||||
var trade = Trades[i];
|
||||
|
||||
var from = trade.From;
|
||||
var to = trade.To;
|
||||
|
||||
if (from.Mobile == Mobile && to.Mobile == m)
|
||||
{
|
||||
return from.Container;
|
||||
}
|
||||
|
||||
if (from.Mobile == m && to.Mobile == Mobile)
|
||||
{
|
||||
return to.Container;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public SecureTradeContainer AddTrade(NetState state)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ public class NameVerificationTests
|
|||
public void Validate_TooManyExceptions_ReturnsFalse()
|
||||
{
|
||||
var exceptions = SearchValues.Create(' ', '-', '.');
|
||||
Assert.False(NameVerification.Validate("A-B.C D-E", 2, 16, true, false, false, 3, exceptions));
|
||||
Assert.False(NameVerification.Validate("A-B.C D-E", 2, 16, true, false, false, 1, exceptions));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -279,53 +279,25 @@ namespace Server.Commands.Generic
|
|||
}
|
||||
case StringOperator.Equal:
|
||||
{
|
||||
if (m_IgnoreCase)
|
||||
{
|
||||
methodName = "InsensitiveEquals";
|
||||
}
|
||||
else
|
||||
{
|
||||
methodName = "EqualsOrdinal";
|
||||
}
|
||||
methodName = m_IgnoreCase ? "InsensitiveEquals" : "EqualsOrdinal";
|
||||
break;
|
||||
}
|
||||
|
||||
case StringOperator.Contains:
|
||||
{
|
||||
if (m_IgnoreCase)
|
||||
{
|
||||
methodName = "InsensitiveContains";
|
||||
}
|
||||
else
|
||||
{
|
||||
methodName = "ContainsOrdinal";
|
||||
}
|
||||
methodName = m_IgnoreCase ? "InsensitiveContains" : "ContainsOrdinal";
|
||||
break;
|
||||
}
|
||||
|
||||
case StringOperator.StartsWith:
|
||||
{
|
||||
if (m_IgnoreCase)
|
||||
{
|
||||
methodName = "InsensitiveStartsWith";
|
||||
}
|
||||
else
|
||||
{
|
||||
methodName = "StartsWithOrdinal";
|
||||
}
|
||||
methodName = m_IgnoreCase ? "InsensitiveStartsWith" : "StartsWithOrdinal";
|
||||
break;
|
||||
}
|
||||
|
||||
case StringOperator.EndsWith:
|
||||
{
|
||||
if (m_IgnoreCase)
|
||||
{
|
||||
methodName = "InsensitiveEndsWith";
|
||||
}
|
||||
else
|
||||
{
|
||||
methodName = "EndsWithOrdinal";
|
||||
}
|
||||
methodName = m_IgnoreCase ? "InsensitiveEndsWith" : "EndsWithOrdinal";
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -342,11 +314,7 @@ namespace Server.Commands.Generic
|
|||
methodName,
|
||||
BindingFlags.Public | BindingFlags.Static,
|
||||
null,
|
||||
new[]
|
||||
{
|
||||
typeof(string),
|
||||
typeof(string)
|
||||
},
|
||||
[typeof(string), typeof(string)],
|
||||
null
|
||||
)
|
||||
);
|
||||
|
|
@ -380,12 +348,9 @@ namespace Server.Commands.Generic
|
|||
emitter.BeginCall(
|
||||
type.GetMethod(
|
||||
methodName,
|
||||
BindingFlags.Public | BindingFlags.Instance,
|
||||
BindingFlags.Public | BindingFlags.Static,
|
||||
null,
|
||||
new[]
|
||||
{
|
||||
typeof(string)
|
||||
},
|
||||
[typeof(string), typeof(string)],
|
||||
null
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server.Collections;
|
||||
using Server.Misc;
|
||||
|
||||
namespace Server.Engines.CannedEvil
|
||||
|
|
@ -29,8 +30,8 @@ namespace Server.Engines.CannedEvil
|
|||
Instance.OnTick();
|
||||
}
|
||||
|
||||
private static readonly HashSet<DungeonChampionSpawn> _dungeonSpawns = new();
|
||||
private static readonly HashSet<LLChampionSpawn> _lostLandsSpawns = new();
|
||||
private static readonly HashSet<ChampionSpawn> _dungeonSpawns = new();
|
||||
private static readonly HashSet<ChampionSpawn> _lostLandsSpawns = new();
|
||||
private static DateTime _sliceTime;
|
||||
|
||||
public static CannedEvilTimer Instance { get; private set; }
|
||||
|
|
@ -38,25 +39,25 @@ namespace Server.Engines.CannedEvil
|
|||
public static void AddSpawn(DungeonChampionSpawn spawn)
|
||||
{
|
||||
_dungeonSpawns.Add(spawn);
|
||||
Instance?.OnSlice(_dungeonSpawns, false);
|
||||
OnSlice(_dungeonSpawns, false);
|
||||
}
|
||||
|
||||
public static void AddSpawn(LLChampionSpawn spawn)
|
||||
{
|
||||
_lostLandsSpawns.Add(spawn);
|
||||
Instance?.OnSlice(_lostLandsSpawns, false);
|
||||
OnSlice(_lostLandsSpawns, false);
|
||||
}
|
||||
|
||||
public static void RemoveSpawn(DungeonChampionSpawn spawn)
|
||||
{
|
||||
_dungeonSpawns.Remove(spawn);
|
||||
Instance?.OnSlice(_dungeonSpawns, false);
|
||||
OnSlice(_dungeonSpawns, false);
|
||||
}
|
||||
|
||||
public static void RemoveSpawn(LLChampionSpawn spawn)
|
||||
{
|
||||
_lostLandsSpawns.Remove(spawn);
|
||||
Instance?.OnSlice(_lostLandsSpawns, false);
|
||||
OnSlice(_lostLandsSpawns, false);
|
||||
}
|
||||
|
||||
public CannedEvilTimer() : base(TimeSpan.Zero, TimeSpan.FromMinutes(1.0))
|
||||
|
|
@ -64,32 +65,34 @@ namespace Server.Engines.CannedEvil
|
|||
_sliceTime = Core.Now;
|
||||
}
|
||||
|
||||
public void OnSlice<T>(ICollection<T> list, bool rotate = true) where T : ChampionSpawn
|
||||
public static void OnSlice(HashSet<ChampionSpawn> spawns, bool rotate = true)
|
||||
{
|
||||
if (list.Count > 0)
|
||||
if (spawns.Count <= 0)
|
||||
{
|
||||
List<T> valid = new List<T>();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (T spawn in list)
|
||||
using var queue = rotate ? PooledRefQueue<Item>.Create() : default;
|
||||
|
||||
foreach (var spawn in spawns)
|
||||
{
|
||||
if (spawn.AlwaysActive && !spawn.Active)
|
||||
{
|
||||
if (spawn.AlwaysActive && !spawn.Active)
|
||||
{
|
||||
spawn.ReadyToActivate = true;
|
||||
}
|
||||
else if (rotate && (!spawn.Active || spawn.Kills == 0 && spawn.Level == 0))
|
||||
{
|
||||
spawn.Active = false;
|
||||
spawn.ReadyToActivate = false;
|
||||
|
||||
valid.Add(spawn);
|
||||
}
|
||||
spawn.ReadyToActivate = true;
|
||||
}
|
||||
|
||||
if (valid.Count > 0)
|
||||
else if (rotate && (!spawn.Active || spawn.Kills == 0 && spawn.Level == 0))
|
||||
{
|
||||
valid[Utility.Random(valid.Count)].ReadyToActivate = true;
|
||||
spawn.Active = false;
|
||||
spawn.ReadyToActivate = false;
|
||||
|
||||
queue.Enqueue(spawn);
|
||||
}
|
||||
}
|
||||
|
||||
if (rotate && queue.Count > 0)
|
||||
{
|
||||
((ChampionSpawn)queue.PeekRandom()).ReadyToActivate = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
|
|
|
|||
|
|
@ -26,11 +26,6 @@ public partial class DungeonChampionSpawn : ChampionSpawn
|
|||
CannedEvilTimer.AddSpawn(this);
|
||||
}
|
||||
|
||||
public DungeonChampionSpawn(Serial serial) : base(serial)
|
||||
{
|
||||
CannedEvilTimer.AddSpawn(this);
|
||||
}
|
||||
|
||||
public override bool ProximitySpawn => true;
|
||||
public override bool AlwaysActive => false;
|
||||
|
||||
|
|
@ -39,4 +34,7 @@ public partial class DungeonChampionSpawn : ChampionSpawn
|
|||
base.OnAfterDelete();
|
||||
CannedEvilTimer.RemoveSpawn(this);
|
||||
}
|
||||
|
||||
[AfterDeserialization]
|
||||
private void AfterDeserialization() => CannedEvilTimer.AddSpawn(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,35 +15,34 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace Server.Engines.CannedEvil
|
||||
namespace Server.Engines.CannedEvil;
|
||||
|
||||
public class ChampionEntry
|
||||
{
|
||||
public record ChampionEntry
|
||||
public readonly bool _randomizeType;
|
||||
public readonly ChampionSpawnType _type;
|
||||
public readonly Point3D _signLocation;
|
||||
public readonly Type _champType;
|
||||
public readonly Map _map;
|
||||
public readonly Point3D _ejectLocation;
|
||||
public readonly Map _ejectMap;
|
||||
|
||||
public ChampionEntry(Type champtype, Point3D signloc, Map map, Point3D ejectloc, Map ejectmap) :
|
||||
this(champtype, ChampionSpawnType.Abyss, signloc, map, ejectloc, ejectmap, true)
|
||||
{
|
||||
public readonly bool m_RandomizeType;
|
||||
public readonly ChampionSpawnType m_Type;
|
||||
public readonly Point3D m_SignLocation;
|
||||
public readonly Type m_ChampType;
|
||||
public readonly Map m_Map;
|
||||
public readonly Point3D m_EjectLocation;
|
||||
public readonly Map m_EjectMap;
|
||||
}
|
||||
|
||||
public ChampionEntry(Type champtype, Point3D signloc, Map map, Point3D ejectloc, Map ejectmap) :
|
||||
this(champtype, ChampionSpawnType.Abyss, signloc, map, ejectloc, ejectmap, true)
|
||||
{
|
||||
}
|
||||
|
||||
public ChampionEntry(
|
||||
Type champtype, ChampionSpawnType type, Point3D signloc, Map map, Point3D ejectloc, Map ejectmap,
|
||||
bool randomizetype = false
|
||||
)
|
||||
{
|
||||
m_ChampType = champtype;
|
||||
m_RandomizeType = randomizetype;
|
||||
m_Type = type;
|
||||
m_SignLocation = signloc;
|
||||
m_Map = map;
|
||||
m_EjectLocation = ejectloc;
|
||||
m_EjectMap = ejectmap;
|
||||
}
|
||||
public ChampionEntry(
|
||||
Type champtype, ChampionSpawnType type, Point3D signloc, Map map, Point3D ejectloc, Map ejectmap,
|
||||
bool randomizetype = false
|
||||
)
|
||||
{
|
||||
_champType = champtype;
|
||||
_randomizeType = randomizetype;
|
||||
_type = type;
|
||||
_signLocation = signloc;
|
||||
_map = map;
|
||||
_ejectLocation = ejectloc;
|
||||
_ejectMap = ejectmap;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,105 +17,106 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Server.Logging;
|
||||
|
||||
namespace Server.Engines.CannedEvil
|
||||
namespace Server.Engines.CannedEvil;
|
||||
|
||||
public static class ChampionGenerator
|
||||
{
|
||||
public static class ChampionGenerator
|
||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChampionGenerator));
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChampionGenerator));
|
||||
CommandSystem.Register("GenChamps", AccessLevel.Developer, ChampGen_OnCommand);
|
||||
}
|
||||
|
||||
public static void Configure()
|
||||
private static readonly ChampionEntry[] LLLocations =
|
||||
[
|
||||
new(typeof(LLChampionSpawn), new Point3D(5511, 2360, 42), Map.Felucca, new Point3D(5439, 2323, 26), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(6038, 2401, 47), Map.Felucca, new Point3D(5988, 2340, 24), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5549, 2640, 16), Map.Felucca, new Point3D(5645, 2696, -8), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5636, 2916, 37), Map.Felucca, new Point3D(5721, 2949, 28), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(6035, 2943, 50), Map.Felucca, new Point3D(6098, 2997, 17), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5265, 3171, 105), Map.Felucca, new Point3D(5314, 3232, 2), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5282, 3368, 50), Map.Felucca, new Point3D(5215, 3318, 3), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5207, 3637, 20), Map.Felucca, new Point3D(5263, 3687, 0), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5954, 3475, 25), Map.Felucca, new Point3D(6013, 3529, 0), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5982, 3882, 20), Map.Felucca, new Point3D(5929, 3820, -1), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5724, 3991, 41), Map.Felucca, new Point3D(5774, 4041, 26), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), ChampionSpawnType.ForestLord, new Point3D(5559, 3757, 21), Map.Felucca, new Point3D(5513, 3878, 3), Map.Felucca)
|
||||
];
|
||||
|
||||
private static readonly ChampionEntry[] DungeonLocations =
|
||||
[
|
||||
new(typeof(DungeonChampionSpawn), ChampionSpawnType.UnholyTerror, new Point3D(5179, 709, 20), Map.Felucca, new Point3D(4111, 432, 5), Map.Felucca),
|
||||
new(typeof(DungeonChampionSpawn), ChampionSpawnType.VerminHorde, new Point3D(5557, 827, 65), Map.Felucca, new Point3D(5580, 632, 30), Map.Felucca),
|
||||
new(typeof(DungeonChampionSpawn), ChampionSpawnType.ColdBlood, new Point3D(5259, 837, 64), Map.Felucca, new Point3D(1176, 2637, 0), Map.Felucca),
|
||||
new(typeof(DungeonChampionSpawn), ChampionSpawnType.Abyss, new Point3D(5815, 1352, 5), Map.Felucca, new Point3D(2923, 3406, 8), Map.Felucca),
|
||||
new(typeof(DungeonChampionSpawn), ChampionSpawnType.Arachnid, new Point3D(5190, 1607, 20), Map.Felucca, new Point3D(5482, 3161, -54), Map.Felucca)
|
||||
];
|
||||
|
||||
[Usage("GenChamps")]
|
||||
[Description("Generates champions for Felucca Dungeons & Lost Lands.")]
|
||||
private static void ChampGen_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
/*
|
||||
//We take the assumption that we are spawning managed champions
|
||||
for (int i = CannedEvilTimer.DungeonSpawns.Count - 1; i >= 0; i--)
|
||||
CannedEvilTimer.DungeonSpawns[i].Delete();
|
||||
|
||||
for (int i = CannedEvilTimer.LLSpawns.Count - 1; i >= 0; i--)
|
||||
CannedEvilTimer.LLSpawns[i].Delete();
|
||||
*/
|
||||
|
||||
//We assume that all champion spawns are generated here.
|
||||
List<ChampionSpawn> spawns = [];
|
||||
foreach (Item item in World.Items.Values)
|
||||
{
|
||||
CommandSystem.Register("GenChamps", AccessLevel.Developer, ChampGen_OnCommand);
|
||||
if (item is ChampionSpawn spawn)
|
||||
{
|
||||
spawns.Add(spawn);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ChampionEntry[] LLLocations = {
|
||||
new(typeof(LLChampionSpawn), new Point3D(5511, 2360, 42), Map.Felucca, new Point3D(5439, 2323, 26 ), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(6038, 2401, 47), Map.Felucca, new Point3D(5988, 2340, 24), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5549, 2640, 16), Map.Felucca, new Point3D(5645, 2696, -8), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5636, 2916, 37), Map.Felucca, new Point3D(5721, 2949, 28), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(6035, 2943, 50), Map.Felucca, new Point3D(6098, 2997, 17), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5265, 3171, 105), Map.Felucca, new Point3D(5314, 3232, 2), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5282, 3368, 50), Map.Felucca, new Point3D(5215, 3318, 3), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5207, 3637, 20), Map.Felucca, new Point3D(5263, 3687, 0), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5954, 3475, 25), Map.Felucca, new Point3D(6013, 3529, 0), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5982, 3882, 20), Map.Felucca, new Point3D(5929, 3820, -1), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), new Point3D(5724, 3991, 41), Map.Felucca, new Point3D(5774, 4041, 26), Map.Felucca),
|
||||
new(typeof(LLChampionSpawn), ChampionSpawnType.ForestLord, new Point3D(5559, 3757, 21), Map.Felucca, new Point3D(5513, 3878, 3), Map.Felucca),
|
||||
};
|
||||
|
||||
private static readonly ChampionEntry[] DungeonLocations = {
|
||||
new(typeof(DungeonChampionSpawn), ChampionSpawnType.UnholyTerror, new Point3D(5179, 709, 20), Map.Felucca, new Point3D(4111, 432, 5), Map.Felucca),
|
||||
new(typeof(DungeonChampionSpawn), ChampionSpawnType.VerminHorde, new Point3D(5557, 827, 65), Map.Felucca, new Point3D(5580, 632, 30), Map.Felucca),
|
||||
new(typeof(DungeonChampionSpawn), ChampionSpawnType.ColdBlood, new Point3D(5259, 837, 64), Map.Felucca, new Point3D(1176, 2637, 0), Map.Felucca),
|
||||
new(typeof(DungeonChampionSpawn), ChampionSpawnType.Abyss, new Point3D(5815, 1352, 5), Map.Felucca, new Point3D(2923, 3406, 8), Map.Felucca),
|
||||
new(typeof(DungeonChampionSpawn), ChampionSpawnType.Arachnid, new Point3D(5190, 1607, 20), Map.Felucca, new Point3D(5482, 3161, -54), Map.Felucca),
|
||||
};
|
||||
|
||||
[Usage("GenChamps")]
|
||||
[Description("Generates champions for Felucca Dungeons & Lost Lands.")]
|
||||
private static void ChampGen_OnCommand(CommandEventArgs e)
|
||||
for (int i = spawns.Count - 1; i >= 0; i--)
|
||||
{
|
||||
/*
|
||||
//We take the assumption that we are spawning managed champions
|
||||
for (int i = CannedEvilTimer.DungeonSpawns.Count - 1; i >= 0; i--)
|
||||
CannedEvilTimer.DungeonSpawns[i].Delete();
|
||||
|
||||
for (int i = CannedEvilTimer.LLSpawns.Count - 1; i >= 0; i--)
|
||||
CannedEvilTimer.LLSpawns[i].Delete();
|
||||
*/
|
||||
|
||||
//We assume that all champion spawns are generated here.
|
||||
List<ChampionSpawn> spawns = new List<ChampionSpawn>();
|
||||
foreach (Item item in World.Items.Values)
|
||||
{
|
||||
if (item is ChampionSpawn spawn)
|
||||
{
|
||||
spawns.Add(spawn);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = spawns.Count - 1; i >= 0; i--)
|
||||
{
|
||||
spawns[i].Delete();
|
||||
}
|
||||
|
||||
Process(DungeonLocations);
|
||||
Process(LLLocations);
|
||||
//ProcessIlshenar();
|
||||
//ProcessTokuno();
|
||||
spawns[i].Delete();
|
||||
}
|
||||
|
||||
private static void Process(ChampionEntry[] entries)
|
||||
{
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
{
|
||||
ChampionEntry entry = entries[i];
|
||||
Process(DungeonLocations);
|
||||
Process(LLLocations);
|
||||
//ProcessIlshenar();
|
||||
//ProcessTokuno();
|
||||
}
|
||||
|
||||
try
|
||||
private static void Process(ChampionEntry[] entries)
|
||||
{
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
{
|
||||
ChampionEntry entry = entries[i];
|
||||
|
||||
try
|
||||
{
|
||||
if (Activator.CreateInstance(entry._champType) is ChampionSpawn spawn)
|
||||
{
|
||||
if (Activator.CreateInstance(entry.m_ChampType) is ChampionSpawn spawn)
|
||||
spawn.RandomizeType = entry._randomizeType;
|
||||
spawn.Type = entry._type;
|
||||
spawn.MoveToWorld(entry._signLocation, entry._map);
|
||||
spawn.EjectLocation = entry._ejectLocation;
|
||||
spawn.EjectMap = entry._ejectMap;
|
||||
if (spawn.AlwaysActive)
|
||||
{
|
||||
spawn.RandomizeType = entry.m_RandomizeType;
|
||||
spawn.Type = entry.m_Type;
|
||||
spawn.MoveToWorld(entry.m_SignLocation, entry.m_Map);
|
||||
spawn.EjectLocation = entry.m_EjectLocation;
|
||||
spawn.EjectMap = entry.m_EjectMap;
|
||||
if (spawn.AlwaysActive)
|
||||
{
|
||||
spawn.ReadyToActivate = true;
|
||||
}
|
||||
spawn.ReadyToActivate = true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error(
|
||||
e,
|
||||
"Failed to generate champion \"{Type}\" at {Location} ({Map}).",
|
||||
entry.m_ChampType.FullName,
|
||||
entry.m_SignLocation,
|
||||
entry.m_Map
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error(
|
||||
e,
|
||||
"Failed to generate champion \"{Type}\" at {Location} ({Map}).",
|
||||
entry._champType.FullName,
|
||||
entry._signLocation,
|
||||
entry._map
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,11 +28,6 @@ public partial class LLChampionSpawn : ChampionSpawn
|
|||
CannedEvilTimer.AddSpawn(this);
|
||||
}
|
||||
|
||||
public LLChampionSpawn(Serial serial) : base(serial)
|
||||
{
|
||||
CannedEvilTimer.AddSpawn(this);
|
||||
}
|
||||
|
||||
public override bool AlwaysActive => false;
|
||||
|
||||
public override void OnAfterDelete()
|
||||
|
|
@ -40,4 +35,7 @@ public partial class LLChampionSpawn : ChampionSpawn
|
|||
base.OnAfterDelete();
|
||||
CannedEvilTimer.RemoveSpawn(this);
|
||||
}
|
||||
|
||||
[AfterDeserialization]
|
||||
private void AfterDeserialization() => CannedEvilTimer.AddSpawn(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -258,5 +258,15 @@ public class DefBowFletching : CraftSystem
|
|||
|
||||
MarkOption = true;
|
||||
Repair = Core.AOS;
|
||||
|
||||
SetSubRes(typeof(Log), 1072643);
|
||||
|
||||
AddSubRes(typeof(Log), 1072643, 0.0, 1044041, 1072652);
|
||||
AddSubRes(typeof(OakLog), 1072644, 65.0, 1044041, 1072652);
|
||||
AddSubRes(typeof(AshLog), 1072645, 80.0, 1044041, 1072652);
|
||||
AddSubRes(typeof(YewLog), 1072646, 95.0, 1044041, 1072652);
|
||||
AddSubRes(typeof(HeartwoodLog), 1072647, 100.0, 1044041, 1072652);
|
||||
AddSubRes(typeof(BloodwoodLog), 1072648, 100.0, 1044041, 1072652);
|
||||
AddSubRes(typeof(FrostwoodLog), 1072649, 100.0, 1044041, 1072652);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,11 +79,7 @@ public partial class MurderContext
|
|||
|
||||
if (_player.Kills > 0)
|
||||
{
|
||||
var timeUntilLong = now + (LongTermElapse - gameTime);
|
||||
if (_nextElapse > timeUntilLong)
|
||||
{
|
||||
_nextElapse = timeUntilLong;
|
||||
}
|
||||
_nextElapse = Utility.Min(_nextElapse, now + (LongTermElapse - gameTime));
|
||||
}
|
||||
|
||||
return _nextElapse != DateTime.MaxValue;
|
||||
|
|
|
|||
|
|
@ -101,9 +101,17 @@ public class PlayerMurderSystem : GenericPersistence
|
|||
|
||||
private static void OnDisconnected(Mobile m)
|
||||
{
|
||||
if (m is PlayerMobile pm && _murderContexts.Remove(pm, out var context))
|
||||
if (m is not PlayerMobile pm || !_murderContexts.TryGetValue(pm, out var context))
|
||||
{
|
||||
_contextTerms.Remove(context);
|
||||
return;
|
||||
}
|
||||
|
||||
context.DecayKills();
|
||||
_contextTerms.Remove(context);
|
||||
|
||||
if (pm.Kills <= 0 && context.ShortTermMurders <= 0)
|
||||
{
|
||||
_murderContexts.Remove(pm);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,6 +172,8 @@ public class PlayerMurderSystem : GenericPersistence
|
|||
{
|
||||
var context = GetOrCreateMurderContext(player);
|
||||
context.ShortTermMurders = shortTermMurders;
|
||||
|
||||
context.ResetKillTime();
|
||||
UpdateMurderContext(context);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ namespace Server.Items
|
|||
}
|
||||
|
||||
[SerializableField(0)]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster, readOnly: true)]
|
||||
public BaseAddon _addon;
|
||||
|
||||
[SerializableField(1)]
|
||||
|
|
|
|||
|
|
@ -270,7 +270,8 @@ namespace Server.Items
|
|||
|
||||
foreach (var c in Components)
|
||||
{
|
||||
c.Delete();
|
||||
// Component can become null if the Addon property is somehow deleted, then the component itself is deleted.
|
||||
c?.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -283,5 +284,12 @@ namespace Server.Items
|
|||
_resource = (CraftResource)reader.ReadEncodedInt();
|
||||
}
|
||||
}
|
||||
|
||||
[AfterDeserialization]
|
||||
private void AfterDeserialization()
|
||||
{
|
||||
// We have had issues in the past, so let's tidy it up.
|
||||
_components?.Tidy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ public partial class FarmableCabbage : FarmableCrop
|
|||
{
|
||||
}
|
||||
|
||||
public static int GetCropID() => 3254;
|
||||
public static int GetCropID() => 0x0C7B;
|
||||
|
||||
public override Item GetCropObject() =>
|
||||
new Cabbage
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ public abstract partial class BaseWeapon
|
|||
|
||||
[SerializableFieldSaveFlag(7)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool ShouldSerializePoison() => _poison?.Level > 0;
|
||||
private bool ShouldSerializePoison() => _poison != null;
|
||||
|
||||
[InvalidateProperties]
|
||||
[SerializableField(8)]
|
||||
|
|
@ -890,6 +890,11 @@ public abstract partial class BaseWeapon
|
|||
|
||||
if (attacker is BaseCreature bc)
|
||||
{
|
||||
if (bc.TriggerAbility(MonsterAbilityTrigger.CombatAction, defender))
|
||||
{
|
||||
return GetDelay(attacker);
|
||||
}
|
||||
|
||||
// Only change direction if they are not a player.
|
||||
attacker.Direction = attacker.GetDirectionTo(defender);
|
||||
var ab = bc.GetWeaponAbility();
|
||||
|
|
@ -1350,7 +1355,7 @@ public abstract partial class BaseWeapon
|
|||
theirValue = Math.Max(0.1, defValue + 50.0);
|
||||
}
|
||||
|
||||
var chance = ourValue / (theirValue * 2.0) * 1.0 + (double)bonus / 100;;
|
||||
var chance = ourValue / (theirValue * 2.0) * 1.0 + (double)bonus / 100;
|
||||
|
||||
if (Core.AOS && chance < 0.02)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ namespace Server.Items
|
|||
return wresValue > incrValue ? wresValue : incrValue;
|
||||
}
|
||||
|
||||
private void CheckPreAOSMoves(Mobile attacker, Mobile defender)
|
||||
private static void CheckPreAOSMoves(Mobile attacker, Mobile defender)
|
||||
{
|
||||
if (!attacker.CanBeginAction<Fists>())
|
||||
{
|
||||
|
|
@ -90,60 +90,67 @@ namespace Server.Items
|
|||
attacker.SendLocalizedMessage(1004010); // You failed in your attempt to stun.
|
||||
defender.SendLocalizedMessage(1004011); // Your opponent tried to stun you and failed.
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else if (attacker.DisarmReady)
|
||||
|
||||
if (!attacker.DisarmReady)
|
||||
{
|
||||
if (!defender.Player && !defender.Body.IsHuman)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1004001); // You cannot disarm your opponent.
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (attacker.Skills.ArmsLore.Value < 80.0 || attacker.Skills.Wrestling.Value < 80.0)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1004002); // You are not skilled enough to disarm your opponent.
|
||||
attacker.DisarmReady = false;
|
||||
return;
|
||||
}
|
||||
if (!defender.Player && !defender.Body.IsHuman)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1004001); // You cannot disarm your opponent.
|
||||
return;
|
||||
}
|
||||
|
||||
if (attacker.Stam < 15)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1004003); // You are too fatigued to attempt anything.
|
||||
return;
|
||||
}
|
||||
if (attacker.Skills.ArmsLore.Value < 80.0 || attacker.Skills.Wrestling.Value < 80.0)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1004002); // You are not skilled enough to disarm your opponent.
|
||||
attacker.DisarmReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var toDisarm = defender.FindItemOnLayer(Layer.OneHanded);
|
||||
if (attacker.Stam < 15)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1004003); // You are too fatigued to attempt anything.
|
||||
return;
|
||||
}
|
||||
|
||||
if (toDisarm?.Movable == false)
|
||||
{
|
||||
toDisarm = defender.FindItemOnLayer(Layer.TwoHanded);
|
||||
}
|
||||
var toDisarm = defender.FindItemOnLayer(Layer.OneHanded);
|
||||
|
||||
var pack = defender.Backpack;
|
||||
if (toDisarm?.Movable != true)
|
||||
{
|
||||
toDisarm = defender.FindItemOnLayer(Layer.TwoHanded);
|
||||
}
|
||||
|
||||
if (pack == null || toDisarm?.Movable == false)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1004001); // You cannot disarm your opponent.
|
||||
}
|
||||
else if (CheckMove(attacker, SkillName.ArmsLore))
|
||||
{
|
||||
StartMoveDelay(attacker);
|
||||
var pack = defender.Backpack;
|
||||
|
||||
attacker.Stam -= 15;
|
||||
attacker.DisarmReady = false;
|
||||
if (pack == null || toDisarm?.Movable != true)
|
||||
{
|
||||
attacker.SendLocalizedMessage(1004001); // You cannot disarm your opponent.
|
||||
return;
|
||||
}
|
||||
|
||||
attacker.SendLocalizedMessage(1004006); // You successfully disarm your opponent!
|
||||
defender.SendLocalizedMessage(1004007); // You have been disarmed!
|
||||
if (CheckMove(attacker, SkillName.ArmsLore))
|
||||
{
|
||||
StartMoveDelay(attacker);
|
||||
|
||||
pack.DropItem(toDisarm);
|
||||
}
|
||||
else
|
||||
{
|
||||
attacker.Stam -= 15;
|
||||
attacker.Stam -= 15;
|
||||
attacker.DisarmReady = false;
|
||||
|
||||
attacker.SendLocalizedMessage(1004004); // You failed in your attempt to disarm.
|
||||
defender.SendLocalizedMessage(1004005); // Your opponent tried to disarm you but failed.
|
||||
}
|
||||
attacker.SendLocalizedMessage(1004006); // You successfully disarm your opponent!
|
||||
defender.SendLocalizedMessage(1004007); // You have been disarmed!
|
||||
|
||||
pack.DropItem(toDisarm);
|
||||
}
|
||||
else
|
||||
{
|
||||
attacker.Stam -= 15;
|
||||
|
||||
attacker.SendLocalizedMessage(1004004); // You failed in your attempt to disarm.
|
||||
defender.SendLocalizedMessage(1004005); // Your opponent tried to disarm you but failed.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,11 @@ namespace Server.Items
|
|||
attacker.DisruptiveAction();
|
||||
attacker.NetState.SendSwing(attacker.Serial, defender.Serial);
|
||||
|
||||
if (attacker is BaseCreature bc && bc.TriggerAbility(MonsterAbilityTrigger.CombatAction, defender))
|
||||
{
|
||||
return GetDelay(attacker);
|
||||
}
|
||||
|
||||
if (OnFired(attacker, defender))
|
||||
{
|
||||
if (CheckHit(attacker, defender))
|
||||
|
|
|
|||
|
|
@ -216,6 +216,7 @@ public static class NameVerification
|
|||
}
|
||||
|
||||
var index = name.IndexOfAny(exceptions);
|
||||
var exceptionCount = 0;
|
||||
|
||||
while (index != -1)
|
||||
{
|
||||
|
|
@ -229,7 +230,7 @@ public static class NameVerification
|
|||
noExceptionsAtStart = false;
|
||||
}
|
||||
|
||||
if (maxExceptions-- <= 0)
|
||||
if (exceptionCount++ >= maxExceptions)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
@ -238,6 +239,10 @@ public static class NameVerification
|
|||
{
|
||||
name = name[(index + 1)..];
|
||||
index = name.IndexOfAny(exceptions);
|
||||
if (index != 0)
|
||||
{
|
||||
exceptionCount = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -626,15 +626,13 @@ public abstract class BaseAI
|
|||
{
|
||||
if (m_Mobile.Summoned || m_Mobile is GrizzledMare)
|
||||
{
|
||||
e.Mobile.SendLocalizedMessage(
|
||||
1005481
|
||||
); // Summoned creatures are loyal only to their summoners.
|
||||
// Summoned creatures are loyal only to their summoners.
|
||||
e.Mobile.SendLocalizedMessage(1005481);
|
||||
}
|
||||
else if (e.Mobile.HasTrade)
|
||||
{
|
||||
e.Mobile.SendLocalizedMessage(
|
||||
1070947
|
||||
); // You cannot friend a pet with a trade pending
|
||||
// You cannot friend a pet with a trade pending
|
||||
e.Mobile.SendLocalizedMessage(1070947);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -742,15 +740,13 @@ public abstract class BaseAI
|
|||
{
|
||||
if (m_Mobile.Summoned || m_Mobile is GrizzledMare)
|
||||
{
|
||||
e.Mobile.SendLocalizedMessage(
|
||||
1005487
|
||||
); // You cannot transfer ownership of a summoned creature.
|
||||
// You cannot transfer ownership of a summoned creature.
|
||||
e.Mobile.SendLocalizedMessage(1005487);
|
||||
}
|
||||
else if (e.Mobile.HasTrade)
|
||||
{
|
||||
e.Mobile.SendLocalizedMessage(
|
||||
1010507
|
||||
); // You cannot transfer a pet with a trade pending
|
||||
// You cannot transfer a pet with a trade pending
|
||||
e.Mobile.SendLocalizedMessage(1010507);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -2975,6 +2971,7 @@ public abstract class BaseAI
|
|||
{
|
||||
if (bc.CheckControlChance(from))
|
||||
{
|
||||
bc.ControlTarget = null;
|
||||
bc.ControlOrder = _order;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,23 @@ namespace Server.Mobiles;
|
|||
public class FanningFire : MonsterAbilitySingleTargetDoT
|
||||
{
|
||||
public override MonsterAbilityType AbilityType => MonsterAbilityType.FanningFire;
|
||||
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.GiveDamage;
|
||||
public override double ChanceToTrigger => 0.05;
|
||||
public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.CombatAction;
|
||||
|
||||
public FanningFire(double chanceToTrigger, int fireResistMod, int minDamage, int maxDamage)
|
||||
{
|
||||
ChanceToTrigger = chanceToTrigger;
|
||||
FireResistMod = fireResistMod;
|
||||
MinDamage = minDamage;
|
||||
MaxDamage = maxDamage;
|
||||
}
|
||||
|
||||
public sealed override double ChanceToTrigger { get; }
|
||||
|
||||
public int FireResistMod { get; }
|
||||
|
||||
public int MinDamage { get; }
|
||||
|
||||
public int MaxDamage { get; }
|
||||
|
||||
public const string Name = "FanningFire";
|
||||
|
||||
|
|
@ -52,16 +67,12 @@ public class FanningFire : MonsterAbilitySingleTargetDoT
|
|||
*/
|
||||
|
||||
source.DoHarmful(defender);
|
||||
var effect = -(defender.FireResistance / 10);
|
||||
defender.AddResistanceMod(new ResistanceMod(ResistanceType.Fire, Name, FireResistMod));
|
||||
|
||||
var mod = new ResistanceMod(ResistanceType.Fire, Name, effect);
|
||||
defender.AddResistanceMod(mod);
|
||||
|
||||
defender.FixedParticles(0x37B9, 10, 30, 0x34, EffectLayer.RightFoot);
|
||||
defender.FixedParticles(0x3709, 10, 30, 0x34, EffectLayer.RightFoot);
|
||||
defender.PlaySound(0x208);
|
||||
|
||||
// TODO: Trigger replaces a normal attack.
|
||||
AOS.Damage(defender, source, Utility.RandomMinMax(35, 45), 0, 100, 0, 0, 0);
|
||||
AOS.Damage(defender, source, Utility.RandomMinMax(MinDamage, MaxDamage), 0, 100, 0, 0, 0);
|
||||
}
|
||||
|
||||
protected override void EffectTick(BaseCreature source, Mobile defender, ref TimeSpan nextDelay)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public static class MonsterAbilities
|
|||
// Resistance Debuffs
|
||||
public static GraspingClaw GraspingClaw => new();
|
||||
public static RuneCorruption RuneCorruption => new();
|
||||
public static FanningFire FanningFire => new();
|
||||
public static FanningFire FanningFire => new(0.05, -10, 35, 45);
|
||||
|
||||
// Summon Undead
|
||||
public static SummonSkeletonsCounter SummonSkeletonsCounter => new();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ModernUO.CodeGeneratedEvents;
|
||||
|
||||
namespace Server.Mobiles;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract class used to build singletons for managing a specific monster ability.
|
||||
/// </summary>
|
||||
public abstract partial class MonsterAbility
|
||||
public abstract class MonsterAbility
|
||||
{
|
||||
private Dictionary<BaseCreature, long> _nextTriggerTicks;
|
||||
|
||||
|
|
@ -18,39 +19,34 @@ public abstract partial class MonsterAbility
|
|||
public virtual TimeSpan MinTriggerCooldown => TimeSpan.Zero;
|
||||
public virtual TimeSpan MaxTriggerCooldown => TimeSpan.Zero;
|
||||
|
||||
public bool WillTrigger(MonsterAbilityTrigger trigger) => (AbilityTrigger & trigger) != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if ability is not on cooldown, and the chance to trigger succeeds.
|
||||
/// </summary>
|
||||
/// <returns>Boolean indicating the ability can trigger.</returns>
|
||||
public virtual bool CanTrigger(BaseCreature source, MonsterAbilityTrigger trigger)
|
||||
{
|
||||
if (source is not { Alive: true, Deleted: false })
|
||||
if ((AbilityTrigger & trigger) == 0 || source is not { Alive: true, Deleted: false })
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_nextTriggerTicks?.TryGetValue(source, out var nextTrigger) == true && nextTrigger - Core.TickCount > 0)
|
||||
if (_nextTriggerTicks?.TryGetValue(source, out var nextTrigger) == true)
|
||||
{
|
||||
return false;
|
||||
if (nextTrigger - Core.TickCount > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_nextTriggerTicks.Remove(source);
|
||||
if (_nextTriggerTicks.Count == 0)
|
||||
{
|
||||
_nextTriggerTicks = null;
|
||||
}
|
||||
}
|
||||
|
||||
var c = ChanceToTrigger;
|
||||
|
||||
if (c >= 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (c <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var rnd = Utility.RandomDouble();
|
||||
|
||||
return c > rnd;
|
||||
return c >= 1 || c > 0 && c > Utility.RandomDouble();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -99,7 +95,23 @@ public abstract partial class MonsterAbility
|
|||
{
|
||||
}
|
||||
|
||||
public virtual void Move(BaseCreature creature, Direction d)
|
||||
public virtual void Move(BaseCreature source, Direction d)
|
||||
{
|
||||
}
|
||||
|
||||
[OnEvent(nameof(BaseCreature.CreatureDeathEvent))]
|
||||
[OnEvent(nameof(BaseCreature.CreatureDeletedEvent))]
|
||||
public static void InvalidateNextAbilityTriggers(BaseCreature source)
|
||||
{
|
||||
var abilities = source.GetMonsterAbilities();
|
||||
if (abilities == null || abilities.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < abilities.Length; i++)
|
||||
{
|
||||
abilities[i]._nextTriggerTicks?.Remove(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
using System;
|
||||
using Server.Random;
|
||||
using WeightedMonsterAbility = Server.Random.WeightedValue<Server.Mobiles.MonsterAbility>;
|
||||
|
||||
namespace Server.Mobiles;
|
||||
|
||||
public class MonsterAbilityGroup : MonsterAbility
|
||||
{
|
||||
private WeightedMonsterAbility[] _weightedAbilities;
|
||||
private WeightedMonsterAbility[] _availableToTrigger;
|
||||
private readonly WeightedMonsterAbility[] _weightedAbilities;
|
||||
private readonly WeightedMonsterAbility[] _availableToTrigger;
|
||||
private readonly MonsterAbilityTrigger _triggers;
|
||||
private int _availableToTriggerCount;
|
||||
private MonsterAbilityTrigger _triggers;
|
||||
|
||||
public MonsterAbilityGroup(params WeightedMonsterAbility[] weightedAbilities)
|
||||
{
|
||||
|
|
@ -18,8 +17,7 @@ public class MonsterAbilityGroup : MonsterAbility
|
|||
|
||||
for (var i = 0; i < _weightedAbilities.Length; i++)
|
||||
{
|
||||
var weightedAbility = _weightedAbilities[i];
|
||||
_triggers |= weightedAbility.Value.AbilityTrigger;
|
||||
_triggers |= _weightedAbilities[i].Value.AbilityTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +64,7 @@ public class MonsterAbilityGroup : MonsterAbility
|
|||
for (var i = 0; i < _weightedAbilities.Length; i++)
|
||||
{
|
||||
var weightedAbility = _weightedAbilities[i];
|
||||
if (weightedAbility.Value.WillTrigger(trigger) && weightedAbility.Value.CanTrigger(source, trigger))
|
||||
if (weightedAbility.Value.CanTrigger(source, trigger))
|
||||
{
|
||||
_availableToTrigger[_availableToTriggerCount++] = weightedAbility;
|
||||
}
|
||||
|
|
@ -83,7 +81,7 @@ public class MonsterAbilityGroup : MonsterAbility
|
|||
return;
|
||||
}
|
||||
|
||||
var slice = new ReadOnlySpan<WeightedValue<MonsterAbility>>(_availableToTrigger, 0, _availableToTriggerCount);
|
||||
var slice = new ReadOnlySpan<WeightedMonsterAbility>(_availableToTrigger, 0, _availableToTriggerCount);
|
||||
var chosenAbility = slice.RandomWeightedElement().Value;
|
||||
|
||||
// Just in case?
|
||||
|
|
|
|||
|
|
@ -8,4 +8,8 @@ public class ReflectPhysicalDamage : MonsterAbility
|
|||
public override MonsterAbilityType AbilityType => MonsterAbilityType.ReflectPhysicalDamage;
|
||||
|
||||
public virtual int PercentReflected => 10;
|
||||
|
||||
public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1216,7 +1216,7 @@ namespace Server.Mobiles
|
|||
for (var i = 0; i < abilities.Length; i++)
|
||||
{
|
||||
var ability = abilities[i];
|
||||
if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger))
|
||||
if (ability.CanTrigger(this, trigger))
|
||||
{
|
||||
ability.Trigger(trigger, this, defender);
|
||||
triggered = true;
|
||||
|
|
@ -1238,7 +1238,7 @@ namespace Server.Mobiles
|
|||
for (var i = 0; i < abilities.Length; i++)
|
||||
{
|
||||
var ability = abilities[i];
|
||||
if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger))
|
||||
if (ability.CanTrigger(this, trigger))
|
||||
{
|
||||
ability.Move(this, d);
|
||||
}
|
||||
|
|
@ -1257,7 +1257,7 @@ namespace Server.Mobiles
|
|||
for (var i = 0; i < abilities.Length; i++)
|
||||
{
|
||||
var ability = abilities[i];
|
||||
if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger))
|
||||
if (ability.CanTrigger(this, trigger))
|
||||
{
|
||||
if ((trigger & MonsterAbilityTrigger.GiveMeleeDamage) != 0)
|
||||
{
|
||||
|
|
@ -1298,7 +1298,7 @@ namespace Server.Mobiles
|
|||
for (var i = 0; i < abilities.Length; i++)
|
||||
{
|
||||
var ability = abilities[i];
|
||||
if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger))
|
||||
if (ability.CanTrigger(this, trigger))
|
||||
{
|
||||
if ((trigger & MonsterAbilityTrigger.GiveSpellDamage) != 0)
|
||||
{
|
||||
|
|
@ -3263,7 +3263,7 @@ namespace Server.Mobiles
|
|||
}
|
||||
|
||||
[GeneratedEvent(nameof(CreatureDeathEvent))]
|
||||
public static partial void CreatureDeathEvent(Mobile m);
|
||||
public static partial void CreatureDeathEvent(BaseCreature bc);
|
||||
|
||||
public override void OnDeath(Container c)
|
||||
{
|
||||
|
|
@ -3454,8 +3454,13 @@ namespace Server.Mobiles
|
|||
CreatureDeathEvent(this);
|
||||
}
|
||||
|
||||
[GeneratedEvent(nameof(CreatureDeletedEvent))]
|
||||
public static partial void CreatureDeletedEvent(BaseCreature bc);
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
CreatureDeletedEvent(this);
|
||||
|
||||
var m = m_ControlMaster;
|
||||
SetControlMaster(null);
|
||||
|
||||
|
|
|
|||
|
|
@ -228,11 +228,6 @@ public partial class BaseHire : BaseCreature
|
|||
|
||||
if (!Controlled)
|
||||
{
|
||||
if (CanPaperdollBeOpenedBy(from))
|
||||
{
|
||||
list.Add(new PaperdollEntry());
|
||||
}
|
||||
|
||||
list.Add(new HireEntry());
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,122 +1,71 @@
|
|||
using ModernUO.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Mobiles
|
||||
namespace Server.Mobiles;
|
||||
|
||||
[SerializationGenerator(0, false)]
|
||||
public partial class LadyJennifyr : SkeletalKnight
|
||||
{
|
||||
[SerializationGenerator(0, false)]
|
||||
public partial class LadyJennifyr : SkeletalKnight
|
||||
[Constructible]
|
||||
public LadyJennifyr()
|
||||
{
|
||||
private static readonly Dictionary<Mobile, ExpireTimer> m_Table = new();
|
||||
IsParagon = true;
|
||||
|
||||
[Constructible]
|
||||
public LadyJennifyr()
|
||||
{
|
||||
IsParagon = true;
|
||||
Hue = 0x76D;
|
||||
|
||||
Hue = 0x76D;
|
||||
SetStr(208, 309);
|
||||
SetDex(91, 118);
|
||||
SetInt(44, 101);
|
||||
|
||||
SetStr(208, 309);
|
||||
SetDex(91, 118);
|
||||
SetInt(44, 101);
|
||||
SetHits(1113, 1285);
|
||||
|
||||
SetHits(1113, 1285);
|
||||
SetDamage(15, 25);
|
||||
|
||||
SetDamage(15, 25);
|
||||
SetDamageType(ResistanceType.Physical, 40);
|
||||
SetDamageType(ResistanceType.Cold, 60);
|
||||
|
||||
SetDamageType(ResistanceType.Physical, 40);
|
||||
SetDamageType(ResistanceType.Cold, 60);
|
||||
SetResistance(ResistanceType.Physical, 56, 65);
|
||||
SetResistance(ResistanceType.Fire, 41, 49);
|
||||
SetResistance(ResistanceType.Cold, 71, 80);
|
||||
SetResistance(ResistanceType.Poison, 41, 50);
|
||||
SetResistance(ResistanceType.Energy, 50, 58);
|
||||
|
||||
SetResistance(ResistanceType.Physical, 56, 65);
|
||||
SetResistance(ResistanceType.Fire, 41, 49);
|
||||
SetResistance(ResistanceType.Cold, 71, 80);
|
||||
SetResistance(ResistanceType.Poison, 41, 50);
|
||||
SetResistance(ResistanceType.Energy, 50, 58);
|
||||
SetSkill(SkillName.Wrestling, 127.9, 137.1);
|
||||
SetSkill(SkillName.Tactics, 128.4, 141.9);
|
||||
SetSkill(SkillName.MagicResist, 102.1, 119.5);
|
||||
SetSkill(SkillName.Anatomy, 129.0, 137.5);
|
||||
|
||||
SetSkill(SkillName.Wrestling, 127.9, 137.1);
|
||||
SetSkill(SkillName.Tactics, 128.4, 141.9);
|
||||
SetSkill(SkillName.MagicResist, 102.1, 119.5);
|
||||
SetSkill(SkillName.Anatomy, 129.0, 137.5);
|
||||
|
||||
Fame = 18000;
|
||||
Karma = -18000;
|
||||
}
|
||||
|
||||
public override string CorpseName => "a Lady Jennifyr corpse";
|
||||
public override string DefaultName => "Lady Jennifyr";
|
||||
|
||||
/*
|
||||
// TODO: Uncomment once added
|
||||
public override void OnDeath( Container c )
|
||||
{
|
||||
base.OnDeath( c );
|
||||
|
||||
if (Utility.RandomDouble() < 0.15)
|
||||
c.DropItem( new DisintegratingThesisNotes() );
|
||||
|
||||
if (Utility.RandomDouble() < 0.1)
|
||||
c.DropItem( new ParrotItem() );
|
||||
}
|
||||
*/
|
||||
|
||||
public override bool GivesMLMinorArtifact => true;
|
||||
|
||||
public override void GenerateLoot()
|
||||
{
|
||||
AddLoot(LootPack.UltraRich, 3);
|
||||
}
|
||||
|
||||
public override void OnGaveMeleeAttack(Mobile defender, int damage)
|
||||
{
|
||||
base.OnGaveMeleeAttack(defender, damage);
|
||||
|
||||
if (Utility.RandomDouble() < 0.9)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_Table.Remove(defender, out var timer))
|
||||
{
|
||||
timer.DoExpire();
|
||||
}
|
||||
|
||||
defender.FixedParticles(0x3709, 10, 30, 5052, EffectLayer.LeftFoot);
|
||||
defender.PlaySound(0x208);
|
||||
// The creature fans you with fire, reducing your resistance to fire attacks.
|
||||
defender.SendLocalizedMessage(1070833);
|
||||
|
||||
var mod = new ResistanceMod(ResistanceType.Fire, "FireResistFanningFire", -10);
|
||||
defender.AddResistanceMod(mod);
|
||||
|
||||
m_Table[defender] = timer = new ExpireTimer(defender, mod);
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
private class ExpireTimer : Timer
|
||||
{
|
||||
private readonly Mobile m_Mobile;
|
||||
private readonly ResistanceMod m_Mod;
|
||||
|
||||
public ExpireTimer(Mobile m, ResistanceMod mod)
|
||||
: base(TimeSpan.FromSeconds(10))
|
||||
{
|
||||
m_Mobile = m;
|
||||
m_Mod = mod;
|
||||
}
|
||||
|
||||
public void DoExpire()
|
||||
{
|
||||
m_Mobile.RemoveResistanceMod(m_Mod);
|
||||
|
||||
Stop();
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
m_Mobile.SendLocalizedMessage(1070834); // Your resistance to fire attacks has returned.
|
||||
DoExpire();
|
||||
m_Table.Remove(m_Mobile);
|
||||
}
|
||||
}
|
||||
Fame = 18000;
|
||||
Karma = -18000;
|
||||
}
|
||||
|
||||
public override string CorpseName => "a Lady Jennifyr corpse";
|
||||
public override string DefaultName => "Lady Jennifyr";
|
||||
|
||||
/*
|
||||
// TODO: Uncomment once added
|
||||
public override void OnDeath( Container c )
|
||||
{
|
||||
base.OnDeath( c );
|
||||
|
||||
if (Utility.RandomDouble() < 0.15)
|
||||
c.DropItem( new DisintegratingThesisNotes() );
|
||||
|
||||
if (Utility.RandomDouble() < 0.1)
|
||||
c.DropItem( new ParrotItem() );
|
||||
}
|
||||
*/
|
||||
|
||||
public override bool GivesMLMinorArtifact => true;
|
||||
|
||||
public override void GenerateLoot()
|
||||
{
|
||||
AddLoot(LootPack.UltraRich, 3);
|
||||
}
|
||||
|
||||
private static readonly MonsterAbility[] _abilities =
|
||||
[
|
||||
new FanningFire(0.10, -10, 35, 45)
|
||||
];
|
||||
|
||||
public override MonsterAbility[] GetMonsterAbilities() => _abilities;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace Server.Mobiles;
|
|||
[SerializationGenerator(0, false)]
|
||||
public partial class Banker : BaseVendor
|
||||
{
|
||||
private readonly List<SBInfo> m_SBInfos = new();
|
||||
private readonly List<SBInfo> m_SBInfos = [];
|
||||
|
||||
[Constructible]
|
||||
public Banker() : base("the banker")
|
||||
|
|
@ -41,7 +41,7 @@ public partial class Banker : BaseVendor
|
|||
}
|
||||
}
|
||||
|
||||
Container bank = m.FindBankNoCreate();
|
||||
var bank = m.FindBankNoCreate();
|
||||
|
||||
if (bank != null)
|
||||
{
|
||||
|
|
@ -80,11 +80,11 @@ public partial class Banker : BaseVendor
|
|||
}
|
||||
}
|
||||
|
||||
Container bank = m.FindBankNoCreate();
|
||||
var bank = m.FindBankNoCreate();
|
||||
|
||||
if (bank != null)
|
||||
{
|
||||
gold = new List<Gold>();
|
||||
gold = [];
|
||||
|
||||
foreach (var g in bank.FindItemsByType<Gold>())
|
||||
{
|
||||
|
|
@ -97,7 +97,7 @@ public partial class Banker : BaseVendor
|
|||
return int.MaxValue;
|
||||
}
|
||||
|
||||
checks = new List<BankCheck>();
|
||||
checks = [];
|
||||
|
||||
foreach (var bc in bank.FindItemsByType<BankCheck>())
|
||||
{
|
||||
|
|
@ -111,7 +111,7 @@ public partial class Banker : BaseVendor
|
|||
|
||||
private static bool HasRequiredBalance(int requiredBalance, Mobile m, out PooledRefList<Gold> gold, out PooledRefList<BankCheck> checks)
|
||||
{
|
||||
Container bank = m.FindBankNoCreate();
|
||||
var bank = m.FindBankNoCreate();
|
||||
|
||||
if (bank == null)
|
||||
{
|
||||
|
|
@ -382,9 +382,7 @@ public partial class Banker : BaseVendor
|
|||
}
|
||||
else if (amount > 0)
|
||||
{
|
||||
var box = e.Mobile.FindBankNoCreate();
|
||||
|
||||
if (box == null || !Withdraw(e.Mobile, amount))
|
||||
if (!Withdraw(e.Mobile, amount))
|
||||
{
|
||||
Say(500384); // Ah, art thou trying to fool me? Thou hast not so much gold!
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ public class TownCrierDurationPrompt : Prompt
|
|||
{
|
||||
if (!TimeSpan.TryParse(text, out var ts))
|
||||
{
|
||||
from.SendMessage("Value was not properly formatted. Use: <hours:minutes:seconds>");
|
||||
from.SendMessage("Value was not properly formatted. Use: <hours:minutes:seconds, 00:00:00>");
|
||||
from.SendGump(new TownCrierGump(from, m_Owner));
|
||||
return;
|
||||
}
|
||||
|
|
@ -272,7 +272,7 @@ public class TownCrierGump : Gump
|
|||
{
|
||||
if (info.ButtonID == 1)
|
||||
{
|
||||
m_From.SendMessage("Enter the duration for the new message. Format: <hours:minutes:seconds>");
|
||||
m_From.SendMessage("Enter the duration for the new message. Format: <hours:minutes:seconds, 00:00:00>");
|
||||
m_From.Prompt = new TownCrierDurationPrompt(m_Owner);
|
||||
}
|
||||
else if (info.ButtonID > 1)
|
||||
|
|
@ -433,7 +433,7 @@ public partial class TownCrier : Mobile, ITownCrierEntryList
|
|||
|
||||
if (tce == null)
|
||||
{
|
||||
_autoShoutTimer.Stop();
|
||||
_autoShoutTimer?.Stop();
|
||||
_autoShoutTimer = null;
|
||||
}
|
||||
else if (_newsTimer == null)
|
||||
|
|
@ -454,7 +454,7 @@ public partial class TownCrier : Mobile, ITownCrierEntryList
|
|||
var index = _newsTimer.Index;
|
||||
if (index >= tce.Lines.Length)
|
||||
{
|
||||
_newsTimer.Stop();
|
||||
_newsTimer?.Stop();
|
||||
_newsTimer = null;
|
||||
}
|
||||
else
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -171,7 +171,7 @@ public partial class PlayerVendor : Mobile
|
|||
reader.ReadBool(); // New vendor system?
|
||||
_shopName = reader.ReadString();
|
||||
_nextPayTime = reader.ReadDeltaTime();
|
||||
_house = reader.ReadEntity<BaseHouse>();
|
||||
House = reader.ReadEntity<BaseHouse>();
|
||||
_owner = reader.ReadEntity<Mobile>();
|
||||
_bankAccount = reader.ReadInt();
|
||||
_holdGold = reader.ReadInt();
|
||||
|
|
@ -201,6 +201,34 @@ public partial class PlayerVendor : Mobile
|
|||
{
|
||||
NameHue = -1;
|
||||
}
|
||||
|
||||
// Do we have a vendor that may have been orphaned? Let's try to recover them and attach them to their house.
|
||||
if (_house == null)
|
||||
{
|
||||
if (_owner == null)
|
||||
{
|
||||
Timer.DelayCall(Delete); // Don't try to dismiss, no owner.
|
||||
}
|
||||
Timer.DelayCall(() =>
|
||||
{
|
||||
var house = BaseHouse.FindHouseAt(this);
|
||||
|
||||
if (house != null)
|
||||
{
|
||||
House = house;
|
||||
}
|
||||
else if (_owner.AccessLevel == AccessLevel.Player)
|
||||
{
|
||||
// If we can't find a house, dismiss the vendor.
|
||||
Dismiss(_owner);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
_house.PlayerVendors.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void InitBody()
|
||||
|
|
|
|||
|
|
@ -175,40 +175,16 @@ namespace Server.Multis
|
|||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public BoatOrder Order { get; set; }
|
||||
|
||||
public int Status
|
||||
{
|
||||
get
|
||||
public int Status =>
|
||||
(Core.Now - (TimeOfDecay - BoatDecayDelay)) switch
|
||||
{
|
||||
var start = Core.Now - TimeOfDecay - BoatDecayDelay;
|
||||
|
||||
if (start < TimeSpan.FromHours(1.0))
|
||||
{
|
||||
return 1043010; // This structure is like new.
|
||||
}
|
||||
|
||||
if (start < TimeSpan.FromDays(2.0))
|
||||
{
|
||||
return 1043011; // This structure is slightly worn.
|
||||
}
|
||||
|
||||
if (start < TimeSpan.FromDays(3.0))
|
||||
{
|
||||
return 1043012; // This structure is somewhat worn.
|
||||
}
|
||||
|
||||
if (start < TimeSpan.FromDays(4.0))
|
||||
{
|
||||
return 1043013; // This structure is fairly worn.
|
||||
}
|
||||
|
||||
if (start < TimeSpan.FromDays(5.0))
|
||||
{
|
||||
return 1043014; // This structure is greatly worn.
|
||||
}
|
||||
|
||||
return 1043015; // This structure is in danger of collapsing.
|
||||
}
|
||||
}
|
||||
var start when start < TimeSpan.FromHours(1) => 1043010, // This structure is like new.
|
||||
var start when start < TimeSpan.FromDays(2) => 1043011, // This structure is slightly worn.
|
||||
var start when start < TimeSpan.FromDays(3) => 1043012, // This structure is somewhat worn.
|
||||
var start when start < TimeSpan.FromDays(4) => 1043013, // This structure is fairly worn.
|
||||
var start when start < TimeSpan.FromDays(5) => 1043014, // This structure is greatly worn.
|
||||
_ => 1043015 // This structure is in danger of collapsing.
|
||||
};
|
||||
|
||||
public virtual int NorthID => 0;
|
||||
public virtual int EastID => 0;
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ namespace Server.Multis.Deeds
|
|||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public Point3D Offset { get; set; }
|
||||
|
||||
public virtual Direction HouseDirection => Direction.South;
|
||||
|
||||
public abstract Rectangle2D[] Area { get; }
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
|
|
@ -153,7 +155,7 @@ namespace Server.Multis.Deeds
|
|||
else
|
||||
{
|
||||
var center = new Point3D(p.X - Offset.X, p.Y - Offset.Y, p.Z - Offset.Z);
|
||||
var res = HousePlacement.Check(from, MultiID, center, out var toMove);
|
||||
var res = HousePlacement.Check(from, MultiID, center, out var toMove, HouseDirection);
|
||||
|
||||
switch (res)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@ namespace Server.Multis
|
|||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public bool RestrictDecay { get; set; }
|
||||
|
||||
public virtual Direction HouseDirection => Direction.South;
|
||||
|
||||
public virtual TimeSpan DecayPeriod => TimeSpan.FromDays(5.0);
|
||||
|
||||
public virtual DecayType DecayType
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server.Collections;
|
||||
using Server.Regions;
|
||||
|
|
@ -37,7 +38,9 @@ namespace Server.Multis
|
|||
0x0150, 0x015C // Furrows
|
||||
};
|
||||
|
||||
public static HousePlacementResult Check(Mobile from, int multiID, Point3D center, out List<IEntity> toMove)
|
||||
public static HousePlacementResult Check(
|
||||
Mobile from, int multiID, Point3D center, out List<IEntity> toMove, Direction houseFacing = Direction.South
|
||||
)
|
||||
{
|
||||
// If this spot is considered valid, every item and mobile in this list will be moved under the house sign
|
||||
toMove = new List<IEntity>();
|
||||
|
|
@ -77,7 +80,7 @@ namespace Server.Multis
|
|||
HouseFoundation.AddStairsTo(ref mcl); // this is a AOS house, add the stairs
|
||||
}
|
||||
|
||||
// Location of the nortwest-most corner of the house
|
||||
// Location of the northwest-most corner of the house
|
||||
var start = new Point3D(center.X + mcl.Min.X, center.Y + mcl.Min.Y, center.Z);
|
||||
|
||||
// These are storage lists. They hold items and mobiles found in the map for further processing
|
||||
|
|
@ -85,7 +88,7 @@ namespace Server.Multis
|
|||
var mobiles = new List<Mobile>();
|
||||
|
||||
// These are also storage lists. They hold location values indicating the yard and border locations.
|
||||
List<Point2D> yard = new(), borders = new();
|
||||
List<Point2D> borders = [];
|
||||
|
||||
/* RULES:
|
||||
*
|
||||
|
|
@ -121,7 +124,7 @@ namespace Server.Multis
|
|||
return HousePlacementResult.BadRegionTemp;
|
||||
}
|
||||
|
||||
if (reg.IsPartOf<TreasureRegion>() || reg.IsPartOf<HouseRegion>())
|
||||
if (reg.IsPartOf<TreasureRegion, HouseRegion>())
|
||||
{
|
||||
return HousePlacementResult.BadRegionHidden;
|
||||
}
|
||||
|
|
@ -218,16 +221,18 @@ namespace Server.Multis
|
|||
{
|
||||
var id = item.ItemData;
|
||||
|
||||
if (addTileTop > item.Z && item.Z + id.CalcHeight > addTileZ)
|
||||
if (addTileTop <= item.Z || item.Z + id.CalcHeight <= addTileZ)
|
||||
{
|
||||
if (item.Movable)
|
||||
{
|
||||
toMove.Add(item);
|
||||
}
|
||||
else if (id.Impassable || id.Surface && !id.Background)
|
||||
{
|
||||
return HousePlacementResult.BadItem; // Broke rule #2
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.Movable)
|
||||
{
|
||||
toMove.Add(item);
|
||||
}
|
||||
else if (id.Impassable || id.Surface && !id.Background)
|
||||
{
|
||||
return HousePlacementResult.BadItem; // Broke rule #2
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -257,17 +262,9 @@ namespace Server.Multis
|
|||
|
||||
if (hasFoundation)
|
||||
{
|
||||
for (var xOffset = -1; xOffset <= 1; ++xOffset)
|
||||
if (!CheckYard(map, tileX, tileY, YardSize, houseFacing))
|
||||
{
|
||||
for (var yOffset = -YardSize; yOffset <= YardSize; ++yOffset)
|
||||
{
|
||||
var yardPoint = new Point2D(tileX + xOffset, tileY + yOffset);
|
||||
|
||||
if (!yard.Contains(yardPoint))
|
||||
{
|
||||
yard.Add(yardPoint);
|
||||
}
|
||||
}
|
||||
return HousePlacementResult.BadStatic; // Broke rule #3
|
||||
}
|
||||
|
||||
for (var xOffset = -1; xOffset <= 1; ++xOffset)
|
||||
|
|
@ -364,37 +361,73 @@ namespace Server.Multis
|
|||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < yard.Count; i++)
|
||||
{
|
||||
var yardPoint = yard[i];
|
||||
return HousePlacementResult.Valid;
|
||||
}
|
||||
|
||||
foreach (var house in map.GetMultisInSector<BaseHouse>(yardPoint))
|
||||
private static bool CheckYard(Map map, int tileX, int tileY, int yardSize, Direction houseFacing)
|
||||
{
|
||||
var isSouthFacing = (houseFacing & Direction.South) != 0;
|
||||
var isEastFacing = (houseFacing & Direction.East) != 0;
|
||||
|
||||
for (var xOffset = -yardSize; xOffset <= yardSize; ++xOffset)
|
||||
{
|
||||
var absXOffset = Math.Abs(xOffset);
|
||||
for (var yOffset = -yardSize; yOffset <= yardSize; ++yOffset)
|
||||
{
|
||||
if (house.Contains(yard[i]))
|
||||
var absYOffset = Math.Abs(yOffset);
|
||||
var yardPoint = new Point2D(tileX + xOffset, tileY + yOffset);
|
||||
|
||||
bool inSouthYard = yOffset > 0 && yOffset <= yardSize && absXOffset <= 1;
|
||||
bool inEastYard = xOffset > 0 && xOffset <= yardSize && absYOffset <= 1;
|
||||
bool inNorthYard = yOffset < 0 && yOffset >= -yardSize && absXOffset <= 1;
|
||||
bool inWestYard = xOffset < 0 && xOffset >= -yardSize && absYOffset <= 1;
|
||||
|
||||
// Check each house at this point
|
||||
foreach (var house in map.GetMultisInSector<BaseHouse>(yardPoint))
|
||||
{
|
||||
return HousePlacementResult.BadStatic; // Broke rule #3
|
||||
if (!house.Contains(yardPoint))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var existingHouseFacing = house.HouseDirection;
|
||||
var existingHouseIsSouthFacing = (existingHouseFacing & Direction.South) != 0;
|
||||
var existingHouseIsEastFacing = (existingHouseFacing & Direction.East) != 0;
|
||||
|
||||
// Sub-Rule 1: No houses within immediate proximity (1 tile radius)
|
||||
if (absXOffset <= 1 && absYOffset <= 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sub-Rule 2: If we're south facing, protect our south yard
|
||||
if (isSouthFacing && inSouthYard)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sub-Rule 3: If we're east facing, protect our east yard
|
||||
if (isEastFacing && inEastYard)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sub-Rule 4: If there's a south-facing house to our north, respect its yard
|
||||
if (inNorthYard && existingHouseIsSouthFacing)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sub-Rule 5: If there's an east-facing house to our west, respect its yard
|
||||
if (inWestYard && existingHouseIsEastFacing)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Should we check for MultiTilesAt each yard point?
|
||||
// for (var i = 0; i < yard.Count; i++)
|
||||
// {
|
||||
// var yardPoint = yard[i];
|
||||
//
|
||||
// foreach (var tiles in map.GetMultiTilesAt(yardPoint))
|
||||
// {
|
||||
// for (int j = 0; j < tiles.Length; ++j)
|
||||
// {
|
||||
// if ((TileData.ItemTable[tiles[j].ID & TileData.MaxItemValue].Flags & (TileFlag.Impassable | TileFlag.Surface)) != 0)
|
||||
// {
|
||||
// return HousePlacementResult.BadStatic; // Broke rule #3
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
return HousePlacementResult.Valid;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ public class HousePlacementEntry
|
|||
|
||||
public HousePlacementEntry(
|
||||
Type type, int description, int storage, int lockdowns, int newStorage, int newLockdowns,
|
||||
int vendors, int cost, int xOffset, int yOffset, int zOffset, int multiID
|
||||
int vendors, int cost, int xOffset, int yOffset, int zOffset, int multiID, Direction direction = Direction.South
|
||||
)
|
||||
{
|
||||
Type = type;
|
||||
|
|
@ -318,6 +318,7 @@ public class HousePlacementEntry
|
|||
Offset = new Point3D(xOffset, yOffset, zOffset);
|
||||
|
||||
MultiID = multiID;
|
||||
HouseDirection = direction;
|
||||
}
|
||||
|
||||
public Type Type { get; }
|
||||
|
|
@ -334,6 +335,8 @@ public class HousePlacementEntry
|
|||
|
||||
public Point3D Offset { get; }
|
||||
|
||||
public Direction HouseDirection { get; }
|
||||
|
||||
public static HousePlacementEntry[] ClassicHouses { get; } =
|
||||
{
|
||||
new(typeof(SmallOldHouse), 1011303, 425, 212, 489, 244, 10, 37000, 0, 4, 0, 0x0064),
|
||||
|
|
@ -1981,7 +1984,7 @@ public class HousePlacementEntry
|
|||
|
||||
prevHouse.Delete();
|
||||
|
||||
var res = HousePlacement.Check(from, MultiID, center, out var toMove);
|
||||
var res = HousePlacement.Check(from, MultiID, center, out var toMove, HouseDirection);
|
||||
|
||||
switch (res)
|
||||
{
|
||||
|
|
@ -2008,21 +2011,18 @@ public class HousePlacementEntry
|
|||
$"{Cost} gold would have been withdrawn from your bank if you were not a GM."
|
||||
);
|
||||
}
|
||||
else if (Banker.Withdraw(from, Cost))
|
||||
{
|
||||
// ~1_AMOUNT~ gold has been withdrawn from your bank box.
|
||||
from.SendLocalizedMessage(1060398, Cost.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Banker.Withdraw(from, Cost))
|
||||
{
|
||||
// ~1_AMOUNT~ gold has been withdrawn from your bank box.
|
||||
from.SendLocalizedMessage(1060398, Cost.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
house.RemoveKeys(from);
|
||||
house.Delete();
|
||||
// You do not have the funds available in your bank box to purchase this house. Try placing a smaller house, or adding gold or checks to your bank box.
|
||||
from.SendLocalizedMessage(1060646);
|
||||
return;
|
||||
}
|
||||
house.RemoveKeys(from);
|
||||
house.Delete();
|
||||
// You do not have the funds available in your bank box to purchase this house. Try placing a smaller house, or adding gold or checks to your bank box.
|
||||
from.SendLocalizedMessage(1060646);
|
||||
return;
|
||||
}
|
||||
|
||||
house.MoveToWorld(center, from.Map);
|
||||
|
|
@ -2087,7 +2087,7 @@ public class HousePlacementEntry
|
|||
}
|
||||
|
||||
var center = new Point3D(p.X - Offset.X, p.Y - Offset.Y, p.Z - Offset.Z);
|
||||
var res = HousePlacement.Check(from, MultiID, center, out var toMove);
|
||||
var res = HousePlacement.Check(from, MultiID, center, out var toMove, HouseDirection);
|
||||
|
||||
switch (res)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -158,6 +158,8 @@ public static class IncomingMobilePackets
|
|||
trade.To.Plat = plat;
|
||||
trade.UpdateToCurrency();
|
||||
}
|
||||
|
||||
cont.ClearChecks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,23 +21,18 @@ namespace Server.SkillHandlers
|
|||
|
||||
m.SendLocalizedMessage(500397); // To whom do you wish to grovel?
|
||||
|
||||
return TimeSpan.FromHours(6.0);
|
||||
return TimeSpan.FromSeconds(30.0);
|
||||
}
|
||||
|
||||
private class InternalTarget : Target
|
||||
{
|
||||
private bool m_SetSkillTime = true;
|
||||
|
||||
public InternalTarget() : base(12, false, TargetFlags.None)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnTargetFinish(Mobile from)
|
||||
protected override void OnTargetCancel(Mobile from, TargetCancelType cancelType)
|
||||
{
|
||||
if (m_SetSkillTime)
|
||||
{
|
||||
from.NextSkillTime = Core.TickCount;
|
||||
}
|
||||
from.NextSkillTime = Core.TickCount;
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
|
|
@ -81,8 +76,6 @@ namespace Server.SkillHandlers
|
|||
from.Animate(32, 5, 1, true, false, 0); // Bow
|
||||
|
||||
new InternalTimer(from, targ).Start();
|
||||
|
||||
m_SetSkillTime = false;
|
||||
}
|
||||
}
|
||||
else // Not a Mobile
|
||||
|
|
@ -125,16 +118,7 @@ namespace Server.SkillHandlers
|
|||
else if (m_From.CheckTargetSkill(SkillName.Begging, m_Target, 0.0, 100.0))
|
||||
{
|
||||
var toConsume = theirPack.GetAmount(typeof(Gold)) / 10;
|
||||
var max = 10 + m_From.Fame / 2500;
|
||||
|
||||
if (max > 14)
|
||||
{
|
||||
max = 14;
|
||||
}
|
||||
else if (max < 10)
|
||||
{
|
||||
max = 10;
|
||||
}
|
||||
var max = Math.Clamp(10 + m_From.Fame / 2500, 10, 14);
|
||||
|
||||
if (toConsume > max)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace Server.SkillHandlers
|
|||
src.SendLocalizedMessage(500819); // Where will you search?
|
||||
src.Target = new InternalTarget();
|
||||
|
||||
return TimeSpan.FromSeconds(6.0);
|
||||
return TimeSpan.FromSeconds(30.0);
|
||||
}
|
||||
|
||||
private class InternalTarget : Target
|
||||
|
|
@ -27,6 +27,11 @@ namespace Server.SkillHandlers
|
|||
{
|
||||
}
|
||||
|
||||
protected override void OnTargetCancel(Mobile from, TargetCancelType cancelType)
|
||||
{
|
||||
from.NextSkillTime = Core.TickCount;
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile src, object targ)
|
||||
{
|
||||
var foundAnyone = false;
|
||||
|
|
@ -108,6 +113,8 @@ namespace Server.SkillHandlers
|
|||
{
|
||||
src.SendLocalizedMessage(500817); // You can see nothing hidden there.
|
||||
}
|
||||
|
||||
src.NextSkillTime = Core.TickCount + 6000; // 6 seconds cooldown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,27 +27,22 @@ namespace Server.SkillHandlers
|
|||
from.RevealingAction();
|
||||
from.SendLocalizedMessage(1049525); // Whom do you wish to calm?
|
||||
from.Target = new InternalTarget(from, instrument);
|
||||
from.NextSkillTime = Core.TickCount + 21600000;
|
||||
from.NextSkillTime = Core.TickCount + 30000; // 30s timeout on the targeter
|
||||
}
|
||||
|
||||
private class InternalTarget : Target
|
||||
{
|
||||
private readonly BaseInstrument m_Instrument;
|
||||
private bool m_SetSkillTime = true;
|
||||
|
||||
public InternalTarget(Mobile from, BaseInstrument instrument) : base(
|
||||
BaseInstrument.GetBardRange(from, SkillName.Peacemaking),
|
||||
false,
|
||||
TargetFlags.None
|
||||
) =>
|
||||
m_Instrument = instrument;
|
||||
) => m_Instrument = instrument;
|
||||
|
||||
protected override void OnTargetFinish(Mobile from)
|
||||
protected override void OnTargetCancel(Mobile from, TargetCancelType cancelType)
|
||||
{
|
||||
if (m_SetSkillTime)
|
||||
{
|
||||
from.NextSkillTime = Core.TickCount;
|
||||
}
|
||||
from.NextSkillTime = Core.TickCount;
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
|
|
@ -60,21 +55,19 @@ namespace Server.SkillHandlers
|
|||
}
|
||||
else if (from.Region.IsPartOf<SafeZone>())
|
||||
{
|
||||
from.SendMessage("You may not peacemake in this area.");
|
||||
from.SendMessage("You may not use peacemaking in this area.");
|
||||
}
|
||||
else if (targ.Region.IsPartOf<SafeZone>())
|
||||
{
|
||||
from.SendMessage("You may not peacemake there.");
|
||||
from.SendMessage("You may not use peacemaking there.");
|
||||
}
|
||||
else if (!m_Instrument.IsChildOf(from.Backpack))
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
1062488
|
||||
); // The instrument you are trying to play is no longer in your backpack!
|
||||
// The instrument you are trying to play is no longer in your backpack!
|
||||
from.SendLocalizedMessage(1062488);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_SetSkillTime = false;
|
||||
from.NextSkillTime = Core.TickCount + 10000;
|
||||
|
||||
if (targeted == from)
|
||||
|
|
@ -149,17 +142,14 @@ namespace Server.SkillHandlers
|
|||
if (!from.CanBeHarmful(targ, false))
|
||||
{
|
||||
from.SendLocalizedMessage(1049528);
|
||||
m_SetSkillTime = true;
|
||||
}
|
||||
else if (bc?.Uncalmable == true)
|
||||
{
|
||||
from.SendLocalizedMessage(1049526); // You have no chance of calming that creature.
|
||||
m_SetSkillTime = true;
|
||||
}
|
||||
else if (bc?.BardPacified == true)
|
||||
{
|
||||
from.SendLocalizedMessage(1049527); // That creature is already being calmed.
|
||||
m_SetSkillTime = true;
|
||||
}
|
||||
else if (!BaseInstrument.CheckMusicianship(from))
|
||||
{
|
||||
|
|
@ -193,10 +183,10 @@ namespace Server.SkillHandlers
|
|||
targ.Combatant = null;
|
||||
targ.Warmode = false;
|
||||
|
||||
from.SendLocalizedMessage(1049532); // You play hypnotic music, calming your target.
|
||||
if (bc != null)
|
||||
{
|
||||
from.SendLocalizedMessage(1049532); // You play hypnotic music, calming your target.
|
||||
|
||||
// You play hypnotic music, calming your target.
|
||||
var seconds = 100 - diff / 1.5;
|
||||
|
||||
if (seconds > 120)
|
||||
|
|
@ -212,8 +202,7 @@ namespace Server.SkillHandlers
|
|||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(1049532); // You play hypnotic music, calming your target.
|
||||
|
||||
// You play hypnotic music, calming your target.
|
||||
// You hear lovely music, and forget to continue battling!
|
||||
targ.SendLocalizedMessage(500616);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,510 +12,503 @@ using Server.Spells.Ninjitsu;
|
|||
using Server.Spells.Seventh;
|
||||
using Server.Targeting;
|
||||
|
||||
namespace Server.SkillHandlers
|
||||
namespace Server.SkillHandlers;
|
||||
|
||||
public static class Stealing
|
||||
{
|
||||
public static class Stealing
|
||||
public static bool ClassicMode { get; private set; }
|
||||
|
||||
public static bool SuspendOnMurder { get; private set; }
|
||||
|
||||
public static int MaxWeightToSteal { get; private set; }
|
||||
|
||||
public static bool CanStealContainers { get; private set; }
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
public static readonly bool ClassicMode = false;
|
||||
public static readonly bool SuspendOnMurder = false;
|
||||
private const int MaxWeightToSteal = 10;
|
||||
ClassicMode = ServerConfiguration.GetSetting("stealing.classicMode", !Core.AOS);
|
||||
SuspendOnMurder = ServerConfiguration.GetSetting("stealing.suspendOnMurder", !Core.AOS);
|
||||
CanStealContainers = ServerConfiguration.GetSetting("stealing.canStealContainers", !Core.AOS);
|
||||
MaxWeightToSteal = ServerConfiguration.GetSetting("stealing.maxWeightToSteal", 10);
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
public static void Initialize()
|
||||
{
|
||||
SkillInfo.Table[33].Callback = OnUse;
|
||||
}
|
||||
|
||||
public static bool IsInGuild(Mobile m) => m is PlayerMobile mobile && mobile.NpcGuild == NpcGuild.ThievesGuild;
|
||||
|
||||
public static bool IsInnocentTo(Mobile from, Mobile to) => Notoriety.Compute(from, to) == Notoriety.Innocent;
|
||||
|
||||
public static bool IsEmptyHanded(Mobile from) =>
|
||||
from.FindItemOnLayer(Layer.OneHanded) == null && from.FindItemOnLayer(Layer.TwoHanded) == null;
|
||||
|
||||
public static TimeSpan OnUse(Mobile m)
|
||||
{
|
||||
if (!IsEmptyHanded(m))
|
||||
{
|
||||
SkillInfo.Table[33].Callback = OnUse;
|
||||
m.SendLocalizedMessage(1005584); // Both hands must be free to steal.
|
||||
}
|
||||
else if (m.Region.IsPartOf<SafeZone>())
|
||||
{
|
||||
m.SendMessage("You may not steal in this area.");
|
||||
}
|
||||
else
|
||||
{
|
||||
m.Target = new StealingTarget(m);
|
||||
m.RevealingAction();
|
||||
|
||||
m.SendLocalizedMessage(502698); // Which item do you want to steal?
|
||||
}
|
||||
|
||||
public static bool IsInGuild(Mobile m) => m is PlayerMobile mobile && mobile.NpcGuild == NpcGuild.ThievesGuild;
|
||||
return TimeSpan.FromSeconds(30.0);
|
||||
}
|
||||
|
||||
public static bool IsInnocentTo(Mobile from, Mobile to) => Notoriety.Compute(from, to) == Notoriety.Innocent;
|
||||
private class StealingTarget : Target
|
||||
{
|
||||
private readonly Mobile _thief;
|
||||
|
||||
public static bool IsEmptyHanded(Mobile from)
|
||||
public StealingTarget(Mobile thief) : base(1, false, TargetFlags.None)
|
||||
{
|
||||
if (from.FindItemOnLayer(Layer.OneHanded) != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (from.FindItemOnLayer(Layer.TwoHanded) != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
_thief = thief;
|
||||
AllowNonlocal = true;
|
||||
}
|
||||
|
||||
public static TimeSpan OnUse(Mobile m)
|
||||
protected override void OnTargetCancel(Mobile from, TargetCancelType cancelType)
|
||||
{
|
||||
if (!IsEmptyHanded(m))
|
||||
{
|
||||
m.SendLocalizedMessage(1005584); // Both hands must be free to steal.
|
||||
}
|
||||
else if (m.Region.IsPartOf<SafeZone>())
|
||||
{
|
||||
m.SendMessage("You may not steal in this area.");
|
||||
}
|
||||
else
|
||||
{
|
||||
m.Target = new StealingTarget(m);
|
||||
m.RevealingAction();
|
||||
|
||||
m.SendLocalizedMessage(502698); // Which item do you want to steal?
|
||||
}
|
||||
|
||||
return TimeSpan.FromSeconds(10.0);
|
||||
from.NextSkillTime = Core.TickCount;
|
||||
}
|
||||
|
||||
private class StealingTarget : Target
|
||||
private Item TryStealItem(Item toSteal, ref bool caught)
|
||||
{
|
||||
private readonly Mobile m_Thief;
|
||||
Item stolen = null;
|
||||
|
||||
public StealingTarget(Mobile thief) : base(1, false, TargetFlags.None)
|
||||
var root = toSteal.RootParent;
|
||||
var mobRoot = root as Mobile;
|
||||
var rootIsPlayer = mobRoot?.Player == true;
|
||||
|
||||
var si = toSteal.Parent == null || !toSteal.Movable
|
||||
? StealableArtifacts.GetStealableInstance(toSteal)
|
||||
: null;
|
||||
|
||||
if (!IsEmptyHanded(_thief))
|
||||
{
|
||||
m_Thief = thief;
|
||||
AllowNonlocal = true;
|
||||
_thief.SendLocalizedMessage(1005584); // Both hands must be free to steal.
|
||||
}
|
||||
|
||||
private Item TryStealItem(Item toSteal, ref bool caught)
|
||||
else if (_thief.Region.IsPartOf<SafeZone>())
|
||||
{
|
||||
Item stolen = null;
|
||||
_thief.SendMessage("You may not steal in this area.");
|
||||
}
|
||||
else if ((_thief as PlayerMobile)?.Young == true && (rootIsPlayer || mobRoot is BaseCreature))
|
||||
{
|
||||
_thief.SendLocalizedMessage(502700); // You cannot steal from people or monsters right now. Practice on chests and barrels.
|
||||
}
|
||||
else if (rootIsPlayer && !IsInGuild(_thief))
|
||||
{
|
||||
_thief.SendLocalizedMessage(1005596); // You must be in the thieves guild to steal from other players.
|
||||
}
|
||||
else if (SuspendOnMurder && rootIsPlayer && IsInGuild(_thief) && _thief.Kills > 0)
|
||||
{
|
||||
_thief.SendLocalizedMessage(502706); // You are currently suspended from the thieves guild.
|
||||
}
|
||||
else if ((mobRoot as PlayerMobile)?.Young == true)
|
||||
{
|
||||
_thief.SendLocalizedMessage(502699); // You cannot steal from the Young.
|
||||
}
|
||||
else if ((root as BaseVendor)?.IsInvulnerable == true)
|
||||
{
|
||||
_thief.SendLocalizedMessage(1005598); // You can't steal from shopkeepers.
|
||||
}
|
||||
else if (root is PlayerVendor)
|
||||
{
|
||||
_thief.SendLocalizedMessage(502709); // You can't steal from vendors.
|
||||
}
|
||||
else if (!_thief.CanSee(toSteal))
|
||||
{
|
||||
_thief.SendLocalizedMessage(500237); // Target can not be seen.
|
||||
}
|
||||
else if (toSteal is Sigil sig)
|
||||
{
|
||||
var pl = PlayerState.Find(_thief);
|
||||
var faction = pl?.Faction;
|
||||
|
||||
var root = toSteal.RootParent;
|
||||
var mobRoot = root as Mobile;
|
||||
var rootIsPlayer = mobRoot?.Player == true;
|
||||
|
||||
StealableArtifacts.StealableInstance si = toSteal.Parent == null || !toSteal.Movable
|
||||
? StealableArtifacts.GetStealableInstance(toSteal)
|
||||
: null;
|
||||
|
||||
if (!IsEmptyHanded(m_Thief))
|
||||
if (!_thief.InRange(sig.GetWorldLocation(), 1))
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(1005584); // Both hands must be free to steal.
|
||||
_thief.SendLocalizedMessage(502703); // You must be standing next to an item to steal it.
|
||||
}
|
||||
else if (m_Thief.Region.IsPartOf<SafeZone>())
|
||||
else if (root != null) // not on the ground
|
||||
{
|
||||
m_Thief.SendMessage("You may not steal in this area.");
|
||||
_thief.SendLocalizedMessage(502710); // You can't steal that!
|
||||
}
|
||||
else if ((m_Thief as PlayerMobile)?.Young == true && (rootIsPlayer || mobRoot is BaseCreature))
|
||||
else if (faction == null)
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502700); // You cannot steal from people or monsters right now. Practice on chests and barrels.
|
||||
_thief.SendLocalizedMessage(1005588); // You must join a faction to do that
|
||||
}
|
||||
else if (rootIsPlayer && !IsInGuild(m_Thief))
|
||||
else if (!_thief.CanBeginAction<IncognitoSpell>())
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(1005596); // You must be in the thieves guild to steal from other players.
|
||||
_thief.SendLocalizedMessage(1010581); // You cannot steal the sigil when you are incognito
|
||||
}
|
||||
else if (SuspendOnMurder && rootIsPlayer && IsInGuild(m_Thief) && m_Thief.Kills > 0)
|
||||
else if (DisguisePersistence.IsDisguised(_thief))
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502706); // You are currently suspended from the thieves guild.
|
||||
_thief.SendLocalizedMessage(1010583); // You cannot steal the sigil while disguised
|
||||
}
|
||||
else if ((mobRoot as PlayerMobile)?.Young == true)
|
||||
else if (!_thief.CanBeginAction<PolymorphSpell>())
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502699); // You cannot steal from the Young.
|
||||
_thief.SendLocalizedMessage(1010582); // You cannot steal the sigil while polymorphed
|
||||
}
|
||||
else if ((root as BaseVendor)?.IsInvulnerable == true)
|
||||
else if (TransformationSpellHelper.UnderTransformation(_thief))
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(1005598); // You can't steal from shopkeepers.
|
||||
_thief.SendLocalizedMessage(1061622); // You cannot steal the sigil while in that form.
|
||||
}
|
||||
else if (root is PlayerVendor)
|
||||
else if (AnimalForm.UnderTransformation(_thief))
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502709); // You can't steal from vendors.
|
||||
_thief.SendLocalizedMessage(1063222); // You cannot steal the sigil while mimicking an animal.
|
||||
}
|
||||
else if (!m_Thief.CanSee(toSteal))
|
||||
else if (pl.IsLeaving)
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(500237); // Target can not be seen.
|
||||
// You are currently quitting a faction and cannot steal the town sigil
|
||||
_thief.SendLocalizedMessage(1005589);
|
||||
}
|
||||
else if (toSteal is Sigil sig)
|
||||
else if (sig.IsBeingCorrupted && sig.LastMonolith.Faction == faction)
|
||||
{
|
||||
var pl = PlayerState.Find(m_Thief);
|
||||
var faction = pl?.Faction;
|
||||
|
||||
if (!m_Thief.InRange(sig.GetWorldLocation(), 1))
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502703); // You must be standing next to an item to steal it.
|
||||
}
|
||||
else if (root != null) // not on the ground
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502710); // You can't steal that!
|
||||
}
|
||||
else if (faction != null)
|
||||
{
|
||||
if (!m_Thief.CanBeginAction<IncognitoSpell>())
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(1010581); // You cannot steal the sigil when you are incognito
|
||||
}
|
||||
else if (DisguisePersistence.IsDisguised(m_Thief))
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(1010583); // You cannot steal the sigil while disguised
|
||||
}
|
||||
else if (!m_Thief.CanBeginAction<PolymorphSpell>())
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(1010582); // You cannot steal the sigil while polymorphed
|
||||
}
|
||||
else if (TransformationSpellHelper.UnderTransformation(m_Thief))
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(1061622); // You cannot steal the sigil while in that form.
|
||||
}
|
||||
else if (AnimalForm.UnderTransformation(m_Thief))
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(1063222); // You cannot steal the sigil while mimicking an animal.
|
||||
}
|
||||
else if (pl.IsLeaving)
|
||||
{
|
||||
// You are currently quitting a faction and cannot steal the town sigil
|
||||
m_Thief.SendLocalizedMessage(1005589);
|
||||
}
|
||||
else if (sig.IsBeingCorrupted && sig.LastMonolith.Faction == faction)
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(1005590); // You cannot steal your own sigil
|
||||
}
|
||||
else if (sig.IsPurifying)
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(1005592); // You cannot steal this sigil until it has been purified
|
||||
}
|
||||
else if (m_Thief.CheckTargetSkill(SkillName.Stealing, toSteal, 80.0, 80.0))
|
||||
{
|
||||
if (Sigil.ExistsOn(m_Thief))
|
||||
{
|
||||
// The sigil has gone back to its home location because you already have a sigil.
|
||||
m_Thief.SendLocalizedMessage(1010258);
|
||||
}
|
||||
else if (m_Thief.Backpack?.CheckHold(m_Thief, sig, false, true) != true)
|
||||
{
|
||||
// The sigil has gone home because your backpack is full
|
||||
m_Thief.SendLocalizedMessage(1010259);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sig.IsBeingCorrupted)
|
||||
{
|
||||
sig.GraceStart = Core.Now; // begin grace period
|
||||
}
|
||||
|
||||
m_Thief.SendLocalizedMessage(1010586); // YOU STOLE THE SIGIL!!! (woah, calm down now)
|
||||
|
||||
if (sig.LastMonolith?.Sigil != null)
|
||||
{
|
||||
sig.LastMonolith.Sigil = null;
|
||||
sig.LastStolen = Core.Now;
|
||||
}
|
||||
|
||||
return sig;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(1005594); // You do not have enough skill to steal the sigil
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(1005588); // You must join a faction to do that
|
||||
}
|
||||
_thief.SendLocalizedMessage(1005590); // You cannot steal your own sigil
|
||||
}
|
||||
else if (m_Thief.Backpack?.CheckHold(m_Thief, toSteal, false, true) != true)
|
||||
else if (sig.IsPurifying)
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(1048147); // Your backpack can't hold anything else.
|
||||
_thief.SendLocalizedMessage(1005592); // You cannot steal this sigil until it has been purified
|
||||
}
|
||||
else if (si == null && (toSteal.Parent == null || !toSteal.Movable))
|
||||
else if (!_thief.CheckTargetSkill(SkillName.Stealing, toSteal, 80.0, 80.0))
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502710); // You can't steal that!
|
||||
_thief.SendLocalizedMessage(1005594); // You do not have enough skill to steal the sigil
|
||||
}
|
||||
else if (toSteal.LootType == LootType.Newbied || toSteal.CheckBlessed(mobRoot))
|
||||
else if (Sigil.ExistsOn(_thief))
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502710); // You can't steal that!
|
||||
// The sigil has gone back to its home location because you already have a sigil.
|
||||
_thief.SendLocalizedMessage(1010258);
|
||||
}
|
||||
else if (Core.AOS && si == null && toSteal is Container)
|
||||
else if (_thief.Backpack?.CheckHold(_thief, sig, false, true) != true)
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502710); // You can't steal that!
|
||||
}
|
||||
else if (!m_Thief.InRange(toSteal.GetWorldLocation(), 1))
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502703); // You must be standing next to an item to steal it.
|
||||
}
|
||||
else if (si != null && m_Thief.Skills.Stealing.Value < 100.0)
|
||||
{
|
||||
// You're not skilled enough to attempt the theft of this item.
|
||||
m_Thief.SendLocalizedMessage(1060025, "", 0x66D);
|
||||
}
|
||||
else if (toSteal.Parent is Mobile)
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(1005585); // You cannot steal items which are equipped.
|
||||
}
|
||||
else if (root == m_Thief)
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502704); // You catch yourself red-handed.
|
||||
}
|
||||
else if (mobRoot?.AccessLevel > AccessLevel.Player)
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502710); // You can't steal that!
|
||||
}
|
||||
else if (mobRoot != null && !m_Thief.CanBeHarmful(mobRoot))
|
||||
{
|
||||
}
|
||||
else if (root is Corpse)
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502710); // You can't steal that!
|
||||
// The sigil has gone home because your backpack is full
|
||||
_thief.SendLocalizedMessage(1010259);
|
||||
}
|
||||
else
|
||||
{
|
||||
var w = toSteal.Weight + toSteal.TotalWeight;
|
||||
|
||||
if (w > MaxWeightToSteal)
|
||||
if (sig.IsBeingCorrupted)
|
||||
{
|
||||
// This item is too heavy to steal from someone's backpack.
|
||||
m_Thief.SendLocalizedMessage(502722);
|
||||
sig.GraceStart = Core.Now; // begin grace period
|
||||
}
|
||||
else
|
||||
|
||||
_thief.SendLocalizedMessage(1010586); // YOU STOLE THE SIGIL!!! (woah, calm down now)
|
||||
|
||||
if (sig.LastMonolith?.Sigil != null)
|
||||
{
|
||||
if (toSteal.Stackable && toSteal.Amount > 1)
|
||||
sig.LastMonolith.Sigil = null;
|
||||
sig.LastStolen = Core.Now;
|
||||
}
|
||||
|
||||
return sig;
|
||||
}
|
||||
}
|
||||
else if (_thief.Backpack?.CheckHold(_thief, toSteal, false, true) != true)
|
||||
{
|
||||
_thief.SendLocalizedMessage(1048147); // Your backpack can't hold anything else.
|
||||
}
|
||||
else if (si == null && (toSteal.Parent == null || !toSteal.Movable) || toSteal.LootType == LootType.Newbied ||
|
||||
toSteal.CheckBlessed(mobRoot) || !CanStealContainers && si == null && toSteal is Container)
|
||||
{
|
||||
_thief.SendLocalizedMessage(502710); // You can't steal that!
|
||||
}
|
||||
else if (!_thief.InRange(toSteal.GetWorldLocation(), 1))
|
||||
{
|
||||
_thief.SendLocalizedMessage(502703); // You must be standing next to an item to steal it.
|
||||
}
|
||||
else if (si != null && _thief.Skills.Stealing.Value < 100.0)
|
||||
{
|
||||
// You're not skilled enough to attempt the theft of this item.
|
||||
_thief.SendLocalizedMessage(1060025, "", 0x66D);
|
||||
}
|
||||
else if (toSteal.Parent is Mobile)
|
||||
{
|
||||
_thief.SendLocalizedMessage(1005585); // You cannot steal items which are equipped.
|
||||
}
|
||||
else if (root == _thief)
|
||||
{
|
||||
_thief.SendLocalizedMessage(502704); // You catch yourself red-handed.
|
||||
}
|
||||
else if (mobRoot?.AccessLevel > AccessLevel.Player)
|
||||
{
|
||||
_thief.SendLocalizedMessage(502710); // You can't steal that!
|
||||
}
|
||||
else if (mobRoot != null && !_thief.CanBeHarmful(mobRoot))
|
||||
{
|
||||
}
|
||||
else if (root is Corpse)
|
||||
{
|
||||
_thief.SendLocalizedMessage(502710); // You can't steal that!
|
||||
}
|
||||
else
|
||||
{
|
||||
var w = toSteal.Weight + toSteal.TotalWeight;
|
||||
|
||||
if (w > MaxWeightToSteal)
|
||||
{
|
||||
// This item is too heavy to steal from someone's backpack.
|
||||
_thief.SendLocalizedMessage(502722);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (toSteal.Stackable && toSteal.Amount > 1)
|
||||
{
|
||||
var maxAmount = Math.Clamp(
|
||||
(int)(_thief.Skills.Stealing.Value / 10.0 / toSteal.Weight),
|
||||
1,
|
||||
toSteal.Amount
|
||||
);
|
||||
|
||||
var amount = Utility.RandomMinMax(1, maxAmount);
|
||||
|
||||
if (amount >= toSteal.Amount)
|
||||
{
|
||||
var maxAmount = Math.Clamp(
|
||||
(int)(m_Thief.Skills.Stealing.Value / 10.0 / toSteal.Weight),
|
||||
1,
|
||||
toSteal.Amount
|
||||
);
|
||||
var pileWeight = (int)Math.Ceiling(toSteal.Weight * toSteal.Amount);
|
||||
pileWeight *= 10;
|
||||
|
||||
var amount = Utility.RandomMinMax(1, maxAmount);
|
||||
|
||||
if (amount >= toSteal.Amount)
|
||||
{
|
||||
var pileWeight = (int)Math.Ceiling(toSteal.Weight * toSteal.Amount);
|
||||
pileWeight *= 10;
|
||||
|
||||
if (m_Thief.CheckTargetSkill(
|
||||
if (_thief.CheckTargetSkill(
|
||||
SkillName.Stealing,
|
||||
toSteal,
|
||||
pileWeight - 22.5,
|
||||
pileWeight + 27.5
|
||||
))
|
||||
{
|
||||
stolen = toSteal;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var pileWeight = (int)Math.Ceiling(toSteal.Weight * amount);
|
||||
pileWeight *= 10;
|
||||
|
||||
if (m_Thief.CheckTargetSkill(
|
||||
SkillName.Stealing,
|
||||
toSteal,
|
||||
pileWeight - 22.5,
|
||||
pileWeight + 27.5
|
||||
))
|
||||
{
|
||||
stolen = Mobile.LiftItemDupe(toSteal, toSteal.Amount - amount) ?? toSteal;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var iw = (int)Math.Ceiling(w);
|
||||
iw *= 10;
|
||||
|
||||
if (m_Thief.CheckTargetSkill(SkillName.Stealing, toSteal, iw - 22.5, iw + 27.5))
|
||||
{
|
||||
stolen = toSteal;
|
||||
}
|
||||
}
|
||||
|
||||
if (stolen != null)
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502724); // You successfully steal the item.
|
||||
|
||||
if (si != null)
|
||||
{
|
||||
toSteal.Movable = true;
|
||||
si.Item = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502723); // You fail to steal the item.
|
||||
}
|
||||
var pileWeight = (int)Math.Ceiling(toSteal.Weight * amount);
|
||||
pileWeight *= 10;
|
||||
|
||||
caught = m_Thief.Skills.Stealing.Value < Utility.Random(150);
|
||||
}
|
||||
}
|
||||
|
||||
return stolen;
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object target)
|
||||
{
|
||||
from.RevealingAction();
|
||||
|
||||
Item stolen = null;
|
||||
IEntity root = null;
|
||||
var caught = false;
|
||||
|
||||
if (target is Item item)
|
||||
{
|
||||
root = item.RootParent;
|
||||
stolen = TryStealItem(item, ref caught);
|
||||
}
|
||||
else if (target is Mobile mobile)
|
||||
{
|
||||
var pack = mobile.Backpack;
|
||||
|
||||
if (pack?.Items.Count > 0)
|
||||
{
|
||||
root = mobile;
|
||||
stolen = TryStealItem(pack.Items.RandomElement(), ref caught);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Thief.SendLocalizedMessage(502710); // You can't steal that!
|
||||
}
|
||||
|
||||
var mobRoot = root as Mobile;
|
||||
|
||||
if (stolen != null)
|
||||
{
|
||||
from.AddToBackpack(stolen);
|
||||
|
||||
if (!(stolen is Container || stolen.Stackable))
|
||||
{
|
||||
StolenItem.Add(stolen, m_Thief, mobRoot);
|
||||
}
|
||||
}
|
||||
|
||||
var corpse = root as Corpse;
|
||||
|
||||
if (caught)
|
||||
{
|
||||
if (root == null || corpse?.IsCriminalAction(m_Thief) == true)
|
||||
{
|
||||
m_Thief.CriminalAction(false);
|
||||
}
|
||||
else if (mobRoot != null)
|
||||
{
|
||||
if (!IsInGuild(mobRoot) && IsInnocentTo(m_Thief, mobRoot))
|
||||
{
|
||||
m_Thief.CriminalAction(false);
|
||||
}
|
||||
|
||||
var message = $"You notice {m_Thief.Name} trying to steal from {mobRoot.Name}.";
|
||||
|
||||
foreach (var ns in m_Thief.GetClientsInRange(8))
|
||||
{
|
||||
if (ns.Mobile != m_Thief)
|
||||
if (_thief.CheckTargetSkill(
|
||||
SkillName.Stealing,
|
||||
toSteal,
|
||||
pileWeight - 22.5,
|
||||
pileWeight + 27.5
|
||||
))
|
||||
{
|
||||
ns.Mobile.SendMessage(message);
|
||||
stolen = Mobile.LiftItemDupe(toSteal, toSteal.Amount - amount) ?? toSteal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (corpse?.IsCriminalAction(m_Thief) == true)
|
||||
{
|
||||
m_Thief.CriminalAction(false);
|
||||
}
|
||||
|
||||
if (mobRoot?.Player == true && m_Thief is PlayerMobile pm &&
|
||||
IsInnocentTo(pm, mobRoot) && !IsInGuild(mobRoot))
|
||||
{
|
||||
pm.PermaFlags.Add(mobRoot);
|
||||
pm.Delta(MobileDelta.Noto);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class StolenItem
|
||||
{
|
||||
public static readonly TimeSpan StealTime = TimeSpan.FromMinutes(2.0);
|
||||
|
||||
private static readonly Queue<StolenItem> m_Queue = new();
|
||||
|
||||
public StolenItem(Item stolen, Mobile thief, Mobile victim)
|
||||
{
|
||||
Stolen = stolen;
|
||||
Thief = thief;
|
||||
Victim = victim;
|
||||
|
||||
Expires = Core.Now + StealTime;
|
||||
}
|
||||
|
||||
public Item Stolen { get; }
|
||||
|
||||
public Mobile Thief { get; }
|
||||
|
||||
public Mobile Victim { get; }
|
||||
|
||||
public DateTime Expires { get; private set; }
|
||||
|
||||
public bool IsExpired => Core.Now >= Expires;
|
||||
|
||||
public static void Add(Item item, Mobile thief, Mobile victim)
|
||||
{
|
||||
Clean();
|
||||
|
||||
m_Queue.Enqueue(new StolenItem(item, thief, victim));
|
||||
}
|
||||
|
||||
public static bool IsStolen(Item item)
|
||||
{
|
||||
Mobile victim = null;
|
||||
|
||||
return IsStolen(item, ref victim);
|
||||
}
|
||||
|
||||
public static bool IsStolen(Item item, ref Mobile victim)
|
||||
{
|
||||
Clean();
|
||||
|
||||
foreach (var si in m_Queue)
|
||||
{
|
||||
if (si.Stolen == item && !si.IsExpired)
|
||||
{
|
||||
victim = si.Victim;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[OnEvent(nameof(PlayerMobile.PlayerDeathEvent))]
|
||||
public static void ReturnOnDeath(Mobile killed)
|
||||
{
|
||||
Clean();
|
||||
|
||||
var corpse = killed.Corpse;
|
||||
|
||||
foreach (var si in m_Queue)
|
||||
{
|
||||
if (si.Stolen.RootParent == corpse && si.Victim != null && !si.IsExpired)
|
||||
{
|
||||
if (si.Victim.AddToBackpack(si.Stolen))
|
||||
{
|
||||
si.Victim.SendLocalizedMessage(1010464); // the item that was stolen is returned to you.
|
||||
}
|
||||
else
|
||||
{
|
||||
si.Victim.SendLocalizedMessage(1010463); // the item that was stolen from you falls to the ground.
|
||||
var iw = (int)Math.Ceiling(w);
|
||||
iw *= 10;
|
||||
|
||||
if (_thief.CheckTargetSkill(SkillName.Stealing, toSteal, iw - 22.5, iw + 27.5))
|
||||
{
|
||||
stolen = toSteal;
|
||||
}
|
||||
}
|
||||
|
||||
si.Expires = Core.Now; // such a hack
|
||||
if (stolen != null)
|
||||
{
|
||||
_thief.SendLocalizedMessage(502724); // You successfully steal the item.
|
||||
|
||||
if (si != null)
|
||||
{
|
||||
toSteal.Movable = true;
|
||||
si.Item = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_thief.SendLocalizedMessage(502723); // You fail to steal the item.
|
||||
}
|
||||
|
||||
caught = _thief.Skills.Stealing.Value < Utility.Random(150);
|
||||
}
|
||||
}
|
||||
|
||||
return stolen;
|
||||
}
|
||||
|
||||
public static void Clean()
|
||||
protected override void OnTarget(Mobile from, object target)
|
||||
{
|
||||
while (m_Queue.Count > 0)
|
||||
{
|
||||
var si = m_Queue.Peek();
|
||||
from.RevealingAction();
|
||||
|
||||
if (si.IsExpired)
|
||||
Item stolen = null;
|
||||
IEntity root = null;
|
||||
var caught = false;
|
||||
|
||||
if (target is Item item)
|
||||
{
|
||||
root = item.RootParent;
|
||||
stolen = TryStealItem(item, ref caught);
|
||||
}
|
||||
else if (target is Mobile mobile)
|
||||
{
|
||||
var pack = mobile.Backpack;
|
||||
|
||||
if (pack?.Items.Count > 0)
|
||||
{
|
||||
m_Queue.Dequeue();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
root = mobile;
|
||||
stolen = TryStealItem(pack.Items.RandomElement(), ref caught);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_thief.SendLocalizedMessage(502710); // You can't steal that!
|
||||
}
|
||||
|
||||
var mobRoot = root as Mobile;
|
||||
|
||||
if (stolen != null)
|
||||
{
|
||||
from.AddToBackpack(stolen);
|
||||
|
||||
if (!(stolen is Container || stolen.Stackable))
|
||||
{
|
||||
StolenItem.Add(stolen, _thief, mobRoot);
|
||||
}
|
||||
}
|
||||
|
||||
var corpse = root as Corpse;
|
||||
|
||||
if (caught)
|
||||
{
|
||||
if (root == null || corpse?.IsCriminalAction(_thief) == true)
|
||||
{
|
||||
_thief.CriminalAction(false);
|
||||
}
|
||||
else if (mobRoot != null)
|
||||
{
|
||||
if (!IsInGuild(mobRoot) && IsInnocentTo(_thief, mobRoot))
|
||||
{
|
||||
_thief.CriminalAction(false);
|
||||
}
|
||||
|
||||
var message = $"You notice {_thief.Name} trying to steal from {mobRoot.Name}.";
|
||||
|
||||
foreach (var ns in _thief.GetClientsInRange(8))
|
||||
{
|
||||
if (ns.Mobile != _thief)
|
||||
{
|
||||
ns.Mobile.SendMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (corpse?.IsCriminalAction(_thief) == true)
|
||||
{
|
||||
_thief.CriminalAction(false);
|
||||
}
|
||||
|
||||
if (mobRoot?.Player == true && _thief is PlayerMobile pm &&
|
||||
IsInnocentTo(pm, mobRoot) && !IsInGuild(mobRoot))
|
||||
{
|
||||
pm.PermaFlags.Add(mobRoot);
|
||||
pm.Delta(MobileDelta.Noto);
|
||||
}
|
||||
|
||||
from.NextSkillTime = Core.TickCount + 10000; // 10 seconds cooldown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class StolenItem
|
||||
{
|
||||
public static readonly TimeSpan StealTime = TimeSpan.FromMinutes(2.0);
|
||||
|
||||
private static readonly Queue<StolenItem> _queue = [];
|
||||
|
||||
public StolenItem(Item stolen, Mobile thief, Mobile victim)
|
||||
{
|
||||
Stolen = stolen;
|
||||
Thief = thief;
|
||||
Victim = victim;
|
||||
|
||||
Expires = Core.Now + StealTime;
|
||||
}
|
||||
|
||||
public Item Stolen { get; }
|
||||
|
||||
public Mobile Thief { get; }
|
||||
|
||||
public Mobile Victim { get; }
|
||||
|
||||
public DateTime Expires { get; private set; }
|
||||
|
||||
public bool IsExpired => Core.Now >= Expires;
|
||||
|
||||
public static void Add(Item item, Mobile thief, Mobile victim)
|
||||
{
|
||||
Clean();
|
||||
|
||||
_queue.Enqueue(new StolenItem(item, thief, victim));
|
||||
}
|
||||
|
||||
public static bool IsStolen(Item item)
|
||||
{
|
||||
Mobile victim = null;
|
||||
|
||||
return IsStolen(item, ref victim);
|
||||
}
|
||||
|
||||
public static bool IsStolen(Item item, ref Mobile victim)
|
||||
{
|
||||
Clean();
|
||||
|
||||
foreach (var si in _queue)
|
||||
{
|
||||
if (si.Stolen == item && !si.IsExpired)
|
||||
{
|
||||
victim = si.Victim;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[OnEvent(nameof(PlayerMobile.PlayerDeathEvent))]
|
||||
public static void ReturnOnDeath(Mobile killed)
|
||||
{
|
||||
Clean();
|
||||
|
||||
var corpse = killed.Corpse;
|
||||
|
||||
foreach (var si in _queue)
|
||||
{
|
||||
if (si.Stolen.RootParent != corpse || si.Victim == null || si.IsExpired)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (si.Victim.AddToBackpack(si.Stolen))
|
||||
{
|
||||
si.Victim.SendLocalizedMessage(1010464); // the item that was stolen is returned to you.
|
||||
}
|
||||
else
|
||||
{
|
||||
si.Victim.SendLocalizedMessage(1010463); // the item that was stolen from you falls to the ground.
|
||||
}
|
||||
|
||||
si.Expires = Core.Now; // such a hack
|
||||
}
|
||||
}
|
||||
|
||||
public static void Clean()
|
||||
{
|
||||
while (_queue.Count > 0)
|
||||
{
|
||||
var si = _queue.Peek();
|
||||
|
||||
if (!si.IsExpired)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
_queue.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ namespace Server.Spells.Fourth
|
|||
var oldDexOffset = SpellHelper.GetCurse(caster, m, StatType.Dex);
|
||||
var oldIntOffset = SpellHelper.GetCurse(caster, m, StatType.Int);
|
||||
|
||||
if (oldStrOffset <= newStrOffset && oldDexOffset <= newDexOffset && oldIntOffset <= newIntOffset)
|
||||
if (oldStrOffset > newStrOffset && oldDexOffset > newDexOffset && oldIntOffset > newIntOffset)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,6 +108,6 @@ Thank you for supporting us! You can find out how by visiting the [sponsors](./S
|
|||
- [Karasho](https://github.com/andreakarasho), [Jaedan](https://github.com/jaedan) and the ClassicUO Community
|
||||
|
||||
</br></br>
|
||||
<p align=center>Development Tools & Plugins provided with ♥ by <br /><a href="https://www.jetbrains.com/?from=ModernUO"><img align=middle src="https://user-images.githubusercontent.com/3953314/86882249-cfb2ea00-c0a4-11ea-9cec-bf3f3bcc6f28.png" height="64px" alt="JetBrains" title="JetBrains" /></a>
|
||||
<p align=center>Development Tools & Plugins provided with ♥ by <br /><a href="https://www.jetbrains.com/?from=ModernUO"><img align=middle src="https://github.com/user-attachments/assets/07b12bd0-ca00-472f-8a47-ab48461f3f1d" height="64px" alt="JetBrains" title="JetBrains" /></a><br />
|
||||
<a href="https://material-theme.com/"><img align=center src="https://material-theme.com/img/logo/material-oceanic.svg" width="64px" alt="Material Theme" title="Material Theme"></a>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
Thank you to all of our generous sponsors that make ModernUO possible.
|
||||
|
||||
**A special thank you to the following sponsors for their considerable contributions:**
|
||||
* [Age of Shadows](https://ageofshadows.gg)
|
||||
* [UO Outlands](https://uooutlands.com)
|
||||
* Prayer ([MagnUm-Opus](https://discord.gg/CzDEq3vv2N))
|
||||
|
||||
|
|
@ -10,7 +11,7 @@ Thank you to all of our generous sponsors that make ModernUO possible.
|
|||
We greatly appreciate the support! Use one of the following platforms below:
|
||||
|
||||
#### GitHub
|
||||
[<img alt="Github Sponsors | ModernUO" data-canonical-src="https://camo.githubusercontent.com/a44b124b41d702cb4a66abb48cf746ffb7aabcc96d0c72cdb34e0bf8f761ff49/68747470733a2f2f6769746875622e6769746875626173736574732e636f6d2f696d616765732f6d6f64756c65732f736974652f73706f6e736f72732f6c6f676f2d6d6f6e612e737667" src="https://camo.githubusercontent.com/a44b124b41d702cb4a66abb48cf746ffb7aabcc96d0c72cdb34e0bf8f761ff49/68747470733a2f2f6769746875622e6769746875626173736574732e636f6d2f696d616765732f6d6f64756c65732f736974652f73706f6e736f72732f6c6f676f2d6d6f6e612e737667" width=128px />](https://github.com/sponsors/modernuo)
|
||||
[<img alt="Github Sponsors | ModernUO" data-canonical-src="https://camo.githubusercontent.com/6e0df12df1fdf5c39e8afea60fda5e925322922f07e59e54f7af0f0d9166e771/68747470733a2f2f6769746875622e6769746875626173736574732e636f6d2f696d616765732f6d6f64756c65732f736974652f73706f6e736f72732f6c6f676f2d6d6f6e612e737667" src="https://camo.githubusercontent.com/6e0df12df1fdf5c39e8afea60fda5e925322922f07e59e54f7af0f0d9166e771/68747470733a2f2f6769746875622e6769746875626173736574732e636f6d2f696d616765732f6d6f64756c65732f736974652f73706f6e736f72732f6c6f676f2d6d6f6e612e737667" width=128px />](https://github.com/sponsors/modernuo)
|
||||
|
||||
#### Patreon
|
||||
[<img alt="Patreon | ModernUO" data-canonical-src="https://user-images.githubusercontent.com/3953314/104968719-7c96e980-599b-11eb-839b-28745496b1a4.png" src="https://user-images.githubusercontent.com/3953314/104968719-7c96e980-599b-11eb-839b-28745496b1a4.png" width=128px />](https://muo.gg/patreon)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue