Search in sources :

Example 21 with UndoList

use of com.elmakers.mine.bukkit.api.block.UndoList in project MagicPlugin by elBukkit.

the class UndoSpell method onCast.

@Override
public SpellResult onCast(ConfigurationSection parameters) {
    Target target = getTarget();
    int timeout = parameters.getInt("target_timeout", 0);
    boolean targetSelf = parameters.getBoolean("target_up_self", false);
    boolean targetDown = parameters.getBoolean("target_down_block", false);
    Entity targetEntity = target.getEntity();
    SpellResult result = SpellResult.CAST;
    if (targetSelf && isLookingUp()) {
        targetEntity = mage.getEntity();
        getCurrentCast().setTargetName(mage.getName());
        result = SpellResult.ALTERNATE_UP;
    }
    if (targetEntity != null && controller.isMage(targetEntity)) {
        Mage targetMage = controller.getMage(targetEntity);
        Batch batch = targetMage.cancelPending();
        if (batch != null) {
            undoListName = (batch instanceof SpellBatch) ? ((SpellBatch) batch).getSpell().getName() : null;
            return SpellResult.ALTERNATE;
        }
        UndoQueue queue = targetMage.getUndoQueue();
        UndoList undoList = queue.undoRecent(timeout);
        if (undoList != null) {
            undoListName = undoList.getName();
        }
        return undoList != null ? result : SpellResult.NO_TARGET;
    }
    if (!parameters.getBoolean("target_blocks", true)) {
        return SpellResult.NO_TARGET;
    }
    Block targetBlock = target.getBlock();
    if (targetDown && isLookingDown()) {
        targetBlock = getLocation().getBlock();
    }
    if (targetBlock != null) {
        boolean targetAll = mage.isSuperPowered();
        if (targetAll) {
            UndoList undid = controller.undoRecent(targetBlock, timeout);
            if (undid != null) {
                Mage targetMage = undid.getOwner();
                undoListName = undid.getName();
                getCurrentCast().setTargetName(targetMage.getName());
                return result;
            }
        } else {
            getCurrentCast().setTargetName(mage.getName());
            UndoList undoList = mage.undo(targetBlock);
            if (undoList != null) {
                undoListName = undoList.getName();
                return result;
            }
        }
    }
    return SpellResult.NO_TARGET;
}
Also used : Entity(org.bukkit.entity.Entity) SpellBatch(com.elmakers.mine.bukkit.api.batch.SpellBatch) Target(com.elmakers.mine.bukkit.utility.Target) UndoQueue(com.elmakers.mine.bukkit.api.block.UndoQueue) UndoList(com.elmakers.mine.bukkit.api.block.UndoList) Batch(com.elmakers.mine.bukkit.api.batch.Batch) SpellBatch(com.elmakers.mine.bukkit.api.batch.SpellBatch) Mage(com.elmakers.mine.bukkit.api.magic.Mage) Block(org.bukkit.block.Block) SpellResult(com.elmakers.mine.bukkit.api.spell.SpellResult)

Example 22 with UndoList

use of com.elmakers.mine.bukkit.api.block.UndoList in project MagicPlugin by elBukkit.

the class ShrinkEntityAction method perform.

