massive branch sync
This commit is contained in:
parent
489bbabe93
commit
b3e8ec0a08
192 changed files with 1249 additions and 1124 deletions
|
|
@ -24,18 +24,19 @@ using Server.Items;
|
|||
using Server.Network;
|
||||
using Server.ContextMenus;
|
||||
using Server.Mobiles;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Mobiles
|
||||
{
|
||||
public class BuyItemStateComparer : IComparer
|
||||
public class BuyItemStateComparer : IComparer<BuyItemState>
|
||||
{
|
||||
public int Compare( object l, object r )
|
||||
public int Compare( BuyItemState l, BuyItemState r )
|
||||
{
|
||||
if ( l == null && r == null ) return 0;
|
||||
if ( l == null ) return -1;
|
||||
if ( r == null ) return 1;
|
||||
|
||||
return ((BuyItemState)l).MySerial.CompareTo( ((BuyItemState)r).MySerial );
|
||||
return l.MySerial.CompareTo( r.MySerial );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@
|
|||
***************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Server.Mobiles;
|
||||
|
||||
namespace Server.Mobiles
|
||||
{
|
||||
|
|
@ -39,8 +40,8 @@ namespace Server
|
|||
{
|
||||
public interface IVendor
|
||||
{
|
||||
bool OnBuyItems( Mobile from, ArrayList list );
|
||||
bool OnSellItems( Mobile from, ArrayList list );
|
||||
bool OnBuyItems( Mobile from, List<BuyItemResponse> list );
|
||||
bool OnSellItems( Mobile from, List<SellItemResponse> list );
|
||||
|
||||
DateTime LastRestock{ get; set; }
|
||||
TimeSpan RestockDelay{ get; }
|
||||
|
|
|
|||
|
|
@ -1119,6 +1119,11 @@ namespace Server
|
|||
((Mobile)m_Parent).GetChildNameProperties( list, item );
|
||||
}
|
||||
|
||||
public virtual bool IsChildVisibleTo( Mobile m, Item child )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Bounce( Mobile from )
|
||||
{
|
||||
if ( m_Parent is Item )
|
||||
|
|
|
|||
|
|
@ -1620,46 +1620,7 @@ namespace Server.Items
|
|||
|
||||
public virtual void DisplayTo( Mobile to )
|
||||
{
|
||||
if ( !IsPublicContainer )
|
||||
{
|
||||
bool contains = false;
|
||||
|
||||
if ( m_Openers != null )
|
||||
{
|
||||
Point3D worldLoc = GetWorldLocation();
|
||||
Map map = this.Map;
|
||||
|
||||
for ( int i = 0; i < m_Openers.Count; ++i )
|
||||
{
|
||||
Mobile mob = m_Openers[i];
|
||||
|
||||
if ( mob == to )
|
||||
{
|
||||
contains = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int range = GetUpdateRange( mob );
|
||||
|
||||
if ( mob.Map != map || !mob.InRange( worldLoc, range ) )
|
||||
m_Openers.RemoveAt( i-- );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !contains )
|
||||
{
|
||||
if ( m_Openers == null ) {
|
||||
m_Openers = new List<Mobile>();
|
||||
}
|
||||
|
||||
m_Openers.Add( to );
|
||||
}
|
||||
else if ( m_Openers != null && m_Openers.Count == 0 )
|
||||
{
|
||||
m_Openers = null;
|
||||
}
|
||||
}
|
||||
ProcessOpeners( to );
|
||||
|
||||
to.Send( new ContainerDisplay( this ) );
|
||||
|
||||
|
|
@ -1677,6 +1638,51 @@ namespace Server.Items
|
|||
}
|
||||
}
|
||||
|
||||
public void ProcessOpeners( Mobile opener )
|
||||
{
|
||||
if ( !IsPublicContainer )
|
||||
{
|
||||
bool contains = false;
|
||||
|
||||
if ( m_Openers != null )
|
||||
{
|
||||
Point3D worldLoc = GetWorldLocation();
|
||||
Map map = this.Map;
|
||||
|
||||
for ( int i = 0; i < m_Openers.Count; ++i )
|
||||
{
|
||||
Mobile mob = m_Openers[i];
|
||||
|
||||
if ( mob == opener )
|
||||
{
|
||||
contains = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int range = GetUpdateRange( mob );
|
||||
|
||||
if ( mob.Map != map || !mob.InRange( worldLoc, range ) )
|
||||
m_Openers.RemoveAt( i-- );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !contains )
|
||||
{
|
||||
if ( m_Openers == null )
|
||||
{
|
||||
m_Openers = new List<Mobile>();
|
||||
}
|
||||
|
||||
m_Openers.Add( opener );
|
||||
}
|
||||
else if ( m_Openers != null && m_Openers.Count == 0 )
|
||||
{
|
||||
m_Openers = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void GetProperties( ObjectPropertyList list )
|
||||
{
|
||||
base.GetProperties( list );
|
||||
|
|
|
|||
|
|
@ -32,12 +32,13 @@ using Server;
|
|||
using Server.Accounting;
|
||||
using Server.Gumps;
|
||||
using Server.Network;
|
||||
using System.Runtime;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public delegate void Slice();
|
||||
|
||||
public class Core
|
||||
public static class Core
|
||||
{
|
||||
private static bool m_Crashed;
|
||||
private static Thread timerThread;
|
||||
|
|
@ -102,11 +103,13 @@ namespace Server
|
|||
internal static bool VBdotNet { get { return m_VBdotNET; } }
|
||||
public static List<string> DataDirectories { get { return m_DataDirectories; } }
|
||||
public static Assembly Assembly { get { return m_Assembly; } set { m_Assembly = value; } }
|
||||
public static Version Version { get { return m_Assembly.GetName().Version; } }
|
||||
public static Process Process { get { return m_Process; } }
|
||||
public static Thread Thread { get { return m_Thread; } }
|
||||
public static MultiTextWriter MultiConsoleOut { get { return m_MultiConOut; } }
|
||||
|
||||
public static readonly bool Is64Bit = (IntPtr.Size == 8);
|
||||
//TODO: Upon public release of .NET 4.0, use Environment.Is64BitOperatingSystem/Process
|
||||
|
||||
private static bool m_MultiProcessor;
|
||||
private static int m_ProcessorCount;
|
||||
|
|
@ -287,12 +290,12 @@ namespace Server
|
|||
private static bool m_Closing;
|
||||
public static bool Closing { get { return m_Closing; } }
|
||||
|
||||
private static uint m_CycleIndex;
|
||||
private static long m_CycleIndex;
|
||||
private static float[] m_CyclesPerSecond = new float[100];
|
||||
|
||||
public static float CyclesPerSecond
|
||||
{
|
||||
get { return m_CyclesPerSecond[((uint)(m_CycleIndex - 1)) % m_CyclesPerSecond.Length]; }
|
||||
get { return m_CyclesPerSecond[(m_CycleIndex - 1) % m_CyclesPerSecond.Length]; }
|
||||
}
|
||||
|
||||
public static float AverageCPS
|
||||
|
|
@ -429,6 +432,9 @@ namespace Server
|
|||
SetConsoleCtrlHandler( m_ConsoleEventHandler, true );
|
||||
}
|
||||
|
||||
if ( GCSettings.IsServerGC )
|
||||
Console.WriteLine("Core: Server garbage collection mode enabled");
|
||||
|
||||
while( !ScriptCompiler.Compile( m_Debug, m_Cache ) )
|
||||
{
|
||||
Console.WriteLine( "Scripts: One or more scripts failed to compile or no script files were found." );
|
||||
|
|
@ -467,7 +473,7 @@ namespace Server
|
|||
const int sampleInterval = 100;
|
||||
const float ticksPerSecond = (float)(TimeSpan.TicksPerSecond * sampleInterval);
|
||||
|
||||
int sample = 0;
|
||||
long sample = 0;
|
||||
|
||||
while( m_Signal.WaitOne() )
|
||||
{
|
||||
|
|
@ -620,6 +626,7 @@ namespace Server
|
|||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine( "Warning: Exception in serialization verification of type {0}", t );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4378,7 +4378,7 @@ namespace Server
|
|||
item.Layer = oldItem.Layer;
|
||||
item.Name = oldItem.Name;
|
||||
item.Weight = oldItem.Weight;
|
||||
//item.Amount = oldItem.amount;
|
||||
|
||||
item.Amount = oldItem.Amount - amount;
|
||||
item.Map = oldItem.Map;
|
||||
|
||||
|
|
@ -8060,8 +8060,10 @@ namespace Server
|
|||
{
|
||||
if( item.Parent is Item )
|
||||
{
|
||||
if( !CanSee( (Item)item.Parent ) )
|
||||
return false;
|
||||
Item parent = item.Parent as Item;
|
||||
|
||||
if ( !(CanSee( parent ) && parent.IsChildVisibleTo( this, item )) )
|
||||
return false;
|
||||
}
|
||||
else if( item.Parent is Mobile )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -400,7 +400,7 @@ namespace Server.Network
|
|||
if ( (msgSize / 7) > 100 )
|
||||
return;
|
||||
|
||||
ArrayList buyList = new ArrayList( msgSize/7 );
|
||||
List<BuyItemResponse> buyList = new List<BuyItemResponse>( msgSize / 7 );
|
||||
for ( ;msgSize>0;msgSize-=7)
|
||||
{
|
||||
byte layer = pvSrc.ReadByte();
|
||||
|
|
@ -442,7 +442,7 @@ namespace Server.Network
|
|||
int count = pvSrc.ReadUInt16();
|
||||
if ( count < 100 && pvSrc.Size == (1+2+4+2+(count*6)) )
|
||||
{
|
||||
ArrayList sellList = new ArrayList( count );
|
||||
List<SellItemResponse> sellList = new List<SellItemResponse>( count );
|
||||
|
||||
for (int i=0;i<count;i++)
|
||||
{
|
||||
|
|
@ -764,8 +764,8 @@ namespace Server.Network
|
|||
{
|
||||
int skillIndex;
|
||||
|
||||
try{ skillIndex = Convert.ToInt32( command.Split( ' ' )[0] ); }
|
||||
catch{ break; }
|
||||
if ( !int.TryParse( command.Split( ' ' )[0], out skillIndex ) )
|
||||
break;
|
||||
|
||||
Skills.UseSkill( m, skillIndex );
|
||||
|
||||
|
|
@ -775,8 +775,8 @@ namespace Server.Network
|
|||
{
|
||||
int booktype;
|
||||
|
||||
try{ booktype = Convert.ToInt32( command ); }
|
||||
catch{ booktype = 1; }
|
||||
if ( !int.TryParse( command, out booktype ) )
|
||||
booktype = 1;
|
||||
|
||||
EventSink.InvokeOpenSpellbookRequest( new OpenSpellbookRequestEventArgs( m, booktype ) );
|
||||
|
||||
|
|
|
|||
|
|
@ -251,7 +251,8 @@ namespace Server.Network
|
|||
|
||||
public sealed class VendorBuyContent : Packet
|
||||
{
|
||||
public VendorBuyContent( ArrayList list ) : base( 0x3c )
|
||||
public VendorBuyContent( List<BuyItemState> list )
|
||||
: base( 0x3c )
|
||||
{
|
||||
this.EnsureCapacity( list.Count*19 + 5 );
|
||||
|
||||
|
|
@ -277,9 +278,9 @@ namespace Server.Network
|
|||
}
|
||||
}
|
||||
|
||||
public sealed class VendorBuyContent6017 : Packet
|
||||
public sealed class VendorBuyContent6017 : Packet
|
||||
{
|
||||
public VendorBuyContent6017( ArrayList list ) : base( 0x3c )
|
||||
public VendorBuyContent6017( List<BuyItemState> list ) : base( 0x3c )
|
||||
{
|
||||
this.EnsureCapacity( list.Count*20 + 5 );
|
||||
|
||||
|
|
@ -317,7 +318,8 @@ namespace Server.Network
|
|||
|
||||
public sealed class VendorBuyList : Packet
|
||||
{
|
||||
public VendorBuyList( Mobile vendor, ArrayList list ) : base( 0x74 )
|
||||
public VendorBuyList( Mobile vendor, List<BuyItemState> list )
|
||||
: base( 0x74 )
|
||||
{
|
||||
this.EnsureCapacity( 256 );
|
||||
|
||||
|
|
@ -328,7 +330,7 @@ namespace Server.Network
|
|||
|
||||
for ( int i = 0; i < list.Count; ++i )
|
||||
{
|
||||
BuyItemState bis = (BuyItemState)list[i];
|
||||
BuyItemState bis = list[i];
|
||||
|
||||
m_Stream.Write( (int) bis.Price );
|
||||
|
||||
|
|
@ -1607,7 +1609,7 @@ namespace Server.Network
|
|||
}
|
||||
}
|
||||
|
||||
public sealed class ContainerContentUpdate6017 : Packet
|
||||
public sealed class ContainerContentUpdate6017 : Packet
|
||||
{
|
||||
public ContainerContentUpdate6017( Item item ) : base( 0x25, 21 )
|
||||
{
|
||||
|
|
@ -1686,7 +1688,7 @@ namespace Server.Network
|
|||
}
|
||||
}
|
||||
|
||||
public sealed class ContainerContent6017 : Packet
|
||||
public sealed class ContainerContent6017 : Packet
|
||||
{
|
||||
public ContainerContent6017( Mobile beholder, Item beheld ) : base( 0x3C )
|
||||
{
|
||||
|
|
|
|||
198
Server/Poison.cs
198
Server/Poison.cs
|
|
@ -20,127 +20,125 @@
|
|||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
[Parsable]
|
||||
public abstract class Poison
|
||||
{
|
||||
/*public abstract TimeSpan Interval{ get; }
|
||||
public abstract TimeSpan Duration{ get; }*/
|
||||
public abstract string Name{ get; }
|
||||
public abstract int Level{ get; }
|
||||
public abstract Timer ConstructTimer( Mobile m );
|
||||
/*public abstract void OnDamage( Mobile m, ref object state );*/
|
||||
[Parsable]
|
||||
public abstract class Poison
|
||||
{
|
||||
/*public abstract TimeSpan Interval{ get; }
|
||||
public abstract TimeSpan Duration{ get; }*/
|
||||
public abstract string Name { get; }
|
||||
public abstract int Level { get; }
|
||||
public abstract Timer ConstructTimer( Mobile m );
|
||||
/*public abstract void OnDamage( Mobile m, ref object state );*/
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.Name;
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return this.Name;
|
||||
}
|
||||
|
||||
|
||||
private static ArrayList m_Poisons = new ArrayList();
|
||||
private static List<Poison> m_Poisons = new List<Poison>();
|
||||
|
||||
public static void Register( Poison reg )
|
||||
{
|
||||
string regName = reg.Name.ToLower();
|
||||
public static void Register( Poison reg )
|
||||
{
|
||||
string regName = reg.Name.ToLower();
|
||||
|
||||
for(int i=0;i<m_Poisons.Count;i++)
|
||||
{
|
||||
if ( reg.Level == ((Poison)m_Poisons[i]).Level )
|
||||
throw new Exception( "A poison with that level already exists." );
|
||||
else if ( regName == ((Poison)m_Poisons[i]).Name.ToLower() )
|
||||
throw new Exception( "A poison with that name already exists." );
|
||||
}
|
||||
for ( int i = 0; i < m_Poisons.Count; i++ )
|
||||
{
|
||||
if ( reg.Level == m_Poisons[i].Level )
|
||||
throw new Exception( "A poison with that level already exists." );
|
||||
else if ( regName == m_Poisons[i].Name.ToLower() )
|
||||
throw new Exception( "A poison with that name already exists." );
|
||||
}
|
||||
|
||||
m_Poisons.Add( reg );
|
||||
}
|
||||
m_Poisons.Add( reg );
|
||||
}
|
||||
|
||||
public static Poison Lesser{ get{ return GetPoison( "Lesser" ); } }
|
||||
public static Poison Regular{ get{ return GetPoison( "Regular" ); } }
|
||||
public static Poison Greater{ get{ return GetPoison( "Greater" ); } }
|
||||
public static Poison Deadly{ get{ return GetPoison( "Deadly" ); } }
|
||||
public static Poison Lethal{ get{ return GetPoison( "Lethal" ); } }
|
||||
public static Poison Lesser { get { return GetPoison( "Lesser" ); } }
|
||||
public static Poison Regular { get { return GetPoison( "Regular" ); } }
|
||||
public static Poison Greater { get { return GetPoison( "Greater" ); } }
|
||||
public static Poison Deadly { get { return GetPoison( "Deadly" ); } }
|
||||
public static Poison Lethal { get { return GetPoison( "Lethal" ); } }
|
||||
|
||||
public static ArrayList Poisons
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_Poisons;
|
||||
}
|
||||
}
|
||||
public static List<Poison> Poisons
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_Poisons;
|
||||
}
|
||||
}
|
||||
|
||||
public static Poison Parse( string value )
|
||||
{
|
||||
Poison p = null;
|
||||
public static Poison Parse( string value )
|
||||
{
|
||||
Poison p = null;
|
||||
|
||||
try
|
||||
{
|
||||
p = GetPoison( Convert.ToInt32( value ) );
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
int plevel;
|
||||
|
||||
if ( p == null )
|
||||
p = GetPoison( value );
|
||||
if ( int.TryParse( value, out plevel ) )
|
||||
p = GetPoison( plevel );
|
||||
|
||||
return p;
|
||||
}
|
||||
if ( p == null )
|
||||
p = GetPoison( value );
|
||||
|
||||
public static Poison GetPoison( int level )
|
||||
{
|
||||
for ( int i = 0; i < m_Poisons.Count; ++i )
|
||||
{
|
||||
Poison p = (Poison)m_Poisons[i];
|
||||
return p;
|
||||
}
|
||||
|
||||
if ( p.Level == level )
|
||||
return p;
|
||||
}
|
||||
public static Poison GetPoison( int level )
|
||||
{
|
||||
for ( int i = 0; i < m_Poisons.Count; ++i )
|
||||
{
|
||||
Poison p = m_Poisons[i];
|
||||
|
||||
return null;
|
||||
}
|
||||
if ( p.Level == level )
|
||||
return p;
|
||||
}
|
||||
|
||||
public static Poison GetPoison( string name )
|
||||
{
|
||||
for ( int i = 0; i < m_Poisons.Count; ++i )
|
||||
{
|
||||
Poison p = (Poison)m_Poisons[i];
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( Utility.InsensitiveCompare( p.Name, name ) == 0 )
|
||||
return p;
|
||||
}
|
||||
public static Poison GetPoison( string name )
|
||||
{
|
||||
for ( int i = 0; i < m_Poisons.Count; ++i )
|
||||
{
|
||||
Poison p = m_Poisons[i];
|
||||
|
||||
return null;
|
||||
}
|
||||
if ( Utility.InsensitiveCompare( p.Name, name ) == 0 )
|
||||
return p;
|
||||
}
|
||||
|
||||
public static void Serialize( Poison p, GenericWriter writer )
|
||||
{
|
||||
if ( p == null )
|
||||
{
|
||||
writer.Write( (byte)0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write( (byte)1 );
|
||||
writer.Write( (byte)p.Level );
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Poison Deserialize( GenericReader reader )
|
||||
{
|
||||
switch ( reader.ReadByte() )
|
||||
{
|
||||
case 1: return GetPoison( reader.ReadByte() );
|
||||
case 2:
|
||||
//no longer used, safe to remove?
|
||||
reader.ReadInt();
|
||||
reader.ReadDouble();
|
||||
reader.ReadInt();
|
||||
reader.ReadTimeSpan();
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static void Serialize( Poison p, GenericWriter writer )
|
||||
{
|
||||
if ( p == null )
|
||||
{
|
||||
writer.Write( (byte)0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write( (byte)1 );
|
||||
writer.Write( (byte)p.Level );
|
||||
}
|
||||
}
|
||||
|
||||
public static Poison Deserialize( GenericReader reader )
|
||||
{
|
||||
switch ( reader.ReadByte() )
|
||||
{
|
||||
case 1: return GetPoison( reader.ReadByte() );
|
||||
case 2:
|
||||
//no longer used, safe to remove?
|
||||
reader.ReadInt();
|
||||
reader.ReadDouble();
|
||||
reader.ReadInt();
|
||||
reader.ReadTimeSpan();
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ namespace Server
|
|||
}
|
||||
|
||||
bin.Write( debug );
|
||||
bin.Write( Core.Version.ToString() );
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Server.Network;
|
||||
using System.Xml;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ namespace Server
|
|||
|
||||
public static bool IPMatchCIDR( string cidr, IPAddress ip )
|
||||
{
|
||||
if ( ip.AddressFamily == AddressFamily.InterNetworkV6 )
|
||||
if ( ip == null || ip.AddressFamily == AddressFamily.InterNetworkV6 )
|
||||
return false; //Just worry about IPv4 for now
|
||||
|
||||
|
||||
|
|
@ -522,7 +522,7 @@ namespace Server
|
|||
#endregion
|
||||
|
||||
#region Get[Something]
|
||||
public static int GetInt32( string intString, int defaultValue )
|
||||
public static int GetXMLInt32( string intString, int defaultValue )
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -530,18 +530,15 @@ namespace Server
|
|||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToInt32( intString );
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
int val;
|
||||
if ( int.TryParse( intString, out val ) )
|
||||
return val;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static DateTime GetDateTime( string dateTimeString, DateTime defaultValue )
|
||||
public static DateTime GetXMLDateTime( string dateTimeString, DateTime defaultValue )
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -558,7 +555,7 @@ namespace Server
|
|||
}
|
||||
}
|
||||
|
||||
public static TimeSpan GetTimeSpan( string timeSpanString, TimeSpan defaultValue )
|
||||
public static TimeSpan GetXMLTimeSpan( string timeSpanString, TimeSpan defaultValue )
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue