diff --git a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java index cec7b0fe1..a889c6e40 100644 --- a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java +++ b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java @@ -60,6 +60,9 @@ import me.clip.placeholderapi.PlaceholderAPI; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; + import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.events.*; import com.nisovin.magicspells.handlers.*; @@ -461,7 +464,7 @@ public void load() { // Load xp system if (config.getBoolean(path + "enable-magic-xp", false)) { log("Loading xp system..."); - magicXpHandler = new MagicXpHandler(this, config); + magicXpHandler = new MagicXpHandler(config); log("...xp system loaded"); } @@ -576,7 +579,7 @@ private void initMetrics() { return map; })); metrics.addCustomChart(new AdvancedPie("passive_listeners", () -> { - IntMap map = new IntMap<>(); + Object2IntMap map = new Object2IntOpenHashMap<>(); if (spells == null) return map; for (Spell spell : spells.values()) { @@ -585,7 +588,7 @@ private void initMetrics() { for (PassiveListener listener : passiveSpell.getPassiveListeners()) { String name = listener.getClass().getSimpleName(); - map.increment(name.substring(0, name.lastIndexOf("Listener"))); + map.mergeInt(name.substring(0, name.lastIndexOf("Listener")), 1, Integer::sum); } } return map; diff --git a/core/src/main/java/com/nisovin/magicspells/Spellbook.java b/core/src/main/java/com/nisovin/magicspells/Spellbook.java index 0ed28abf4..4d73c3dd6 100644 --- a/core/src/main/java/com/nisovin/magicspells/Spellbook.java +++ b/core/src/main/java/com/nisovin/magicspells/Spellbook.java @@ -7,7 +7,6 @@ import org.bukkit.plugin.Plugin; import org.bukkit.inventory.ItemStack; -import com.nisovin.magicspells.util.Util; import com.nisovin.magicspells.util.CastItem; import com.nisovin.magicspells.spells.BuffSpell; import com.nisovin.magicspells.util.compat.EventUtil; @@ -18,8 +17,6 @@ public class Spellbook { private Player player; - private String playerName; - private String uniqueId; private final Set spells = new HashSet<>() { @@ -57,10 +54,8 @@ public void clear() { public Spellbook(Player player) { this.player = player; - playerName = player.getName(); - uniqueId = Util.getUniqueId(player); - MagicSpells.debug(1, "Loading player spell list: " + playerName); + MagicSpells.debug(1, "Loading player spell list: " + player.getName()); load(); } @@ -73,7 +68,6 @@ public void destroy() { temporarySpells.clear(); player = null; - playerName = null; } public void load() { @@ -107,7 +101,7 @@ public void save() { } public void reload() { - MagicSpells.debug(1, "Reloading data for player '" + playerName + "'..."); + MagicSpells.debug(1, "Reloading data for player '" + player.getName() + "'..."); removeAllSpells(); MagicSpells.getStorageHandler().load(this); MagicSpells.debug(1, "...done"); @@ -486,8 +480,8 @@ public Map> getTemporarySpells() { @Override public String toString() { - return "Spellbook:[playerName=" + playerName - + ",uniqueId=" + uniqueId + return "Spellbook:[playerName=" + player.getName() + + ",uniqueId=" + player.getUniqueId() + ",spells=" + spells + ",itemSpells=" + itemSpells + ",activeSpells=" + activeSpells diff --git a/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/ChestContainsCondition.java b/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/ChestContainsCondition.java index 0f3350c29..6af7f7490 100644 --- a/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/ChestContainsCondition.java +++ b/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/ChestContainsCondition.java @@ -12,7 +12,6 @@ import org.jetbrains.annotations.NotNull; import com.nisovin.magicspells.util.Name; -import com.nisovin.magicspells.util.BlockUtils; import com.nisovin.magicspells.util.LocationUtil; import com.nisovin.magicspells.castmodifiers.Condition; import com.nisovin.magicspells.util.magicitems.MagicItems; @@ -57,9 +56,9 @@ public boolean check(LivingEntity caster, Location location) { private boolean checkChest() { Block block = location.getBlock(); - if (!BlockUtils.isChest(block)) return false; + if (!(block.getState() instanceof Chest chest)) return false; - for (ItemStack item : ((Chest) block.getState()).getInventory().getContents()) { + for (ItemStack item : chest.getInventory().getContents()) { MagicItemData data = MagicItems.getMagicItemDataFromItemStack(item); if (data == null) continue; if (itemData.matches(data)) return true; diff --git a/core/src/main/java/com/nisovin/magicspells/handlers/MagicXpHandler.java b/core/src/main/java/com/nisovin/magicspells/handlers/MagicXpHandler.java index 0835a172f..cedd99135 100644 --- a/core/src/main/java/com/nisovin/magicspells/handlers/MagicXpHandler.java +++ b/core/src/main/java/com/nisovin/magicspells/handlers/MagicXpHandler.java @@ -1,56 +1,46 @@ package com.nisovin.magicspells.handlers; import java.io.File; -import java.util.Map; -import java.util.Set; -import java.util.List; -import java.util.HashMap; -import java.util.HashSet; -import java.util.ArrayList; +import java.util.*; import java.text.NumberFormat; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.Listener; import org.bukkit.event.EventHandler; -import org.bukkit.entity.LivingEntity; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.configuration.file.YamlConfiguration; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; + import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.Spellbook; import com.nisovin.magicspells.util.Util; -import com.nisovin.magicspells.util.IntMap; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.util.TimeUtil; import com.nisovin.magicspells.util.MagicConfig; import com.nisovin.magicspells.Spell.PostCastAction; import com.nisovin.magicspells.Spell.SpellCastState; -import com.nisovin.magicspells.util.compat.EventUtil; import com.nisovin.magicspells.events.SpellLearnEvent; import com.nisovin.magicspells.events.SpellCastedEvent; import com.nisovin.magicspells.events.SpellLearnEvent.LearnSource; public class MagicXpHandler implements Listener { - private MagicSpells plugin; + private final Map schools = new HashMap<>(); + private final Map> xp = new HashMap<>(); + private final Map> spellSchoolRequirements = new HashMap<>(); - private Map schools = new HashMap<>(); - private Map> xp = new HashMap<>(); - private Map currentWorld = new HashMap<>(); - private Map> spellSchoolRequirements = new HashMap<>(); + private final Set dirty = new HashSet<>(); - private Set dirty = new HashSet<>(); + private final boolean autoLearn; + private final String strXpHeader; + private final String strNoXp; - private boolean autoLearn; - private String strXpHeader; - private String strNoXp; - - public MagicXpHandler(MagicSpells plugin, MagicConfig config) { - this.plugin = plugin; - + public MagicXpHandler(MagicConfig config) { Set keys = config.getKeys("general.magic-schools"); if (keys != null) { for (String school : keys) { @@ -58,180 +48,166 @@ public MagicXpHandler(MagicSpells plugin, MagicConfig config) { if (name != null) schools.put(school.toLowerCase(), name); } } + autoLearn = config.getBoolean("general.magic-xp-auto-learn", false); strXpHeader = config.getString("general.str-xp-header", null); strNoXp = config.getString("general.str-no-xp", null); - + for (Spell spell : MagicSpells.spells()) { Map xpRequired = spell.getXpRequired(); if (xpRequired == null) continue; for (String school : xpRequired.keySet()) { - List list = spellSchoolRequirements.computeIfAbsent(school.toLowerCase(), s -> new ArrayList<>()); + List list = spellSchoolRequirements.computeIfAbsent(school.toLowerCase(), _ -> new ArrayList<>()); list.add(spell); } } + Util.forEachPlayerOnline(this::load); MagicSpells.scheduleRepeatingTask(this::saveAll, TimeUtil.TICKS_PER_MINUTE, TimeUtil.TICKS_PER_MINUTE); MagicSpells.registerEvents(this); } - + public void showXpInfo(Player player) { - MagicSpells.sendMessage(strXpHeader, player, MagicSpells.NULL_ARGS); - IntMap playerXp = xp.get(player.getName()); + MagicSpells.sendMessage(player, strXpHeader); + Object2IntMap playerXp = xp.get(player.getUniqueId()); + if (playerXp == null || playerXp.isEmpty()) { - MagicSpells.sendMessage(strNoXp, player, MagicSpells.NULL_ARGS); + MagicSpells.sendMessage(player, strNoXp); return; } + for (String school : playerXp.keySet()) { String schoolName = schools.get(school); if (schoolName == null) continue; - String amt = NumberFormat.getInstance().format(playerXp.get(school)); - MagicSpells.sendMessage(schoolName + ": " + amt, player, MagicSpells.NULL_ARGS); + + String amt = NumberFormat.getInstance().format(playerXp.getInt(school)); + MagicSpells.sendMessage(player, schoolName + ": " + amt); } } - + public int getXp(Player player, String school) { - IntMap playerXp = xp.get(player.getName()); - if (playerXp != null) return playerXp.get(school.toLowerCase()); - return 0; + Object2IntMap playerXp = xp.get(player.getUniqueId()); + return playerXp == null ? 0 : playerXp.getInt(school.toLowerCase()); } - + @EventHandler public void onCast(SpellCastedEvent event) { if (event.getPostCastAction() == PostCastAction.ALREADY_HANDLED) return; if (event.getSpellCastState() != SpellCastState.NORMAL) return; - + final Map xpGranted = event.getSpell().getXpGranted(); if (xpGranted == null) return; - // Get player xp - IntMap playerXp = xp.computeIfAbsent(event.getCaster().getName(), s -> new IntMap<>()); - - // Grant xp - // FIXME use entry set here - for (String school : xpGranted.keySet()) { - playerXp.increment(school.toLowerCase(), xpGranted.get(school)); - } + Object2IntMap playerXp = xp.computeIfAbsent(event.getCaster().getUniqueId(), _ -> new Object2IntOpenHashMap<>()); + xpGranted.forEach((key, value) -> playerXp.mergeInt(key, value, Integer::sum)); - dirty.add(event.getCaster().getName()); + dirty.add(event.getCaster().getUniqueId()); if (!autoLearn) return; - final LivingEntity caster = event.getCaster(); - if (!(caster instanceof Player player)) return; + if (!(event.getCaster() instanceof Player player)) return; - final Spell castedSpell = event.getSpell(); MagicSpells.scheduleDelayedTask(() -> { - - // Get spells to check if learned Set toCheck = new HashSet<>(); for (String school : xpGranted.keySet()) { List list = spellSchoolRequirements.get(school.toLowerCase()); if (list != null) toCheck.addAll(list); } - - // Check for new learned spells if (toCheck.isEmpty()) return; + boolean learned = false; Spellbook spellbook = MagicSpells.getSpellbook(player); for (Spell spell : toCheck) { - if (!spellbook.hasSpell(spell, false) && spellbook.canLearn(spell)) { - SpellLearnEvent evt = new SpellLearnEvent(spell, player, LearnSource.MAGIC_XP, castedSpell); - EventUtil.call(evt); - if (!evt.isCancelled()) { - spellbook.addSpell(spell); - MagicSpells.sendMessage(spell.getStrXpLearned(), player, MagicSpells.NULL_ARGS); - learned = true; - } - } + if (spellbook.hasSpell(spell, false) || !spellbook.canLearn(spell)) continue; + if (!new SpellLearnEvent(spell, player, LearnSource.MAGIC_XP, event.getSpell()).callEvent()) continue; + + spellbook.addSpell(spell); + MagicSpells.sendMessage(player, spell.getStrXpLearned()); + learned = true; } if (learned) spellbook.save(); }, 1); } - + @EventHandler public void onJoin(PlayerJoinEvent event) { - currentWorld.put(event.getPlayer().getName(), event.getPlayer().getWorld().getName()); - dirty.remove(event.getPlayer().getName()); - load(event.getPlayer()); + Player player = event.getPlayer(); + dirty.remove(player.getUniqueId()); + load(player); } @EventHandler public void onChangeWorld(PlayerChangedWorldEvent event) { if (!MagicSpells.arePlayerSpellsSeparatedPerWorld()) return; + Player player = event.getPlayer(); - String playerName = player.getName(); - if (dirty.contains(playerName)) save(player); - currentWorld.put(playerName, player.getWorld().getName()); + if (dirty.remove(player.getUniqueId())) save(player); load(player); - dirty.remove(playerName); } @EventHandler public void onQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); - String playerName = player.getName(); - if (dirty.contains(playerName)) save(player); - xp.remove(playerName); - dirty.remove(playerName); - currentWorld.remove(playerName); + UUID uuid = player.getUniqueId(); + + if (dirty.remove(uuid)) save(player); + xp.remove(uuid); } - + + @SuppressWarnings("ResultOfMethodCallIgnored") public void load(Player player) { - File folder = new File(plugin.getDataFolder(), "xp"); + File folder = new File(MagicSpells.getInstance().getDataFolder(), "xp"); if (!folder.exists()) folder.mkdirs(); + if (MagicSpells.arePlayerSpellsSeparatedPerWorld()) { - String world = currentWorld.get(player.getName()); - if (world == null) world = player.getWorld().getName(); - folder = new File(folder, world); + folder = new File(folder, player.getWorld().getName()); if (!folder.exists()) folder.mkdirs(); } - String uuid = Util.getUniqueId(player); - File file = new File(folder, uuid + ".txt"); - if (!file.exists()) { - File file2 = new File(folder, player.getName().toLowerCase()); - if (file2.exists()) file2.renameTo(file); - } + + File file = new File(folder, Util.getUniqueId(player) + ".txt"); if (!file.exists()) return; + YamlConfiguration conf = new YamlConfiguration(); try { conf.load(file); - IntMap playerXp = new IntMap<>(); + Object2IntMap playerXp = new Object2IntOpenHashMap<>(); for (String school : conf.getKeys(false)) { playerXp.put(school.toLowerCase(), conf.getInt(school, 0)); } - xp.put(player.getName(), playerXp); + xp.put(player.getUniqueId(), playerXp); } catch (Exception e) { MagicSpells.error("Error while loading player XP for player " + player.getName()); MagicSpells.handleException(e); } } - + public void saveAll() { - for (String playerName : dirty) { - Player player = Bukkit.getPlayerExact(playerName); - if (player != null) save(player); + for (UUID uuid : dirty) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) continue; + save(player); } dirty.clear(); } - + + @SuppressWarnings("ResultOfMethodCallIgnored") public void save(Player player) { - String world = currentWorld.get(player.getName()); - if (world == null) world = player.getWorld().getName(); - File folder = new File(plugin.getDataFolder(), "xp"); + File folder = new File(MagicSpells.getInstance().getDataFolder(), "xp"); if (!folder.exists()) folder.mkdirs(); + if (MagicSpells.arePlayerSpellsSeparatedPerWorld()) { - folder = new File(folder, world); + folder = new File(folder, player.getWorld().getName()); if (!folder.exists()) folder.mkdirs(); } + File file = new File(folder, Util.getUniqueId(player) + ".txt"); if (file.exists()) file.delete(); - + YamlConfiguration conf = new YamlConfiguration(); - IntMap playerXp = xp.get(player.getName()); + Object2IntMap playerXp = xp.get(player.getUniqueId()); if (playerXp != null) { for (String school : playerXp.keySet()) { - conf.set(school.toLowerCase(), playerXp.get(school)); + conf.set(school.toLowerCase(), playerXp.getInt(school)); } } @@ -242,5 +218,5 @@ public void save(Player player) { MagicSpells.handleException(e); } } - + } diff --git a/core/src/main/java/com/nisovin/magicspells/mana/ManaBar.java b/core/src/main/java/com/nisovin/magicspells/mana/ManaBar.java index 5963476e9..a3294865c 100644 --- a/core/src/main/java/com/nisovin/magicspells/mana/ManaBar.java +++ b/core/src/main/java/com/nisovin/magicspells/mana/ManaBar.java @@ -1,5 +1,7 @@ package com.nisovin.magicspells.mana; +import java.util.UUID; + import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -8,7 +10,7 @@ public class ManaBar { - private final String playerName; + private final UUID player; private ManaRank rank; @@ -18,7 +20,7 @@ public class ManaBar { private String barFormat; public ManaBar(Player player, ManaRank rank) { - playerName = player.getName().toLowerCase(); + this.player = player.getUniqueId(); setRank(rank); } @@ -31,7 +33,7 @@ public void setRank(ManaRank rank) { } public Player getPlayer() { - return Bukkit.getPlayerExact(playerName); + return Bukkit.getPlayer(player); } public ManaRank getManaRank() { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/buff/DodgeSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/buff/DodgeSpell.java index a343da70d..559e2812a 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/buff/DodgeSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/buff/DodgeSpell.java @@ -126,13 +126,14 @@ private void dodge(LivingEntity caster, ParticleProjectileTracker tracker, Spell Vector v = RandomUtils.getRandomCircleVector().multiply(distance); targetLoc.add(v); targetLoc.setDirection(caster.getLocation().getDirection()); + targetLoc = BlockUtils.adjustToSafeLocation(caster, targetLoc); if (spellBeforeDodge != null) { SpellData castData = subData.builder().caster(caster).target(null).location(casterLoc).recipient(null).build(); spellBeforeDodge.subcast(castData); } - if (!targetLoc.getBlock().isPassable() || !targetLoc.getBlock().getRelative(BlockFace.UP).isPassable()) return; + if (targetLoc == null) return; caster.teleportAsync(targetLoc); addUseAndChargeCost(caster); diff --git a/core/src/main/java/com/nisovin/magicspells/spells/instant/GateSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/instant/GateSpell.java index 52729c442..a9b7ee715 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/instant/GateSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/instant/GateSpell.java @@ -3,7 +3,6 @@ import org.bukkit.World; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.block.Block; import org.bukkit.entity.Vehicle; import com.nisovin.magicspells.util.*; @@ -84,8 +83,8 @@ public CastResult cast(SpellData data) { } MagicSpells.debug(3, "Gate location: " + location); - Block b = location.getBlock(); - if (!b.isPassable() || !b.getRelative(0, 1, 0).isPassable()) { + location = BlockUtils.adjustToSafeLocation(data.caster(), location); + if (location == null) { MagicSpells.error("GateSpell '" + internalName + "' has landing spot blocked!"); sendMessage(strGateFailed, data.caster(), data); return new CastResult(PostCastAction.ALREADY_HANDLED, data); diff --git a/core/src/main/java/com/nisovin/magicspells/spells/instant/PhaseSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/instant/PhaseSpell.java index 2e13fa0b7..69e0f276b 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/instant/PhaseSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/instant/PhaseSpell.java @@ -2,10 +2,13 @@ import java.util.List; import java.util.ArrayList; +import java.util.function.Predicate; import org.bukkit.Material; import org.bukkit.Location; import org.bukkit.block.Block; +import org.bukkit.util.BoundingBox; +import org.bukkit.FluidCollisionMode; import org.bukkit.util.BlockIterator; import com.nisovin.magicspells.util.*; @@ -15,34 +18,45 @@ public class PhaseSpell extends InstantSpell { - private final List phasableBlocks; - private final List nonPhasableBlocks; - private final ConfigData maxDistance; + private final ConfigData requireGroundDistance; + + private final ConfigData airOnlyExit; + private final ConfigData snapToGround; + private final ConfigData ignoreTransparentBlocks; private final ConfigData powerAffectsMaxDistance; - private String strCantPhase; + + private final String strCantPhase; + + private final List enterBlocks = new ArrayList<>(); + private final List nonExitBlocks = new ArrayList<>(); + private final List phasableBlocks = new ArrayList<>(); + private final List nonPhasableBlocks = new ArrayList<>(); public PhaseSpell(MagicConfig config, String spellName) { super(config, spellName); maxDistance = getConfigDataInt("max-distance", 15); - strCantPhase = getConfigString("str-cant-phase", "Unable to find place to phase to."); + requireGroundDistance = getConfigDataInt("require-ground-distance", 0); + + airOnlyExit = getConfigDataBoolean("air-only-exit", true); + snapToGround = getConfigDataBoolean("snap-to-ground", false); + ignoreTransparentBlocks = getConfigDataBoolean("ignore-transparent-blocks", false); powerAffectsMaxDistance = getConfigDataBoolean("power-affects-max-distance", true); - phasableBlocks = new ArrayList<>(); - nonPhasableBlocks = new ArrayList<>(); + strCantPhase = getConfigString("str-cant-phase", "Unable to find place to phase to."); + processMaterials(enterBlocks, "enter-blocks"); + processMaterials(nonExitBlocks, "non-exit-blocks"); processMaterials(phasableBlocks, "phasable-blocks"); processMaterials(nonPhasableBlocks, "non-phasable-blocks"); } private void processMaterials(List materials, String path) { - List matList = getConfigStringList(path, null); - if (matList == null || matList.isEmpty()) return; - for (String mat : matList) { + for (String mat : getConfigStringList(path, List.of())) { Material material = Util.getMaterial(mat); if (material == null) { - MagicSpells.error("PhaseSpell has an invalid material specified on '" + path + "': " + mat); + MagicSpells.error("PhaseSpell '" + internalName + "' has an invalid material specified on '" + path + "': " + mat); continue; } materials.add(material); @@ -51,51 +65,68 @@ private void processMaterials(List materials, String path) { @Override public CastResult cast(SpellData data) { - int r = getRange(data); + int range = getRange(data); + int rangeSquared = range * range; + Location casterLoc = data.caster().getLocation(); int distance = maxDistance.get(data); if (powerAffectsMaxDistance.get(data)) distance = Math.round(distance * data.power()); + int distanceSquared = distance * distance; BlockIterator iter; try { - iter = new BlockIterator(data.caster(), distance << 1); + iter = new BlockIterator(data.caster(), distance); } catch (IllegalStateException e) { - sendMessage(strCantPhase, data.caster(), data); + sendMessage(strCantPhase, data); return new CastResult(PostCastAction.ALREADY_HANDLED, data); } - int i = 0; - Block start = null; - Location location = null, casterLoc = data.caster().getLocation(); + Predicate transparent = ignoreTransparentBlocks.get(data) ? isTransparent(data) : l -> l.getBlock().isEmpty(); - while (i++ < r << 1 && iter.hasNext()) { + while (iter.hasNext()) { Block b = iter.next(); - if (b.getType().isAir()) continue; - if (casterLoc.distanceSquared(b.getLocation()) >= r * r) continue; - start = b; - break; + Location loc = b.getLocation(); + + if (enterBlocks.contains(b.getType())) break; + if (transparent.test(loc)) continue; + + if (casterLoc.distanceSquared(loc) < rangeSquared && canPassThrough(b)) break; + + sendMessage(strCantPhase, data); + return new CastResult(PostCastAction.ALREADY_HANDLED, data); } - if (start != null) { - if (canPassThrough(start)) { - while (i++ < distance << 1 && iter.hasNext()) { - Block block = iter.next(); - if (block.getType().isAir() && block.getRelative(0, 1, 0).getType().isAir() && casterLoc.distanceSquared(block.getLocation()) < distance * distance) { - location = block.getLocation(); - break; - } - if (!canPassThrough(block)) break; - } + Location location = null; + boolean airOnlyExit = this.airOnlyExit.get(data); + boolean snapToGround = this.snapToGround.get(data); + int requireGroundDistance = this.requireGroundDistance.get(data); + + while (iter.hasNext()) { + Block block = iter.next(); + Location loc = block.getLocation().add(0.5, 0, 0.5); + + if (casterLoc.distanceSquared(loc) >= distanceSquared) break; + + Location adjusted = BlockUtils.adjustToSafeLocation(data.caster(), loc, requireGroundDistance, snapToGround); + if (adjusted == null) continue; + + BoundingBox box = data.caster().getBoundingBox(); + box.shift(adjusted.clone().subtract(casterLoc)); + if (!Util.hasCollisionsIn(data.caster().getWorld(), box, false, FluidCollisionMode.NEVER, + b -> (airOnlyExit && !b.isEmpty()) || nonExitBlocks.contains(b.getType()) + )) { + location = adjusted; + break; } + + if (!canPassThrough(adjusted.getBlock())) break; } if (location == null) { - sendMessage(strCantPhase, data.caster(), data); + sendMessage(strCantPhase, data); return new CastResult(PostCastAction.ALREADY_HANDLED, data); } - location.setX(location.getX() + 0.5); - location.setZ(location.getZ() + 0.5); location.setPitch(casterLoc.getPitch()); location.setYaw(casterLoc.getYaw()); data = data.location(location); @@ -107,25 +138,8 @@ public CastResult cast(SpellData data) { } private boolean canPassThrough(Block block) { - // Check only blacklist. - if (phasableBlocks.isEmpty()) return !nonPhasableBlocks.contains(block.getType()); - return phasableBlocks.contains(block.getType()) && !nonPhasableBlocks.contains(block.getType()); - } - - public List getPhasableBlocks() { - return phasableBlocks; - } - - public List getNonPhasableBlocks() { - return nonPhasableBlocks; - } - - public String getStrCantPhase() { - return strCantPhase; - } - - public void setStrCantPhase(String strCantPhase) { - this.strCantPhase = strCantPhase; + Material type = block.getType(); + return !nonPhasableBlocks.contains(type) && (phasableBlocks.isEmpty() || phasableBlocks.contains(type)); } } diff --git a/core/src/main/java/com/nisovin/magicspells/spells/instant/ThrowBlockSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/instant/ThrowBlockSpell.java index 3a6de2df6..d4df685b1 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/instant/ThrowBlockSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/instant/ThrowBlockSpell.java @@ -301,7 +301,7 @@ public void run() { if (block.getVelocity().lengthSquared() < .01) { if (!info.preventBlocks) { Block b = block.getLocation().getBlock(); - if (b.getType() == Material.AIR) BlockUtils.setBlockFromFallingBlock(b, block, true); + if (b.getType() == Material.AIR) b.setBlockData(block.getBlockData()); } if (!info.spellActivated && spellOnLand != null) { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/instant/WallSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/instant/WallSpell.java index a903e3556..f08e841db 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/instant/WallSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/instant/WallSpell.java @@ -171,7 +171,7 @@ private CastResult makeWall(SpellData data) { MagicSpellsBlockPlaceEvent event = new MagicSpellsBlockPlaceEvent(target, eventBlockState, target, caster.getInventory().getItemInMainHand(), caster, true); if (!event.callEvent()) return noTarget(data); - BlockUtils.setTypeAndData(target, Material.AIR, Material.AIR.createBlockData(), false); + target.setType(Material.AIR, false); } int yOffset = this.yOffset.get(data); diff --git a/core/src/main/java/com/nisovin/magicspells/spells/targeted/BlinkSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/targeted/BlinkSpell.java index 9bc886720..6f0d05b2b 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/targeted/BlinkSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/targeted/BlinkSpell.java @@ -2,6 +2,7 @@ import org.bukkit.Location; import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; import org.bukkit.util.RayTraceResult; import com.nisovin.magicspells.util.*; @@ -12,15 +13,21 @@ public class BlinkSpell extends TargetedSpell implements TargetedLocationSpell { - private final ConfigData passThroughCeiling; - private final String strCantBlink; + private final ConfigData requireGroundDistance; + + private final ConfigData snapToGround; + private final ConfigData passThroughCeiling; + public BlinkSpell(MagicConfig config, String spellName) { super(config, spellName); strCantBlink = getConfigString("str-cant-blink", "You can't blink there."); + requireGroundDistance = getConfigDataInt("require-ground-distance", 0); + + snapToGround = getConfigDataBoolean("snap-to-ground", false); passThroughCeiling = getConfigDataBoolean("pass-through-ceiling", false); } @@ -30,21 +37,25 @@ public CastResult cast(SpellData data) { if (result == null) return noTarget(strCantBlink, data); Block found = result.getHitBlock(); - Block prev = found.getRelative(result.getHitBlockFace()); + BlockFace face = result.getHitBlockFace(); + Block prev = found.getRelative(face); Location loc = null; - if (!passThroughCeiling.get(data) && found.getRelative(0, -1, 0).equals(prev) && prev.isPassable()) { - Block under = prev.getRelative(0, -1, 0); - if (under.isPassable()) loc = under.getLocation().add(0.5, 0, 0.5); - } else if (found.getRelative(0, 1, 0).isPassable() && found.getRelative(0, 2, 0).isPassable()) { - loc = found.getLocation().add(0.5, 1, 0.5); - } else if (prev.isPassable() && prev.getRelative(0, 1, 0).isPassable()) { - loc = prev.getLocation().add(0.5, 0, 0.5); + + boolean snapToGround = this.snapToGround.get(data); + int requireGroundDistance = this.requireGroundDistance.get(data); + + // Under + if (face == BlockFace.DOWN && !passThroughCeiling.get(data)) { + Location target = prev.getLocation().add(0.5, -1, 0.5); + loc = BlockUtils.adjustToSafeLocation(data.caster(), target, requireGroundDistance, snapToGround); } - if (loc == null) return noTarget(strCantBlink, data); + // Above + if (loc == null) loc = BlockUtils.adjustToSafeLocation(data.caster(), found.getLocation().add(0.5, 0, 0.5)); + // Side + if (loc == null) loc = BlockUtils.adjustToSafeLocation(data.caster(), prev.getLocation().add(0.5, 0, 0.5), requireGroundDistance, snapToGround); - loc.setPitch(data.caster().getPitch()); - loc.setYaw(data.caster().getYaw()); + if (loc == null) return noTarget(strCantBlink, data); SpellTargetLocationEvent targetEvent = new SpellTargetLocationEvent(this, data, loc); if (!targetEvent.callEvent()) return noTarget(strCantBlink, targetEvent); diff --git a/core/src/main/java/com/nisovin/magicspells/spells/targeted/EntombSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/targeted/EntombSpell.java index fe08d7bc6..daf118308 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/targeted/EntombSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/targeted/EntombSpell.java @@ -8,6 +8,7 @@ import org.bukkit.Material; import org.bukkit.Location; import org.bukkit.block.Block; +import org.bukkit.util.BoundingBox; import org.bukkit.event.EventHandler; import org.bukkit.entity.LivingEntity; import org.bukkit.block.data.BlockData; @@ -29,6 +30,8 @@ public class EntombSpell extends TargetedSpell implements TargetedEntitySpell { private final ConfigData duration; private final boolean allowBreaking; + + private final ConfigData centerTarget; private final ConfigData closeTopAndBottom; private final ConfigData powerAffectsDuration; @@ -42,6 +45,8 @@ public EntombSpell(MagicConfig config, String spellName) { duration = getConfigDataInt("duration", 20); allowBreaking = getConfigBoolean("allow-breaking", true); + + centerTarget = getConfigDataBoolean("center-target", true); closeTopAndBottom = getConfigDataBoolean("close-top-and-bottom", true); powerAffectsDuration = getConfigDataBoolean("power-affects-duration", true); @@ -73,27 +78,44 @@ public CastResult castAtEntity(SpellData data) { List tombBlocks = new ArrayList<>(); LivingEntity target = data.target(); - Block feet = target.getLocation().getBlock(); - float pitch = target.getLocation().getPitch(); - float yaw = target.getLocation().getYaw(); - - Location tpLoc = feet.getLocation().add(0.5, 0, 0.5); - tpLoc.setYaw(yaw); - tpLoc.setPitch(pitch); - target.teleportAsync(tpLoc); - - tempBlocks.add(feet.getRelative(1, 0, 0)); - tempBlocks.add(feet.getRelative(1, 1, 0)); - tempBlocks.add(feet.getRelative(-1, 0, 0)); - tempBlocks.add(feet.getRelative(-1, 1, 0)); - tempBlocks.add(feet.getRelative(0, 0, 1)); - tempBlocks.add(feet.getRelative(0, 1, 1)); - tempBlocks.add(feet.getRelative(0, 0, -1)); - tempBlocks.add(feet.getRelative(0, 1, -1)); - - if (closeTopAndBottom.get(data)) { - tempBlocks.add(feet.getRelative(0, -1, 0)); - tempBlocks.add(feet.getRelative(0, 2, 0)); + + if (centerTarget.get(data)) { + Location location = target.getLocation(); + location.setX(location.getBlockX() + 0.5); + location.setZ(location.getBlockZ() + 0.5); + target.teleport(location); + } + + BoundingBox box = target.getBoundingBox(); + BoundingBox exp = box.clone().expand(1 - 1e-7); + + int minX = (int) Math.floor(exp.getMinX()); + int minY = (int) Math.floor(exp.getMinY()); + int minZ = (int) Math.floor(exp.getMinZ()); + + int maxX = (int) Math.ceil(exp.getMaxX()); + int maxY = (int) Math.ceil(exp.getMaxY()); + int maxZ = (int) Math.ceil(exp.getMaxZ()); + + boolean closeTopAndBottom = this.closeTopAndBottom.get(data); + for (int x = minX; x < maxX; x++) { + for (int y = minY; y < maxY; y++) { + for (int z = minZ; z < maxZ; z++) { + Block block = target.getWorld().getBlockAt(x, y, z); + if (box.overlaps(BoundingBox.of(block))) continue; + + int boundary = 0; + if (x == minX || x == maxX - 1) boundary++; + if (y == minY || y == maxY - 1) { + if (!closeTopAndBottom) continue; + boundary++; + } + if (z == minZ || z == maxZ - 1) boundary++; + if (boundary > 1) continue; + + tempBlocks.add(block); + } + } } BlockData blockType = this.blockType.get(data); diff --git a/core/src/main/java/com/nisovin/magicspells/spells/targeted/FireballSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/targeted/FireballSpell.java index 68420bb86..6dbcbb61f 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/targeted/FireballSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/targeted/FireballSpell.java @@ -216,7 +216,7 @@ public void onExplosionPrime(ExplosionPrimeEvent event) { for (int z = loc.getBlockZ() - 1; z <= loc.getBlockZ() + 1; z++) { if (!loc.getWorld().getBlockAt(x, y, z).getType().isAir()) continue; Block b = loc.getWorld().getBlockAt(x, y, z); - BlockUtils.setTypeAndData(b, Material.FIRE, Material.FIRE.createBlockData(), false); + b.setType(Material.FIRE, false); fires.add(b); } } diff --git a/core/src/main/java/com/nisovin/magicspells/spells/targeted/ShadowstepSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/targeted/ShadowstepSpell.java index 008c4d2f1..e1b2adc92 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/targeted/ShadowstepSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/targeted/ShadowstepSpell.java @@ -1,9 +1,7 @@ package com.nisovin.magicspells.spells.targeted; import org.bukkit.Location; -import org.bukkit.block.Block; import org.bukkit.util.Vector; -import org.bukkit.block.BlockFace; import org.bukkit.entity.LivingEntity; import com.nisovin.magicspells.util.*; @@ -18,6 +16,10 @@ public class ShadowstepSpell extends TargetedSpell implements TargetedEntitySpel private final ConfigData distance; + private final ConfigData snapToGround; + + private final ConfigData requireGroundDistance; + private final ConfigData relativeOffset; private final String strNoLandingSpot; @@ -30,6 +32,10 @@ public ShadowstepSpell(MagicConfig config, String spellName) { distance = getConfigDataDouble("distance", -1); + snapToGround = getConfigDataBoolean("snap-to-ground", false); + + requireGroundDistance = getConfigDataInt("require-ground-distance", 0); + relativeOffset = getConfigDataVector("relative-offset", new Vector(-1, 0, 0)); strNoLandingSpot = getConfigString("str-no-landing-spot", "Cannot shadowstep there."); @@ -60,8 +66,8 @@ public CastResult castAtEntity(SpellData data) { targetLoc.setPitch(pitch.get(data)); targetLoc.setYaw(targetLoc.getYaw() + yaw.get(data)); - Block b = targetLoc.getBlock(); - if (!b.isPassable() || !b.getRelative(BlockFace.UP).isPassable()) return noTarget(strNoLandingSpot, data); + targetLoc = BlockUtils.adjustToSafeLocation(data.caster(), targetLoc, requireGroundDistance.get(data), snapToGround.get(data)); + if (targetLoc == null) return noTarget(strNoLandingSpot, data); playSpellEffects(data.caster(), targetLoc, data); data.caster().teleportAsync(targetLoc); diff --git a/core/src/main/java/com/nisovin/magicspells/spells/targeted/SummonSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/targeted/SummonSpell.java index 27b0e9154..6803f5742 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/targeted/SummonSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/targeted/SummonSpell.java @@ -37,6 +37,7 @@ public class SummonSpell extends TargetedSpell implements TargetedEntitySpell, T private final Map pending; private final ConfigData maxAcceptDelay; + private final ConfigData requireGroundDistance; private final ConfigData requireExactName; private final ConfigData requireAcceptance; @@ -51,6 +52,7 @@ public SummonSpell(MagicConfig config, String spellName) { super(config, spellName); maxAcceptDelay = getConfigDataInt("max-accept-delay", 90); + requireGroundDistance = getConfigDataInt("require-ground-distance", 2); requireExactName = getConfigDataBoolean("require-exact-name", false); requireAcceptance = getConfigDataBoolean("require-acceptance", true); @@ -91,12 +93,6 @@ public CastResult cast(SpellData data) { return new CastResult(PostCastAction.ALREADY_HANDLED, data); } - // Check location - if (!BlockUtils.isSafeToStand(landLoc.clone())) { - sendMessage(strUsage, caster, data); - return new CastResult(PostCastAction.ALREADY_HANDLED, data); - } - // Get player LivingEntity target = requireExactName.get(data) ? Bukkit.getPlayerExact(targetName) : Bukkit.getPlayer(targetName); if (target == null || !validTargetList.canTarget(caster, target)) return noTarget(data); @@ -107,6 +103,12 @@ public CastResult cast(SpellData data) { data = targetEvent.getSpellData(); target = data.target(); + landLoc = BlockUtils.adjustToSafeLocation(target, landLoc, requireGroundDistance.get(data), false); + if (landLoc == null) { + sendMessage(strUsage, caster, data); + return new CastResult(PostCastAction.ALREADY_HANDLED, data); + } + // Teleport player if (requireAcceptance.get(data)) { pending.put(target.getUniqueId(), new SummonData(landLoc, System.currentTimeMillis(), maxAcceptDelay.get(data), data)); diff --git a/core/src/main/java/com/nisovin/magicspells/spells/targeted/TelekinesisSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/targeted/TelekinesisSpell.java index b79de7407..efdae7e19 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/targeted/TelekinesisSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/targeted/TelekinesisSpell.java @@ -11,6 +11,9 @@ import org.bukkit.event.Event.Result; import org.bukkit.event.block.Action; import org.bukkit.util.RayTraceResult; +import org.bukkit.block.data.Powerable; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.AnaloguePowerable; import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.spells.TargetedSpell; @@ -61,9 +64,7 @@ public CastResult cast(SpellData data) { @Override public CastResult castAtLocation(SpellData data) { Block block = data.location().getBlock(); - - Material type = block.getType(); - if (!checkType(type)) return noTarget(data); + if (!checkType(block.getType())) return noTarget(data); return activate(block, data); } @@ -76,7 +77,15 @@ private CastResult activate(Block block, SpellData data) { if (event.useInteractedBlock() == Result.DENY) return noTarget(data); } - BlockUtils.activatePowerable(block); + BlockData blockData = block.getBlockData(); + if (blockData instanceof Powerable powerable) { + powerable.setPowered(true); + block.setBlockData(powerable); + } else if (blockData instanceof AnaloguePowerable powerable) { + powerable.setPower(powerable.getMaximumPower()); + block.setBlockData(powerable); + } + playSpellEffects(data); return new CastResult(PostCastAction.HANDLE_NORMALLY, data); diff --git a/core/src/main/java/com/nisovin/magicspells/spells/targeted/TeleportSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/targeted/TeleportSpell.java index 52c9525e8..bf06365a5 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/targeted/TeleportSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/targeted/TeleportSpell.java @@ -17,6 +17,8 @@ public class TeleportSpell extends TargetedSpell implements TargetedEntitySpell private final ConfigData relativeOffset; + private final ConfigData requireGroundDistance; + private final String strCantTeleport; public TeleportSpell(MagicConfig config, String spellName) { @@ -25,7 +27,9 @@ public TeleportSpell(MagicConfig config, String spellName) { yaw = getConfigDataFloat("yaw", 0); pitch = getConfigDataFloat("pitch", 0); - relativeOffset = getConfigDataVector("relative-offset", new Vector(0, 0.1, 0)); + requireGroundDistance = getConfigDataInt("require-ground-distance", 0); + + relativeOffset = getConfigDataVector("relative-offset", new Vector()); strCantTeleport = getConfigString("str-cant-teleport", ""); } @@ -52,7 +56,8 @@ public CastResult castAtEntity(SpellData data) { targetLoc.setPitch(startLoc.getPitch() - pitch.get(data)); targetLoc.setYaw(startLoc.getYaw() + yaw.get(data)); - if (!targetLoc.getBlock().isPassable()) return noTarget(strCantTeleport, data); + targetLoc = BlockUtils.adjustToSafeLocation(data.caster(), targetLoc, requireGroundDistance.get(data), false); + if (targetLoc == null) return noTarget(strCantTeleport, data); playSpellEffects(EffectPosition.CASTER, data.caster(), data); playSpellEffects(EffectPosition.TARGET, data.target(), data); diff --git a/core/src/main/java/com/nisovin/magicspells/storage/types/TXTFileStorage.java b/core/src/main/java/com/nisovin/magicspells/storage/types/TXTFileStorage.java index 202d90f00..5b8a28e29 100644 --- a/core/src/main/java/com/nisovin/magicspells/storage/types/TXTFileStorage.java +++ b/core/src/main/java/com/nisovin/magicspells/storage/types/TXTFileStorage.java @@ -46,19 +46,8 @@ public void load(Spellbook spellbook) { if (MagicSpells.arePlayerSpellsSeparatedPerWorld()) { File folder = new File(plugin.getDataFolder(), path + worldName); if (!folder.exists()) folder.mkdir(); - file = new File(plugin.getDataFolder(), path + worldName + File.separator + id + ".txt"); - if (!file.exists()) { - File file2 = new File(plugin.getDataFolder(), path + worldName + File.separator + pl.getName().toLowerCase() + ".txt"); - if (file2.exists()) file2.renameTo(file); - } - } else { - file = new File(plugin.getDataFolder(), path + id + ".txt"); - if (!file.exists()) { - File file2 = new File(plugin.getDataFolder(), path + pl.getName().toLowerCase() + ".txt"); - if (file2.exists()) file2.renameTo(file); - } - } + } else file = new File(plugin.getDataFolder(), path + id + ".txt"); if (!file.exists()) return; @@ -115,14 +104,8 @@ public void save(Spellbook spellbook) { if (MagicSpells.arePlayerSpellsSeparatedPerWorld()) { File folder = new File(plugin.getDataFolder(), path + worldName); if (!folder.exists()) folder.mkdirs(); - File oldFile = new File(plugin.getDataFolder(), path + worldName + File.separator + pl.getName() + ".txt"); - if (oldFile.exists()) oldFile.delete(); file = new File(plugin.getDataFolder(), path + worldName + File.separator + id + ".txt"); - } else { - File oldFile = new File(plugin.getDataFolder(), path + pl.getName() + ".txt"); - if (oldFile.exists()) oldFile.delete(); - file = new File(plugin.getDataFolder(), path + id + ".txt"); - } + } else file = new File(plugin.getDataFolder(), path + id + ".txt"); Set items; StringBuilder builder; diff --git a/core/src/main/java/com/nisovin/magicspells/util/BlockUtils.java b/core/src/main/java/com/nisovin/magicspells/util/BlockUtils.java index 1c7497b76..03a9ed921 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/BlockUtils.java +++ b/core/src/main/java/com/nisovin/magicspells/util/BlockUtils.java @@ -1,16 +1,14 @@ package com.nisovin.magicspells.util; import java.util.List; -import java.util.ArrayList; import org.bukkit.Location; -import org.bukkit.Material; import org.bukkit.block.Block; -import org.bukkit.entity.FallingBlock; +import org.bukkit.entity.Entity; +import org.bukkit.util.BoundingBox; import org.bukkit.entity.LivingEntity; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.Powerable; -import org.bukkit.block.data.AnaloguePowerable; + +import org.jetbrains.annotations.NotNull; import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.MagicSpells; @@ -18,18 +16,6 @@ public class BlockUtils { - public static List getNearbyBlocks(Location location, int radius, int height) { - List blocks = new ArrayList<>(); - for (int x = location.getBlockX() - radius; x <= location.getBlockX() + radius; x++) { - for (int y = location.getBlockY() - height; y <= location.getBlockY() + height; y++) { - for (int z = location.getBlockZ() - radius; z <= location.getBlockZ() + radius; z++) { - blocks.add(location.getWorld().getBlockAt(x, y, z)); - } - } - } - return blocks; - } - public static Block getTargetBlock(Spell spell, LivingEntity entity, int range) { try { if (spell != null) return entity.getTargetBlock(spell.getLosTransparentBlocks(), range); @@ -49,47 +35,49 @@ public static List getLastTwoTargetBlock(Spell spell, LivingEntity entity } } - public static void setTypeAndData(Block block, Material material, BlockData data, boolean physics) { - block.setType(material); - block.setBlockData(data, physics); + public static Location adjustToSafeLocation(@NotNull Entity entity, @NotNull Location location) { + return adjustToSafeLocation(entity, location, 0, false); } - public static void setBlockFromFallingBlock(Block block, FallingBlock fallingBlock, boolean physics) { - BlockData blockData = fallingBlock.getBlockData(); - block.setType(blockData.getMaterial()); - block.setBlockData(blockData, physics); - } - - public static boolean isChest(Block block) { - return switch (block.getType()) { - case CHEST, TRAPPED_CHEST -> true; - default -> false; - }; - } + public static Location adjustToSafeLocation(@NotNull Entity entity, @NotNull Location location, int requireGroundDistance, boolean snapToGround) { + Location adjusted = location.clone(); - public static boolean isPathable(Material mat) { - return switch (mat) { - case LIGHT, SNOW -> true; - default -> mat.isBlock() && !mat.isCollidable(); - }; - } + // Check if the entity collides because the block's collision box. + if (entity.collidesAt(adjusted)) { + if (adjusted.getBlock().isPassable()) { + adjusted.subtract(0, 1, 0); + if (adjusted.getBlock().isPassable()) return null; + } - public static boolean isSafeToStand(Location location) { - if (!location.getBlock().isPassable()) return false; - if (!location.add(0, 1, 0).getBlock().isPassable()) return false; - return !location.subtract(0, 2, 0).getBlock().isPassable() || !location.subtract(0, 1, 0).getBlock().isPassable(); - } + adjusted = getYMaxCollision(adjusted); - public static void activatePowerable(Block block) { - if (block.getBlockData() instanceof Powerable powerable) { - powerable.setPowered(true); - block.setBlockData(powerable, true); + if (entity.collidesAt(adjusted)) return null; } - if (block.getBlockData() instanceof AnaloguePowerable powerable) { - powerable.setPower(powerable.getMaximumPower()); - block.setBlockData(powerable, true); + if (requireGroundDistance < 1 || !adjusted.getBlock().isPassable()) return adjusted; + + Location ground = adjusted.clone(); + for (int i = 1; i <= requireGroundDistance; i++) { + ground.subtract(0, 1, 0); + if (ground.getBlock().isPassable()) continue; + + return snapToGround ? getYMaxCollision(ground) : adjusted; } + + return null; + } + + private static Location getYMaxCollision(@NotNull Location location) { + // Note: getBoundingBoxes[i] is [0.0, x] + double maxCollisionY = location.getBlock() + .getCollisionShape() + .getBoundingBoxes() + .stream() + .mapToDouble(BoundingBox::getMaxY) + .max() + .orElse(0); + + return location.clone().add(0, maxCollisionY, 0); } } diff --git a/core/src/main/java/com/nisovin/magicspells/util/IntMap.java b/core/src/main/java/com/nisovin/magicspells/util/IntMap.java deleted file mode 100644 index ee566ab6d..000000000 --- a/core/src/main/java/com/nisovin/magicspells/util/IntMap.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.nisovin.magicspells.util; - -import java.util.HashMap; - -public class IntMap extends HashMap { - - /** - * Same as {@link HashMap#getOrDefault(Object, Object)} with a default value of 0. - * @return the value to which the specified key is mapped, or 0 if this map contains no mapping for the key - */ - @Override - public Integer get(Object key) { - return super.getOrDefault(key, 0); - } - - /** - * @return the previous value associated with {@code key}, or {@code 0} if there was no mapping for {@code key}. - */ - @Override - public Integer put(T key, Integer value) { - Integer prev = super.put(key, value); - return prev == null ? 0 : prev; - } - - /** - * @return the previous value associated with {@code key}, or {@code 0} if there was no mapping for {@code key}. - */ - @Override - public Integer remove(Object key) { - Integer prev = super.remove(key); - return prev == null ? 0 : prev; - } - - public int increment(T key) { - return increment(key, 1); - } - - public int increment(T key, int amount) { - return compute(key, (k, v) -> (v == null ? 0 : v) + amount); - } - - public int decrement(T key) { - return decrement(key, 1); - } - - public int decrement(T key, int amount) { - return compute(key, (k, v) -> (v == null ? 0 : v) - amount); - } - -} diff --git a/core/src/main/java/com/nisovin/magicspells/util/TemporaryBlockSet.java b/core/src/main/java/com/nisovin/magicspells/util/TemporaryBlockSet.java index c3e429a2f..f70e5c770 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/TemporaryBlockSet.java +++ b/core/src/main/java/com/nisovin/magicspells/util/TemporaryBlockSet.java @@ -65,7 +65,7 @@ public void add(Block block) { MagicSpellsBlockPlaceEvent event = null; if (livingEntity instanceof Player) event = new MagicSpellsBlockPlaceEvent(block, state, block, livingEntity.getEquipment().getItemInMainHand(), (Player) livingEntity, true); if (event != null) EventUtil.call(event); - if (event != null && event.isCancelled()) BlockUtils.setTypeAndData(block, original, original.createBlockData(), false); + if (event != null && event.isCancelled()) block.setType(original, false); else blocks.add(block); } diff --git a/core/src/main/java/com/nisovin/magicspells/util/managers/VariableManager.java b/core/src/main/java/com/nisovin/magicspells/util/managers/VariableManager.java index c001732f1..0f0696f64 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/managers/VariableManager.java +++ b/core/src/main/java/com/nisovin/magicspells/util/managers/VariableManager.java @@ -526,10 +526,6 @@ public void saveGlobalVariables() { public void loadPlayerVariables(String player, String uniqueId) { if (!folder.exists()) folder.mkdir(); File file = new File(folder, "PLAYER_" + uniqueId + ".txt"); - if (!file.exists()) { - File file2 = new File(folder, "PLAYER_" + player + ".txt"); - if (file2.exists()) file2.renameTo(file); - } if (!file.exists()) { dirtyPlayerVars.remove(player); return;