@Override
public SpellResult perform(CastContext context) {
    Entity targetEntity = context.getTargetEntity();
    MageController controller = context.getController();
    if (controller.isElemental(targetEntity)) {
        double elementalSize = controller.getElementalScale(targetEntity);
        if (elementalSize < 0.1) {
            return super.perform(context);
        }
        elementalSize /= 2;
        controller.setElementalScale(targetEntity, elementalSize);
        return SpellResult.CAST;
    }
    if (!(targetEntity instanceof LivingEntity))
        return SpellResult.NO_TARGET;
    LivingEntity li = (LivingEntity) targetEntity;
    boolean alreadyDead = li.isDead() || li.getHealth() <= 0;
    String ownerName = null;
    String itemName = null;
    byte data = 3;
    if (li instanceof Player) {
        ownerName = ((Player) li).getName();
    } else {
        itemName = DeprecatedUtils.getName(li.getType()) + " Head";
        switch(li.getType()) {
            case CREEPER:
                data = 4;
                break;
            case ZOMBIE:
                data = 2;
                break;
            case SKELETON:
                Skeleton skeleton = (Skeleton) li;
                data = (byte) (skeleton.getSkeletonType() == SkeletonType.NORMAL ? 0 : 1);
                break;
            default:
                ownerName = controller.getMobSkin(li.getType());
        }
    }
    if (itemName == null && ownerName != null) {
        itemName = ownerName + "'s Head";
    }
    Location targetLocation = targetEntity.getLocation();
    if (li instanceof Player) {
        super.perform(context);
        if (li.isDead() && !alreadyDead) {
            dropPlayerHead(targetEntity.getLocation(), (Player) li, itemName);
        }
    } else if (li.getType() == EntityType.GIANT) {
        UndoList spawnedList = com.elmakers.mine.bukkit.block.UndoList.getUndoList(li);
        context.registerModified(li);
        li.remove();
        Entity zombie = targetLocation.getWorld().spawnEntity(targetLocation, EntityType.ZOMBIE);
        if (zombie instanceof Zombie) {
            ((Zombie) zombie).setBaby(false);
        }
        context.registerForUndo(zombie);
        if (spawnedList != null) {
            spawnedList.add(zombie);
        }
    } else if (li instanceof Ageable && ((Ageable) li).isAdult() && !(li instanceof Player)) {
        context.registerModified(li);
        ((Ageable) li).setBaby();
    } else if (li instanceof Zombie && !((Zombie) li).isBaby()) {
        context.registerModified(li);
        ((Zombie) li).setBaby(true);
    } else if (li instanceof PigZombie && !((PigZombie) li).isBaby()) {
        context.registerModified(li);
        ((PigZombie) li).setBaby(true);
    } else if (li instanceof Slime && ((Slime) li).getSize() > 1) {
        context.registerModified(li);
        Slime slime = (Slime) li;
        slime.setSize(slime.getSize() - 1);
    } else if (li instanceof Skeleton && skeletons && ((Skeleton) li).getSkeletonType() == SkeletonType.WITHER) {
        context.registerModified(li);
        Skeleton skeleton = (Skeleton) li;
        skeleton.setSkeletonType(SkeletonType.NORMAL);
    } else {
        super.perform(context);
        if ((ownerName != null || data != 3) && (li.isDead() || li.getHealth() == 0) && !alreadyDead) {
            dropHead(targetEntity.getLocation(), ownerName, itemName, data);
        }
    }
    return SpellResult.CAST;
}
Also used : Entity(org.bukkit.entity.Entity) LivingEntity(org.bukkit.entity.LivingEntity) Player(org.bukkit.entity.Player) Zombie(org.bukkit.entity.Zombie) PigZombie(org.bukkit.entity.PigZombie) Ageable(org.bukkit.entity.Ageable) Slime(org.bukkit.entity.Slime) LivingEntity(org.bukkit.entity.LivingEntity) MageController(com.elmakers.mine.bukkit.api.magic.MageController) UndoList(com.elmakers.mine.bukkit.api.block.UndoList) PigZombie(org.bukkit.entity.PigZombie) Skeleton(org.bukkit.entity.Skeleton) Location(org.bukkit.Location)

Example 23 with UndoList

use of com.elmakers.mine.bukkit.api.block.UndoList in project MagicPlugin by elBukkit.

the class ModifyBlockAction method perform.

