Skip to content

Commit 0a3dc10

Browse files
authored
Merge pull request #52 from InstantlyMoist/fix/look-at-path
fix looking at issues
2 parents d2ba24f + 1bd6248 commit 0a3dc10

3 files changed

Lines changed: 182 additions & 126 deletions

File tree

spaceNPC/src/main/java/me/tofaa/entitylib/npc/NPC.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ public void spawn(@NotNull Location location) {
154154
.getEntityIdProvider()
155155
.provide(profile.getUUID(), entityType)
156156
);
157-
wrapperPlayer.setInTablist(true);
157+
wrapperPlayer.setInTablist(false);
158158
wrapperPlayer.setGameMode(
159159
GameMode.SURVIVAL
160160
);

spaceNPC/src/main/java/me/tofaa/entitylib/npc/NPCMovement.java

Lines changed: 151 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,22 @@
22

33
import com.github.retrooper.packetevents.PacketEventsAPI;
44
import com.github.retrooper.packetevents.protocol.world.Location;
5-
import com.github.retrooper.packetevents.util.Vector3f;
65
import java.util.Map;
76
import java.util.UUID;
8-
import java.util.concurrent.CompletableFuture;
97
import java.util.concurrent.ConcurrentHashMap;
10-
118
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityHeadLook;
129
import me.tofaa.entitylib.EntityLib;
13-
import me.tofaa.entitylib.meta.display.AbstractDisplayMeta;
1410
import me.tofaa.entitylib.movement.MovementEngine;
15-
import me.tofaa.entitylib.movement.MovementEngine.MovementSettings;
16-
import me.tofaa.entitylib.movement.MovementEngine.Path;
17-
import me.tofaa.entitylib.movement.MovementEngine.PathfindSettings;
1811
import me.tofaa.entitylib.movement.SpigotMovementEngine;
1912
import me.tofaa.entitylib.npc.path.NPCPath;
2013
import me.tofaa.entitylib.npc.NPCRegistry;
2114
import me.tofaa.entitylib.wrapper.WrapperEntity;
2215
import org.bukkit.Bukkit;
23-
import org.bukkit.Material;
2416
import org.bukkit.World;
2517
import org.bukkit.block.Block;
2618
import org.bukkit.scheduler.BukkitTask;
2719
import org.bukkit.entity.Player;
2820
import org.jetbrains.annotations.NotNull;
29-
import org.jetbrains.annotations.Nullable;
3021

3122
public class NPCMovement {
3223

@@ -103,15 +94,7 @@ private static void processViewerSync() {
10394
Location npcLocation = entity.getLocation();
10495

10596
npc.getHologram().ifPresent(hologram -> {
106-
double yOffset = npc.getOptions().isSitting() ? 2.76 : 2.26;
107-
Location holoLoc = new Location(
108-
npcLocation.getX(),
109-
npcLocation.getY() + yOffset,
110-
npcLocation.getZ(),
111-
npcLocation.getYaw(),
112-
npcLocation.getPitch()
113-
);
114-
hologram.teleport(holoLoc);
97+
hologram.setParent(npc.getEntity().get());
11598
});
11699

117100
boolean permanentlyVisible = npc.getOptions().isPermanentlyVisible();
@@ -169,26 +152,13 @@ private static void processAllNPCHeadRotation() {
169152
yaw = npcLocation.getYaw();
170153
}
171154
} else if (npc.getOptions().isLookAtPath()) {
172-
yaw = npcLocation.getYaw();
155+
// Gets handled by the movement itself
156+
continue;
173157
} else {
174158
continue;
175159
}
176160

177-
PacketEventsAPI<?> api = EntityLib.getApi().getPacketEvents();
178-
for (UUID viewerId : entity.getViewers()) {
179-
Player player = org.bukkit.Bukkit.getPlayer(viewerId);
180-
if (player == null || player.getWorld() != world) continue;
181-
182-
WrapperPlayServerEntityHeadLook headPacket = new WrapperPlayServerEntityHeadLook(
183-
entity.getEntityId(),
184-
yaw
185-
);
186-
187-
Object channel = api.getProtocolManager().getChannel(viewerId);
188-
if (channel != null) {
189-
api.getProtocolManager().sendPacket(channel, headPacket);
190-
}
191-
}
161+
entity.rotateHead(yaw, 0);
192162
}
193163
}
194164

