package tc.oc.pgm.flag.state;
import javax.annotation.Nullable;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.Effect;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Projectile;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import tc.oc.commons.bukkit.util.BlockStateUtils;
import tc.oc.commons.bukkit.util.Materials;
import tc.oc.commons.bukkit.util.NMSHacks;
import tc.oc.commons.bukkit.util.materials.Banners;
import tc.oc.pgm.PGM;
import tc.oc.pgm.events.BlockTransformEvent;
import tc.oc.pgm.flag.Flag;
import tc.oc.pgm.flag.Post;
import tc.oc.pgm.flag.event.FlagPickupEvent;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.Party;
/**
* Base class for flag states in which the banner is placed
* on the ground somewhere as a block
*/
public abstract class Uncarried extends Spawned {
protected final Location location;
protected final BlockState oldBlock;
protected final BlockState oldBase;
protected ArmorStand labelEntity;
private @Nullable MatchPlayer pickingUp;
public Uncarried(Flag flag, Post post, @Nullable Location location) {
super(flag, post);
if(location == null) location = flag.getReturnPoint(post);
this.location = new Location(location.getWorld(),
location.getBlockX() + 0.5,
location.getBlockY(),
location.getBlockZ() + 0.5,
location.getYaw(),
location.getPitch());
if(!flag.getMatch().getWorld().equals(this.location.getWorld())) {
throw new IllegalStateException("Tried to place flag in the wrong world");
}
Block block = this.location.getBlock();
if(block.getType() == Material.STANDING_BANNER) {
// Banner may already be here at match start
this.oldBlock = BlockStateUtils.cloneWithMaterial(block, Material.AIR);
} else {
this.oldBlock = block.getState();
}
this.oldBase = block.getRelative(BlockFace.DOWN).getState();
}
@Override
public Location getLocation() {
return this.location;
}
protected void placeBanner() {
if(!this.flag.canDropOn(oldBase)) {
oldBase.getBlock().setType(Material.SEA_LANTERN, false);
} else if(Materials.isWater(oldBase.getType())) {
oldBase.getBlock().setType(Material.ICE, false);
}
if(!Banners.placeStanding(this.location, this.flag.getBannerMeta())) {
PGM.get().getRootMapLogger().severe("Failed to place flag at " + location);
forceRecover();
return;
}
this.labelEntity = this.location.getWorld().spawn(this.location.clone().add(0, 0.7, 0), ArmorStand.class);
this.labelEntity.setVisible(false);
this.labelEntity.setGravity(false);
this.labelEntity.setRemoveWhenFarAway(false);
this.labelEntity.setSmall(true);
this.labelEntity.setArms(false);
this.labelEntity.setBasePlate(false);
this.labelEntity.setCustomName(this.flag.getColoredName());
this.labelEntity.setCustomNameVisible(true);
NMSHacks.enableArmorSlots(this.labelEntity, false);
}
protected void breakBanner() {
this.labelEntity.remove();
oldBase.update(true, false);
oldBlock.update(true, false);
}
@Override
public void enterState() {
super.enterState();
this.placeBanner();
this.flag.playFlareEffect();
}
@Override
public void leaveState() {
this.flag.playFlareEffect();
this.breakBanner();
super.leaveState();
}
@Override
public boolean isCarrying(MatchPlayer player) {
// This allows CarryingFlagFilter to match and cancel the pickup before it actually happens
return player == this.pickingUp || super.isCarrying(player);
}
@Override
public boolean isCarrying(Party party) {
return (this.pickingUp != null && party == this.pickingUp.getParty()) || super.isCarrying(party);
}
protected boolean pickupFlag(MatchPlayer carrier) {
try {
this.pickingUp = carrier;
FlagPickupEvent event = new FlagPickupEvent(this.flag, carrier, this.location);
this.flag.getMatch().getPluginManager().callEvent(event);
if(event.isCancelled()) return false;
}
finally {
this.pickingUp = null;
}
this.flag.playStatusSound(Flag.PICKUP_SOUND_OWN, Flag.PICKUP_SOUND);
this.flag.touch(carrier.getParticipantState());
this.flag.transition(new Carried(this.flag, this.post, carrier, this.location));
return true;
}
protected boolean inPickupRange(Location playerLoc) {
Location flagLoc = this.getLocation();
if(playerLoc.getY() < flagLoc.getY() + 2 && playerLoc.getY() >= flagLoc.getY() - 2) {
double dx = playerLoc.getX() - flagLoc.getX();
double dz = playerLoc.getZ() - flagLoc.getZ();
if(dx * dx + dz * dz <= 1) {
return true;
}
}
return false;
}
protected boolean canPickup(MatchPlayer player) {
if(this.pickingUp != null) return false; // Prevent infinite recursion
if(flag.getMatch()
.features()
.all(Flag.class)
.anyMatch(flag -> flag.isCarrying(player))) {
return false;
}
return this.flag.canPickup(player, this.post);
}
@Override
public void onEvent(PlayerMoveEvent event) {
super.onEvent(event);
MatchPlayer player = this.flag.getMatch().getPlayer(event.getPlayer());
if(player == null || !player.canInteract() || player.getBukkit().isDead()) return;
if(this.inPickupRange(player.getBukkit().getLocation()) && this.canPickup(player)) {
this.pickupFlag(player);
}
}
@Override
public void onEvent(BlockTransformEvent event) {
super.onEvent(event);
Block block = event.getOldState().getBlock();
Block flagBlock = this.location.getBlock();
if(block.equals(flagBlock) || block.equals(flagBlock.getRelative(BlockFace.UP))) {
event.setCancelled(true, new TranslatableComponent("match.flag.cannotBreak"));
} else if(block.equals(flagBlock.getRelative(BlockFace.DOWN))) {
event.setCancelled(true, new TranslatableComponent("match.flag.cannotBreakBlockUnder"));
}
}
@Override
public void onEvent(EntityDamageEvent event) {
super.onEvent(event);
if(event.getEntity() == this.labelEntity) {
event.setCancelled(true);
if(event instanceof EntityDamageByEntityEvent && ((EntityDamageByEntityEvent) event).getDamager() instanceof Projectile) {
((EntityDamageByEntityEvent) event).getDamager().remove();
}
}
}
@Override
public void tickLoaded() {
super.tickLoaded();
if(this.particleClock % 10 == 0) {
this.flag.getMatch().getWorld().playEffect(this.getLocation().clone().add(0, 1, 0),
Effect.PORTAL,
0, 0,
0, 0, 0,
1, 8, 64);
}
}
}