diff --git a/src/main/java/in/twizmwaz/cardinal/module/ModuleFactory.java b/src/main/java/in/twizmwaz/cardinal/module/ModuleFactory.java index 926ea9ff3..fd19d67a6 100644 --- a/src/main/java/in/twizmwaz/cardinal/module/ModuleFactory.java +++ b/src/main/java/in/twizmwaz/cardinal/module/ModuleFactory.java @@ -57,6 +57,7 @@ import in.twizmwaz.cardinal.module.modules.rage.RageBuilder; import in.twizmwaz.cardinal.module.modules.rank.RankModuleBuilder; import in.twizmwaz.cardinal.module.modules.regions.RegionModuleBuilder; +import in.twizmwaz.cardinal.module.modules.renewables.RenewablesBuilder; import in.twizmwaz.cardinal.module.modules.respawn.RespawnModuleBuilder; import in.twizmwaz.cardinal.module.modules.score.ScoreModuleBuilder; import in.twizmwaz.cardinal.module.modules.scoreboard.ScoreboardModuleBuilder; @@ -185,7 +186,8 @@ private void addBuilders() { PostBuilder.class, FlagBuilder.class, NetBuilder.class, - StatsBuilder.class + StatsBuilder.class, + RenewablesBuilder.class )); } diff --git a/src/main/java/in/twizmwaz/cardinal/module/modules/filter/type/BlockFilter.java b/src/main/java/in/twizmwaz/cardinal/module/modules/filter/type/BlockFilter.java index ef7479b91..1938a958a 100644 --- a/src/main/java/in/twizmwaz/cardinal/module/modules/filter/type/BlockFilter.java +++ b/src/main/java/in/twizmwaz/cardinal/module/modules/filter/type/BlockFilter.java @@ -5,6 +5,7 @@ import in.twizmwaz.cardinal.module.modules.filter.parsers.BlockFilterParser; import org.bukkit.Material; import org.bukkit.block.Block; +import org.bukkit.material.MaterialData; import static in.twizmwaz.cardinal.module.modules.filter.FilterState.ABSTAIN; import static in.twizmwaz.cardinal.module.modules.filter.FilterState.ALLOW; @@ -23,7 +24,6 @@ public BlockFilter(final BlockFilterParser parser) { @Override public FilterState evaluate(final Object... objects) { - for (Object object : objects) { if (object instanceof Block) { if (((Block) object).getType().equals(material) && (damageValue == -1 || (int) ((Block) object).getState().getData().getData() == damageValue)) @@ -33,6 +33,10 @@ public FilterState evaluate(final Object... objects) { if ((object).equals(material)) return ALLOW; else return DENY; + } else if (object instanceof MaterialData) { + if (((MaterialData) object).getItemType().equals(material) && (damageValue == -1 || (int) ((MaterialData) object).getData() == damageValue)) + return ALLOW; + else return DENY; } } return (getParent() == null ? ABSTAIN : getParent().evaluate(objects)); diff --git a/src/main/java/in/twizmwaz/cardinal/module/modules/renewables/Renewable.java b/src/main/java/in/twizmwaz/cardinal/module/modules/renewables/Renewable.java new file mode 100644 index 000000000..6bd7a4ff0 --- /dev/null +++ b/src/main/java/in/twizmwaz/cardinal/module/modules/renewables/Renewable.java @@ -0,0 +1,278 @@ +package in.twizmwaz.cardinal.module.modules.renewables; + +import com.google.common.collect.Lists; +import in.twizmwaz.cardinal.Cardinal; +import in.twizmwaz.cardinal.GameHandler; +import in.twizmwaz.cardinal.event.MatchEndEvent; +import in.twizmwaz.cardinal.module.TaskedModule; +import in.twizmwaz.cardinal.module.modules.filter.FilterModule; +import in.twizmwaz.cardinal.module.modules.filter.FilterState; +import in.twizmwaz.cardinal.module.modules.regions.RegionModule; +import in.twizmwaz.cardinal.module.modules.team.TeamModule; +import in.twizmwaz.cardinal.util.Teams; +import net.minecraft.server.SoundCategory; +import net.minecraft.server.SoundEffectType; +import net.minecraft.server.World; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.player.PlayerBucketEmptyEvent; +import org.bukkit.event.player.PlayerBucketFillEvent; +import org.bukkit.material.MaterialData; +import org.bukkit.util.BlockVector; +import org.bukkit.util.Vector; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +public class Renewable implements TaskedModule { + + private RegionModule region; + private FilterModule renewFilter; + private FilterModule replaceFilter; + private FilterModule shuffleFilter; + private RenewMode mode; + private double ratePerTick; + private double intervalTicks; + private boolean grow; + private boolean particles; + private boolean sound; + private int avoidPlayers; + + private Random random = new Random(); + private double renewals; + + private Map blocks = new HashMap<>(); + private List toRenew = Lists.newArrayList(); + private List tasks = Lists.newArrayList(); + + private static BlockFace[] faces = {BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, BlockFace.UP, BlockFace.DOWN}; + + Renewable(RegionModule region, + FilterModule renewFilter, + FilterModule replaceFilter, + FilterModule shuffleFilter, + double rate, + double interval, + boolean grow, + boolean particles, + boolean sound, + int avoidPlayers) { + this.region = region; + this.renewFilter = renewFilter; + this.replaceFilter = replaceFilter; + this.shuffleFilter = shuffleFilter; + this.mode = interval < 0 ? RenewMode.RATE : RenewMode.INTERVAL; + this.intervalTicks = (int) interval * 20; + this.ratePerTick = rate / 20; + this.grow = grow; + this.particles = particles; + this.sound = sound; + this.avoidPlayers = avoidPlayers * avoidPlayers; + } + + @Override + public void unload() { + HandlerList.unregisterAll(this); + } + + @Override + public void run() { + if (GameHandler.getGameHandler().getMatch().isRunning() && mode.equals(RenewMode.RATE)) { + renewals += ratePerTick; + + int fails = 0, maxFails = 5 + (toRenew.size() / 4); + + while(fails < maxFails && toRenew.size() > 0 && renewals > 1) { + BlockVector loc = toRenew.get(random.nextInt(toRenew.size())); + + Boolean renew = attemptRenew(loc); + if (renew != null) { + if (renew) renewals--; + else fails++; + } + + } + if (renewals > 1) renewals -= (int) renewals; + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockBreak(BlockBreakEvent event) { + editedBlock(event.getBlock().getLocation(), event.getBlock().getState().getMaterialData()); + } + + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockPlace(BlockPlaceEvent event) { + editedBlock(event.getBlock().getLocation(), event.getBlockReplacedState().getMaterialData()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBucketFill(PlayerBucketFillEvent event) { + editedBlock(event.getBlockClicked().getLocation(), event.getBlockClicked().getState().getMaterialData()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBucketEmpty(PlayerBucketEmptyEvent event) { + Block relative = event.getBlockClicked().getRelative(event.getBlockFace()); + editedBlock(relative.getLocation(), relative.getState().getMaterialData()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockBlow(EntityExplodeEvent event) { + for (Block block : event.blockList()) { + editedBlock(block.getLocation(), block.getState().getMaterialData()); + } + } + + @EventHandler + public void onMatchEnd(MatchEndEvent event) { + for (int id : tasks) { + Bukkit.getScheduler().cancelTask(id); + } + tasks.clear(); + } + + public void stopTask(int id) { + Bukkit.getScheduler().cancelTask(id); + tasks.remove((Integer) id); + } + + private void editedBlock(Vector loc, MaterialData save) { + if (isInRegion(loc)) { + BlockVector block = loc.toBlockVector(); + if (!blocks.containsKey(block)) { + blocks.put(block, save); + } + if (!toRenew.contains(block) && renewFilter.evaluate(save).equals(FilterState.ALLOW)) { + toRenew.add(block); + if (mode.equals(RenewMode.INTERVAL)) { + RenewRunnable renewRunnable = new RenewRunnable(this, block); + int taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(Cardinal.getInstance(), renewRunnable, (long) intervalTicks, (long) intervalTicks); + renewRunnable.setTask(taskId); + tasks.add(taskId); + } + } + } + } + + public Boolean attemptRenew(BlockVector loc) { + Block block = loc.toLocation(GameHandler.getGameHandler().getMatchWorld()).getBlock(); + + if (isRenewed(block)) { + toRenew.remove(loc); + return null; + } else if (canRenew(block)) { + resetBlock(block); + toRenew.remove(loc); + return true; + } else { + return false; + } + } + + private void resetBlock(Block block) { + MaterialData original = blocks.get(block.getLocation().toBlockVector()); + + block.setTypeIdAndData(original.getItemTypeId(), original.getData(), true); + + World nmsWorld = ((CraftWorld) GameHandler.getGameHandler().getMatchWorld()).getHandle(); + if (sound) { + SoundEffectType sound = CraftMagicNumbers.getBlock(original.getItemType()).getSoundEffects(); + nmsWorld.playSoundEffect(null, block.getX(), block.getY(), block.getZ(), sound.breakSound(), SoundCategory.BLOCKS, sound.b(), sound.a()); + } + /*if (particles) { + PacketPlayOutWorldParticles packet = new PacketPlayOutWorldParticles(EnumParticle.ITEM_CRACK, true, (float)block.getX(), (float)block.getY(), (float)block.getZ(), 0.15f, 24.0f, 0.15f, 0.0f, 40, 35, (int)color.getWoolData()); + PacketUtils.broadcastPacket(packet); + }*/ + } + + private boolean hasAdjacentBlocks(Block block) { + for (BlockFace face : faces) { + Block relative = block.getRelative(face); + if (!relative.getType().equals(Material.AIR) && isRenewed(relative)) return true; + } + return false; + } + + private boolean isRenewed(Block block) { + if (!isInRegion(block.getLocation())) return false; + BlockVector loc = block.getLocation().toBlockVector(); + if (!blocks.containsKey(loc)) { + return true; + } else if (shuffleFilter.evaluate(blocks.get(loc)).equals(FilterState.ALLOW)) { + return shuffleFilter.evaluate(block).equals(FilterState.ALLOW); + } else { + return blocks.get(loc).equals(block.getState().getMaterialData()); + } + } + + public boolean canRenew(Block block) { + BlockVector pos = block.getLocation().toVector().toBlockVector(); + + if(isInRegion(pos) && blocks.containsKey(pos) && !blocks.get(pos).getItemType().equals(Material.AIR) + && replaceFilter.evaluate(block).equals(FilterState.ALLOW) && (!grow || hasAdjacentBlocks(block))) { + if (avoidPlayers != 0) { + for (TeamModule team : Teams.getTeams()) { + if (team.isObserver()) continue; + for (Player player : (List) team) { + if (player.getLocation().plus(0, 0.5, 0).distanceSquared(block.getLocation().plus(0.5, 0.5, 0.5)) < avoidPlayers) { + return false; + } + } + } + } + return true; + } + return false; + } + + private boolean isInRegion(Vector pos) { + Vector vec = new Vector(pos.getBlockX() + 0.5, pos.getBlockY() + 0.5, pos.getBlockZ() + 0.5); + return region.contains(vec); + } + + enum RenewMode { + RATE(), + INTERVAL(); + } + + private class RenewRunnable implements Runnable { + + private Renewable renewable; + private BlockVector toRenew; + private int taskId; + + public RenewRunnable(Renewable renewable, BlockVector block) { + this.renewable = renewable; + this.toRenew = block; + } + + public void setTask(int taskId) { + this.taskId = taskId; + } + + @Override + public void run() { + Boolean renew = renewable.attemptRenew(toRenew); + if (renew == null || renew) { + renewable.stopTask(taskId); + } + } + + } + +} diff --git a/src/main/java/in/twizmwaz/cardinal/module/modules/renewables/RenewablesBuilder.java b/src/main/java/in/twizmwaz/cardinal/module/modules/renewables/RenewablesBuilder.java new file mode 100644 index 000000000..5fe89d1ef --- /dev/null +++ b/src/main/java/in/twizmwaz/cardinal/module/modules/renewables/RenewablesBuilder.java @@ -0,0 +1,65 @@ +package in.twizmwaz.cardinal.module.modules.renewables; + +import in.twizmwaz.cardinal.match.Match; +import in.twizmwaz.cardinal.module.ModuleBuilder; +import in.twizmwaz.cardinal.module.ModuleCollection; +import in.twizmwaz.cardinal.module.modules.filter.FilterModule; +import in.twizmwaz.cardinal.module.modules.filter.FilterModuleBuilder; +import in.twizmwaz.cardinal.module.modules.filter.parsers.BlockFilterParser; +import in.twizmwaz.cardinal.module.modules.filter.type.BlockFilter; +import in.twizmwaz.cardinal.module.modules.regions.RegionModule; +import in.twizmwaz.cardinal.module.modules.regions.RegionModuleBuilder; +import in.twizmwaz.cardinal.util.Numbers; +import in.twizmwaz.cardinal.util.Parser; +import in.twizmwaz.cardinal.util.Proto; +import in.twizmwaz.cardinal.util.Strings; +import org.bukkit.Bukkit; +import org.jdom2.Element; + +public class RenewablesBuilder implements ModuleBuilder { + + @Override + public ModuleCollection load(Match match) { + ModuleCollection results = new ModuleCollection<>(); + for (Element renewables : match.getDocument().getRootElement().getChildren("renewables")) { + for (Element renewable : renewables.getChildren("renewable")) { + results.add(getRenewable(match.getProto(), renewable, renewables)); + } + for (Element renewables2 : renewables.getChildren("renewables")) { + for (Element renewable : renewables2.getChildren("renewable")) { + results.add(getRenewable(match.getProto(), renewable, renewables2, renewable)); + } + } + } + return results; + } + + private Renewable getRenewable(Proto proto, Element... elements) { + RegionModule region = RegionModuleBuilder.getAttributeOrChild("region", elements); + + FilterModule renewFilter, replaceFilter, shuffleFilter; + + if (proto.greaterOrEqualTo(Proto.parseProto("1.4.0"))) { + Bukkit.broadcastMessage("Proto 1.4"); + renewFilter = FilterModuleBuilder.getAttributeOrChild("renew-filter", "always", elements); + replaceFilter = FilterModuleBuilder.getAttributeOrChild("replace-filter", "always", elements); + shuffleFilter = FilterModuleBuilder.getAttributeOrChild("shuffle-filter", "never", elements); + } else { + Bukkit.broadcastMessage("Proto <1.4"); + renewFilter = new BlockFilter(new BlockFilterParser(elements[0].getChild("renew"))); + replaceFilter = new BlockFilter(new BlockFilterParser(elements[0].getChild("replace"))); + shuffleFilter = FilterModuleBuilder.getAttributeOrChild("shuffle", "never", elements); + } + + double rate = Numbers.parseDouble(Parser.getOrderedAttribute("rate", elements), 1); + double interval = Strings.timeStringToExactSeconds(Strings.fallback(Parser.getOrderedAttribute("interval", elements), "-1s")); + + boolean grow = Numbers.parseBoolean(Parser.getOrderedAttribute("grow", elements), true); + boolean particles = Numbers.parseBoolean(Parser.getOrderedAttribute("particles", elements), true); + boolean sound = Numbers.parseBoolean(Parser.getOrderedAttribute("sound", elements), true); + int avoidPlayers = Numbers.parseInt(Parser.getOrderedAttribute("avoid-players", elements), 2); + + return new Renewable(region, renewFilter, replaceFilter, shuffleFilter, rate, interval, grow, particles, sound, avoidPlayers); + } + +} diff --git a/src/main/java/in/twizmwaz/cardinal/util/Strings.java b/src/main/java/in/twizmwaz/cardinal/util/Strings.java index e4df631f5..5159b1ee9 100644 --- a/src/main/java/in/twizmwaz/cardinal/util/Strings.java +++ b/src/main/java/in/twizmwaz/cardinal/util/Strings.java @@ -162,4 +162,9 @@ public static String getCurrentChatColor(String str, int index) { } return color; } + + public static String fallback(String string, String fallback) { + return string == null ? fallback : string; + } + }