TeamHalle #6
@ -24,6 +24,7 @@ import de.steamwar.lobby.command.FlyCommand;
|
||||
import de.steamwar.lobby.command.HologramCommand;
|
||||
import de.steamwar.lobby.command.PortalCommand;
|
||||
import de.steamwar.lobby.listener.*;
|
||||
import de.steamwar.lobby.team.TeamPlayer;
|
||||
import de.steamwar.message.Message;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
@ -58,6 +59,7 @@ public class LobbySystem extends JavaPlugin {
|
||||
new WorldInteraction();
|
||||
new PlayerSeatListener();
|
||||
new MapsRotateListener();
|
||||
new TeamPlayer();
|
||||
|
||||
new AlphaWall(l -> l.getX() > 1199, AlphaWall.REFLECT_X);
|
||||
new AlphaWall(l -> l.getX() < 2977, AlphaWall.REFLECT_X);
|
||||
@ -66,6 +68,10 @@ public class LobbySystem extends JavaPlugin {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
TeamPlayer.cleanup();
|
||||
}
|
||||
|
||||
public static LobbySystem getPlugin() {
|
||||
return plugin;
|
||||
|
@ -3,6 +3,10 @@ TIME = HH:mm:ss
|
||||
DATE=........
|
||||
COMMAND_HELP_HEAD=§7---=== (§e{0}§7) ===---
|
||||
|
||||
# ServerTeamNPC's
|
||||
NPC_CHAT_1 = §fHallo, ich bin {0} und bin ein {1}.
|
||||
NPC_CHAT_2 = §fWillkommen auf §eSteam§8War§f, viel Spaß dir.
|
||||
|
||||
# Portal Command
|
||||
PORTAL_COMMAND_LIST_HELP = §8/§7portal §elist §8- §7Listet alle Portale auf
|
||||
PORTAL_COMMAND_ADD_HELP = §8/§7portal §ecreate §8[§7PortalType§8] §8[§7PortalName§8] §8- §7Fügt ein Portal hinzu
|
||||
|
@ -38,24 +38,34 @@ public class Displayable extends BasicListener {
|
||||
|
||||
private final Set<Player> visible = new HashSet<>();
|
||||
|
||||
private final int chunkX;
|
||||
private final int chunkZ;
|
||||
private int chunkX;
|
||||
private int chunkZ;
|
||||
private final Consumer<Player> show;
|
||||
private final Consumer<Player> hide;
|
||||
private final Consumer<Player> move;
|
||||
private final Function<Player, Boolean> playerFilter;
|
||||
|
||||
public Displayable(Location location, Consumer<Player> show, Consumer<Player> hide) {
|
||||
this(location, show, hide, player -> true);
|
||||
public Displayable(Location location, Consumer<Player> show, Consumer<Player> hide, Consumer<Player> move) {
|
||||
this(location, show, hide, move, player -> true);
|
||||
}
|
||||
|
||||
public Displayable(Location location, Consumer<Player> show, Consumer<Player> hide, Function<Player, Boolean> playerFilter) {
|
||||
this.chunkX = posToChunk(location.getX());
|
||||
this.chunkZ = posToChunk(location.getZ());
|
||||
this(location, show, hide, player -> {}, playerFilter);
|
||||
}
|
||||
|
||||
public Displayable(Location location, Consumer<Player> show, Consumer<Player> hide, Consumer<Player> move, Function<Player, Boolean> playerFilter) {
|
||||
this.show = show;
|
||||
this.hide = hide;
|
||||
this.move = move;
|
||||
this.playerFilter = playerFilter;
|
||||
setLocation(location);
|
||||
}
|
||||
|
||||
public void setLocation(Location location) {
|
||||
chunkX = posToChunk(location.getX());
|
||||
chunkZ = posToChunk(location.getZ());
|
||||
Bukkit.getOnlinePlayers().forEach(this::checkLocation);
|
||||
visible.forEach(move);
|
||||
}
|
||||
|
||||
public Set<Player> getVisitors() {
|
||||
|
@ -22,9 +22,12 @@ package de.steamwar.lobby.display;
|
||||
import com.comphenix.tinyprotocol.Reflection;
|
||||
import com.comphenix.tinyprotocol.TinyProtocol;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
@ -62,46 +65,137 @@ public class NPC {
|
||||
private static final Reflection.ConstructorInvoker headRotationConstructor = Reflection.getConstructor(headRotationPacket);
|
||||
private static final Reflection.FieldAccessor<Integer> headRotationEntity = Reflection.getField(headRotationPacket, int.class, 0);
|
||||
private static final Reflection.FieldAccessor<Byte> headRotationYaw = Reflection.getField(headRotationPacket, byte.class, 0);
|
||||
private static final Class<?> movePacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutEntityTeleport");
|
||||
private static final Reflection.ConstructorInvoker movePacketConstructor = Reflection.getConstructor(movePacket);
|
||||
private static final Reflection.FieldAccessor<Integer> movePacketEntity = Reflection.getField(movePacket, int.class, 0);
|
||||
private static final Reflection.FieldAccessor<Double> movePacketX = Reflection.getField(movePacket, double.class, 0);
|
||||
private static final Reflection.FieldAccessor<Double> movePacketY = Reflection.getField(movePacket, double.class, 1);
|
||||
private static final Reflection.FieldAccessor<Double> movePacketZ = Reflection.getField(movePacket, double.class, 2);
|
||||
private static final Reflection.FieldAccessor<Byte> movePacketYaw = Reflection.getField(movePacket, byte.class, 0);
|
||||
private static final Reflection.FieldAccessor<Byte> movePacketPitch = Reflection.getField(movePacket, byte.class, 1);
|
||||
private static final Reflection.FieldAccessor<Boolean> movePacketOnGround = Reflection.getField(movePacket, boolean.class, 0);
|
||||
|
||||
private static final Class<?> dataWatcherObject = Reflection.getClass("{nms.network.syncher}.DataWatcherObject");
|
||||
private static final Class<?> dataWatcherRegistry = Reflection.getClass("{nms.network.syncher}.DataWatcherRegistry");
|
||||
private static final Class<?> dataWatcherSerializer = Reflection.getClass("{nms.network.syncher}.DataWatcherSerializer");
|
||||
private static final Reflection.ConstructorInvoker dataWatcherObjectConstructor = Reflection.getConstructor(dataWatcherObject, int.class, dataWatcherSerializer);
|
||||
private static Object getDataWatcherObject(int index, Class<?> type) {
|
||||
for(Field field : dataWatcherRegistry.getFields()) {
|
||||
if(dataWatcherSerializer.isAssignableFrom(field.getType()) && type.equals(((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0])) {
|
||||
try {
|
||||
return dataWatcherObjectConstructor.invoke(index, field.get(null));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new SecurityException("Could not get field", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new SecurityException("Could not find Serializer for " + type.getName());
|
||||
}
|
||||
|
||||
private static final Class<?> item = Reflection.getClass("{nms.network.syncher}.DataWatcher$Item");
|
||||
private static final Reflection.ConstructorInvoker itemConstructor = Reflection.getConstructor(item, dataWatcherObject, Object.class);
|
||||
private static Object getDataWatcherItem(Object dwo, Object value) {
|
||||
return itemConstructor.invoke(dwo, value);
|
||||
}
|
||||
|
||||
private static final Class<?> metadataPacket = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutEntityMetadata");
|
||||
private static final Reflection.FieldAccessor<Integer> metadataEntity = Reflection.getField(metadataPacket, int.class, 0);
|
||||
private static final Reflection.FieldAccessor<List> metadataMetadata = Reflection.getField(metadataPacket, List.class, 0);
|
||||
private Object getDataWatcherPacket(Object dataWatcherObject, Object value) {
|
||||
Object packet = Reflection.newInstance(metadataPacket);
|
||||
metadataEntity.set(packet, entityId);
|
||||
metadataMetadata.set(packet, Collections.singletonList(getDataWatcherItem(dataWatcherObject, value)));
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static Object skinPartsDataWatcherObject = getDataWatcherObject(16, Byte.class);
|
||||
|
||||
private final Displayable display;
|
||||
|
||||
private final int entityId;
|
||||
private final UUID uuid;
|
||||
private final String name;
|
||||
private final Location location;
|
||||
private Location location;
|
||||
|
||||
private final Object addPlayerInfo;
|
||||
private final Object namedSpawn;
|
||||
private final Object skinParts;
|
||||
private final Object headRotation;
|
||||
private final Object removePlayerInfo;
|
||||
private final Object destroy;
|
||||
private Object move;
|
||||
|
||||
public NPC(Location location, UUID uuid, String name) {
|
||||
this.entityId = Hologram.createEntityId();
|
||||
this.uuid = uuid;
|
||||
this.name = name;
|
||||
this.location = location;
|
||||
byte yaw = (byte)(int)(location.getYaw() * 256.0 / 360.0);
|
||||
|
||||
GameProfile profile = new GameProfile(uuid, name);
|
||||
addPlayerInfo = playerInfoPacket(addPlayer, profile);
|
||||
removePlayerInfo = playerInfoPacket(removePlayer, profile);
|
||||
destroy = Hologram.destroyPacket(entityId);
|
||||
|
||||
skinParts = getDataWatcherPacket(skinPartsDataWatcherObject, (byte) 0x7F);
|
||||
|
||||
YoyoNow markierte diese Unterhaltung als gelöst
Veraltet
|
||||
namedSpawn = namedSpawnConstructor.invoke();
|
||||
namedSpawnEntity.set(namedSpawn, entityId);
|
||||
namedSpawnUUID.set(namedSpawn, uuid);
|
||||
|
||||
headRotation = headRotationConstructor.invoke();
|
||||
headRotationEntity.set(headRotation, entityId);
|
||||
|
||||
move = movePacketConstructor.invoke();
|
||||
movePacketEntity.set(move, entityId);
|
||||
|
||||
setPackets(location);
|
||||
|
||||
display = new Displayable(location, this::show, this::hide, this::move);
|
||||
}
|
||||
|
||||
public void setLocation(Location location) {
|
||||
if (isSimilarLocation(location)) {
|
||||
return;
|
||||
}
|
||||
this.location = location;
|
||||
setPackets(location);
|
||||
display.setLocation(location);
|
||||
}
|
||||
|
||||
private void setPackets(Location location) {
|
||||
byte yaw = (byte)(int)(location.getYaw() * 256.0 / 360.0);
|
||||
byte pitch = (byte)(int)(location.getPitch() * 256.0 / 360.0);
|
||||
headRotationYaw.set(headRotation, yaw);
|
||||
movePacketX.set(move, location.getX());
|
||||
movePacketY.set(move, location.getY());
|
||||
movePacketZ.set(move, location.getZ());
|
||||
movePacketYaw.set(move, yaw);
|
||||
movePacketPitch.set(move, pitch);
|
||||
movePacketOnGround.set(move, true);
|
||||
namedSpawnX.set(namedSpawn, location.getX());
|
||||
namedSpawnY.set(namedSpawn, location.getY());
|
||||
namedSpawnZ.set(namedSpawn, location.getZ());
|
||||
namedSpawnYaw.set(namedSpawn, yaw);
|
||||
namedSpawnPitch.set(namedSpawn, (byte)(int)(location.getPitch() * 256.0 / 360.0));
|
||||
namedSpawnPitch.set(namedSpawn, pitch);
|
||||
}
|
||||
|
||||
headRotation = headRotationConstructor.invoke();
|
||||
headRotationEntity.set(headRotation, entityId);
|
||||
headRotationYaw.set(headRotation, yaw);
|
||||
|
||||
display = new Displayable(location, this::show, this::hide);
|
||||
private boolean isSimilarLocation(Location location) {
|
||||
if (this.location == null) {
|
||||
return false;
|
||||
}
|
||||
if (Location.normalizeYaw(this.location.getYaw()) != Location.normalizeYaw(location.getYaw())) {
|
||||
return false;
|
||||
}
|
||||
if (Location.normalizePitch(this.location.getPitch()) != Location.normalizePitch(location.getPitch())) {
|
||||
return false;
|
||||
}
|
||||
if (Math.abs(this.location.getX() - location.getX()) > 0.1) {
|
||||
return false;
|
||||
}
|
||||
if (Math.abs(this.location.getY() - location.getY()) > 0.1) {
|
||||
return false;
|
||||
}
|
||||
return !(Math.abs(this.location.getZ() - location.getZ()) > 0.1);
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
@ -116,13 +210,21 @@ public class NPC {
|
||||
TinyProtocol.instance.sendPacket(player, addPlayerInfo);
|
||||
TinyProtocol.instance.sendPacket(player, namedSpawn);
|
||||
TinyProtocol.instance.sendPacket(player, headRotation);
|
||||
TinyProtocol.instance.sendPacket(player, skinParts);
|
||||
}
|
||||
|
||||
private void hide(Player player) {
|
||||
if (Bukkit.getOnlinePlayers().stream().noneMatch(p -> p.getUniqueId().equals(uuid))) {
|
||||
TinyProtocol.instance.sendPacket(player, removePlayerInfo);
|
||||
}
|
||||
TinyProtocol.instance.sendPacket(player, destroy);
|
||||
}
|
||||
|
||||
private void move(Player player) {
|
||||
TinyProtocol.instance.sendPacket(player, headRotation);
|
||||
TinyProtocol.instance.sendPacket(player, move);
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
display.delete();
|
||||
}
|
||||
|
204
src/de/steamwar/lobby/team/TeamPlayer.java
Normale Datei
204
src/de/steamwar/lobby/team/TeamPlayer.java
Normale Datei
@ -0,0 +1,204 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2022 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.team;
|
||||
|
||||
import de.steamwar.lobby.LobbySystem;
|
||||
import de.steamwar.lobby.display.NPC;
|
||||
import de.steamwar.lobby.listener.BasicListener;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Villager;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.EntityInteractEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class TeamPlayer extends BasicListener {
|
||||
|
||||
private static final List<Cuboid> cuboids = new ArrayList<>();
|
||||
|
||||
static {
|
||||
cuboids.add(new Cuboid(1509, 52, 1464, 1510, 58, 1469));
|
||||
cuboids.add(new Cuboid(1538, 52, 1464, 1539, 58, 1469));
|
||||
cuboids.add(new Cuboid(1518, 55, 1433, 1530, 60, 1434));
|
||||
cuboids.add(new Cuboid(1587, 52, 1471, 1588, 56, 1475));
|
||||
cuboids.add(new Cuboid(1478, 52, 1461, 1479, 56, 1463));
|
||||
}
|
||||
|
||||
private static final World world = Bukkit.getWorlds().get(0);
|
||||
private static final Map<String, NPC> entities = new HashMap<>();
|
||||
|
||||
private Set<Player> players = new HashSet<>();
|
||||
YoyoNow markierte diese Unterhaltung als gelöst
Veraltet
Lixfel
hat
Bitte nicht den Weltnamen hardcoden... Bitte nicht den Weltnamen hardcoden...
YoyoNow
hat
Ist die Lobby denn immer die Ist die Lobby denn immer die `Bukkit.getWorlds().get(0)`
|
||||
|
||||
private Random random = new Random();
|
||||
private List<String> strings = new ArrayList<>();
|
||||
{
|
||||
strings.add("NPC_CHAT_1");
|
||||
strings.add("NPC_CHAT_2");
|
||||
}
|
||||
|
||||
public static void spawnTeamPlayer(World world, SteamwarUser steamwarUser) {
|
||||
Location location = new Location(world, 1524.5, 52, 1484.5);
|
||||
String name = steamwarUser.getUserName();
|
||||
NPC npc = new NPC(location, steamwarUser.getUUID(), name);
|
||||
YoyoNow markierte diese Unterhaltung als gelöst
Veraltet
Lixfel
hat
Und das wartest du bei jeder Teammitgliedänderung? bitte aus der Datenbank holen... Und das wartest du bei jeder Teammitgliedänderung? bitte aus der Datenbank holen...
YoyoNow
hat
Ja ich werde es aus der DB holen, wo soll ich den SQL quack dafür schmeißen? Ja ich werde es aus der DB holen, wo soll ich den SQL quack dafür schmeißen?
|
||||
Villager villager = (Villager) world.spawnEntity(location, EntityType.VILLAGER);
|
||||
villager.setSilent(true);
|
||||
villager.setInvulnerable(true);
|
||||
villager.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 1, false, false, false));
|
||||
villager.setCustomName(name);
|
||||
villager.setProfession(Villager.Profession.NONE);
|
||||
entities.put(name, npc);
|
||||
}
|
||||
|
||||
public static void cleanup() {
|
||||
forceLoad(world, true);
|
||||
world.getEntitiesByClasses(Villager.class).forEach(Entity::remove);
|
||||
forceLoad(world, false);
|
||||
}
|
||||
|
||||
private static void forceLoad(World world, boolean setTo) {
|
||||
for (int x = -10; x < 10; x++) {
|
||||
for (int z = -10; z < 10; z++) {
|
||||
world.setChunkForceLoaded(95 + x, 93 + z, setTo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
forceLoad(world, true);
|
||||
world.getEntitiesByClasses(Villager.class).forEach(Entity::remove);
|
||||
forceLoad(world, false);
|
||||
|
||||
SteamwarUser.getServerTeam().forEach(user -> {
|
||||
spawnTeamPlayer(world, user);
|
||||
});
|
||||
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
LobbySystem.getPlugin().getLogger().log(Level.INFO, "Loaded " + entities.size() + " team players");
|
||||
Bukkit.getScheduler().runTaskTimer(LobbySystem.getPlugin(), () -> {
|
||||
count.incrementAndGet();
|
||||
if (count.get() % (20 * 60 * 60) == 0) {
|
||||
count.set(0);
|
||||
List<SteamwarUser> steamwarUsers = SteamwarUser.getServerTeam();
|
||||
AtomicInteger added = new AtomicInteger();
|
||||
steamwarUsers.forEach(user -> {
|
||||
if (!entities.containsKey(user.getUserName())) {
|
||||
spawnTeamPlayer(world, user);
|
||||
added.incrementAndGet();
|
||||
}
|
||||
});
|
||||
AtomicInteger removed = new AtomicInteger();
|
||||
List<String> names = steamwarUsers.stream().map(SteamwarUser::getUserName).collect(Collectors.toList());
|
||||
new HashSet<>(entities.keySet()).stream().filter(name -> !names.contains(name)).forEach(name -> {
|
||||
world.getEntitiesByClasses(Villager.class).forEach(entity -> {
|
||||
if (entity.getName().equals(name)) {
|
||||
entity.remove();
|
||||
}
|
||||
});
|
||||
entities.remove(name).delete();
|
||||
removed.incrementAndGet();
|
||||
});
|
||||
if (added.get() > 0 || removed.get() > 0) {
|
||||
LobbySystem.getPlugin().getLogger().log(Level.INFO, "Loaded " + added.get() + " team players, removed " + removed.get() + " team players");
|
||||
}
|
||||
}
|
||||
world.getEntitiesByClasses(Villager.class).forEach(entity -> {
|
||||
NPC npc = entities.get(entity.getName());
|
||||
if (npc != null) {
|
||||
if (illegalLocation(entity.getLocation())) {
|
||||
entity.teleport(npc.getLocation());
|
||||
return;
|
||||
}
|
||||
npc.setLocation(entity.getLocation());
|
||||
}
|
||||
});
|
||||
}, 1L, 1L);
|
||||
}
|
||||
|
||||
private boolean illegalLocation(Location location) {
|
||||
for (Cuboid cuboid : cuboids) {
|
||||
if (cuboid.contains(location)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
|
||||
if (!(event.getRightClicked() instanceof Villager)) {
|
||||
return;
|
||||
}
|
||||
if (!players.add(event.getPlayer())) {
|
||||
players.remove(event.getPlayer());
|
||||
return;
|
||||
}
|
||||
SteamwarUser user = SteamwarUser.get(event.getRightClicked().getName());
|
||||
String message = strings.get(random.nextInt(strings.size()));
|
||||
LobbySystem.getMessage().send(message, event.getPlayer(), event.getRightClicked().getName(), user.getUserGroup().getColorCode() + user.getUserGroup().name());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
players.remove(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onEntityInteract(EntityInteractEvent event) {
|
||||
if (event.getEntityType() == EntityType.VILLAGER) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onEntityDamage(EntityDamageEvent event) {
|
||||
if (event.getEntityType() == EntityType.VILLAGER) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private static class Cuboid {
|
||||
private double x1;
|
||||
private double y1;
|
||||
private double z1;
|
||||
private double x2;
|
||||
private double y2;
|
||||
private double z2;
|
||||
|
||||
public boolean contains(Location location) {
|
||||
return location.getX() >= x1 && location.getX() <= x2 && location.getY() >= y1 && location.getY() <= y2 && location.getZ() >= z1 && location.getZ() <= z2;
|
||||
}
|
||||
}
|
||||
}
|
In neuem Issue referenzieren
Einen Benutzer sperren
Du weist doch gar nicht, welche SkinParts angezeigt werden sollen, warum darum kümmern?
Ich würde einfach alle anzeigen und fertig sein, aber noch tut es eh nicht.