fix: Consolidates and fixes movement/direction checks (#1861)
This commit is contained in:
parent
06e05f2e52
commit
e6bfc45228
11 changed files with 146 additions and 320 deletions
|
|
@ -3836,24 +3836,27 @@ public class Item : IHued, IComparable<Item>, ISpawnable, IObjectPropertyListEnt
|
|||
// return root == null ? m_Location : new Point3D( (IPoint3D) root );
|
||||
}
|
||||
|
||||
public Point3D GetSurfaceTop()
|
||||
public IPoint3D GetSurfaceTop()
|
||||
{
|
||||
var root = RootParent;
|
||||
|
||||
if (root == null)
|
||||
{
|
||||
return new Point3D(
|
||||
m_Location.m_X,
|
||||
m_Location.m_Y,
|
||||
m_Location.m_Z + (ItemData.Surface ? ItemData.CalcHeight : 0)
|
||||
);
|
||||
}
|
||||
|
||||
return root.Location;
|
||||
return (root as Item)?.GetSurfaceTop() ?? (ItemData.Surface ? new Point3D(
|
||||
m_Location.m_X,
|
||||
m_Location.m_Y,
|
||||
m_Location.m_Z + ItemData.CalcHeight
|
||||
) : this);
|
||||
}
|
||||
|
||||
public Point3D GetWorldTop() => RootParent?.Location ??
|
||||
new Point3D(m_Location.m_X, m_Location.m_Y, m_Location.m_Z + ItemData.CalcHeight);
|
||||
public Point3D GetWorldTop()
|
||||
{
|
||||
var root = RootParent;
|
||||
|
||||
return (root as Item)?.GetWorldTop() ?? new Point3D(
|
||||
m_Location.m_X,
|
||||
m_Location.m_Y,
|
||||
m_Location.m_Z + ItemData.CalcHeight
|
||||
);
|
||||
}
|
||||
|
||||
public void SendLocalizedMessageTo(Mobile to, int number, string args = "")
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4089,10 +4089,12 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
return true;
|
||||
}
|
||||
|
||||
public virtual bool CheckMovement(Direction d, out int newZ) => Movement.Movement.CheckMovement(this, d, out newZ);
|
||||
public virtual bool CheckMovement(Direction d, out int newZ) => CalcMoves.CheckMovement(this, d, out newZ);
|
||||
|
||||
private bool CanMove(Direction d, Point3D oldLocation, ref Point3D newLocation)
|
||||
private bool CanMove(Direction d, Point3D oldLocation, out Point3D newLocation)
|
||||
{
|
||||
newLocation = oldLocation;
|
||||
|
||||
if (m_Spell?.OnCasterMoving(d) == false)
|
||||
{
|
||||
return false;
|
||||
|
|
@ -4110,61 +4112,13 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
return false;
|
||||
}
|
||||
|
||||
int x = oldLocation.m_X, y = oldLocation.m_Y;
|
||||
int oldX = x, oldY = y;
|
||||
var oldX = oldLocation.m_X;
|
||||
var oldY = oldLocation.m_Y;
|
||||
var oldZ = oldLocation.m_Z;
|
||||
|
||||
switch (d & Direction.Mask)
|
||||
{
|
||||
case Direction.North:
|
||||
{
|
||||
--y;
|
||||
break;
|
||||
}
|
||||
case Direction.Right:
|
||||
{
|
||||
++x;
|
||||
--y;
|
||||
break;
|
||||
}
|
||||
case Direction.East:
|
||||
{
|
||||
++x;
|
||||
break;
|
||||
}
|
||||
case Direction.Down:
|
||||
{
|
||||
++x;
|
||||
++y;
|
||||
break;
|
||||
}
|
||||
case Direction.South:
|
||||
{
|
||||
++y;
|
||||
break;
|
||||
}
|
||||
case Direction.Left:
|
||||
{
|
||||
--x;
|
||||
++y;
|
||||
break;
|
||||
}
|
||||
case Direction.West:
|
||||
{
|
||||
--x;
|
||||
break;
|
||||
}
|
||||
case Direction.Up:
|
||||
{
|
||||
--x;
|
||||
--y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
newLocation.m_X = x;
|
||||
newLocation.m_Y = y;
|
||||
newLocation.m_Z = newZ;
|
||||
var x = oldX;
|
||||
var y = oldY;
|
||||
CalcMoves.Offset(d, ref x, ref y);
|
||||
newLocation = new Point3D(x, y, newZ);
|
||||
|
||||
Pushing = false;
|
||||
|
||||
|
|
@ -4344,18 +4298,22 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
}
|
||||
|
||||
var oldLocation = m_Location;
|
||||
Point3D newLocation = oldLocation;
|
||||
Point3D newLocation;
|
||||
|
||||
if ((m_Direction & Direction.Mask) == (d & Direction.Mask))
|
||||
{
|
||||
// We are actually moving (not just a direction change)
|
||||
if (!CanMove(d, oldLocation, ref newLocation))
|
||||
if (!CanMove(d, oldLocation, out newLocation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DisruptiveAction();
|
||||
}
|
||||
else
|
||||
{
|
||||
newLocation = oldLocation;
|
||||
}
|
||||
|
||||
if (m_NetState != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Server.Movement;
|
||||
|
||||
public static class Movement
|
||||
|
|
@ -56,52 +58,63 @@ public static class Movement
|
|||
return false;
|
||||
}
|
||||
|
||||
public static void Offset(Direction d, ref int x, ref int y)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Offset(Direction d, ref Point3D p, int count = 1)
|
||||
{
|
||||
var x = p.m_X;
|
||||
var y = p.m_Y;
|
||||
Offset(d, ref x, ref y, count);
|
||||
p = new Point3D(x, y, p.m_Z);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Offset(Direction d, ref int x, ref int y, int count = 1)
|
||||
{
|
||||
switch (d & Direction.Mask)
|
||||
{
|
||||
case Direction.North:
|
||||
{
|
||||
--y;
|
||||
y -= count;
|
||||
break;
|
||||
}
|
||||
case Direction.South:
|
||||
{
|
||||
++y;
|
||||
y += count;
|
||||
break;
|
||||
}
|
||||
case Direction.West:
|
||||
{
|
||||
--x;
|
||||
x -= count;
|
||||
break;
|
||||
}
|
||||
case Direction.East:
|
||||
{
|
||||
++x;
|
||||
x += count;
|
||||
break;
|
||||
}
|
||||
case Direction.Right:
|
||||
{
|
||||
++x;
|
||||
--y;
|
||||
x += count;
|
||||
y -= count;
|
||||
break;
|
||||
}
|
||||
case Direction.Left:
|
||||
{
|
||||
--x;
|
||||
++y;
|
||||
x -= count;
|
||||
y += count;
|
||||
break;
|
||||
}
|
||||
case Direction.Down:
|
||||
{
|
||||
++x;
|
||||
++y;
|
||||
x += count;
|
||||
y += count;
|
||||
|
||||
break;
|
||||
}
|
||||
case Direction.Up:
|
||||
{
|
||||
--x;
|
||||
--y;
|
||||
x -= count;
|
||||
y -= count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,9 +54,9 @@ namespace Server.Movement
|
|||
|
||||
var checkDiagonals = ((int)d & 0x1) == 0x1;
|
||||
|
||||
Offset(d, ref xForward, ref yForward);
|
||||
Offset((Direction)(((int)d - 1) & 0x7), ref xLeft, ref yLeft);
|
||||
Offset((Direction)(((int)d + 1) & 0x7), ref xRight, ref yRight);
|
||||
Movement.Offset(d, ref xForward, ref yForward);
|
||||
Movement.Offset((Direction)(((int)d - 1) & 0x7), ref xLeft, ref yLeft);
|
||||
Movement.Offset((Direction)(((int)d + 1) & 0x7), ref xRight, ref yRight);
|
||||
|
||||
if (xForward < 0 || yForward < 0 || xForward >= map.Width || yForward >= map.Height)
|
||||
{
|
||||
|
|
@ -578,57 +578,5 @@ namespace Server.Movement
|
|||
zTop = loc.Z;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Offset(Direction d, ref int x, ref int y)
|
||||
{
|
||||
switch (d & Direction.Mask)
|
||||
{
|
||||
case Direction.North:
|
||||
{
|
||||
--y;
|
||||
break;
|
||||
}
|
||||
case Direction.South:
|
||||
{
|
||||
++y;
|
||||
break;
|
||||
}
|
||||
case Direction.West:
|
||||
{
|
||||
--x;
|
||||
break;
|
||||
}
|
||||
case Direction.East:
|
||||
{
|
||||
++x;
|
||||
break;
|
||||
}
|
||||
case Direction.Right:
|
||||
{
|
||||
++x;
|
||||
--y;
|
||||
break;
|
||||
}
|
||||
case Direction.Left:
|
||||
{
|
||||
--x;
|
||||
++y;
|
||||
break;
|
||||
}
|
||||
case Direction.Down:
|
||||
{
|
||||
++x;
|
||||
++y;
|
||||
break;
|
||||
}
|
||||
case Direction.Up:
|
||||
{
|
||||
--x;
|
||||
--y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,12 +42,7 @@ namespace Server
|
|||
|
||||
if (index >= 0 && index < dirs.Length)
|
||||
{
|
||||
int x = p.X, y = p.Y;
|
||||
|
||||
CalcMoves.Offset(dirs[index], ref x, ref y);
|
||||
|
||||
p.X = x;
|
||||
p.Y = y;
|
||||
CalcMoves.Offset(dirs[index], ref p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Runtime.CompilerServices;
|
||||
using ModernUO.Serialization;
|
||||
using Server.Targeting;
|
||||
using CalcMoves = Server.Movement.Movement;
|
||||
|
||||
namespace Server.Items;
|
||||
|
||||
|
|
@ -233,53 +234,7 @@ public abstract partial class BaseDoor : Item, ILockable, ITelekinesisable
|
|||
int x = m.X;
|
||||
int y = m.Y;
|
||||
|
||||
switch (m.Direction & Direction.Mask)
|
||||
{
|
||||
case Direction.North:
|
||||
{
|
||||
--y;
|
||||
break;
|
||||
}
|
||||
case Direction.Right:
|
||||
{
|
||||
++x;
|
||||
--y;
|
||||
break;
|
||||
}
|
||||
case Direction.East:
|
||||
{
|
||||
++x;
|
||||
break;
|
||||
}
|
||||
case Direction.Down:
|
||||
{
|
||||
++x;
|
||||
++y;
|
||||
break;
|
||||
}
|
||||
case Direction.South:
|
||||
{
|
||||
++y;
|
||||
break;
|
||||
}
|
||||
case Direction.Left:
|
||||
{
|
||||
--x;
|
||||
++y;
|
||||
break;
|
||||
}
|
||||
case Direction.West:
|
||||
{
|
||||
--x;
|
||||
break;
|
||||
}
|
||||
case Direction.Up:
|
||||
{
|
||||
--x;
|
||||
--y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CalcMoves.Offset(m.Direction, ref x, ref y);
|
||||
|
||||
foreach (var item in m.Map.GetItemsAt(x, y))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,132 +1,82 @@
|
|||
using System;
|
||||
using ModernUO.Serialization;
|
||||
using Server.Targeting;
|
||||
using CalcMoves = Server.Movement.Movement;
|
||||
|
||||
namespace Server.Items
|
||||
namespace Server.Items;
|
||||
|
||||
[Flippable(0xF52, 0xF51)]
|
||||
[SerializationGenerator(0, false)]
|
||||
public partial class ThrowingDagger : Item
|
||||
{
|
||||
[Flippable(0xF52, 0xF51)]
|
||||
[SerializationGenerator(0, false)]
|
||||
public partial class ThrowingDagger : Item
|
||||
[Constructible]
|
||||
public ThrowingDagger() : base(0xF52)
|
||||
{
|
||||
[Constructible]
|
||||
public ThrowingDagger() : base(0xF52)
|
||||
Weight = 1.0;
|
||||
Layer = Layer.OneHanded;
|
||||
}
|
||||
|
||||
public override string DefaultName => "a throwing dagger";
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (from.Items.Contains(this))
|
||||
{
|
||||
Weight = 1.0;
|
||||
Layer = Layer.OneHanded;
|
||||
var t = new InternalTarget(this);
|
||||
from.Target = t;
|
||||
}
|
||||
|
||||
public override string DefaultName => "a throwing dagger";
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
else
|
||||
{
|
||||
if (from.Items.Contains(this))
|
||||
from.SendMessage("You must be holding that weapon to use it.");
|
||||
}
|
||||
}
|
||||
|
||||
private class InternalTarget : Target
|
||||
{
|
||||
private readonly ThrowingDagger m_Dagger;
|
||||
|
||||
public InternalTarget(ThrowingDagger dagger) : base(10, false, TargetFlags.Harmful) => m_Dagger = dagger;
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (m_Dagger.Deleted)
|
||||
{
|
||||
var t = new InternalTarget(this);
|
||||
from.Target = t;
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
if (!from.Items.Contains(m_Dagger))
|
||||
{
|
||||
from.SendMessage("You must be holding that weapon to use it.");
|
||||
}
|
||||
}
|
||||
|
||||
private class InternalTarget : Target
|
||||
{
|
||||
private readonly ThrowingDagger m_Dagger;
|
||||
|
||||
public InternalTarget(ThrowingDagger dagger) : base(10, false, TargetFlags.Harmful) => m_Dagger = dagger;
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
else if (targeted is Mobile m && m != from && from.HarmfulCheck(m))
|
||||
{
|
||||
if (m_Dagger.Deleted)
|
||||
var to = from.GetDirectionTo(m);
|
||||
|
||||
from.Direction = to;
|
||||
|
||||
from.Animate(from.Mounted ? 26 : 9, 7, 1, true, false, 0);
|
||||
|
||||
if (Utility.RandomDouble() >= Math.Sqrt(m.Dex / 100.0) * 0.8)
|
||||
{
|
||||
return;
|
||||
from.MovingEffect(m, 0x1BFE, 7, 1, false, false, 0x481, 0);
|
||||
|
||||
AOS.Damage(m, from, Utility.Random(5, from.Str / 10), 100, 0, 0, 0, 0);
|
||||
|
||||
m_Dagger.MoveToWorld(m.Location, m.Map);
|
||||
}
|
||||
|
||||
if (!from.Items.Contains(m_Dagger))
|
||||
else
|
||||
{
|
||||
from.SendMessage("You must be holding that weapon to use it.");
|
||||
}
|
||||
else if (targeted is Mobile m && m != from && from.HarmfulCheck(m))
|
||||
{
|
||||
var to = from.GetDirectionTo(m);
|
||||
var p = m.Location;
|
||||
CalcMoves.Offset(to, ref p);
|
||||
|
||||
from.Direction = to;
|
||||
p.X += Utility.Random(-1, 3);
|
||||
p.Y += Utility.Random(-1, 3);
|
||||
|
||||
from.Animate(from.Mounted ? 26 : 9, 7, 1, true, false, 0);
|
||||
m_Dagger.MoveToWorld(p, m.Map);
|
||||
|
||||
if (Utility.RandomDouble() >= Math.Sqrt(m.Dex / 100.0) * 0.8)
|
||||
{
|
||||
from.MovingEffect(m, 0x1BFE, 7, 1, false, false, 0x481, 0);
|
||||
from.MovingEffect(m_Dagger, 0x1BFE, 7, 1, false, false, 0x481, 0);
|
||||
|
||||
AOS.Damage(m, from, Utility.Random(5, from.Str / 10), 100, 0, 0, 0, 0);
|
||||
|
||||
m_Dagger.MoveToWorld(m.Location, m.Map);
|
||||
}
|
||||
else
|
||||
{
|
||||
int x = 0, y = 0;
|
||||
|
||||
switch (to & Direction.Mask)
|
||||
{
|
||||
case Direction.North:
|
||||
{
|
||||
--y;
|
||||
break;
|
||||
}
|
||||
case Direction.South:
|
||||
{
|
||||
++y;
|
||||
break;
|
||||
}
|
||||
case Direction.West:
|
||||
{
|
||||
--x;
|
||||
break;
|
||||
}
|
||||
case Direction.East:
|
||||
{
|
||||
++x;
|
||||
break;
|
||||
}
|
||||
case Direction.Up:
|
||||
{
|
||||
--x;
|
||||
--y;
|
||||
break;
|
||||
}
|
||||
case Direction.Down:
|
||||
{
|
||||
++x;
|
||||
++y;
|
||||
break;
|
||||
}
|
||||
case Direction.Left:
|
||||
{
|
||||
--x;
|
||||
++y;
|
||||
break;
|
||||
}
|
||||
case Direction.Right:
|
||||
{
|
||||
++x;
|
||||
--y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
x += Utility.Random(-1, 3);
|
||||
y += Utility.Random(-1, 3);
|
||||
|
||||
x += m.X;
|
||||
y += m.Y;
|
||||
|
||||
m_Dagger.MoveToWorld(new Point3D(x, y, m.Z), m.Map);
|
||||
|
||||
from.MovingEffect(m_Dagger, 0x1BFE, 7, 1, false, false, 0x481, 0);
|
||||
|
||||
from.SendMessage("You miss.");
|
||||
}
|
||||
from.SendMessage("You miss.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2182,7 +2182,8 @@ public abstract class BaseAI
|
|||
|
||||
if (map != null)
|
||||
{
|
||||
int x = m_Mobile.X, y = m_Mobile.Y;
|
||||
var x = m_Mobile.X;
|
||||
var y = m_Mobile.Y;
|
||||
Movement.Movement.Offset(d, ref x, ref y);
|
||||
|
||||
using var queue = PooledRefQueue<Item>.Create();
|
||||
|
|
|
|||
|
|
@ -1763,8 +1763,9 @@ namespace Server.Mobiles
|
|||
|
||||
newZ = foundation.Z + HouseFoundation.GetLevelZ(context.Level, context.Foundation);
|
||||
|
||||
int newX = X, newY = Y;
|
||||
Movement.Movement.Offset(d, ref newX, ref newY);
|
||||
var newX = X;
|
||||
var newY = Y;
|
||||
CalcMoves.Offset(d, ref newX, ref newY);
|
||||
|
||||
var startX = foundation.X + foundation.Components.Min.X + 1;
|
||||
var startY = foundation.Y + foundation.Components.Min.Y + 1;
|
||||
|
|
|
|||
|
|
@ -282,14 +282,15 @@ namespace Server.Multis
|
|||
SPlank.SetFacing(_facing);
|
||||
}
|
||||
|
||||
int xOffset = 0, yOffset = 0;
|
||||
var xOffset = X;
|
||||
var yOffset = Y;
|
||||
Movement.Movement.Offset(_facing, ref xOffset, ref yOffset);
|
||||
|
||||
if (TillerMan != null)
|
||||
{
|
||||
TillerMan.Location = new Point3D(
|
||||
X + xOffset * TillerManDistance + (_facing == Direction.North ? 1 : 0),
|
||||
Y + yOffset * TillerManDistance,
|
||||
xOffset * TillerManDistance + (_facing == Direction.North ? 1 : 0),
|
||||
yOffset * TillerManDistance,
|
||||
TillerMan.Z
|
||||
);
|
||||
TillerMan.SetFacing(_facing);
|
||||
|
|
@ -298,7 +299,7 @@ namespace Server.Multis
|
|||
|
||||
if (Hold != null)
|
||||
{
|
||||
Hold.Location = new Point3D(X + xOffset * HoldDistance, Y + yOffset * HoldDistance, Hold.Z);
|
||||
Hold.Location = new Point3D(xOffset * HoldDistance, yOffset * HoldDistance, Hold.Z);
|
||||
Hold.SetFacing(_facing);
|
||||
}
|
||||
}
|
||||
|
|
@ -1722,7 +1723,8 @@ namespace Server.Multis
|
|||
return false;
|
||||
}
|
||||
|
||||
int rx = 0, ry = 0;
|
||||
var rx = 0;
|
||||
var ry = 0;
|
||||
var d = (Direction)(((int)_facing + (int)dir) & 0x7);
|
||||
Movement.Movement.Offset(d, ref rx, ref ry);
|
||||
|
||||
|
|
@ -2083,7 +2085,8 @@ namespace Server.Multis
|
|||
PPlank?.SetFacing(facing);
|
||||
SPlank?.SetFacing(facing);
|
||||
|
||||
int xOffset = 0, yOffset = 0;
|
||||
var xOffset = X;
|
||||
var yOffset = Y;
|
||||
Movement.Movement.Offset(facing, ref xOffset, ref yOffset);
|
||||
|
||||
var count = ((_facing - old) & 0x7) / 2;
|
||||
|
|
@ -2109,15 +2112,15 @@ namespace Server.Multis
|
|||
if (TillerMan != null)
|
||||
{
|
||||
TillerMan.Location = new Point3D(
|
||||
X + xOffset * TillerManDistance + (facing == Direction.North ? 1 : 0),
|
||||
Y + yOffset * TillerManDistance,
|
||||
xOffset * TillerManDistance + (facing == Direction.North ? 1 : 0),
|
||||
yOffset * TillerManDistance,
|
||||
TillerMan.Z
|
||||
);
|
||||
}
|
||||
|
||||
if (Hold != null)
|
||||
{
|
||||
Hold.Location = new Point3D(X + xOffset * HoldDistance, Y + yOffset * HoldDistance, Hold.Z);
|
||||
Hold.Location = new Point3D(xOffset * HoldDistance, yOffset * HoldDistance, Hold.Z);
|
||||
}
|
||||
|
||||
if (PPlank != null)
|
||||
|
|
|
|||
|
|
@ -176,23 +176,22 @@ namespace Server.Spells
|
|||
return false;
|
||||
}
|
||||
|
||||
public static void Turn(Mobile from, object to)
|
||||
public static void Turn(Mobile from, IPoint3D to)
|
||||
{
|
||||
if (to is not IPoint3D target)
|
||||
if (from == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (target is Item item)
|
||||
var root = (to as Item)?.RootParent;
|
||||
if (from != root)
|
||||
{
|
||||
if (item.RootParent != from)
|
||||
{
|
||||
from.Direction = from.GetDirectionTo(item.GetWorldLocation());
|
||||
}
|
||||
to = root;
|
||||
}
|
||||
else if (!from.Equals(target))
|
||||
|
||||
if (!from.Equals(to))
|
||||
{
|
||||
from.Direction = from.GetDirectionTo(target);
|
||||
from.Direction = from.GetDirectionTo(to);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue