mirror of
https://github.com/islehorse/HISP.git
synced 2025-04-10 23:25:41 +12:00
make brickpoet functional
This commit is contained in:
parent
2d7eb8c6c2
commit
6e671be0b5
9 changed files with 2838 additions and 0 deletions
File diff suppressed because it is too large
Load diff
163
Horse Isle Server/Horse Isle Server/Game/SwfModules/Brickpoet.cs
Normal file
163
Horse Isle Server/Horse Isle Server/Game/SwfModules/Brickpoet.cs
Normal file
|
@ -0,0 +1,163 @@
|
|||
using HISP.Server;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HISP.Game.SwfModules
|
||||
{
|
||||
class Brickpoet
|
||||
{
|
||||
public struct PoetryEntry {
|
||||
public int Id;
|
||||
public string Word;
|
||||
public int Room;
|
||||
|
||||
}
|
||||
|
||||
public class PoetryPeice
|
||||
{
|
||||
public PoetryPeice(int PoetId, int RoomId, string PeiceWord)
|
||||
{
|
||||
if(!Database.GetPoetExist(PoetId, RoomId))
|
||||
{
|
||||
Id = PoetId;
|
||||
x = GameServer.RandomNumberGenerator.Next(0, 365);
|
||||
y = GameServer.RandomNumberGenerator.Next(0, 280);
|
||||
Word = PeiceWord;
|
||||
roomId = RoomId;
|
||||
Database.AddPoetWord(PoetId, x, y, RoomId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Id = PoetId;
|
||||
roomId = RoomId;
|
||||
Word = PeiceWord;
|
||||
x = Database.GetPoetPositionX(Id, roomId);
|
||||
y = Database.GetPoetPositionY(Id, roomId);
|
||||
}
|
||||
}
|
||||
public int Id;
|
||||
public int RoomId
|
||||
{
|
||||
get
|
||||
{
|
||||
return roomId;
|
||||
}
|
||||
}
|
||||
public int X
|
||||
{
|
||||
get
|
||||
{
|
||||
return x;
|
||||
}
|
||||
set
|
||||
{
|
||||
Database.SetPoetPosition(Id, value, y, roomId);
|
||||
x = value;
|
||||
}
|
||||
|
||||
}
|
||||
public int Y
|
||||
{
|
||||
get
|
||||
{
|
||||
return y;
|
||||
}
|
||||
set
|
||||
{
|
||||
Database.SetPoetPosition(Id, x, value, roomId);
|
||||
y = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public string Word;
|
||||
|
||||
private int x;
|
||||
private int y;
|
||||
|
||||
private int roomId;
|
||||
|
||||
}
|
||||
|
||||
public static List<PoetryEntry> PoetList = new List<PoetryEntry>();
|
||||
private static List<PoetryPeice[]> poetryRooms = new List<PoetryPeice[]>();
|
||||
public static PoetryPeice[][] PoetryRooms
|
||||
{
|
||||
get
|
||||
{
|
||||
return poetryRooms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static PoetryEntry[] getPoetsInRoom(int roomId)
|
||||
{
|
||||
List<PoetryEntry> entries = new List<PoetryEntry>();
|
||||
|
||||
foreach(PoetryEntry poet in PoetList.ToArray())
|
||||
{
|
||||
if(poet.Room == roomId)
|
||||
{
|
||||
entries.Add(poet);
|
||||
}
|
||||
}
|
||||
|
||||
return entries.ToArray();
|
||||
}
|
||||
private static PoetryPeice[] getPoetryRoom(int roomId)
|
||||
{
|
||||
PoetryEntry[] poetryEntries = getPoetsInRoom(roomId);
|
||||
List<PoetryPeice> peices = new List<PoetryPeice>();
|
||||
foreach (PoetryEntry poetryEntry in poetryEntries)
|
||||
{
|
||||
PoetryPeice peice = new PoetryPeice(poetryEntry.Id, roomId, poetryEntry.Word);
|
||||
peices.Add(peice);
|
||||
}
|
||||
|
||||
return peices.ToArray();
|
||||
}
|
||||
|
||||
public static PoetryPeice[] GetPoetryRoom(int roomId)
|
||||
{
|
||||
foreach(PoetryPeice[] peices in PoetryRooms)
|
||||
{
|
||||
if (peices[0].RoomId == roomId)
|
||||
return peices;
|
||||
}
|
||||
throw new KeyNotFoundException("No poetry room of id: " + roomId + " exists.");
|
||||
}
|
||||
public static PoetryPeice GetPoetryPeice(PoetryPeice[] room, int id)
|
||||
{
|
||||
foreach(PoetryPeice peice in room)
|
||||
{
|
||||
if (peice.Id == id)
|
||||
return peice;
|
||||
}
|
||||
throw new KeyNotFoundException("Peice with id: " + id + " not found in room.");
|
||||
}
|
||||
public static void LoadPoetryRooms()
|
||||
{
|
||||
List<int> rooms = new List<int>();
|
||||
foreach(PoetryEntry entry in PoetList)
|
||||
{
|
||||
if (!rooms.Contains(entry.Room))
|
||||
rooms.Add(entry.Room);
|
||||
}
|
||||
|
||||
foreach(int room in rooms)
|
||||
{
|
||||
Logger.InfoPrint("Loading poetry room: " + room.ToString());
|
||||
poetryRooms.Add(getPoetryRoom(room));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -83,6 +83,7 @@
|
|||
<Compile Include="Game\Services\Inn.cs" />
|
||||
<Compile Include="Game\Services\Shop.cs" />
|
||||
<Compile Include="Game\Inventory\ShopInventory.cs" />
|
||||
<Compile Include="Game\SwfModules\Brickpoet.cs" />
|
||||
<Compile Include="Player\Award.cs" />
|
||||
<Compile Include="Player\Equips\Jewelry.cs" />
|
||||
<Compile Include="Player\Equips\CompetitionGear.cs" />
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System.IO;
|
||||
using System.Reflection;
|
||||
using HISP.Game;
|
||||
using HISP.Game.SwfModules;
|
||||
using HISP.Security;
|
||||
using HISP.Server;
|
||||
namespace HISP
|
||||
|
@ -19,6 +20,7 @@ namespace HISP
|
|||
Map.OpenMap();
|
||||
World.ReadWorldData();
|
||||
DroppedItems.Init();
|
||||
Brickpoet.LoadPoetryRooms();
|
||||
GameServer.StartServer();
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ namespace HISP.Server
|
|||
string AbuseReorts = "CREATE TABLE AbuseReports(ReportCreator TEXT(1028), Reporting TEXT(1028), ReportReason TEXT(1028))";
|
||||
string Leaderboards = "CREATE TABLE Leaderboards(playerId INT, minigame TEXT(128), wins INT, looses INT, timesplayed INT, score INT, type TEXT(128))";
|
||||
string NpcStartPoint = "CREATE TABLE NpcStartPoint(playerId INT, npcId INT, chatpointId INT)";
|
||||
string PoetryRooms = "CREATE TABLE PoetryRooms(poetId INT, X INT, Y INT, roomId INT)";
|
||||
string DeleteOnlineUsers = "DELETE FROM OnlineUsers";
|
||||
|
||||
|
||||
|
@ -175,6 +176,19 @@ namespace HISP.Server
|
|||
{
|
||||
Logger.WarnPrint(e.Message);
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
MySqlCommand sqlCommand = db.CreateCommand();
|
||||
sqlCommand.CommandText = PoetryRooms;
|
||||
sqlCommand.ExecuteNonQuery();
|
||||
sqlCommand.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.WarnPrint(e.Message);
|
||||
};
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -263,6 +277,93 @@ namespace HISP.Server
|
|||
|
||||
}
|
||||
|
||||
public static void AddPoetWord(int id, int x, int y, int room)
|
||||
{
|
||||
using (MySqlConnection db = new MySqlConnection(ConnectionString))
|
||||
{
|
||||
db.Open();
|
||||
MySqlCommand sqlCommand = db.CreateCommand();
|
||||
sqlCommand.CommandText = "INSERT INTO PoetryRooms VALUES(@id,@x,@y,@room)";
|
||||
sqlCommand.Parameters.AddWithValue("@id", id);
|
||||
sqlCommand.Parameters.AddWithValue("@x", x);
|
||||
sqlCommand.Parameters.AddWithValue("@y", y);
|
||||
sqlCommand.Parameters.AddWithValue("@room", room);
|
||||
sqlCommand.Prepare();
|
||||
sqlCommand.ExecuteNonQuery();
|
||||
|
||||
sqlCommand.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetPoetPosition(int id, int x, int y, int room)
|
||||
{
|
||||
using (MySqlConnection db = new MySqlConnection(ConnectionString))
|
||||
{
|
||||
db.Open();
|
||||
MySqlCommand sqlCommand = db.CreateCommand();
|
||||
sqlCommand.CommandText = "UPDATE PoetryRooms SET X=@x, Y=@y WHERE poetId=@id AND roomId=@room";
|
||||
sqlCommand.Parameters.AddWithValue("@id", id);
|
||||
sqlCommand.Parameters.AddWithValue("@x", x);
|
||||
sqlCommand.Parameters.AddWithValue("@y", y);
|
||||
sqlCommand.Parameters.AddWithValue("@room", room);
|
||||
sqlCommand.Prepare();
|
||||
sqlCommand.ExecuteNonQuery();
|
||||
|
||||
sqlCommand.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool GetPoetExist(int id, int room)
|
||||
{
|
||||
using (MySqlConnection db = new MySqlConnection(ConnectionString))
|
||||
{
|
||||
db.Open();
|
||||
MySqlCommand sqlCommand = db.CreateCommand();
|
||||
sqlCommand.CommandText = "SELECT COUNT(1) FROM PoetryRooms WHERE poetId=@id AND roomId=@room";
|
||||
sqlCommand.Parameters.AddWithValue("@id", id);
|
||||
sqlCommand.Parameters.AddWithValue("@room", room);
|
||||
sqlCommand.Prepare();
|
||||
int count = Convert.ToInt32(sqlCommand.ExecuteScalar());
|
||||
|
||||
sqlCommand.Dispose();
|
||||
return count > 0;
|
||||
}
|
||||
}
|
||||
public static int GetPoetPositionX(int id, int room)
|
||||
{
|
||||
using (MySqlConnection db = new MySqlConnection(ConnectionString))
|
||||
{
|
||||
db.Open();
|
||||
MySqlCommand sqlCommand = db.CreateCommand();
|
||||
sqlCommand.CommandText = "SELECT X FROM PoetryRooms WHERE poetId=@id AND roomId=@room";
|
||||
sqlCommand.Parameters.AddWithValue("@id", id);
|
||||
sqlCommand.Parameters.AddWithValue("@room", room);
|
||||
sqlCommand.Prepare();
|
||||
int xpos = Convert.ToInt32(sqlCommand.ExecuteScalar());
|
||||
|
||||
sqlCommand.Dispose();
|
||||
return xpos;
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetPoetPositionY(int id, int room)
|
||||
{
|
||||
using (MySqlConnection db = new MySqlConnection(ConnectionString))
|
||||
{
|
||||
db.Open();
|
||||
MySqlCommand sqlCommand = db.CreateCommand();
|
||||
sqlCommand.CommandText = "SELECT Y FROM PoetryRooms WHERE poetId=@id AND roomId=@room";
|
||||
sqlCommand.Parameters.AddWithValue("@id", id);
|
||||
sqlCommand.Parameters.AddWithValue("@room", room);
|
||||
sqlCommand.Prepare();
|
||||
int ypos = Convert.ToInt32(sqlCommand.ExecuteScalar());
|
||||
|
||||
sqlCommand.Dispose();
|
||||
return ypos;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void SetServerTime(int time, int day, int year)
|
||||
{
|
||||
using (MySqlConnection db = new MySqlConnection(ConnectionString))
|
||||
|
@ -418,6 +519,8 @@ namespace HISP.Server
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void SetJewelrySlot1(int playerId, int itemId)
|
||||
{
|
||||
using (MySqlConnection db = new MySqlConnection(ConnectionString))
|
||||
|
|
|
@ -242,6 +242,9 @@ namespace HISP.Server
|
|||
case PacketBuilder.PACKET_NPC:
|
||||
GameServer.OnNpcInteraction(this, Packet);
|
||||
break;
|
||||
case PacketBuilder.PACKET_SWFMODULE:
|
||||
GameServer.OnSwfModuleCommunication(this, Packet);
|
||||
break;
|
||||
case PacketBuilder.PACKET_WISH:
|
||||
GameServer.OnWish(this, Packet);
|
||||
break;
|
||||
|
|
|
@ -5,6 +5,7 @@ using HISP.Game;
|
|||
using HISP.Game.Chat;
|
||||
using HISP.Player;
|
||||
using HISP.Game.Services;
|
||||
using HISP.Game.SwfModules;
|
||||
|
||||
namespace HISP.Server
|
||||
{
|
||||
|
@ -439,6 +440,19 @@ namespace HISP.Server
|
|||
Logger.DebugPrint("Reigstered Inn: " + inn.Id + " Buying at: " + inn.BuyPercentage.ToString() + "%!");
|
||||
}
|
||||
|
||||
int totalPoets = gameData.poetry.Count;
|
||||
for(int i = 0; i < totalPoets; i++)
|
||||
{
|
||||
Brickpoet.PoetryEntry entry = new Brickpoet.PoetryEntry();
|
||||
entry.Id = gameData.poetry[i].id;
|
||||
entry.Word = gameData.poetry[i].word;
|
||||
entry.Room = gameData.poetry[i].room_id;
|
||||
Brickpoet.PoetList.Add(entry);
|
||||
|
||||
Logger.DebugPrint("Registed poet: " + entry.Id.ToString() + " word: " + entry.Word + " in room " + entry.Room.ToString());
|
||||
}
|
||||
|
||||
|
||||
Item.Present = gameData.item.special.present;
|
||||
Item.MailMessage = gameData.item.special.mail_message;
|
||||
Item.DorothyShoes = gameData.item.special.dorothy_shoes;
|
||||
|
|
|
@ -13,6 +13,7 @@ using HISP.Player.Equips;
|
|||
using System.Drawing;
|
||||
using HISP.Game.Services;
|
||||
using HISP.Game.Inventory;
|
||||
using HISP.Game.SwfModules;
|
||||
|
||||
namespace HISP.Server
|
||||
{
|
||||
|
@ -417,6 +418,139 @@ namespace HISP.Server
|
|||
|
||||
}
|
||||
|
||||
public static void OnSwfModuleCommunication(GameClient sender, byte[] packet)
|
||||
{
|
||||
if (!sender.LoggedIn)
|
||||
{
|
||||
Logger.ErrorPrint(sender.RemoteIp + " tried to send swf communication when not logged in.");
|
||||
return;
|
||||
}
|
||||
if (packet.Length < 4)
|
||||
{
|
||||
Logger.ErrorPrint(sender.LoggedinUser.Username + " Sent an invalid swf commmunication Packet");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
byte module = packet[1];
|
||||
switch(module)
|
||||
{
|
||||
case PacketBuilder.SWFMODULE_BRICKPOET:
|
||||
if(packet.Length < 5)
|
||||
{
|
||||
Logger.ErrorPrint(sender.LoggedinUser.Username + " Sent invalid BRICKPOET packet (swf communication, WRONG SIZE)");
|
||||
break;
|
||||
}
|
||||
if(packet[2] == PacketBuilder.BRICKPOET_LIST_ALL)
|
||||
{
|
||||
if (packet.Length < 6)
|
||||
{
|
||||
Logger.ErrorPrint(sender.LoggedinUser.Username + " Sent invalid BRICKPOET LIST ALL packet (swf communication, WRONG SIZE)");
|
||||
break;
|
||||
}
|
||||
|
||||
int roomId = packet[3] - 40;
|
||||
Brickpoet.PoetryPeice[] room;
|
||||
try
|
||||
{
|
||||
room = Brickpoet.GetPoetryRoom(roomId);
|
||||
}
|
||||
catch(KeyNotFoundException)
|
||||
{
|
||||
Logger.ErrorPrint(sender.LoggedinUser.Username + " tried to load an invalid brickpoet room: " + roomId);
|
||||
break;
|
||||
}
|
||||
|
||||
byte[] poetPacket = PacketBuilder.CreateBrickPoetListPacket(room);
|
||||
sender.SendPacket(poetPacket);
|
||||
}
|
||||
else if(packet[3] == PacketBuilder.BRICKPOET_MOVE)
|
||||
{
|
||||
if (packet.Length < 0xB)
|
||||
{
|
||||
Logger.ErrorPrint(sender.LoggedinUser.Username + " Sent invalid BRICKPOET MOVE packet (swf communication, WRONG SIZE)");
|
||||
break;
|
||||
}
|
||||
string packetStr = Encoding.UTF8.GetString(packet);
|
||||
if(!packetStr.Contains('|'))
|
||||
{
|
||||
Logger.ErrorPrint(sender.LoggedinUser.Username + " Sent invalid BRICKPOET MOVE packet (swf communication, NO | SEPERATOR)");
|
||||
break;
|
||||
}
|
||||
string[] args = packetStr.Split('|');
|
||||
if(args.Length < 5)
|
||||
{
|
||||
Logger.ErrorPrint(sender.LoggedinUser.Username + " Sent invalid BRICKPOET MOVE Packet (swf communication, NOT ENOUGH | SEPERATORS.");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
int roomId = packet[2] - 40;
|
||||
int peiceId;
|
||||
int x;
|
||||
int y;
|
||||
Brickpoet.PoetryPeice[] room;
|
||||
Brickpoet.PoetryPeice peice;
|
||||
|
||||
try
|
||||
{
|
||||
peiceId = int.Parse(args[1]);
|
||||
x = int.Parse(args[2]);
|
||||
y = int.Parse(args[3]);
|
||||
|
||||
|
||||
room = Brickpoet.GetPoetryRoom(roomId);
|
||||
peice = Brickpoet.GetPoetryPeice(room, peiceId);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Logger.ErrorPrint(sender.LoggedinUser.Username + " tried to move a peice in an invalid brickpoet room: " + roomId);
|
||||
break;
|
||||
}
|
||||
peice.X = x;
|
||||
peice.Y = y;
|
||||
|
||||
foreach(GameClient client in connectedClients)
|
||||
{
|
||||
if(client.LoggedIn)
|
||||
{
|
||||
if (client.LoggedinUser.Id == sender.LoggedinUser.Id)
|
||||
continue;
|
||||
|
||||
if (World.InSpecialTile(client.LoggedinUser.X, client.LoggedinUser.Y))
|
||||
{
|
||||
World.SpecialTile tile = World.GetSpecialTile(client.LoggedinUser.X, client.LoggedinUser.Y);
|
||||
|
||||
if (tile.Code.StartsWith("MULTIROOM-"))
|
||||
{
|
||||
string roomNo = tile.Code.Split('-')[1];
|
||||
if (roomNo == "P" + roomId.ToString())
|
||||
{
|
||||
byte[] updatePoetRoomPacket = PacketBuilder.CreateBrickPoetMovePacket(peice);
|
||||
client.SendPacket(updatePoetRoomPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.DebugPrint(" packet dump: " + BitConverter.ToString(packet).Replace("-", " "));
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
Logger.DebugPrint("Unknown moduleid : " + module + " packet dump: " + BitConverter.ToString(packet).Replace("-"," "));
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void OnWish(GameClient sender, byte[] packet)
|
||||
{
|
||||
if (!sender.LoggedIn)
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using HISP.Game;
|
||||
using HISP.Game.SwfModules;
|
||||
|
||||
namespace HISP.Server
|
||||
{
|
||||
class PacketBuilder
|
||||
|
@ -39,6 +41,12 @@ namespace HISP.Server
|
|||
public const byte PACKET_PLAYERINFO = 0x16;
|
||||
public const byte PACKET_INFORMATION = 0x28;
|
||||
public const byte PACKET_WISH = 0x2C;
|
||||
public const byte PACKET_SWFMODULE = 0x50;
|
||||
|
||||
public const byte SWFMODULE_BRICKPOET = 0x5A;
|
||||
|
||||
public const byte BRICKPOET_LIST_ALL = 0x14;
|
||||
public const byte BRICKPOET_MOVE = 0x55;
|
||||
|
||||
public const byte WISH_MONEY = 0x31;
|
||||
public const byte WISH_ITEMS = 0x32;
|
||||
|
@ -112,7 +120,50 @@ namespace HISP.Server
|
|||
public const byte DIRECTION_TELEPORT = 4;
|
||||
public const byte DIRECTION_NONE = 10;
|
||||
|
||||
public static byte[] CreateBrickPoetMovePacket(Brickpoet.PoetryPeice peice)
|
||||
{
|
||||
|
||||
MemoryStream ms = new MemoryStream();
|
||||
ms.WriteByte(PacketBuilder.PACKET_SWFMODULE);
|
||||
ms.WriteByte(PacketBuilder.BRICKPOET_MOVE);
|
||||
string packetStr = "|";
|
||||
packetStr += peice.Id + "|";
|
||||
packetStr += peice.X + "|";
|
||||
packetStr += peice.Y + "|";
|
||||
packetStr += "^";
|
||||
|
||||
byte[] infoBytes = Encoding.UTF8.GetBytes(packetStr);
|
||||
ms.Write(infoBytes, 0x00, infoBytes.Length);
|
||||
ms.WriteByte(PACKET_TERMINATOR);
|
||||
ms.Seek(0x00, SeekOrigin.Begin);
|
||||
return ms.ToArray();
|
||||
}
|
||||
public static byte[] CreateBrickPoetListPacket(Brickpoet.PoetryPeice[] room)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
ms.WriteByte(PacketBuilder.PACKET_SWFMODULE);
|
||||
string packetStr = "";
|
||||
foreach(Brickpoet.PoetryPeice peice in room)
|
||||
{
|
||||
packetStr += "A";
|
||||
packetStr += "|";
|
||||
packetStr += peice.Id;
|
||||
packetStr += "|";
|
||||
packetStr += peice.Word.ToUpper();
|
||||
packetStr += "|";
|
||||
packetStr += peice.X;
|
||||
packetStr += "|";
|
||||
packetStr += peice.Y;
|
||||
packetStr += "|";
|
||||
packetStr += "^";
|
||||
}
|
||||
byte[] packetBytes = Encoding.UTF8.GetBytes(packetStr);
|
||||
ms.Write(packetBytes, 0x00, packetBytes.Length);
|
||||
ms.WriteByte(PacketBuilder.PACKET_TERMINATOR);
|
||||
|
||||
ms.Seek(0x00, SeekOrigin.Begin);
|
||||
return ms.ToArray();
|
||||
}
|
||||
public static byte[] CreatePlaysoundPacket(string sound)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
|
|
Loading…
Add table
Reference in a new issue