11package com .eternalcode .combat .fight .knockback ;
22
33import com .eternalcode .combat .config .implementation .PluginConfig ;
4- import com .eternalcode .combat .region .Point ;
54import com .eternalcode .combat .region .Region ;
65import com .eternalcode .combat .region .RegionProvider ;
76import com .eternalcode .commons .bukkit .scheduler .MinecraftScheduler ;
7+ import com .eternalcode .commons .scheduler .Task ;
88import io .papermc .lib .PaperLib ;
9- import java .time .Duration ;
10- import java .util .HashMap ;
11- import java .util .Map ;
12- import java .util .Optional ;
13- import java .util .UUID ;
149import org .bukkit .Location ;
10+ import org .bukkit .Material ;
1511import org .bukkit .entity .Player ;
1612import org .bukkit .event .player .PlayerTeleportEvent .TeleportCause ;
1713import org .bukkit .util .Vector ;
1814
15+ import java .time .Duration ;
16+ import java .util .*;
17+
1918public final class KnockbackService {
2019
2120 private final PluginConfig config ;
2221 private final MinecraftScheduler scheduler ;
2322 private final RegionProvider regionProvider ;
2423
2524 private final Map <UUID , Region > insideRegion = new HashMap <>();
25+ private final Set <UUID > fallbackActive = new HashSet <>();
2626
2727 public KnockbackService (PluginConfig config , MinecraftScheduler scheduler , RegionProvider regionProvider ) {
2828 this .config = config ;
@@ -31,57 +31,257 @@ public KnockbackService(PluginConfig config, MinecraftScheduler scheduler, Regio
3131 }
3232
3333 public void knockbackLater (Region region , Player player , Duration duration ) {
34- this .scheduler .runLater (() -> this .knockback (region , player ), duration );
34+ scheduler .runLater (() -> this .knockback (region , player ), duration );
35+ }
36+
37+ public void knockback (Region region , Player player ) {
38+
39+ if (player .isInsideVehicle ()) {
40+ player .leaveVehicle ();
41+ }
42+
43+ Location loc = player .getLocation ();
44+ Vector direction = getDirectionToEdge (region , loc );
45+
46+ if (config .knockback .dampenVelocity ) {
47+ player .setVelocity (player .getVelocity ().multiply (config .knockback .dampenFactor ));
48+ }
49+
50+ boolean onGround = Math .abs (player .getVelocity ().getY ()) < 0.08 ;
51+
52+ double y = onGround
53+ ? config .knockback .vertical
54+ : Math .min (player .getVelocity ().getY (), config .knockback .maxAirVertical );
55+
56+ Vector velocity = direction .multiply (config .knockback .multiplier ).setY (y );
57+
58+ player .setFallDistance (0 );
59+ player .setVelocity (velocity );
60+
61+
62+ if (config .knockback .useTeleport ) {
63+ scheduleSmartFallback (player , region );
64+ }
3565 }
3666
3767 public void forceKnockbackLater (Player player , Region region ) {
38- if (insideRegion .containsKey (player .getUniqueId ())) {
68+ UUID uuid = player .getUniqueId ();
69+
70+ if (insideRegion .containsKey (uuid )) {
3971 return ;
4072 }
4173
42- insideRegion .put (player . getUniqueId () , region );
74+ insideRegion .put (uuid , region );
4375
4476 scheduler .runLater (player .getLocation (), () -> {
45- insideRegion .remove (player .getUniqueId ());
4677
47- Location playerLocation = player .getLocation ();
48- if (!region .contains (playerLocation ) && !regionProvider .isInRegion (playerLocation )) {
78+ insideRegion .remove (uuid );
79+
80+ Location loc = player .getLocation ();
81+ double velocity = player .getVelocity ().lengthSquared ();
82+
83+ if (velocity > 0.02 ) {
4984 return ;
5085 }
5186
52- if (player .isInsideVehicle ()) {
53- player .leaveVehicle ();
87+ if (!region .contains (loc )) {
88+ return ;
89+ }
90+
91+ double distanceToEdge = getDistanceToEdge (region , loc );
92+
93+ if (distanceToEdge > 1.5 ) {
94+ return ;
95+ }
96+
97+ if (fallbackActive .contains (uuid )) {
98+ return ;
99+ }
100+
101+ Location generated = generate (
102+ player .getLocation (),
103+ Point2D .from (region .getMin ()),
104+ Point2D .from (region .getMax ()),
105+ 0
106+ );
107+
108+ Location safe = makeSafe (generated );
109+ if (safe == null || safe .getWorld () == null ) {
110+ return ;
54111 }
55112
56- Location location = generate ( playerLocation , Point2D . from ( region . getMin ()), Point2D . from ( region . getMax ()) );
113+ PaperLib . teleportAsync ( player , safe . clone (), TeleportCause . PLUGIN );
57114
58- PaperLib .teleportAsync (player , location , TeleportCause .PLUGIN );
59- }, this .config .knockback .forceDelay );
115+ }, config .knockback .forceDelay );
60116 }
61117
62- private Location generate (Location playerLocation , Point2D minX , Point2D maxX ) {
118+ private void scheduleSmartFallback (Player player , Region region ) {
119+ UUID uuid = player .getUniqueId ();
120+
121+ if (fallbackActive .contains (uuid )) {
122+ return ;
123+ }
124+
125+ fallbackActive .add (uuid );
126+
127+ final Task [] taskRef = new Task [1 ];
128+
129+ taskRef [0 ] = scheduler .timer (() -> {
130+
131+ Location check = player .getLocation ();
132+ double velocity = player .getVelocity ().lengthSquared ();
133+
134+ if (!region .contains (check )) {
135+ fallbackActive .remove (uuid );
136+ taskRef [0 ].cancel ();
137+ return ;
138+ }
139+
140+ if (velocity > 0.02 ) {
141+ return ;
142+ }
143+
144+ Location generated = generate (
145+ player .getLocation (),
146+ Point2D .from (region .getMin ()),
147+ Point2D .from (region .getMax ()),
148+ 0
149+ );
150+
151+ Location safe = makeSafe (generated );
152+ if (safe == null || safe .getWorld () == null ) {
153+ fallbackActive .remove (uuid );
154+ taskRef [0 ].cancel ();
155+ return ;
156+ }
157+
158+ PaperLib .teleportAsync (player , safe .clone (), TeleportCause .PLUGIN );
159+
160+ fallbackActive .remove (uuid );
161+ taskRef [0 ].cancel ();
162+
163+ },
164+ Duration .ofMillis (100 ),
165+ Duration .ofMillis (100 ));
166+ }
167+
168+ private Location makeSafe (Location loc ) {
169+ if (loc == null || loc .getWorld () == null ) return loc ;
170+
171+ return config .knockback .safeGroundCheck
172+ ? findSafeGround (loc )
173+ : loc .getWorld ().getHighestBlockAt (loc ).getLocation ().add (0 , config .knockback .groundOffset , 0 );
174+ }
175+
176+ private Location findSafeGround (Location loc ) {
177+
178+ if (loc .getWorld () == null ) return loc ;
179+
180+ Location check = loc .clone ();
181+ int minY = loc .getWorld ().getMinHeight ();
182+
183+ for (int y = check .getBlockY (); y > minY ; y --) {
184+ check .setY (y );
185+
186+ Material type = check .getBlock ().getType ();
187+ Material above = check .clone ().add (0 , 1 , 0 ).getBlock ().getType ();
188+ Material above2 = check .clone ().add (0 , 2 , 0 ).getBlock ().getType ();
189+
190+ if (type .isSolid ()
191+ && !config .knockback .unsafeGroundBlocks .contains (type )
192+ && above .isAir ()
193+ && above2 .isAir ()) {
194+
195+ return check .clone ().add (0 , config .knockback .groundOffset , 0 );
196+ }
197+ }
198+
199+ return getSafeHighest (loc );
200+ }
201+
202+ private Location getSafeHighest (Location loc ) {
203+ if (loc == null || loc .getWorld () == null ) return loc ;
204+
205+ if (!config .knockback .safeHighestFallback ) {
206+ return loc .getWorld ()
207+ .getHighestBlockAt (loc )
208+ .getLocation ()
209+ .add (0 , config .knockback .groundOffset , 0 );
210+ }
211+
212+ Location highest = loc .getWorld ().getHighestBlockAt (loc ).getLocation ();
213+ int minY = loc .getWorld ().getMinHeight ();
214+
215+ int startY = highest .getBlockY ();
216+ int maxScan = config .knockback .safeHighestMaxScan ;
217+
218+ int endY = (maxScan < 0 )
219+ ? minY
220+ : Math .max (minY , startY - maxScan );
221+
222+ for (int y = startY ; y > endY ; y --) {
223+ highest .setY (y );
224+
225+ Material type = highest .getBlock ().getType ();
226+ Material above = highest .clone ().add (0 , 1 , 0 ).getBlock ().getType ();
227+ Material above2 = highest .clone ().add (0 , 2 , 0 ).getBlock ().getType ();
228+
229+ if (type .isSolid ()
230+ && !config .knockback .unsafeGroundBlocks .contains (type )
231+ && above .isAir ()
232+ && above2 .isAir ()) {
233+
234+ return highest .clone ().add (0 , config .knockback .groundOffset , 0 );
235+ }
236+ }
237+
238+ return config .knockback .cancelIfNoSafeGround ? null : loc ;
239+ }
240+
241+ private Location generate (Location playerLocation , Point2D minX , Point2D maxX , int attempts ) {
242+ if (attempts >= config .knockback .maxAttempts ) {
243+ return playerLocation ;
244+ }
245+
63246 Location location = KnockbackOutsideRegionGenerator .generate (minX , maxX , playerLocation );
247+
64248 Optional <Region > otherRegion = regionProvider .getRegion (location );
65249 if (otherRegion .isPresent ()) {
250+
66251 Region region = otherRegion .get ();
67- return generate (playerLocation , minX .min (region .getMin ()), maxX .max (region .getMax ()));
252+
253+ return generate (
254+ playerLocation ,
255+ minX .min (region .getMin ()),
256+ maxX .max (region .getMax ()),
257+ attempts + 1
258+ );
68259 }
69260
70261 return location ;
71262 }
72263
73- public void knockback (Region region , Player player ) {
74- if (player .isInsideVehicle ()) {
75- player .leaveVehicle ();
76- }
264+ private Vector getDirectionToEdge (Region region , Location loc ) {
265+ double dxMin = loc .getX () - region .getMin ().getX ();
266+ double dxMax = region .getMax ().getX () - loc .getX ();
267+ double dzMin = loc .getZ () - region .getMin ().getZ ();
268+ double dzMax = region .getMax ().getZ () - loc .getZ ();
269+
270+ double min = Math .min (Math .min (dxMin , dxMax ), Math .min (dzMin , dzMax ));
77271
78- Point point = region .getCenter ();
79- Location subtract = player .getLocation ().subtract (point .x (), 0 , point .z ());
272+ if (Math .abs (min - dxMin ) < 1e-6 ) return new Vector (-1 , 0 , 0 );
273+ if (Math .abs (min - dxMax ) < 1e-6 ) return new Vector (1 , 0 , 0 );
274+ if (Math .abs (min - dzMin ) < 1e-6 ) return new Vector (0 , 0 , -1 );
275+
276+ return new Vector (0 , 0 , 1 );
277+ }
80278
81- Vector knockbackVector = new Vector (subtract .getX (), 0 , subtract .getZ ()).normalize ();
82- double multiplier = this .config .knockback .multiplier ;
83- Vector configuredVector = new Vector (multiplier , 0.5 , multiplier );
279+ private double getDistanceToEdge (Region region , Location loc ) {
280+ double dxMin = loc .getX () - region .getMin ().getX ();
281+ double dxMax = region .getMax ().getX () - loc .getX ();
282+ double dzMin = loc .getZ () - region .getMin ().getZ ();
283+ double dzMax = region .getMax ().getZ () - loc .getZ ();
84284
85- player . setVelocity ( knockbackVector . multiply ( configuredVector ));
285+ return Math . min ( Math . min ( dxMin , dxMax ), Math . min ( dzMin , dzMax ));
86286 }
87287}
0 commit comments