Skip to content

Commit e274f29

Browse files
committed
feat: Improve collision and ground checks
1 parent 09666da commit e274f29

7 files changed

Lines changed: 100 additions & 33 deletions

File tree

‎core/src/main/java/com/nisovin/magicspells/spells/buff/DodgeSpell.java‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,14 @@ private void dodge(LivingEntity caster, ParticleProjectileTracker tracker, Spell
126126
Vector v = RandomUtils.getRandomCircleVector().multiply(distance);
127127
targetLoc.add(v);
128128
targetLoc.setDirection(caster.getLocation().getDirection());
129+
targetLoc = BlockUtils.adjustToSafeLocation(caster, targetLoc);
129130

130131
if (spellBeforeDodge != null) {
131132
SpellData castData = subData.builder().caster(caster).target(null).location(casterLoc).recipient(null).build();
132133
spellBeforeDodge.subcast(castData);
133134
}
134135

135-
if (!targetLoc.getBlock().isPassable() || !targetLoc.getBlock().getRelative(BlockFace.UP).isPassable()) return;
136+
if (targetLoc == null) return;
136137
caster.teleportAsync(targetLoc);
137138
addUseAndChargeCost(caster);
138139

‎core/src/main/java/com/nisovin/magicspells/spells/instant/GateSpell.java‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import org.bukkit.World;
44
import org.bukkit.Bukkit;
55
import org.bukkit.Location;
6-
import org.bukkit.block.Block;
76
import org.bukkit.entity.Vehicle;
87

98
import com.nisovin.magicspells.util.*;
@@ -84,8 +83,8 @@ public CastResult cast(SpellData data) {
8483
}
8584
MagicSpells.debug(3, "Gate location: " + location);
8685

87-
Block b = location.getBlock();
88-
if (!b.isPassable() || !b.getRelative(0, 1, 0).isPassable()) {
86+
location = BlockUtils.adjustToSafeLocation(data.caster(), location);
87+
if (location == null) {
8988
MagicSpells.error("GateSpell '" + internalName + "' has landing spot blocked!");
9089
sendMessage(strGateFailed, data.caster(), data);
9190
return new CastResult(PostCastAction.ALREADY_HANDLED, data);

‎core/src/main/java/com/nisovin/magicspells/spells/targeted/BlinkSpell.java‎

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.bukkit.Location;
44
import org.bukkit.block.Block;
5+
import org.bukkit.block.BlockFace;
56
import org.bukkit.util.RayTraceResult;
67

78
import com.nisovin.magicspells.util.*;
@@ -12,15 +13,21 @@
1213

1314
public class BlinkSpell extends TargetedSpell implements TargetedLocationSpell {
1415

15-
private final ConfigData<Boolean> passThroughCeiling;
16-
1716
private final String strCantBlink;
1817

18+
private final ConfigData<Integer> requireGroundDistance;
19+
20+
private final ConfigData<Boolean> snapToGround;
21+
private final ConfigData<Boolean> passThroughCeiling;
22+
1923
public BlinkSpell(MagicConfig config, String spellName) {
2024
super(config, spellName);
2125

2226
strCantBlink = getConfigString("str-cant-blink", "You can't blink there.");
2327

28+
requireGroundDistance = getConfigDataInt("require-ground-distance", 0);
29+
30+
snapToGround = getConfigDataBoolean("snap-to-ground", false);
2431
passThroughCeiling = getConfigDataBoolean("pass-through-ceiling", false);
2532
}
2633

@@ -30,21 +37,25 @@ public CastResult cast(SpellData data) {
3037
if (result == null) return noTarget(strCantBlink, data);
3138

3239
Block found = result.getHitBlock();
33-
Block prev = found.getRelative(result.getHitBlockFace());
40+
BlockFace face = result.getHitBlockFace();
41+
Block prev = found.getRelative(face);
3442

3543
Location loc = null;
36-
if (!passThroughCeiling.get(data) && found.getRelative(0, -1, 0).equals(prev) && prev.isPassable()) {
37-
Block under = prev.getRelative(0, -1, 0);
38-
if (under.isPassable()) loc = under.getLocation().add(0.5, 0, 0.5);
39-
} else if (found.getRelative(0, 1, 0).isPassable() && found.getRelative(0, 2, 0).isPassable()) {
40-
loc = found.getLocation().add(0.5, 1, 0.5);
41-
} else if (prev.isPassable() && prev.getRelative(0, 1, 0).isPassable()) {
42-
loc = prev.getLocation().add(0.5, 0, 0.5);
44+
45+
boolean snapToGround = this.snapToGround.get(data);
46+
int requireGroundDistance = this.requireGroundDistance.get(data);
47+
48+
// Under
49+
if (face == BlockFace.DOWN && !passThroughCeiling.get(data)) {
50+
Location target = prev.getLocation().add(0.5, -1, 0.5);
51+
loc = BlockUtils.adjustToSafeLocation(data.caster(), target, requireGroundDistance, snapToGround);
4352
}
44-
if (loc == null) return noTarget(strCantBlink, data);
53+
// Above
54+
if (loc == null) loc = BlockUtils.adjustToSafeLocation(data.caster(), found.getLocation().add(0.5, 0, 0.5));
55+
// Side
56+
if (loc == null) loc = BlockUtils.adjustToSafeLocation(data.caster(), prev.getLocation().add(0.5, 0, 0.5), requireGroundDistance, snapToGround);
4557

46-
loc.setPitch(data.caster().getPitch());
47-
loc.setYaw(data.caster().getYaw());
58+
if (loc == null) return noTarget(strCantBlink, data);
4859

4960
SpellTargetLocationEvent targetEvent = new SpellTargetLocationEvent(this, data, loc);
5061
if (!targetEvent.callEvent()) return noTarget(strCantBlink, targetEvent);

‎core/src/main/java/com/nisovin/magicspells/spells/targeted/ShadowstepSpell.java‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package com.nisovin.magicspells.spells.targeted;
22

33
import org.bukkit.Location;
4-
import org.bukkit.block.Block;
54
import org.bukkit.util.Vector;
6-
import org.bukkit.block.BlockFace;
75
import org.bukkit.entity.LivingEntity;
86

97
import com.nisovin.magicspells.util.*;
@@ -18,6 +16,10 @@ public class ShadowstepSpell extends TargetedSpell implements TargetedEntitySpel
1816

1917
private final ConfigData<Double> distance;
2018

19+
private final ConfigData<Boolean> snapToGround;
20+
21+
private final ConfigData<Integer> requireGroundDistance;
22+
2123
private final ConfigData<Vector> relativeOffset;
2224

2325
private final String strNoLandingSpot;
@@ -30,6 +32,10 @@ public ShadowstepSpell(MagicConfig config, String spellName) {
3032

3133
distance = getConfigDataDouble("distance", -1);
3234

35+
snapToGround = getConfigDataBoolean("snap-to-ground", false);
36+
37+
requireGroundDistance = getConfigDataInt("require-ground-distance", 0);
38+
3339
relativeOffset = getConfigDataVector("relative-offset", new Vector(-1, 0, 0));
3440

3541
strNoLandingSpot = getConfigString("str-no-landing-spot", "Cannot shadowstep there.");
@@ -60,8 +66,8 @@ public CastResult castAtEntity(SpellData data) {
6066
targetLoc.setPitch(pitch.get(data));
6167
targetLoc.setYaw(targetLoc.getYaw() + yaw.get(data));
6268

63-
Block b = targetLoc.getBlock();
64-
if (!b.isPassable() || !b.getRelative(BlockFace.UP).isPassable()) return noTarget(strNoLandingSpot, data);
69+
targetLoc = BlockUtils.adjustToSafeLocation(data.caster(), targetLoc, requireGroundDistance.get(data), snapToGround.get(data));
70+
if (targetLoc == null) return noTarget(strNoLandingSpot, data);
6571

6672
playSpellEffects(data.caster(), targetLoc, data);
6773
data.caster().teleportAsync(targetLoc);

‎core/src/main/java/com/nisovin/magicspells/spells/targeted/SummonSpell.java‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public class SummonSpell extends TargetedSpell implements TargetedEntitySpell, T
3737
private final Map<UUID, SummonData> pending;
3838

3939
private final ConfigData<Integer> maxAcceptDelay;
40+
private final ConfigData<Integer> requireGroundDistance;
4041

4142
private final ConfigData<Boolean> requireExactName;
4243
private final ConfigData<Boolean> requireAcceptance;
@@ -51,6 +52,7 @@ public SummonSpell(MagicConfig config, String spellName) {
5152
super(config, spellName);
5253

5354
maxAcceptDelay = getConfigDataInt("max-accept-delay", 90);
55+
requireGroundDistance = getConfigDataInt("require-ground-distance", 2);
5456

5557
requireExactName = getConfigDataBoolean("require-exact-name", false);
5658
requireAcceptance = getConfigDataBoolean("require-acceptance", true);
@@ -91,12 +93,6 @@ public CastResult cast(SpellData data) {
9193
return new CastResult(PostCastAction.ALREADY_HANDLED, data);
9294
}
9395

94-
// Check location
95-
if (!BlockUtils.isSafeToStand(landLoc.clone())) {
96-
sendMessage(strUsage, caster, data);
97-
return new CastResult(PostCastAction.ALREADY_HANDLED, data);
98-
}
99-
10096
// Get player
10197
LivingEntity target = requireExactName.get(data) ? Bukkit.getPlayerExact(targetName) : Bukkit.getPlayer(targetName);
10298
if (target == null || !validTargetList.canTarget(caster, target)) return noTarget(data);
@@ -107,6 +103,12 @@ public CastResult cast(SpellData data) {
107103
data = targetEvent.getSpellData();
108104
target = data.target();
109105

106+
landLoc = BlockUtils.adjustToSafeLocation(target, landLoc, requireGroundDistance.get(data), false);
107+
if (landLoc == null) {
108+
sendMessage(strUsage, caster, data);
109+
return new CastResult(PostCastAction.ALREADY_HANDLED, data);
110+
}
111+
110112
// Teleport player
111113
if (requireAcceptance.get(data)) {
112114
pending.put(target.getUniqueId(), new SummonData(landLoc, System.currentTimeMillis(), maxAcceptDelay.get(data), data));

‎core/src/main/java/com/nisovin/magicspells/spells/targeted/TeleportSpell.java‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public class TeleportSpell extends TargetedSpell implements TargetedEntitySpell
1717

1818
private final ConfigData<Vector> relativeOffset;
1919

20+
private final ConfigData<Integer> requireGroundDistance;
21+
2022
private final String strCantTeleport;
2123

2224
public TeleportSpell(MagicConfig config, String spellName) {
@@ -25,7 +27,9 @@ public TeleportSpell(MagicConfig config, String spellName) {
2527
yaw = getConfigDataFloat("yaw", 0);
2628
pitch = getConfigDataFloat("pitch", 0);
2729

28-
relativeOffset = getConfigDataVector("relative-offset", new Vector(0, 0.1, 0));
30+
requireGroundDistance = getConfigDataInt("require-ground-distance", 0);
31+
32+
relativeOffset = getConfigDataVector("relative-offset", new Vector());
2933

3034
strCantTeleport = getConfigString("str-cant-teleport", "");
3135
}
@@ -52,7 +56,8 @@ public CastResult castAtEntity(SpellData data) {
5256
targetLoc.setPitch(startLoc.getPitch() - pitch.get(data));
5357
targetLoc.setYaw(startLoc.getYaw() + yaw.get(data));
5458

55-
if (!targetLoc.getBlock().isPassable()) return noTarget(strCantTeleport, data);
59+
targetLoc = BlockUtils.adjustToSafeLocation(data.caster(), targetLoc, requireGroundDistance.get(data), false);
60+
if (targetLoc == null) return noTarget(strCantTeleport, data);
5661

5762
playSpellEffects(EffectPosition.CASTER, data.caster(), data);
5863
playSpellEffects(EffectPosition.TARGET, data.target(), data);

‎core/src/main/java/com/nisovin/magicspells/util/BlockUtils.java‎

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
import org.bukkit.Location;
77
import org.bukkit.Material;
88
import org.bukkit.block.Block;
9+
import org.bukkit.entity.Entity;
10+
import org.bukkit.util.BoundingBox;
911
import org.bukkit.entity.FallingBlock;
1012
import org.bukkit.entity.LivingEntity;
1113
import org.bukkit.block.data.BlockData;
1214
import org.bukkit.block.data.Powerable;
1315
import org.bukkit.block.data.AnaloguePowerable;
1416

17+
import org.jetbrains.annotations.NotNull;
18+
1519
import com.nisovin.magicspells.Spell;
1620
import com.nisovin.magicspells.MagicSpells;
1721
import com.nisovin.magicspells.handlers.DebugHandler;
@@ -74,10 +78,49 @@ public static boolean isPathable(Material mat) {
7478
};
7579
}
7680

77-
public static boolean isSafeToStand(Location location) {
78-
if (!location.getBlock().isPassable()) return false;
79-
if (!location.add(0, 1, 0).getBlock().isPassable()) return false;
80-
return !location.subtract(0, 2, 0).getBlock().isPassable() || !location.subtract(0, 1, 0).getBlock().isPassable();
81+
public static Location adjustToSafeLocation(@NotNull Entity entity, @NotNull Location location) {
82+
return adjustToSafeLocation(entity, location, 0, false);
83+
}
84+
85+
public static Location adjustToSafeLocation(@NotNull Entity entity, @NotNull Location location, int requireGroundDistance, boolean snapToGround) {
86+
Location adjusted = location.clone();
87+
88+
// Check if the entity collides because the block's collision box.
89+
if (entity.collidesAt(adjusted)) {
90+
if (adjusted.getBlock().isPassable()) {
91+
adjusted.subtract(0, 1, 0);
92+
if (adjusted.getBlock().isPassable()) return null;
93+
}
94+
95+
adjusted = getYMaxCollision(adjusted);
96+
97+
if (entity.collidesAt(adjusted)) return null;
98+
}
99+
100+
if (requireGroundDistance < 1 || !adjusted.getBlock().isPassable()) return adjusted;
101+
102+
Location ground = adjusted.clone();
103+
for (int i = 1; i <= requireGroundDistance; i++) {
104+
ground.subtract(0, 1, 0);
105+
if (ground.getBlock().isPassable()) continue;
106+
107+
return snapToGround ? getYMaxCollision(ground) : adjusted;
108+
}
109+
110+
return null;
111+
}
112+
113+
private static Location getYMaxCollision(@NotNull Location location) {
114+
// Note: getBoundingBoxes[i] is [0.0, x]
115+
double maxCollisionY = location.getBlock()
116+
.getCollisionShape()
117+
.getBoundingBoxes()
118+
.stream()
119+
.mapToDouble(BoundingBox::getMaxY)
120+
.max()
121+
.orElse(0);
122+
123+
return location.clone().add(0, maxCollisionY, 0);
81124
}
82125

83126
public static void activatePowerable(Block block) {

0 commit comments

Comments
 (0)