@SuppressWarnings("deprecation")
@Override
public SpellResult perform(CastContext context) {
    MaterialBrush brush = context.getBrush();
    if (brush == null) {
        return SpellResult.FAIL;
    }
    Block block = context.getTargetBlock();
    if (brush.isErase()) {
        if (!context.hasBreakPermission(block)) {
            return SpellResult.INSUFFICIENT_PERMISSION;
        }
    } else {
        if (!context.hasBuildPermission(block)) {
            return SpellResult.INSUFFICIENT_PERMISSION;
        }
    }
    if (commit) {
        if (!context.areAnyDestructible(block)) {
            return SpellResult.NO_TARGET;
        }
    } else if (!context.isDestructible(block)) {
        return SpellResult.NO_TARGET;
    }
    Material previousMaterial = block.getType();
    byte previousData = block.getData();
    Mage mage = context.getMage();
    brush.update(mage, context.getTargetSourceLocation());
    if (!brush.isDifferent(block)) {
        return SpellResult.NO_TARGET;
    }
    if (!brush.isReady()) {
        brush.prepare();
        return SpellResult.PENDING;
    }
    if (!brush.isValid()) {
        return SpellResult.FAIL;
    }
    if (consumeBlocks && !context.isConsumeFree() && !brush.isErase()) {
        UndoList undoList = context.getUndoList();
        if (undoList != null) {
            undoList.setConsumed(true);
        }
        ItemStack requires = brush.getItemStack(1);
        if (!mage.hasItem(requires, consumeVariants)) {
            String requiresMessage = context.getMessage("insufficient_resources");
            context.sendMessage(requiresMessage.replace("$cost", brush.getName()));
            return SpellResult.STOP;
        }
        mage.removeItem(requires, consumeVariants);
    }
    if (!commit) {
        context.registerForUndo(block);
        if (brush.isErase()) {
            context.clearAttachables(block);
        }
    }
    UndoList undoList = context.getUndoList();
    if (undoList != null) {
        undoList.setApplyPhysics(applyPhysics);
    }
    brush.modify(block, applyPhysics);
    boolean spawnFalling = spawnFallingBlocks && previousMaterial != Material.AIR;
    if (spawnFalling && fallingProbability < 1) {
        spawnFalling = context.getRandom().nextDouble() < fallingProbability;
    }
    if (spawnFalling) {
        Location blockLocation = block.getLocation();
        Location blockCenter = new Location(blockLocation.getWorld(), blockLocation.getX() + 0.5, blockLocation.getY() + 0.5, blockLocation.getZ() + 0.5);
        Vector fallingBlockVelocity = null;
        if (fallingBlockSpeed > 0) {
            Location source = context.getTargetCenterLocation();
            fallingBlockVelocity = blockCenter.clone().subtract(source).toVector();
            fallingBlockVelocity.normalize();
            if (fallingBlockDirection != null) {
                fallingBlockVelocity.add(fallingBlockDirection).normalize();
            }
            fallingBlockVelocity.multiply(fallingBlockSpeed);
        }
        if (fallingBlockVelocity != null && (Double.isNaN(fallingBlockVelocity.getX()) || Double.isNaN(fallingBlockVelocity.getY()) || Double.isNaN(fallingBlockVelocity.getZ()) || Double.isInfinite(fallingBlockVelocity.getX()) || Double.isInfinite(fallingBlockVelocity.getY()) || Double.isInfinite(fallingBlockVelocity.getZ()))) {
            fallingBlockVelocity = null;
        }
        boolean spawned = false;
        if (usePhysicsBlocks) {
            spawned = context.getController().spawnPhysicsBlock(blockCenter, previousMaterial, previousData, fallingBlockVelocity);
        }
        if (!spawned) {
            FallingBlock falling = block.getWorld().spawnFallingBlock(blockCenter, previousMaterial, previousData);
            falling.setDropItem(false);
            if (fallingBlockVelocity != null) {
                SafetyUtils.setVelocity(falling, fallingBlockVelocity);
            }
            if (fallingBlockMaxDamage > 0 && fallingBlockFallDamage > 0) {
                CompatibilityUtils.setFallingBlockDamage(falling, fallingBlockFallDamage, fallingBlockMaxDamage);
            } else {
                falling.setHurtEntities(fallingBlocksHurt);
            }
            context.registerForUndo(falling);
        }
    }
    if (breakable > 0) {
        context.registerBreakable(block, breakable);
    }
    if (backfireChance > 0) {
        context.registerReflective(block, backfireChance);
    }
    if (commit) {
        com.elmakers.mine.bukkit.api.block.BlockData blockData = com.elmakers.mine.bukkit.block.UndoList.register(block);
        blockData.commit();
    }
    return SpellResult.CAST;
}
Also used : Material(org.bukkit.Material) FallingBlock(org.bukkit.entity.FallingBlock) MaterialBrush(com.elmakers.mine.bukkit.api.block.MaterialBrush) UndoList(com.elmakers.mine.bukkit.api.block.UndoList) Mage(com.elmakers.mine.bukkit.api.magic.Mage) Block(org.bukkit.block.Block) FallingBlock(org.bukkit.entity.FallingBlock) ItemStack(org.bukkit.inventory.ItemStack) Vector(org.bukkit.util.Vector) Location(org.bukkit.Location)

Example 24 with UndoList

