package jk_5.nailed.server.player;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
import jk_5.nailed.api.GameMode;
import jk_5.nailed.api.chat.BaseComponent;
import jk_5.nailed.api.chat.ClickEvent;
import jk_5.nailed.api.chat.HoverEvent;
import jk_5.nailed.api.chat.TextComponent;
import jk_5.nailed.api.chat.serialization.ComponentSerializer;
import jk_5.nailed.api.map.Map;
import jk_5.nailed.api.map.Team;
import jk_5.nailed.api.math.EulerDirection;
import jk_5.nailed.api.math.Vector3d;
import jk_5.nailed.api.math.Vector3f;
import jk_5.nailed.api.messaging.StandardMessenger;
import jk_5.nailed.api.player.Player;
import jk_5.nailed.api.plugin.PluginIdentifier;
import jk_5.nailed.api.potion.Potion;
import jk_5.nailed.api.potion.PotionEffect;
import jk_5.nailed.api.scoreboard.ScoreboardManager;
import jk_5.nailed.api.util.Checks;
import jk_5.nailed.api.util.Location;
import jk_5.nailed.api.util.TeleportOptions;
import jk_5.nailed.api.util.TitleMessage;
import jk_5.nailed.api.world.World;
import jk_5.nailed.server.NailedEventFactory;
import jk_5.nailed.server.NailedPlatform;
import jk_5.nailed.server.scoreboard.PlayerScoreboardManager;
import jk_5.nailed.server.teleport.Teleporter;
import jk_5.nailed.server.world.NailedWorld;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.NetHandlerPlayServer;
import net.minecraft.network.Packet;
import net.minecraft.network.play.server.S02PacketChat;
import net.minecraft.network.play.server.S3FPacketCustomPayload;
import net.minecraft.network.play.server.S45PacketTitle;
import net.minecraft.util.DamageSource;
import net.minecraft.util.IChatComponent;
import net.minecraft.world.WorldSettings;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class NailedPlayer implements Player {
private static final Logger logger = LogManager.getLogger();
private final UUID uuid;
private final String name;
private final PlayerScoreboardManager scoreboardManager;
private final Set<String> channels = new HashSet<String>();
public EntityPlayerMP entity;
private String displayName;
public NetHandlerPlayServer netHandler;
private boolean isAllowedToFly;
public boolean isOnline = false;
private BaseComponent[] subtitle;
public World world;
public Map map;
public NailedPlayer(UUID uuid, String name) {
this.uuid = uuid;
this.name = name;
this.displayName = name;
this.scoreboardManager = new PlayerScoreboardManager(this);
}
@Override
public ScoreboardManager getScoreboardManager() {
return this.scoreboardManager;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getDisplayName() {
return this.displayName;
}
@Override
public UUID getUniqueId() {
return this.uuid;
}
@Override
public void sendMessage(BaseComponent... component) {
if(this.netHandler != null){
this.netHandler.sendPacket(new S02PacketChat(component));
}
}
@Override
public void heal(double amount) {
double newAmount = this.getHealth() + amount;
this.setHealth(Math.min(this.getMaxHealth(), newAmount));
}
@Override
public double getMaxHealth() {
return this.entity.getMaxHealth(); //TODO: this does not return the adjusted value (see setMaxHealth)
}
@Override
public void setMaxHealth(double maxHealth) {
Checks.positive(maxHealth, "Max health must be greater than 0");
entity.getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(maxHealth);
if(this.getHealth() > this.getMaxHealth()){
setHealth(maxHealth);
}
}
@Override
public void resetMaxHealth() {
this.setMaxHealth(this.entity.getMaxHealth());
}
@Override
public boolean isBurning() {
return this.entity.fire != 0;
}
@Override
public int getBurnDuration() {
return this.entity.fire;
}
@Override
public void setBurnDuration(int ticks) {
this.entity.fire = ticks;
}
@Override
public double getExperience() {
return this.entity.experience;
}
@Override
public int getLevel() {
return this.entity.experienceLevel;
}
@Override
public void setExperience(double experience) {
this.entity.experience = (float) experience;
}
@Override
public void setLevel(int level) {
this.entity.experienceLevel = level;
}
@Override
public void damage(double amount) {
this.setHealth(this.getHealth() - amount);
}
@Override
public double getHealth() {
return Math.min(Math.max(0, entity.getHealth()), this.getMaxHealth());
}
@Override
public void setHealth(double health) {
Checks.positiveOrZero(health, "health");
Checks.smallerThanOrEqual(health, getMaxHealth(), "health");
if(health == 0){
entity.onDeath(DamageSource.generic);
}
entity.setHealth((float) health);
}
@Nonnull
@Override
public Vector3d getPosition() {
return this.getLocation();
}
@Override
public void setPosition(@Nonnull Vector3d position) {
throw new UnsupportedOperationException();
}
@Nonnull
@Override
public Vector3f getVectorRotation() {
throw new UnsupportedOperationException();
}
@Override
public void setVectorRotation(@Nonnull Vector3f rotation) {
throw new UnsupportedOperationException();
}
@Nonnull
@Override
public EulerDirection getRotation() {
throw new UnsupportedOperationException();
}
@Override
public void setRotation(@Nonnull EulerDirection rotation) {
throw new UnsupportedOperationException();
}
@Nonnull
@Override
public Vector3f getVelocity() {
throw new UnsupportedOperationException();
}
@Override
public void setVelocity(@Nonnull Vector3f velocity) {
throw new UnsupportedOperationException();
}
@Override
public double getSaturation() {
throw new UnsupportedOperationException();
}
@Override
public void setSaturation(double saturation) {
throw new UnsupportedOperationException();
}
@Override
public double getHunger() {
return 20 + entity.getFoodStats().getFoodLevel();
}
@Override
public void setHunger(double hunger) {
entity.getFoodStats().setFoodLevel(20 - (int) hunger);
}
@Override
public void teleportTo(World world) {
Teleporter.teleportPlayer(this, new TeleportOptions(world.getConfig() != null ? Location.builder().copy(world.getConfig().spawnPoint()).setWorld(world).build() : new Location(world, 0, 64, 0)));
}
public EntityPlayerMP getEntity(){
return this.entity;
}
@Nullable
@Override
public Map getMap() {
return this.map;
}
@Override
public World getWorld() {
return this.world;
}
public Location getLocation(){
return new Location(this.world, entity.posX, entity.posY, entity.posZ, entity.rotationYaw, entity.rotationPitch);
}
public void sendPacket(Packet packet){
if(this.netHandler != null){
this.netHandler.sendPacket(packet);
}
}
@Override
public GameMode getGameMode() {
return GameMode.byId(this.entity.theItemInWorldManager.getGameType().getID());
}
@Override
public void setGameMode(GameMode gameMode) {
entity.setGameType(WorldSettings.GameType.getByID(gameMode.getId()));
}
@Override
public void setAllowedToFly(boolean allowed) {
this.isAllowedToFly = allowed;
this.entity.capabilities.allowFlying = allowed;
this.entity.capabilities.isFlying = allowed;
this.entity.sendPlayerAbilities();
}
/*override def getInventorySize: Int = this.getEntity.inventory.getSizeInventory
override def getInventorySlotContent(slot: Int): ItemStack = ItemStackConverter.toNailed(this.getEntity.inventory.getStackInSlot(slot)) //TODO: maybe save inventories in our system instead the vanilla one
override def setInventorySlot(slot: Int, stack: ItemStack){
this.getEntity.inventory.setInventorySlotContents(slot, ItemStackConverter.toVanilla(stack))
this.getEntity.updateCraftingInventory(this.getEntity.inventoryContainer, this.getEntity.inventoryContainer.getInventory)
}
override def addToInventory(stack: ItemStack){
this.getEntity.inventory.addItemStackToInventory(ItemStackConverter.toVanilla(stack))
this.getEntity.updateCraftingInventory(this.getEntity.inventoryContainer, this.getEntity.inventoryContainer.getInventory)
}
override def iterateInventory(p: ItemStack => Unit){
for(i <- 0 until this.getInventorySize) p(getInventorySlotContent(i))
}*/
@Override
public void kick(@Nonnull String reason) {
Checks.notNull(reason, "Reason may not be null");
if(this.netHandler != null){
this.netHandler.kickPlayerFromServer(reason);
}
}
@Override
public BaseComponent getDescriptionComponent() {
TextComponent comp = new TextComponent(this.getDisplayName());
comp.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TextComponent(this.getUniqueId().toString())));
comp.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/msg " + this.getName() + " "));
return comp;
}
@Override
public void clearAllEffects() {
this.entity.clearActivePotions();
}
@Override
public void addPotionEffect(@Nonnull PotionEffect effect) {
this.entity.addPotionEffect(new net.minecraft.potion.PotionEffect(effect.getPotion().getId(), effect.getDuration(), effect.getLevel() - 1, effect.isAmbient(), effect.isShowParticles()));
}
@Override
public void removePotionEffect(@Nonnull Potion potion) {
this.entity.removePotionEffect(potion.getId());
}
@Nonnull
@Override
public Collection<PotionEffect> getActiveEffects() {
ImmutableSet.Builder<PotionEffect> newCollection = ImmutableSet.builder();
for (net.minecraft.potion.PotionEffect effect : ((Collection<net.minecraft.potion.PotionEffect>) this.entity.getActivePotionEffects())) {
PotionEffect.Builder builder = PotionEffect.builder();
Potion pot = Potion.byId(effect.getPotionID());
if(pot != null){
builder.setPotion(pot);
builder.setAmbient(effect.getIsAmbient());
builder.setShowParticles(effect.getIsShowParticles());
builder.setLevel(effect.getAmplifier() + 1);
builder.setDuration(effect.getDuration());
newCollection.add(builder.build());
}
}
return newCollection.build();
}
@Override
public void loadResourcePack(String url, String hash) {
this.entity.loadResourcePack(url, hash);
}
@Override
public void sendPluginMessage(PluginIdentifier source, String channel, byte[] message) {
StandardMessenger.validatePluginMessage(NailedPlatform.instance().getMessenger(), source, channel, message);
if(netHandler == null){
return;
}
if(channels.contains(channel)){
this.sendPacket(new S3FPacketCustomPayload(channel, Unpooled.copiedBuffer(message)));
}
}
public void sendSupportedChannels(){
if(netHandler == null){
return;
}
Set<String> listening = NailedPlatform.instance().getMessenger().getIncomingChannels();
if(!listening.isEmpty()){
ByteArrayOutputStream stream = new ByteArrayOutputStream();
for (String channel : listening) {
try{
stream.write(channel.getBytes(CharsetUtil.UTF_8));
stream.write((byte) 0);
}catch(IOException e){
logger.error("Could not send Plugin Channel REGISTER to " + getName(), e);
}
}
sendPacket(new S3FPacketCustomPayload("REGISTER", Unpooled.copiedBuffer(stream.toByteArray())));
}
}
public void addChannel(String channel){
if(channels.add(channel)){
NailedEventFactory.firePlayerRegisterChannelEvent(this, channel);
}
}
public void removeChannel(String channel){
if(channels.remove(channel)){
NailedEventFactory.firePlayerUnregisterChannelEvent(this, channel);
}
}
@Override
public Set<String> getListeningPluginChannels(){
return ImmutableSet.copyOf(channels);
}
@Override
public void displayTitle(@Nonnull TitleMessage title) {
IChatComponent main = (title.getTitle() != null && title.getTitle().length != 0) ? IChatComponent.Serializer.jsonToComponent(ComponentSerializer.toString(title.getTitle())) : null;
IChatComponent sub = (title.getSubtitle() != null && title.getSubtitle().length != 0) ? IChatComponent.Serializer.jsonToComponent(ComponentSerializer.toString(title.getSubtitle())) : null;
sendPacket(new S45PacketTitle(title.getFadeInTime(), title.getDisplayTime(), title.getFadeOutTime()));
if(main != null){
this.sendPacket(new S45PacketTitle(S45PacketTitle.Type.TITLE, main));
}
if(sub != null){
this.sendPacket(new S45PacketTitle(S45PacketTitle.Type.SUBTITLE, sub));
}
}
@Override
public void clearTitle() {
this.sendPacket(new S45PacketTitle(S45PacketTitle.Type.CLEAR, null));
}
@Override
public void displaySubtitle(BaseComponent... message) {
this.sendPacket(new S02PacketChat((byte) 2, message));
}
@Override
public void setSubtitle(BaseComponent... message) {
this.displaySubtitle(message);
this.subtitle = message;
}
@Override
public void clearSubtitle() {
this.subtitle = null;
}
@Override
public void clearInventory() {
entity.inventory.clear();
//was sendContainerAndContentsToPlayer
entity.updateCraftingInventory(entity.inventoryContainer, entity.inventoryContainer.getInventory());
}
@Override
public boolean isOnline() {
return this.isOnline;
}
public Location getSpawnPoint(){
Team team = this.map.getPlayerTeam(this); //TODO: this line throws an NPE when the player logs in for the second time (over EntityPlayerMP)
if(team == null){
return ((NailedWorld) world).getWrapped().provider.getSpawnPoint();
}else{
Location s = team.getSpawnPoint();
if(s == null){
return ((NailedWorld) world).getWrapped().provider.getSpawnPoint();
}else{
return s;
}
}
}
public BaseComponent[] getSubtitle() {
return subtitle;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("uuid", uuid)
.add("name", name)
.add("isOnline", isOnline)
.add("gameMode", this.getGameMode())
.add("eid", this.entity == null ? -1 : this.entity.getEntityId())
.toString();
}
}