22
33import java .util .List ;
44import java .util .ArrayList ;
5+ import java .util .function .Predicate ;
56
67import org .bukkit .Material ;
78import org .bukkit .Location ;
89import org .bukkit .block .Block ;
10+ import org .bukkit .util .BoundingBox ;
11+ import org .bukkit .FluidCollisionMode ;
912import org .bukkit .util .BlockIterator ;
1013
1114import com .nisovin .magicspells .util .*;
1518
1619public class PhaseSpell extends InstantSpell {
1720
18- private final List <Material > phasableBlocks ;
19- private final List <Material > nonPhasableBlocks ;
20-
2121 private final ConfigData <Integer > maxDistance ;
22+ private final ConfigData <Integer > requireGroundDistance ;
23+
24+ private final ConfigData <Boolean > airOnlyExit ;
25+ private final ConfigData <Boolean > snapToGround ;
26+ private final ConfigData <Boolean > ignoreTransparentBlocks ;
2227 private final ConfigData <Boolean > powerAffectsMaxDistance ;
23- private String strCantPhase ;
28+
29+ private final String strCantPhase ;
30+
31+ private final List <Material > enterBlocks = new ArrayList <>();
32+ private final List <Material > nonExitBlocks = new ArrayList <>();
33+ private final List <Material > phasableBlocks = new ArrayList <>();
34+ private final List <Material > nonPhasableBlocks = new ArrayList <>();
2435
2536 public PhaseSpell (MagicConfig config , String spellName ) {
2637 super (config , spellName );
2738
2839 maxDistance = getConfigDataInt ("max-distance" , 15 );
29- strCantPhase = getConfigString ("str-cant-phase" , "Unable to find place to phase to." );
40+ requireGroundDistance = getConfigDataInt ("require-ground-distance" , 0 );
41+
42+ airOnlyExit = getConfigDataBoolean ("air-only-exit" , true );
43+ snapToGround = getConfigDataBoolean ("snap-to-ground" , false );
44+ ignoreTransparentBlocks = getConfigDataBoolean ("ignore-transparent-blocks" , false );
3045 powerAffectsMaxDistance = getConfigDataBoolean ("power-affects-max-distance" , true );
3146
32- phasableBlocks = new ArrayList <>();
33- nonPhasableBlocks = new ArrayList <>();
47+ strCantPhase = getConfigString ("str-cant-phase" , "Unable to find place to phase to." );
3448
49+ processMaterials (enterBlocks , "enter-blocks" );
50+ processMaterials (nonExitBlocks , "non-exit-blocks" );
3551 processMaterials (phasableBlocks , "phasable-blocks" );
3652 processMaterials (nonPhasableBlocks , "non-phasable-blocks" );
3753 }
3854
3955 private void processMaterials (List <Material > materials , String path ) {
40- List <String > matList = getConfigStringList (path , null );
41- if (matList == null || matList .isEmpty ()) return ;
42- for (String mat : matList ) {
56+ for (String mat : getConfigStringList (path , List .of ())) {
4357 Material material = Util .getMaterial (mat );
4458 if (material == null ) {
45- MagicSpells .error ("PhaseSpell has an invalid material specified on '" + path + "': " + mat );
59+ MagicSpells .error ("PhaseSpell '" + internalName + "' has an invalid material specified on '" + path + "': " + mat );
4660 continue ;
4761 }
4862 materials .add (material );
@@ -51,51 +65,68 @@ private void processMaterials(List<Material> materials, String path) {
5165
5266 @ Override
5367 public CastResult cast (SpellData data ) {
54- int r = getRange (data );
68+ int range = getRange (data );
69+ int rangeSquared = range * range ;
70+ Location casterLoc = data .caster ().getLocation ();
5571
5672 int distance = maxDistance .get (data );
5773 if (powerAffectsMaxDistance .get (data )) distance = Math .round (distance * data .power ());
74+ int distanceSquared = distance * distance ;
5875
5976 BlockIterator iter ;
6077 try {
61- iter = new BlockIterator (data .caster (), distance << 1 );
78+ iter = new BlockIterator (data .caster (), distance );
6279 } catch (IllegalStateException e ) {
63- sendMessage (strCantPhase , data . caster (), data );
80+ sendMessage (strCantPhase , data );
6481 return new CastResult (PostCastAction .ALREADY_HANDLED , data );
6582 }
6683
67- int i = 0 ;
68- Block start = null ;
69- Location location = null , casterLoc = data .caster ().getLocation ();
84+ Predicate <Location > transparent = ignoreTransparentBlocks .get (data ) ? isTransparent (data ) : l -> l .getBlock ().isEmpty ();
7085
71- while (i ++ < r << 1 && iter .hasNext ()) {
86+ while (iter .hasNext ()) {
7287 Block b = iter .next ();
73- if (b .getType ().isAir ()) continue ;
74- if (casterLoc .distanceSquared (b .getLocation ()) >= r * r ) continue ;
75- start = b ;
76- break ;
88+ Location loc = b .getLocation ();
89+
90+ if (enterBlocks .contains (b .getType ())) break ;
91+ if (transparent .test (loc )) continue ;
92+
93+ if (casterLoc .distanceSquared (loc ) < rangeSquared && canPassThrough (b )) break ;
94+
95+ sendMessage (strCantPhase , data );
96+ return new CastResult (PostCastAction .ALREADY_HANDLED , data );
7797 }
7898
79- if (start != null ) {
80- if (canPassThrough (start )) {
81- while (i ++ < distance << 1 && iter .hasNext ()) {
82- Block block = iter .next ();
83- if (block .getType ().isAir () && block .getRelative (0 , 1 , 0 ).getType ().isAir () && casterLoc .distanceSquared (block .getLocation ()) < distance * distance ) {
84- location = block .getLocation ();
85- break ;
86- }
87- if (!canPassThrough (block )) break ;
88- }
99+ Location location = null ;
100+ boolean airOnlyExit = this .airOnlyExit .get (data );
101+ boolean snapToGround = this .snapToGround .get (data );
102+ int requireGroundDistance = this .requireGroundDistance .get (data );
103+
104+ while (iter .hasNext ()) {
105+ Block block = iter .next ();
106+ Location loc = block .getLocation ().add (0.5 , 0 , 0.5 );
107+
108+ if (casterLoc .distanceSquared (loc ) >= distanceSquared ) break ;
109+
110+ Location adjusted = BlockUtils .adjustToSafeLocation (data .caster (), loc , requireGroundDistance , snapToGround );
111+ if (adjusted == null ) continue ;
112+
113+ BoundingBox box = data .caster ().getBoundingBox ();
114+ box .shift (adjusted .clone ().subtract (casterLoc ));
115+ if (!Util .hasCollisionsIn (data .caster ().getWorld (), box , false , FluidCollisionMode .NEVER ,
116+ b -> (airOnlyExit && !b .isEmpty ()) || nonExitBlocks .contains (b .getType ())
117+ )) {
118+ location = adjusted ;
119+ break ;
89120 }
121+
122+ if (!canPassThrough (adjusted .getBlock ())) break ;
90123 }
91124
92125 if (location == null ) {
93- sendMessage (strCantPhase , data . caster (), data );
126+ sendMessage (strCantPhase , data );
94127 return new CastResult (PostCastAction .ALREADY_HANDLED , data );
95128 }
96129
97- location .setX (location .getX () + 0.5 );
98- location .setZ (location .getZ () + 0.5 );
99130 location .setPitch (casterLoc .getPitch ());
100131 location .setYaw (casterLoc .getYaw ());
101132 data = data .location (location );
@@ -107,25 +138,8 @@ public CastResult cast(SpellData data) {
107138 }
108139
109140 private boolean canPassThrough (Block block ) {
110- // Check only blacklist.
111- if (phasableBlocks .isEmpty ()) return !nonPhasableBlocks .contains (block .getType ());
112- return phasableBlocks .contains (block .getType ()) && !nonPhasableBlocks .contains (block .getType ());
113- }
114-
115- public List <Material > getPhasableBlocks () {
116- return phasableBlocks ;
117- }
118-
119- public List <Material > getNonPhasableBlocks () {
120- return nonPhasableBlocks ;
121- }
122-
123- public String getStrCantPhase () {
124- return strCantPhase ;
125- }
126-
127- public void setStrCantPhase (String strCantPhase ) {
128- this .strCantPhase = strCantPhase ;
141+ Material type = block .getType ();
142+ return !nonPhasableBlocks .contains (type ) && (phasableBlocks .isEmpty () || phasableBlocks .contains (type ));
129143 }
130144
131145}
0 commit comments