@@ -210,92 +180,131 @@ private static void processPathFollowing() {
210180
.getEntity()
211181
.ifPresent(entity -> {
212182
Location current = entity.getLocation();
213-
double distance = Math.sqrt(
183+
184+
// Use horizontal (XZ) distance for waypoint arrival check
185+
double hDistSq =
214186
Math.pow(target.getX() - current.getX(), 2) +
215-
Math.pow(target.getY() - current.getY(), 2) +
216-
Math.pow(target.getZ() - current.getZ(), 2)
217-
);
187+
Math.pow(target.getZ() - current.getZ(), 2);
218188

219-
if (distance < 0.5) {
189+
if (hDistSq < 0.25 && Math.abs(target.getY() - current.getY()) < 1.5) {
220190
path.advanceToNext();
191+
return;
192+
}
193+
194+
double speed = npc.getOptions().getMovementSpeed() * 0.1;
195+
196+
// --- Horizontal movement (XZ only) ---
197+
double dx = target.getX() - current.getX();
198+
double dz = target.getZ() - current.getZ();
199+
double hLen = Math.sqrt(dx * dx + dz * dz);
200+
201+
double newX, newZ;
202+
if (hLen > 0.01) {
203+
dx /= hLen;
204+
dz /= hLen;
205+
newX = current.getX() + dx * speed;
206+
newZ = current.getZ() + dz * speed;
221207
} else {
222-
double speed =
223-
npc.getOptions().getMovementSpeed() * 0.1;
224-
double dx = target.getX() - current.getX();
225-
double dy = target.getY() - current.getY();
226-
double dz = target.getZ() - current.getZ();
227-
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
228-
229-
dx /= len;
230-
dy /= len;
231-
dz /= len;
232-
233-
// float yaw = (float) Math.toDegrees(Math.atan2(dz, dx));
234-
float yaw = getYawTowards(
235-
target,
236-
new org.bukkit.Location(
237-
null,
238-
target.getX(),
239-
target.getY(),
240-
target.getZ(),
241-
target.getYaw(),
242-
target.getPitch()
243-
)
244-
);
245-
246-
double newY = current.getY() + dy * speed;
247-
double newX = current.getX() + dx * speed;
248-
double newZ = current.getZ() + dz * speed;
249-
250-
if (npc.getOptions().isClampToGround()) {
251-
World world = npc.getWorld();
252-
if (world != null) {
253-
int cx = (int) Math.floor(newX);
254-
int cz = (int) Math.floor(newZ);
255-
int cy = (int) Math.floor(current.getY());
256-
257-
Block feet = world.getBlockAt(
258-
cx,
259-
cy,
260-
cz
261-
);
262-
Block below = world.getBlockAt(
263-
cx,
264-
cy - 1,
265-
cz
266-
);
267-
268-
if (
269-
below.getType() == Material.AIR
270-
) {
271-
newY = newY - 1;
272-
}
273-
274-
if (
275-
target.getY() > current.getY() + 0.1 &&
276-
feet.getType() != org.bukkit.Material.AIR
277-
) {
278-
newY = current.getY() + 0.5;
279-
}
208+
newX = current.getX();
209+
newZ = current.getZ();
210+
dx = 0;
211+
dz = 0;
212+
}
213+
214+
// --- Vertical movement (jump + gravity physics) ---
215+
double newY = current.getY();
216+
217+
if (npc.getOptions().isClampToGround()) {
218+
var world = npc.getWorld();
219+
if (world != null) {
220+
int bx = (int) Math.floor(newX);
221+
int bz = (int) Math.floor(newZ);
222+
int feetY = (int) Math.floor(current.getY());
223+
224+
// Check if there's a solid block ahead at feet level (obstacle)
225+
var blockAtFeet = world.getBlockAt(bx, feetY, bz);
226+
var blockAboveFeet = world.getBlockAt(bx, feetY + 1, bz);
227+
228+
boolean obstacleAhead = !blockAtFeet.isPassable()
229+
&& blockAboveFeet.isPassable();
230+
231+
// If we hit a 1-block obstacle and we're on the ground, jump
232+
if (obstacleAhead && follower.isOnGround()) {
233+
follower.jump();
280234
}
281-
}
282235

283-
Location newLoc = new Location(
284-
newX,
285-
newY,
286-
newZ,
287-
yaw,
288-
0
289-
);
236+
// Apply vertical physics (gravity + velocity)
237+
newY = follower.applyVerticalPhysics(current.getY());
238+
239+
// Resolve ground collision: find solid ground below new position
240+
int newFeetY = (int) Math.floor(newY);
241+
double groundLevel = findGroundLevel(world, bx, newFeetY, bz, feetY + 2);
290242

291-
entity.teleport(newLoc);
243+
if (newY <= groundLevel) {
244+
// Landed on ground
245+
newY = groundLevel;
246+
follower.land(groundLevel);
247+
} else {
248+
// Still airborne
249+
follower.setOnGround(false);
250+
}
292251

293-
entity.rotateHead(yaw, 0);
252+
// If jumping into a ceiling (block above head), stop upward velocity
253+
int headY = (int) Math.floor(newY + 1.8);
254+
var blockAtHead = world.getBlockAt(bx, headY, bz);
255+
if (!blockAtHead.isPassable() && follower.verticalVelocity > 0) {
256+
follower.verticalVelocity = 0;
257+
}
258+
259+
// If obstacle is 2+ blocks tall, don't move horizontally into it
260+
if (!blockAtFeet.isPassable() && !blockAboveFeet.isPassable()) {
261+
newX = current.getX();
262+
newZ = current.getZ();
263+
}
264+
}
265+
} else {
266+
// No ground clamping: use linear Y interpolation (original behavior)
267+
double dy = target.getY() - current.getY();
268+
double fullLen = Math.sqrt(dx * dx + dy * dy + dz * dz);
269+
if (fullLen > 0.01) {
270+
newY = current.getY() + (dy / fullLen) * speed;
271+
}
294272
}
273+
274+
float yaw = npc.getOptions().isLookAtPath() ? npc.getPath().getYaw() : current.getYaw();
275+
Location newLoc = new Location(
276+
newX,
277+
newY,
278+
newZ,
279+
yaw,
280+
0
281+
);
282+
283+
entity.teleport(newLoc);
284+
entity.rotateHead(yaw, 0);
295285
});
296286
}
297287
}
298288

289+
/**
290+
* Finds the Y level of the ground (top of highest solid block) at the given XZ,
291+
* searching downward from startY. Returns the Y on top of the first solid block found.
292+
* If no solid block is found down to y=minY or world min, returns startY (no change).
293+
*/
294+
private static double findGroundLevel(org.bukkit.World world, int bx, int startY, int bz, int maxY) {
295+
// Don't search too far down - limit to 4 blocks below current position
296+
int minSearch = Math.max(world.getMinHeight(), startY - 4);
297+
for (int y = startY; y >= minSearch; y--) {
298+
var block = world.getBlockAt(bx, y, bz);
299+
if (!block.isPassable()) {
300+
// Ground found: NPC stands on top of this block
301+
return y + 1;
302+
}
303+
}
304+
// No ground found within search range, keep falling
305+
return startY;
306+
}
307+
299308
private static void updateGlobalHeadRotation(
300309
NPC npc,
301310
WrapperEntity entity,
@@ -426,23 +435,6 @@ private static float getYawTowards(
426435
return (float) (Math.toDegrees(Math.atan2(dz, dx)) - 90);
427436
}
428437

429-
private static double getGroundY(
430-
org.bukkit.World world,
431-
double x,
432-
double y,
433-
double z
434-
) {
435-
int cx = (int) Math.floor(x);
436-
int cz = (int) Math.floor(z);
437-
for (int cy = (int) Math.floor(y); cy >= 0; cy--) {
438-
org.bukkit.block.Block block = world.getBlockAt(cx, cy, cz);
439-
if (block.getType() != org.bukkit.Material.AIR) {
440-
return cy + 1;
441-
}
442-
}
443-
return y;
444-
}
445-
446438
public static void startPathFollowing(NPC npc) {
447439
NPCPath path = npc.getPath();
448440
if (path.getWaypointCount() == 0) {
@@ -469,8 +461,13 @@ public static boolean isMoving(NPC npc) {
469461

470462
public static class PathFollowing {
471463

464+
private static final double GRAVITY = 0.08;
465+
private static final double JUMP_VELOCITY = 0.42;
466+
472467
private final NPC npc;
473468
private final long startTime;
469+
private double verticalVelocity = 0.0;
470+
private boolean onGround = true;
474471

475472
public PathFollowing(NPC npc) {
476473
this.npc = npc;
@@ -484,5 +481,35 @@ public PathFollowing(NPC npc) {
484481
public long getStartTime() {
485482
return startTime;
486483
}
484+
485+
public void jump() {
486+
if (onGround) {
487+
verticalVelocity = JUMP_VELOCITY;
488+
onGround = false;
489+
}
490+
}
491+
492+
public double applyVerticalPhysics(double currentY) {
493+
if (onGround && verticalVelocity <= 0) {
494+
verticalVelocity = 0;
495+
return currentY;
496+
}
497+
verticalVelocity -= GRAVITY;
498+
double newY = currentY + verticalVelocity;
499+
return newY;
500+
}
501+
502+
public void land(double groundY) {
503+
onGround = true;
504+
verticalVelocity = 0.0;
505+
}
506+
507+
public boolean isOnGround() {
508+
return onGround;
509+
}
510+
511+
public void setOnGround(boolean onGround) {
512+
this.onGround = onGround;
513+
}
487514
}
488515
}

0 commit comments

Comments
 (0)