/******************************************************************************* * Copyright 2014 Tobias Welther * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package de.tobiyas.racesandclasses.traitcontainer.traits.pattern; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.event.block.BlockBreakEvent; import de.tobiyas.racesandclasses.eventprocessing.eventresolvage.EventWrapper; import de.tobiyas.racesandclasses.playermanagement.player.RaCPlayer; import de.tobiyas.racesandclasses.traitcontainer.interfaces.TraitResults; import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.configuration.TraitConfigurationField; import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.configuration.TraitConfigurationNeeded; import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.configuration.TraitEventsUsed; import de.tobiyas.racesandclasses.traitcontainer.traits.magic.AbstractMagicSpellTrait; import de.tobiyas.racesandclasses.util.friend.EnemyChecker; import de.tobiyas.racesandclasses.util.friend.TargetType; import de.tobiyas.racesandclasses.util.traitutil.TraitConfiguration; import de.tobiyas.racesandclasses.util.traitutil.TraitConfigurationFailedException; public abstract class AbstractTotemTrait extends AbstractMagicSpellTrait{ /** * The Duration the totem lasts. */ protected int duration = 5; /** * The Range of the Totem. */ protected int range = 10; /** * The Target of this totem. */ protected TargetType target = TargetType.ALL; @TraitEventsUsed(bypassClasses = {BlockBreakEvent.class}) @Override public void generalInit() { } /** * Removes the totem if block of totem is destroyed. */ @Override protected TraitResults otherEventTriggered(EventWrapper eventWrapper, TraitResults result){ result = TraitResults.False(); if(eventWrapper.getEvent() instanceof BlockBreakEvent){ BlockBreakEvent bEvent = (BlockBreakEvent) eventWrapper.getEvent(); Block block = bEvent.getBlock(); for(TotemInfos totem : activedTotems.values()) { if(block.equals(totem.topLocation.getBlock())){ //is part of a totem. removePlacedTotem(totem); bEvent.setCancelled(true); return result; } if(block.equals(totem.bottomLocation.getBlock())){ //is part of a totem. removePlacedTotem(totem); bEvent.setCancelled(true); return result; } } } return result; } /** * The ticks every . */ protected int tickEvery = 1; /** * The Material for the Top block */ protected Material topMaterial = Material.SKULL; /** * The Material for the bottom block */ protected Material bottomMaterial = Material.FENCE; /** * The Map of currently active Totems. * <br>We assume a player can only have a totem once at a Time. */ protected final Map<RaCPlayer, TotemInfos> activedTotems = new HashMap<RaCPlayer, TotemInfos>(); @Override protected void magicSpellTriggered(RaCPlayer player, TraitResults result) { Location location = getLocationNear(player); if(location == null){ //no free space to place totem. result.setTriggered(false); return; } result.copyFrom(placeTotem(player, location)); } /** * A random to use. */ private final Random rand = new Random(); /** * Returns a location near the player. * * @param player to search for * * @return a location near the player. */ private Location getLocationNear(RaCPlayer player) { Location base = player.getLocation().clone(); List<Location> free = new LinkedList<Location>(); for(int x = -2; x <= 2; x ++){ for(int z = -2; z <= 2; z++){ Location toCheck = base.clone().add(x, 0, z); if(isFree(toCheck.clone())) free.add(toCheck); } } //do some randomness among the totem. return free.isEmpty() ? null : free.get(rand.nextInt(free.size())); } /** * Checks if the Location is free to place something on. * <br>This also involves the location 1 block above. * * @param location to check * * @return true if is free. */ private boolean isFree(Location location){ //check if bottom block is blocked. if(location.getBlock().getType() != Material.AIR) return false; //check if upper block is blocked. if(location.add(0, 1, 0).getBlock().getType() != Material.AIR) return false; return true; } @TraitConfigurationNeeded( fields = { @TraitConfigurationField(fieldName = "duration", classToExpect = Integer.class, optional = true), @TraitConfigurationField(fieldName = "bottomBlock", classToExpect = Material.class, optional = true), @TraitConfigurationField(fieldName = "upperBlock", classToExpect = Material.class, optional = true), @TraitConfigurationField(fieldName = "range", classToExpect = Integer.class, optional = true), @TraitConfigurationField(fieldName = "tickEvery", classToExpect = Integer.class, optional = true), @TraitConfigurationField(fieldName = "target", classToExpect = String.class, optional = true) } ) @Override public void setConfiguration(TraitConfiguration configMap) throws TraitConfigurationFailedException { super.setConfiguration(configMap); if(configMap.containsKey("duration")){ duration = (Integer) configMap.get("duration"); } if(configMap.containsKey("bottomBlock")){ bottomMaterial = (Material) configMap.get("bootomBlock"); } if(configMap.containsKey("upperBlock")){ topMaterial = (Material) configMap.get("upperBlock"); } if(configMap.containsKey("tickEvery")){ tickEvery = (Integer) configMap.get("tickEvery"); } if(configMap.containsKey("range")){ range = (Integer) configMap.get("range"); } if(configMap.containsKey("target")){ String target = configMap.getAsString("target").toLowerCase(); if(target.startsWith("all")) this.target = TargetType.ALL; if(target.startsWith("fr") || target.startsWith("ally")) this.target = TargetType.FRIEND; if(target.startsWith("e") || target.startsWith("fe")) this.target = TargetType.ENEMY; } } /** * Places to the player. * * @param player the player to place to * @param location the location to place at */ protected TraitResults placeTotem(RaCPlayer player, Location location){ Location bottomLocation = location.clone(); Location topLocation = location.clone().add(0, 1, 0); TotemInfos infos = new TotemInfos(); infos.bottomLocation = bottomLocation.clone(); infos.topLocation = topLocation.clone(); infos.owner = player; bottomLocation.getBlock().setType(bottomMaterial); topLocation.getBlock().setType(topMaterial); activedTotems.put(player, infos); scheduleRemove(infos); return TraitResults.True(); } /** * Schedules the romve of a totem. * * @param player to remove */ protected void scheduleRemove(final TotemInfos infos){ final int bukkitSchedulerID = Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new Runnable() { @Override public void run() { totemTick(infos); } }, 0, tickEvery * 20); infos.bukkitSchedulerID = bukkitSchedulerID; Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { @Override public void run() { removePlacedTotem(infos); } }, 20 * duration); } /** * The Totem ticks. * <br>This method CAN be overriden. * <br>If it is overriden, {@link #tickOnPlayer(TotemInfos, Player)} is NOT called any more. * * @param infos to the totem. */ protected void totemTick(TotemInfos infos){ Location loc = infos.topLocation; List<Player> near = new LinkedList<Player>(); int squaredRange = range * range; for(Player player : loc.getWorld().getPlayers()){ if(player.getLocation().distanceSquared(loc) <= squaredRange){ near.add(player); } } //tick on all players near. for(Player player : near){ if(target == TargetType.ENEMY){ if(!EnemyChecker.areEnemies(infos.getOwner().getPlayer(), player)) continue; } if(target == TargetType.FRIEND){ if(!EnemyChecker.areAllies(infos.getOwner().getPlayer(), player)) continue; } tickOnPlayer(infos, player); } //now to the Monster ticks. List<LivingEntity> entities = new LinkedList<LivingEntity>(); for(Entity entity : loc.getWorld().getEntities()){ if(!(entity instanceof LivingEntity)) continue; if(entity.getLocation().distanceSquared(loc) <= squaredRange){ entities.add((LivingEntity) entity); } } for(LivingEntity entity : entities){ if(target == TargetType.ENEMY){ if(!EnemyChecker.areEnemies(infos.getOwner().getPlayer(), entity)) continue; } if(target == TargetType.FRIEND){ if(!EnemyChecker.areAllies(infos.getOwner().getPlayer(), entity)) continue; } tickOnNonPlayer(infos, entity); } } /** * The totem ticks on the player passed. * <br>This method is ONLY fired, if {@link #totemTick(TotemInfos)} is NOT Overriden! * * @param infos of the totem. * @param player to tick on. */ protected abstract void tickOnPlayer(TotemInfos infos, Player player); /** * The Totem ticks on a NO player target. * * @param infos of the totem * @param entity to tick on. */ protected abstract void tickOnNonPlayer(TotemInfos infos, LivingEntity entity); /** * Removes a placed Totem. * <br>A totem is defined as a * * @param location to remove from. */ protected void removePlacedTotem(TotemInfos infos){ Location bottomLocation = infos.bottomLocation; Location topLocation = infos.topLocation; if(bottomLocation == null || topLocation == null) return; bottomLocation.getBlock().setType(Material.AIR); topLocation.getBlock().setType(Material.AIR); activedTotems.remove(infos.owner); Bukkit.getScheduler().cancelTask(infos.bukkitSchedulerID); } /** * checks if the location is a totem. * * @param location to check * * @return true if it is */ protected boolean isTotem(Location location){ Block block = location.getBlock(); for(TotemInfos info : activedTotems.values()){ Location top = info.topLocation; Location bottom = info.bottomLocation; if(top.getBlock() == block || bottom.getBlock() == block) return true; } return false; } protected static class TotemInfos{ protected Location bottomLocation; protected Location topLocation; protected int bukkitSchedulerID; protected RaCPlayer owner; public Location getBottomLocation() { return bottomLocation.clone(); } public Location getTopLocation() { return topLocation.clone(); } public int getBukkitSchedulerID() { return bukkitSchedulerID; } public RaCPlayer getOwner() { return owner; } } }