|
1 | 1 | package com.hanprogramer.androids.entities.android.scripting; |
2 | 2 |
|
3 | 3 | import com.hanprogramer.androids.entities.android.AndroidEntity; |
4 | | -import com.hanprogramer.androids.entities.android.util.ContainerExtractor; |
5 | | -import com.hanprogramer.androids.entities.android.util.Seeder; |
| 4 | +import com.hanprogramer.androids.entities.android.util.*; |
| 5 | +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; |
6 | 6 | import net.minecraft.block.BlockState; |
7 | 7 | import net.minecraft.block.entity.BlockEntity; |
8 | 8 | import net.minecraft.entity.Entity; |
|
13 | 13 | import net.minecraft.item.ItemStack; |
14 | 14 | import net.minecraft.item.Items; |
15 | 15 | import net.minecraft.registry.Registries; |
| 16 | +import net.minecraft.server.MinecraftServer; |
16 | 17 | import net.minecraft.server.world.ServerWorld; |
17 | 18 | import net.minecraft.text.Text; |
18 | 19 | import net.minecraft.util.Identifier; |
19 | 20 | import net.minecraft.util.math.BlockPos; |
20 | 21 | import net.minecraft.util.math.Box; |
21 | 22 | import net.minecraft.util.math.Vec3d; |
22 | 23 | import net.minecraft.world.World; |
| 24 | +import org.graalvm.polyglot.Context; |
23 | 25 | import org.graalvm.polyglot.HostAccess; |
24 | 26 | import org.graalvm.polyglot.Value; |
25 | 27 | import org.graalvm.polyglot.proxy.ProxyArray; |
| 28 | +import org.graalvm.polyglot.proxy.ProxyExecutable; |
26 | 29 | import org.jetbrains.annotations.NotNull; |
27 | 30 | import org.jetbrains.annotations.Nullable; |
28 | 31 |
|
29 | 32 | import java.lang.ref.WeakReference; |
30 | 33 | import java.util.ArrayList; |
31 | 34 | import java.util.List; |
32 | 35 | import java.util.Optional; |
| 36 | +import java.util.concurrent.CompletableFuture; |
| 37 | +import java.util.concurrent.atomic.AtomicInteger; |
33 | 38 | import java.util.function.Predicate; |
34 | 39 |
|
35 | 40 | // Minimal bridges with explicit exposure |
@@ -112,6 +117,131 @@ public void navigateTo(int @Nullable [] pos) throws Exception { |
112 | 117 | e.getNavigation().startMovingAlong(e.getNavigation().findPathTo(pos[0] + 0.5, pos[1], pos[2] + 0.5, 0), (double) 0.5F); |
113 | 118 | } |
114 | 119 | } |
| 120 | + @HostAccess.Export |
| 121 | + public Value navigateToAsync(int[] pos, int timeoutTicks, double acceptDistance) { |
| 122 | + AndroidEntity e = get(); |
| 123 | + if (e == null) { return null; } |
| 124 | + |
| 125 | + var jsCtx = e.getJsCtx(); |
| 126 | + MinecraftServer server = ((ServerWorld)e.getWorld()).getServer(); |
| 127 | + // call your original CF-returning method |
| 128 | + CompletableFuture<Boolean> cf = navigateToAsyncCF(pos, timeoutTicks, acceptDistance); |
| 129 | + // bridge to a JS Promise so 'await' works |
| 130 | + return JsInterop.toPromise(jsCtx, server, cf); |
| 131 | + } |
| 132 | + public CompletableFuture<Boolean> navigateToAsyncCF(int[] pos, int timeoutTicks, double acceptDistance) { |
| 133 | + AndroidEntity e = get(); |
| 134 | + if (e == null) return CompletableFuture.completedFuture(false); |
| 135 | + if (pos == null || pos.length < 3) { |
| 136 | + CompletableFuture<Boolean> f = new CompletableFuture<>(); |
| 137 | + f.completeExceptionally(new IllegalArgumentException("pos must be length >= 3")); |
| 138 | + return f; |
| 139 | + } |
| 140 | + if (e.getWorld().isClient()) return CompletableFuture.completedFuture(false); |
| 141 | + |
| 142 | + ServerWorld serverWorld = (ServerWorld) e.getWorld(); |
| 143 | + MinecraftServer server = serverWorld.getServer(); |
| 144 | + |
| 145 | + final double speed = 0.5; |
| 146 | + final double tx = pos[0] + 0.5; |
| 147 | + final double ty = pos[1] + 1.0; |
| 148 | + final double tz = pos[2] + 0.5; |
| 149 | + final Vec3d target = new Vec3d(tx, ty, tz); |
| 150 | + |
| 151 | + CompletableFuture<Boolean> result = new CompletableFuture<>(); |
| 152 | + |
| 153 | + // Start navigation on the server thread |
| 154 | + server.execute(() -> { |
| 155 | + try { |
| 156 | + e.getNavigation().startMovingTo(tx, ty, tz, speed); |
| 157 | + } catch (Throwable ex) { |
| 158 | + e.logMessage("Can't start navigation: " + ex.getMessage()); |
| 159 | + result.complete(false); |
| 160 | + return; |
| 161 | + } |
| 162 | + |
| 163 | + // Begin polling loop using Sleeper to wait ticks between checks |
| 164 | + // Obtain your Sleeper instance; adapt as needed (injected, singleton, etc.) |
| 165 | + Sleeper sleeper = getSleeperInstance(server); // <-- implement this to return your Sleeper |
| 166 | + final double acceptSq = acceptDistance * acceptDistance; |
| 167 | + final AtomicInteger ticksLeft = new AtomicInteger(Math.max(1, timeoutTicks)); |
| 168 | + final int retryEvery = 5; |
| 169 | + final AtomicInteger ticksSinceRetry = new AtomicInteger(0); |
| 170 | + |
| 171 | + // Polling function: checks conditions and either completes or waits one tick and re-checks |
| 172 | + Runnable[] pollRef = new Runnable[1]; |
| 173 | + pollRef[0] = () -> { |
| 174 | + try { |
| 175 | + // If future already completed, stop |
| 176 | + if (result.isDone()) return; |
| 177 | + |
| 178 | + if (!e.isAlive() || serverWorld.isClient()) { |
| 179 | + result.complete(false); |
| 180 | + return; |
| 181 | + } |
| 182 | + |
| 183 | + double sqDist = e.getPos().squaredDistanceTo(target); |
| 184 | + boolean closeEnough = sqDist <= acceptSq; |
| 185 | + boolean navIdle; |
| 186 | + try { |
| 187 | + navIdle = e.getNavigation().isIdle(); |
| 188 | + } catch (Throwable ex) { |
| 189 | + navIdle = true; |
| 190 | + } |
| 191 | + |
| 192 | + if (closeEnough) { |
| 193 | + result.complete(true); |
| 194 | + return; |
| 195 | + } |
| 196 | + |
| 197 | + if (!navIdle) { |
| 198 | + if (ticksLeft.decrementAndGet() <= 0) { |
| 199 | + e.logMessage("Can't reach target block, timeout"); |
| 200 | + result.complete(false); |
| 201 | + return; |
| 202 | + } |
| 203 | + // still navigating: wait 1 tick then poll again |
| 204 | + sleeper.sleepTicksCF(1).whenComplete((v, err) -> { |
| 205 | + if (err != null) { |
| 206 | + result.completeExceptionally(err); |
| 207 | + } else { |
| 208 | + // schedule next poll on server thread to ensure world-safety |
| 209 | + server.execute(pollRef[0]); |
| 210 | + } |
| 211 | + }); |
| 212 | + return; |
| 213 | + } |
| 214 | + |
| 215 | + // navIdle && not close enough -> cancel and log |
| 216 | + e.logMessage("Can't reach target block, navigation idle and not close enough"); |
| 217 | + result.complete(false); |
| 218 | + } catch (Throwable t) { |
| 219 | + result.completeExceptionally(t); |
| 220 | + } |
| 221 | + }; |
| 222 | + |
| 223 | + // schedule the first poll on the next tick so nav has time to begin |
| 224 | + sleeper.sleepTicksCF(1).whenComplete((v, err) -> { |
| 225 | + if (err != null) { |
| 226 | + result.completeExceptionally(err); |
| 227 | + } else { |
| 228 | + server.execute(pollRef[0]); |
| 229 | + } |
| 230 | + }); |
| 231 | + }); |
| 232 | + |
| 233 | + return result; |
| 234 | + } |
| 235 | + |
| 236 | + private static Sleeper sleeper; |
| 237 | + private Sleeper getSleeperInstance(MinecraftServer server) { |
| 238 | + if(sleeper == null) |
| 239 | + sleeper = new Sleeper(server); |
| 240 | + return sleeper; |
| 241 | + } |
| 242 | + |
| 243 | + |
| 244 | + |
115 | 245 |
|
116 | 246 | @HostAccess.Export |
117 | 247 | public Value exportBlockPos(String key, String displayName, int @Nullable [] defaultVal) throws Exception { |
@@ -316,7 +446,7 @@ public boolean destroyBlock(int[] _targetPos) throws Exception { |
316 | 446 | @HostAccess.Export |
317 | 447 | public boolean plantSeed(int[] targetPos, String seedId) { |
318 | 448 | var entity = get(); |
319 | | - if(entity == null) return false; |
| 449 | + if (entity == null) return false; |
320 | 450 | var pos = new BlockPos(targetPos[0], targetPos[1], targetPos[2]); |
321 | 451 | return Seeder.plantSeedById(entity, entity.getWorld(), pos, seedId); |
322 | 452 | } |
|
0 commit comments