use of com.elmakers.mine.bukkit.api.block.UndoList in project MagicPlugin by elBukkit.

the class ConfigurationMageDataStore method load.

public static MageData load(MageController controller, String id, ConfigurationSection saveFile) {
    MageData data = new MageData(id);
    // Load brush data
    ConfigurationSection brushConfig = saveFile.getConfigurationSection("brush");
    if (brushConfig != null) {
        BrushData brushData = new BrushData();
        try {
            brushData.setCloneLocation(ConfigurationUtils.getLocation(brushConfig, "clone_location"));
            brushData.setCloneTarget(ConfigurationUtils.getLocation(brushConfig, "clone_target"));
            brushData.setMaterialTarget(ConfigurationUtils.getLocation(brushConfig, "material_target"));
            brushData.setSchematicName(brushConfig.getString("schematic", ""));
            brushData.setMapId((short) brushConfig.getInt("map_id", -1));
            brushData.setMaterial(ConfigurationUtils.getMaterial(brushConfig, "material", Material.AIR));
            brushData.setMaterialData((short) brushConfig.getInt("data", 0));
            brushData.setScale(brushConfig.getDouble("scale", 1));
            brushData.setFillWithAir(brushConfig.getBoolean("erase", true));
            data.setBrushData(brushData);
        } catch (Exception ex) {
            controller.getLogger().warning("Failed to load brush data: " + ex.getMessage());
            ex.printStackTrace();
        }
    }
    // Load bound wand data
    if (saveFile.contains("wands")) {
        HashMap<String, ItemStack> boundWands = new HashMap<>();
        ConfigurationSection wands = saveFile.getConfigurationSection("wands");
        Set<String> keys = wands.getKeys(false);
        for (String key : keys) {
            ItemStack boundWand = controller.deserialize(wands, key);
            if (boundWand == null) {
                controller.getLogger().warning("Error loading bound wand: " + key);
            } else {
                boundWands.put(key, boundWand);
            }
        }
        data.setBoundWands(boundWands);
    }
    // Load properties
    data.setProperties(saveFile.getConfigurationSection("properties"));
    // Load classes
    Map<String, ConfigurationSection> classProperties = new HashMap<>();
    ConfigurationSection classes = saveFile.getConfigurationSection("classes");
    if (classes != null) {
        Set<String> classKeys = classes.getKeys(false);
        for (String classKey : classKeys) {
            classProperties.put(classKey, classes.getConfigurationSection(classKey));
        }
    }
    data.setClassProperties(classProperties);
    data.setActiveClass(saveFile.getString("active_class"));
    // Load extra data
    data.setExtraData(saveFile.getConfigurationSection("data"));
    // Fall protection data
    data.setFallProtectionCount(saveFile.getLong("fall_protection_count", 0));
    data.setFallProtectionDuration(saveFile.getLong("fall_protection", 0));
    // Random data and mage properties
    data.setName(saveFile.getString("name", ""));
    data.setLastDeathLocation(ConfigurationUtils.getLocation(saveFile, "last_death_location"));
    data.setLocation(ConfigurationUtils.getLocation(saveFile, "location"));
    data.setLastCast(saveFile.getLong("last_cast", 0));
    data.setCooldownExpiration(saveFile.getLong("cooldown_expiration", 0));
    data.setDestinationWarp(saveFile.getString("destination_warp"));
    // Load undo queue
    UndoData undoData = new UndoData();
    Collection<ConfigurationSection> nodeList = ConfigurationUtils.getNodeList(saveFile, "undo");
    if (nodeList != null) {
        for (ConfigurationSection listNode : nodeList) {
            // The owner will get set by UndoQueue.load
            // This is .. kind of hacky, but allows us to use UndoList as a data
            // storage mechanism instead of making a separate DAO for it right now.
            UndoList list = new com.elmakers.mine.bukkit.block.UndoList(null);
            list.load(listNode);
            undoData.getBlockList().add(list);
        }
    }
    data.setUndoData(undoData);
    // Load spell data
    ConfigurationSection spellSection = saveFile.getConfigurationSection("spells");
    if (spellSection != null) {
        Set<String> keys = spellSection.getKeys(false);
        Map<String, SpellData> spellDataMap = new HashMap<>();
        for (String key : keys) {
            ConfigurationSection node = spellSection.getConfigurationSection(key);
            SpellData spellData = spellDataMap.get(key);
            if (spellData == null) {
                spellData = new SpellData(key);
                spellDataMap.put(key, spellData);
            }
            spellData.setCastCount(spellData.getCastCount() + node.getLong("cast_count", 0));
            spellData.setLastCast(Math.max(spellData.getLastCast(), node.getLong("last_cast", 0)));
            spellData.setLastEarn(Math.max(spellData.getLastEarn(), node.getLong("last_earn", 0)));
            spellData.setCooldownExpiration(Math.max(spellData.getCooldownExpiration(), node.getLong("cooldown_expiration", 0)));
            node.set("cast_count", null);
            node.set("last_cast", null);
            node.set("last_earn", null);
            node.set("cooldown_expiration", null);
            spellData.setExtraData(node);
        }
        data.setSpellData(spellDataMap.values());
    }
    // Load respawn inventory
    ConfigurationSection respawnData = saveFile.getConfigurationSection("respawn_inventory");
    if (respawnData != null) {
        Collection<String> keys = respawnData.getKeys(false);
        Map<Integer, ItemStack> respawnInventory = new HashMap<>();
        for (String key : keys) {
            try {
                int index = Integer.parseInt(key);
                ItemStack item = controller.deserialize(respawnData, key);
                respawnInventory.put(index, item);
            } catch (Exception ex) {
                controller.getLogger().log(Level.WARNING, "Error loading respawn inventory for " + id, ex);
            }
        }
        data.setRespawnInventory(respawnInventory);
    }
    // Load respawn armor
    ConfigurationSection respawnArmorData = saveFile.getConfigurationSection("respawn_armor");
    if (respawnArmorData != null) {
        Collection<String> keys = respawnArmorData.getKeys(false);
        Map<Integer, ItemStack> respawnArmor = new HashMap<>();
        for (String key : keys) {
            try {
                int index = Integer.parseInt(key);
                ItemStack item = controller.deserialize(respawnArmorData, key);
                respawnArmor.put(index, item);
            } catch (Exception ex) {
                controller.getLogger().log(Level.WARNING, "Error loading respawn armor inventory for " + id, ex);
            }
        }
        data.setRespawnArmor(respawnArmor);
    }
    // Load brush data
    if (saveFile.contains("brush")) {
        try {
            ConfigurationSection node = saveFile.getConfigurationSection("brush");
            BrushData brushData = new BrushData();
            brushData.setCloneLocation(ConfigurationUtils.getLocation(node, "clone_location"));
            brushData.setCloneTarget(ConfigurationUtils.getLocation(node, "clone_target"));
            brushData.setMaterialTarget(ConfigurationUtils.getLocation(node, "material_target"));
            brushData.setSchematicName(node.getString("schematic"));
            brushData.setMapId((short) node.getInt("map_id"));
            brushData.setMaterial(ConfigurationUtils.getMaterial(node, "material"));
            brushData.setMaterialData((short) node.getInt("data"));
            brushData.setScale(node.getDouble("scale"));
            brushData.setFillWithAir(node.getBoolean("erase"));
            data.setBrushData(brushData);
        } catch (Exception ex) {
            ex.printStackTrace();
            controller.getLogger().warning("Failed to load brush data: " + ex.getMessage());
        }
    }
    // Load stored inventory
    if (saveFile.contains("inventory")) {
        @SuppressWarnings("unchecked") List<ItemStack> inventory = (List<ItemStack>) saveFile.getList("inventory");
        data.setStoredInventory(inventory);
    }
    if (saveFile.contains("experience")) {
        data.setStoredExperience((float) saveFile.getDouble("experience"));
    }
    if (saveFile.contains("level")) {
        data.setStoredLevel(saveFile.getInt("level"));
    }
    data.setOpenWand(saveFile.getBoolean("open_wand", false));
    data.setGaveWelcomeWand(saveFile.getBoolean("gave_welcome_wand", false));
    return data;
}
Also used : HashMap(java.util.HashMap) UndoList(com.elmakers.mine.bukkit.api.block.UndoList) SpellData(com.elmakers.mine.bukkit.api.data.SpellData) MageData(com.elmakers.mine.bukkit.api.data.MageData) UndoList(com.elmakers.mine.bukkit.api.block.UndoList) ArrayList(java.util.ArrayList) List(java.util.List) ItemStack(org.bukkit.inventory.ItemStack) UndoData(com.elmakers.mine.bukkit.api.data.UndoData) BrushData(com.elmakers.mine.bukkit.api.data.BrushData) ConfigurationSection(org.bukkit.configuration.ConfigurationSection)

