2019-07-05 16:17:54 +00:00
|
|
|
package io.izzel.taboolib.module.nms;
|
2018-11-09 08:41:22 +00:00
|
|
|
|
2019-07-05 16:17:54 +00:00
|
|
|
import io.izzel.taboolib.Version;
|
|
|
|
import io.izzel.taboolib.module.lite.SimpleReflection;
|
|
|
|
import io.izzel.taboolib.module.nms.nbt.NBTCompound;
|
|
|
|
import io.izzel.taboolib.module.nms.nbt.NBTList;
|
|
|
|
import io.izzel.taboolib.module.packet.TPacketHandler;
|
2018-11-09 08:41:22 +00:00
|
|
|
import net.minecraft.server.v1_12_R1.ChatMessageType;
|
2019-05-22 00:41:34 +00:00
|
|
|
import net.minecraft.server.v1_12_R1.EntityVillager;
|
2019-01-20 08:45:02 +00:00
|
|
|
import net.minecraft.server.v1_12_R1.MinecraftServer;
|
2019-05-22 00:41:34 +00:00
|
|
|
import net.minecraft.server.v1_12_R1.NBTTagCompound;
|
|
|
|
import net.minecraft.server.v1_13_R2.IRegistry;
|
2019-05-24 16:08:37 +00:00
|
|
|
import net.minecraft.server.v1_8_R3.*;
|
2019-07-05 16:17:54 +00:00
|
|
|
import org.bukkit.Bukkit;
|
|
|
|
import org.bukkit.Particle;
|
|
|
|
import org.bukkit.craftbukkit.v1_12_R1.CraftParticle;
|
|
|
|
import org.bukkit.craftbukkit.v1_13_R2.CraftServer;
|
2019-05-22 00:41:34 +00:00
|
|
|
import org.bukkit.craftbukkit.v1_13_R2.entity.CraftVillager;
|
|
|
|
import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack;
|
|
|
|
import org.bukkit.entity.Entity;
|
2018-11-09 08:41:22 +00:00
|
|
|
import org.bukkit.entity.Player;
|
2019-05-22 00:41:34 +00:00
|
|
|
import org.bukkit.entity.Villager;
|
|
|
|
import org.bukkit.inventory.ItemStack;
|
|
|
|
import org.bukkit.inventory.meta.PotionMeta;
|
|
|
|
|
|
|
|
import java.lang.reflect.Field;
|
2019-05-24 16:08:37 +00:00
|
|
|
import java.util.List;
|
2019-06-13 15:42:47 +00:00
|
|
|
import java.util.Map;
|
2018-11-09 08:41:22 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @Author 坏黑
|
|
|
|
* @Since 2018-11-09 14:42
|
|
|
|
*/
|
2019-07-06 10:50:05 +00:00
|
|
|
public class NMSImpl extends NMS {
|
2018-11-09 08:41:22 +00:00
|
|
|
|
2019-05-22 00:41:34 +00:00
|
|
|
private Field entityTypesField;
|
|
|
|
|
2019-05-24 16:08:37 +00:00
|
|
|
static {
|
|
|
|
SimpleReflection.saveField(NBTTagString.class);
|
|
|
|
SimpleReflection.saveField(NBTTagDouble.class);
|
|
|
|
SimpleReflection.saveField(NBTTagInt.class);
|
|
|
|
SimpleReflection.saveField(NBTTagFloat.class);
|
|
|
|
SimpleReflection.saveField(NBTTagShort.class);
|
|
|
|
SimpleReflection.saveField(NBTTagLong.class);
|
|
|
|
SimpleReflection.saveField(NBTTagByte.class);
|
|
|
|
SimpleReflection.saveField(NBTTagIntArray.class);
|
|
|
|
SimpleReflection.saveField(NBTTagByteArray.class);
|
|
|
|
SimpleReflection.saveField(NBTTagList.class);
|
2019-06-13 15:42:47 +00:00
|
|
|
SimpleReflection.saveField(NBTTagCompound.class);
|
2019-05-24 16:08:37 +00:00
|
|
|
}
|
|
|
|
|
2019-07-06 10:50:05 +00:00
|
|
|
public NMSImpl() {
|
2019-07-05 16:17:54 +00:00
|
|
|
if (Version.isAfter(Version.v1_13)) {
|
|
|
|
SimpleReflection.saveField(net.minecraft.server.v1_12_R1.Entity.class);
|
|
|
|
for (Field declaredField : SimpleReflection.getFields(net.minecraft.server.v1_12_R1.Entity.class).values()) {
|
2019-05-22 00:41:34 +00:00
|
|
|
if (declaredField.getType().getSimpleName().equals("EntityTypes")) {
|
|
|
|
entityTypesField = declaredField;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-07-05 16:17:54 +00:00
|
|
|
SimpleReflection.saveField(MinecraftServer.class);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean isRunning() {
|
|
|
|
return !SimpleReflection.getFieldValue(MinecraftServer.class, ((CraftServer) Bukkit.getServer()).getServer(), "hasStopped", false);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Object toPacketPlayOutWorldParticles(Particle var1, boolean var2, float var3, float var4, float var5, float var6, float var7, float var8, float var9, int var10, Object var11) {
|
|
|
|
return new net.minecraft.server.v1_12_R1.PacketPlayOutWorldParticles(CraftParticle.toNMS(var1), var2, var3, var4, var5, var6, var7, var8, var9, var10, CraftParticle.toData(var1, var11));
|
2019-05-22 00:41:34 +00:00
|
|
|
}
|
|
|
|
|
2019-05-24 16:08:37 +00:00
|
|
|
@Override
|
|
|
|
public double[] getTPS() {
|
|
|
|
return MinecraftServer.getServer().recentTps;
|
|
|
|
}
|
|
|
|
|
2019-05-22 00:41:34 +00:00
|
|
|
@Override
|
|
|
|
public String getName(ItemStack itemStack) {
|
|
|
|
Object nmsItem = CraftItemStack.asNMSCopy(itemStack);
|
2019-07-05 16:17:54 +00:00
|
|
|
if (Version.isAfter(Version.v1_13)) {
|
2019-05-22 00:41:34 +00:00
|
|
|
String name = ((net.minecraft.server.v1_8_R3.ItemStack) nmsItem).getItem().getName();
|
|
|
|
if (itemStack.getItemMeta() instanceof PotionMeta) {
|
|
|
|
name += ".effect." + ((net.minecraft.server.v1_8_R3.ItemStack) nmsItem).getTag().getString("Potion").replaceAll("minecraft:(strong_|long_)?", "");
|
|
|
|
}
|
|
|
|
return name;
|
2019-07-05 16:17:54 +00:00
|
|
|
} else if (Version.isAfter(Version.v1_11)) {
|
2019-05-22 00:41:34 +00:00
|
|
|
String name = ((net.minecraft.server.v1_12_R1.ItemStack) nmsItem).getItem().a((net.minecraft.server.v1_12_R1.ItemStack) nmsItem);
|
|
|
|
if (itemStack.getItemMeta() instanceof PotionMeta) {
|
2019-06-24 08:59:45 +00:00
|
|
|
return name.replace("item.", "") + ".effect." + ((net.minecraft.server.v1_8_R3.ItemStack) nmsItem).getTag().getString("Potion").replaceAll("(minecraft:)?(strong_|long_)?", "");
|
|
|
|
}
|
|
|
|
return name + ".name";
|
|
|
|
} else {
|
|
|
|
String name = ((net.minecraft.server.v1_8_R3.ItemStack) nmsItem).getItem().getName();
|
|
|
|
if (itemStack.getItemMeta() instanceof PotionMeta) {
|
|
|
|
return name.replace("item.", "") + ".effect." + ((net.minecraft.server.v1_8_R3.ItemStack) nmsItem).getTag().getString("Potion").replaceAll("(minecraft:)?(strong_|long_)?", "");
|
2019-05-22 00:41:34 +00:00
|
|
|
}
|
|
|
|
return name + ".name";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getName(Entity entity) {
|
2019-07-05 16:17:54 +00:00
|
|
|
if (Version.isAfter(Version.v1_14)) {
|
2019-07-11 08:20:35 +00:00
|
|
|
Object minecraftKey = net.minecraft.server.v1_14_R1.EntityTypes.getName(((org.bukkit.craftbukkit.v1_14_R1.entity.CraftEntity) entity).getHandle().getEntityType());
|
|
|
|
return "entity.minecraft." + ((net.minecraft.server.v1_14_R1.MinecraftKey) minecraftKey).getKey();
|
2019-07-05 16:17:54 +00:00
|
|
|
} else if (Version.isAfter(Version.v1_13)) {
|
2019-06-24 13:53:36 +00:00
|
|
|
try {
|
|
|
|
String name = "entity.minecraft." + IRegistry.ENTITY_TYPE.getKey((net.minecraft.server.v1_13_R2.EntityTypes<?>) entityTypesField.get(((org.bukkit.craftbukkit.v1_13_R2.entity.CraftEntity) entity).getHandle())).getKey();
|
|
|
|
if (entity instanceof Villager && ((CraftVillager) entity).getCareer() != null) {
|
|
|
|
name += "." + String.valueOf(((CraftVillager) entity).getCareer()).toLowerCase();
|
|
|
|
}
|
|
|
|
return name;
|
|
|
|
} catch (Throwable t) {
|
|
|
|
t.printStackTrace();
|
|
|
|
}
|
|
|
|
return "entity.null";
|
|
|
|
} else {
|
2019-05-22 00:41:34 +00:00
|
|
|
try {
|
|
|
|
if (entity instanceof Player) {
|
|
|
|
return "entity.Player.name";
|
|
|
|
}
|
|
|
|
if (entity instanceof Villager) {
|
|
|
|
String name = "name";
|
|
|
|
Object villager = ((org.bukkit.craftbukkit.v1_12_R1.entity.CraftVillager) entity).getHandle();
|
|
|
|
Object export = new NBTTagCompound();
|
|
|
|
((EntityVillager) villager).b((NBTTagCompound) export);
|
|
|
|
int career = ((NBTTagCompound) export).getInt("Career");
|
|
|
|
switch (((EntityVillager) villager).getProfession()) {
|
|
|
|
case 0:
|
|
|
|
if (career == 1) {
|
|
|
|
name = "farmer";
|
|
|
|
} else if (career == 2) {
|
|
|
|
name = "fisherman";
|
|
|
|
} else if (career == 3) {
|
|
|
|
name = "shepherd";
|
|
|
|
} else if (career == 4) {
|
|
|
|
name = "fletcher";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
if (career == 1) {
|
|
|
|
name = "librarian";
|
|
|
|
} else if (career == 2) {
|
|
|
|
name = "cartographer";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
name = "cleric";
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
if (career == 1) {
|
|
|
|
name = "armor";
|
|
|
|
} else if (career == 2) {
|
|
|
|
name = "weapon";
|
|
|
|
} else if (career == 3) {
|
|
|
|
name = "tool";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
if (career == 1) {
|
|
|
|
name = "butcher";
|
|
|
|
} else if (career == 2) {
|
|
|
|
name = "leather";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
name = "nitwit";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return "entity.Villager." + name;
|
|
|
|
}
|
|
|
|
return "entity." + entity.getType().getEntityClass().getSimpleName() + ".name";
|
|
|
|
} catch (Throwable t) {
|
|
|
|
t.printStackTrace();
|
|
|
|
}
|
|
|
|
return "entity.null";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-09 08:41:22 +00:00
|
|
|
@Override
|
|
|
|
public void sendTitle(Player player, String title, int titleFadein, int titleStay, int titleFadeout, String subtitle, int subtitleFadein, int subtitleStay, int subtitleFadeout) {
|
2019-02-10 07:05:03 +00:00
|
|
|
TPacketHandler.sendPacket(player, new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.TIMES, new ChatComponentText(String.valueOf(title)), titleFadein, titleStay, titleFadeout));
|
|
|
|
TPacketHandler.sendPacket(player, new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.TITLE, new ChatComponentText(String.valueOf(title)), titleFadein, titleStay, titleFadeout));
|
|
|
|
TPacketHandler.sendPacket(player, new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.TIMES, new ChatComponentText(String.valueOf(subtitle)), subtitleFadein, subtitleStay, subtitleFadeout));
|
|
|
|
TPacketHandler.sendPacket(player, new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.SUBTITLE, new ChatComponentText(String.valueOf(subtitle)), subtitleFadein, subtitleStay, subtitleFadeout));
|
2018-11-09 08:41:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void sendActionBar(Player player, String text) {
|
2019-07-05 16:17:54 +00:00
|
|
|
if (Version.isAfter(Version.v1_12)) {
|
2018-11-09 08:41:22 +00:00
|
|
|
TPacketHandler.sendPacket(player, new net.minecraft.server.v1_12_R1.PacketPlayOutChat(new net.minecraft.server.v1_12_R1.ChatComponentText(String.valueOf(text)), ChatMessageType.GAME_INFO));
|
|
|
|
} else {
|
|
|
|
TPacketHandler.sendPacket(player, new PacketPlayOutChat(new ChatComponentText(String.valueOf(text)), (byte) 2));
|
|
|
|
}
|
|
|
|
}
|
2019-01-20 08:45:02 +00:00
|
|
|
|
|
|
|
@Override
|
2019-05-26 07:02:29 +00:00
|
|
|
public ItemStack _NBT(ItemStack itemStack, Object compound) {
|
2019-05-24 16:08:37 +00:00
|
|
|
Object nmsItem = CraftItemStack.asNMSCopy(itemStack);
|
|
|
|
try {
|
2019-07-05 16:17:54 +00:00
|
|
|
((net.minecraft.server.v1_8_R3.ItemStack) nmsItem).setTag((net.minecraft.server.v1_8_R3.NBTTagCompound) toNBTBase((io.izzel.taboolib.module.nms.nbt.NBTBase) compound));
|
2019-05-24 16:08:37 +00:00
|
|
|
} catch (Throwable t) {
|
|
|
|
t.printStackTrace();
|
|
|
|
}
|
2019-06-28 07:17:35 +00:00
|
|
|
return CraftItemStack.asBukkitCopy((net.minecraft.server.v1_8_R3.ItemStack) nmsItem);
|
2019-05-24 16:08:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Object _NBT(ItemStack itemStack) {
|
|
|
|
Object nmsItem = CraftItemStack.asNMSCopy(itemStack);
|
|
|
|
try {
|
|
|
|
return ((net.minecraft.server.v1_8_R3.ItemStack) nmsItem).hasTag() ? fromNBTBase(((net.minecraft.server.v1_8_R3.ItemStack) nmsItem).getTag()) : new NBTCompound();
|
|
|
|
} catch (Throwable t) {
|
|
|
|
t.printStackTrace();
|
|
|
|
}
|
|
|
|
return new NBTCompound();
|
|
|
|
}
|
|
|
|
|
2019-07-05 16:17:54 +00:00
|
|
|
private Object toNBTBase(io.izzel.taboolib.module.nms.nbt.NBTBase base) {
|
2019-05-26 07:02:29 +00:00
|
|
|
switch (base.getType().getId()) {
|
|
|
|
case 1:
|
2019-05-24 16:08:37 +00:00
|
|
|
return new NBTTagByte(base.asByte());
|
2019-05-26 07:02:29 +00:00
|
|
|
case 2:
|
|
|
|
return new NBTTagShort(base.asShort());
|
|
|
|
case 3:
|
|
|
|
return new NBTTagInt(base.asInt());
|
|
|
|
case 4:
|
2019-05-24 16:08:37 +00:00
|
|
|
return new NBTTagLong(base.asLong());
|
2019-05-26 07:02:29 +00:00
|
|
|
case 5:
|
2019-05-24 16:08:37 +00:00
|
|
|
return new NBTTagFloat(base.asFloat());
|
2019-05-26 07:02:29 +00:00
|
|
|
case 6:
|
2019-05-24 16:08:37 +00:00
|
|
|
return new NBTTagDouble(base.asDouble());
|
2019-05-26 07:02:29 +00:00
|
|
|
case 7:
|
2019-05-24 16:08:37 +00:00
|
|
|
return new NBTTagByteArray(base.asByteArray());
|
2019-05-26 07:02:29 +00:00
|
|
|
case 11:
|
|
|
|
return new NBTTagIntArray(base.asIntArray());
|
|
|
|
case 8:
|
|
|
|
return new NBTTagString(base.asString());
|
|
|
|
case 9:
|
2019-05-24 16:08:37 +00:00
|
|
|
Object nmsList = new NBTTagList();
|
2019-07-05 16:17:54 +00:00
|
|
|
for (io.izzel.taboolib.module.nms.nbt.NBTBase value : base.asList()) {
|
2019-06-25 14:24:48 +00:00
|
|
|
// 1.14+
|
2019-07-05 16:17:54 +00:00
|
|
|
if (Version.isAfter(Version.v1_14)) {
|
2019-06-25 14:24:48 +00:00
|
|
|
((net.minecraft.server.v1_14_R1.NBTTagList) nmsList).add(((net.minecraft.server.v1_14_R1.NBTTagList) nmsList).size(), (net.minecraft.server.v1_14_R1.NBTBase) toNBTBase(value));
|
|
|
|
}
|
2019-06-28 07:17:35 +00:00
|
|
|
// 1.13
|
2019-07-05 16:17:54 +00:00
|
|
|
else if (Version.isAfter(Version.v1_13)) {
|
2019-06-28 07:17:35 +00:00
|
|
|
((net.minecraft.server.v1_13_R2.NBTTagList) nmsList).add((net.minecraft.server.v1_13_R2.NBTBase) toNBTBase(value));
|
|
|
|
}
|
|
|
|
// 1.12-
|
2019-06-25 14:24:48 +00:00
|
|
|
else {
|
|
|
|
((NBTTagList) nmsList).add((NBTBase) toNBTBase(value));
|
|
|
|
}
|
2019-06-13 15:42:47 +00:00
|
|
|
}
|
2019-05-24 16:08:37 +00:00
|
|
|
return nmsList;
|
2019-05-26 07:02:29 +00:00
|
|
|
case 10:
|
2019-05-24 16:08:37 +00:00
|
|
|
Object nmsTag = new net.minecraft.server.v1_8_R3.NBTTagCompound();
|
2019-07-05 16:17:54 +00:00
|
|
|
for (Map.Entry<String, io.izzel.taboolib.module.nms.nbt.NBTBase> entry : base.asCompound().entrySet()) {
|
2019-06-25 07:08:42 +00:00
|
|
|
((Map) SimpleReflection.getFieldValue(NBTTagCompound.class, nmsTag, "map")).put(entry.getKey(), toNBTBase(entry.getValue()));
|
2019-06-13 15:42:47 +00:00
|
|
|
}
|
2019-05-24 16:08:37 +00:00
|
|
|
return nmsTag;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Object fromNBTBase(Object base) {
|
2019-06-13 15:42:47 +00:00
|
|
|
if (base instanceof net.minecraft.server.v1_8_R3.NBTTagCompound) {
|
|
|
|
NBTCompound nbtCompound = new NBTCompound();
|
|
|
|
for (Map.Entry<String, net.minecraft.server.v1_12_R1.NBTBase> entry : ((Map<String, net.minecraft.server.v1_12_R1.NBTBase>) SimpleReflection.getFieldValue(NBTTagCompound.class, base, "map")).entrySet()) {
|
2019-07-05 16:17:54 +00:00
|
|
|
nbtCompound.put(entry.getKey(), (io.izzel.taboolib.module.nms.nbt.NBTBase) fromNBTBase(entry.getValue()));
|
2019-06-13 15:42:47 +00:00
|
|
|
}
|
|
|
|
return nbtCompound;
|
|
|
|
} else if (base instanceof NBTTagList) {
|
|
|
|
NBTList nbtList = new NBTList();
|
|
|
|
for (Object v : (List) SimpleReflection.getFieldValue(NBTTagList.class, base, "list")) {
|
2019-07-05 16:17:54 +00:00
|
|
|
nbtList.add((io.izzel.taboolib.module.nms.nbt.NBTBase) fromNBTBase(v));
|
2019-06-13 15:42:47 +00:00
|
|
|
}
|
|
|
|
return nbtList;
|
|
|
|
} else if (base instanceof NBTTagString) {
|
2019-07-05 16:17:54 +00:00
|
|
|
return new io.izzel.taboolib.module.nms.nbt.NBTBase(SimpleReflection.getFieldValue(NBTTagString.class, base, "data", ""));
|
2019-05-24 16:08:37 +00:00
|
|
|
} else if (base instanceof NBTTagDouble) {
|
2019-07-05 16:17:54 +00:00
|
|
|
return new io.izzel.taboolib.module.nms.nbt.NBTBase(SimpleReflection.getFieldValue(NBTTagDouble.class, base, "data", 0D));
|
2019-05-24 16:08:37 +00:00
|
|
|
} else if (base instanceof NBTTagInt) {
|
2019-07-05 16:17:54 +00:00
|
|
|
return new io.izzel.taboolib.module.nms.nbt.NBTBase(SimpleReflection.getFieldValue(NBTTagInt.class, base, "data", 0));
|
2019-05-24 16:08:37 +00:00
|
|
|
} else if (base instanceof NBTTagFloat) {
|
2019-07-05 16:17:54 +00:00
|
|
|
return new io.izzel.taboolib.module.nms.nbt.NBTBase(SimpleReflection.getFieldValue(NBTTagFloat.class, base, "data", (float) 0));
|
2019-05-24 16:08:37 +00:00
|
|
|
} else if (base instanceof NBTTagShort) {
|
2019-07-05 16:17:54 +00:00
|
|
|
return new io.izzel.taboolib.module.nms.nbt.NBTBase(SimpleReflection.getFieldValue(NBTTagShort.class, base, "data", (short) 0));
|
2019-05-24 16:08:37 +00:00
|
|
|
} else if (base instanceof NBTTagLong) {
|
2019-07-05 16:17:54 +00:00
|
|
|
return new io.izzel.taboolib.module.nms.nbt.NBTBase(SimpleReflection.getFieldValue(NBTTagLong.class, base, "data", 0L));
|
2019-05-24 16:08:37 +00:00
|
|
|
} else if (base instanceof NBTTagByte) {
|
2019-07-05 16:17:54 +00:00
|
|
|
return new io.izzel.taboolib.module.nms.nbt.NBTBase(SimpleReflection.getFieldValue(NBTTagByte.class, base, "data", (byte) 0D));
|
2019-05-24 16:08:37 +00:00
|
|
|
} else if (base instanceof NBTTagIntArray) {
|
2019-07-05 16:17:54 +00:00
|
|
|
return new io.izzel.taboolib.module.nms.nbt.NBTBase(SimpleReflection.getFieldValue(NBTTagIntArray.class, base, "data", new int[0]));
|
2019-05-24 16:08:37 +00:00
|
|
|
} else if (base instanceof NBTTagByteArray) {
|
2019-07-05 16:17:54 +00:00
|
|
|
return new io.izzel.taboolib.module.nms.nbt.NBTBase(SimpleReflection.getFieldValue(NBTTagByteArray.class, base, "data", new byte[0]));
|
2019-05-24 16:08:37 +00:00
|
|
|
}
|
|
|
|
return null;
|
2019-01-20 08:45:02 +00:00
|
|
|
}
|
2018-11-09 08:41:22 +00:00
|
|
|
}
|