Example 25 with UndoList

use of com.elmakers.mine.bukkit.api.block.UndoList in project MagicPlugin by elBukkit.

the class ThrowBlockAction method start.

@Override
public SpellResult start(CastContext context) {
    Location location = context.getLocation();
    if (!context.hasBuildPermission(location.getBlock())) {
        return SpellResult.INSUFFICIENT_PERMISSION;
    }
    location.setY(location.getY() - 1);
    MaterialBrush buildWith = context.getBrush();
    buildWith.setTarget(location);
    if (buildWith.isErase() || buildWith.getMaterial() == Material.AIR) {
        return SpellResult.NO_TARGET;
    }
    if (consumeBlocks && !context.isConsumeFree()) {
        Mage mage = context.getMage();
        UndoList undoList = context.getUndoList();
        if (undoList != null) {
            undoList.setConsumed(true);
        }
        ItemStack requires = buildWith.getItemStack(1);
        if (!mage.hasItem(requires, consumeVariants)) {
            String requiresMessage = context.getMessage("insufficient_resources");
            context.sendMessage(requiresMessage.replace("$cost", buildWith.getName()));
            return SpellResult.STOP;
        }
        mage.removeItem(requires, consumeVariants);
    }
    Material material = buildWith.getMaterial();
    byte data = buildWith.getBlockData();
    location = sourceLocation.getLocation(context);
    Vector direction = location.getDirection();
    double speed = context.getRandom().nextDouble() * (speedMax - speedMin) + speedMin;
    direction.normalize().multiply(speed);
    Vector up = new Vector(0, 1, 0);
    Vector perp = new Vector();
    perp.copy(direction);
    perp.crossProduct(up);
    FallingBlock falling = DeprecatedUtils.spawnFallingBlock(location, material, data);
    if (falling == null) {
        return SpellResult.FAIL;
    }
    track(context, falling);
    if (!consumeBlocks) {
        falling.setDropItem(false);
    }
    SafetyUtils.setVelocity(falling, direction);
    if (maxDamage > 0 && fallDamage > 0) {
        CompatibilityUtils.setFallingBlockDamage(falling, fallDamage, maxDamage);
    } else {
        falling.setHurtEntities(hurts);
    }
    return checkTracking(context);
}
Also used : FallingBlock(org.bukkit.entity.FallingBlock) MaterialBrush(com.elmakers.mine.bukkit.api.block.MaterialBrush) UndoList(com.elmakers.mine.bukkit.api.block.UndoList) Mage(com.elmakers.mine.bukkit.api.magic.Mage) Material(org.bukkit.Material) ItemStack(org.bukkit.inventory.ItemStack) Vector(org.bukkit.util.Vector) Location(org.bukkit.Location) SourceLocation(com.elmakers.mine.bukkit.magic.SourceLocation)

Aggregations

UndoList (com.elmakers.mine.bukkit.api.block.UndoList)36 EventHandler (org.bukkit.event.EventHandler)15 Entity (org.bukkit.entity.Entity)14 Block (org.bukkit.block.Block)13 Location (org.bukkit.Location)10 FallingBlock (org.bukkit.entity.FallingBlock)10 Mage (com.elmakers.mine.bukkit.api.magic.Mage)8 LivingEntity (org.bukkit.entity.LivingEntity)6 Player (org.bukkit.entity.Player)6 ItemStack (org.bukkit.inventory.ItemStack)6 Ageable (org.bukkit.entity.Ageable)4 PigZombie (org.bukkit.entity.PigZombie)4 Slime (org.bukkit.entity.Slime)4 Zombie (org.bukkit.entity.Zombie)4 MageController (com.elmakers.mine.bukkit.api.magic.MageController)3 SpellResult (com.elmakers.mine.bukkit.api.spell.SpellResult)3 Target (com.elmakers.mine.bukkit.utility.Target)3 Nullable (javax.annotation.Nullable)3 BlockFace (org.bukkit.block.BlockFace)3 Skeleton (org.bukkit.entity.Skeleton)3