chore: tony starkified clod
This commit is contained in:
Binary file not shown.
@@ -24,6 +24,9 @@ _last_build_time: float = 0.0
|
||||
_BUILD_SESSION_TIMEOUT = 3.0 # seconds of inactivity before session ends
|
||||
_build_session_origin: tuple[float, float, float] | None = None
|
||||
_watcher_task: asyncio.Task | None = None
|
||||
_build_bounds: dict | None = None # {"min_x", "max_x", "min_y", "max_y", "min_z", "max_z"}
|
||||
_build_flying: bool = False # whether current session uses flying mode
|
||||
_build_fly_xz: tuple[float, float] | None = None # fixed XZ while flying
|
||||
|
||||
|
||||
async def _request(method: str, path: str, **kwargs) -> Any:
|
||||
@@ -67,6 +70,10 @@ async def _look_at_pos(x: float, y: float, z: float) -> None:
|
||||
await _request("POST", "/look_at", json={"x": x, "y": y, "z": z})
|
||||
|
||||
|
||||
async def _set_bot_flying(flying: bool) -> None:
|
||||
await _request("POST", "/set_flying", json={"flying": flying})
|
||||
|
||||
|
||||
async def _get_bot_position() -> tuple[float, float, float] | None:
|
||||
data = await _request("GET", "/status")
|
||||
if isinstance(data, dict) and "position" in data:
|
||||
@@ -78,8 +85,42 @@ async def _get_bot_position() -> tuple[float, float, float] | None:
|
||||
def _compute_viewing_position(
|
||||
build_x: float, build_y: float, build_z: float,
|
||||
bot_x: float, bot_y: float, bot_z: float,
|
||||
bounds: dict | None = None,
|
||||
) -> tuple[float, float, float]:
|
||||
"""Compute a spot 5 blocks from the build, on the side the bot is on."""
|
||||
"""Compute a viewing spot outside the build volume.
|
||||
|
||||
When *bounds* is provided, position 3 blocks outside the nearest edge
|
||||
of the build footprint (on the side the bot is already on).
|
||||
Otherwise fall back to 5 blocks from the center point.
|
||||
"""
|
||||
if bounds is not None:
|
||||
min_x = bounds["min_x"]
|
||||
max_x = bounds["max_x"]
|
||||
min_z = bounds["min_z"]
|
||||
max_z = bounds["max_z"]
|
||||
center_x = (min_x + max_x) / 2.0
|
||||
center_z = (min_z + max_z) / 2.0
|
||||
dx = bot_x - center_x
|
||||
dz = bot_z - center_z
|
||||
if abs(dx) < 0.01 and abs(dz) < 0.01:
|
||||
dx = 1.0
|
||||
# Pick the axis the bot is farthest on
|
||||
if abs(dx) >= abs(dz):
|
||||
# Position outside X edge
|
||||
if dx >= 0:
|
||||
vx = max_x + 3.5
|
||||
else:
|
||||
vx = min_x - 3.5
|
||||
vz = center_z
|
||||
else:
|
||||
# Position outside Z edge
|
||||
vx = center_x
|
||||
if dz >= 0:
|
||||
vz = max_z + 3.5
|
||||
else:
|
||||
vz = min_z - 3.5
|
||||
return (vx, build_y, vz)
|
||||
|
||||
dx = bot_x - build_x
|
||||
dz = bot_z - build_z
|
||||
dist = math.hypot(dx, dz)
|
||||
@@ -90,35 +131,67 @@ def _compute_viewing_position(
|
||||
return (build_x + dx * scale, build_y, build_z + dz * scale)
|
||||
|
||||
|
||||
async def _ensure_build_session(x: float, y: float, z: float) -> None:
|
||||
async def _ensure_build_session(
|
||||
x: float, y: float, z: float,
|
||||
bounds: dict | None = None,
|
||||
flying: bool = False,
|
||||
) -> None:
|
||||
"""Start or continue a build session, managing state/teleport/look."""
|
||||
global _build_session_active, _last_build_time, _build_session_origin
|
||||
global _watcher_task
|
||||
global _watcher_task, _build_bounds, _build_flying, _build_fly_xz
|
||||
|
||||
async with _build_session_lock:
|
||||
_last_build_time = time.monotonic()
|
||||
|
||||
if _build_session_active:
|
||||
# Session already running — just look at the new target
|
||||
# Session already running
|
||||
_build_session_origin = (x, y, z)
|
||||
await _look_at_pos(x, y, z)
|
||||
if _build_flying and _build_fly_xz is not None:
|
||||
# Flying mode: keep XZ fixed, only update Y to rise with layers
|
||||
fx, fz = _build_fly_xz
|
||||
await _teleport_bot(fx, y, fz)
|
||||
# Look at the center of the build at the current layer height
|
||||
if _build_bounds:
|
||||
cx = (_build_bounds["min_x"] + _build_bounds["max_x"]) / 2.0
|
||||
cz = (_build_bounds["min_z"] + _build_bounds["max_z"]) / 2.0
|
||||
await _look_at_pos(cx, y, cz)
|
||||
else:
|
||||
await _look_at_pos(x, y, z)
|
||||
else:
|
||||
await _look_at_pos(x, y, z)
|
||||
return
|
||||
|
||||
# --- Start a new session ---
|
||||
_build_session_active = True
|
||||
_build_session_origin = (x, y, z)
|
||||
_build_bounds = bounds
|
||||
_build_flying = flying
|
||||
|
||||
await _set_bot_state("building")
|
||||
|
||||
pos = await _get_bot_position()
|
||||
if pos is not None:
|
||||
bx, by, bz = pos
|
||||
dist = math.sqrt((bx - x) ** 2 + (by - y) ** 2 + (bz - z) ** 2)
|
||||
if dist > 16:
|
||||
vx, vy, vz = _compute_viewing_position(x, y, z, bx, by, bz)
|
||||
await _teleport_bot(vx, vy, vz)
|
||||
|
||||
await _look_at_pos(x, y, z)
|
||||
if bounds is not None and flying:
|
||||
# Compute safe position outside build volume
|
||||
vx, vy, vz = _compute_viewing_position(x, y, z, bx, by, bz, bounds=bounds)
|
||||
_build_fly_xz = (vx, vz)
|
||||
await _set_bot_flying(True)
|
||||
await _teleport_bot(vx, y, vz)
|
||||
else:
|
||||
_build_fly_xz = None
|
||||
dist = math.sqrt((bx - x) ** 2 + (by - y) ** 2 + (bz - z) ** 2)
|
||||
if dist > 16:
|
||||
vx, vy, vz = _compute_viewing_position(x, y, z, bx, by, bz)
|
||||
await _teleport_bot(vx, vy, vz)
|
||||
|
||||
if bounds is not None:
|
||||
cx = (bounds["min_x"] + bounds["max_x"]) / 2.0
|
||||
cz = (bounds["min_z"] + bounds["max_z"]) / 2.0
|
||||
await _look_at_pos(cx, y, cz)
|
||||
else:
|
||||
await _look_at_pos(x, y, z)
|
||||
|
||||
# Start the watcher if not already running
|
||||
if _watcher_task is None or _watcher_task.done():
|
||||
@@ -128,6 +201,7 @@ async def _ensure_build_session(x: float, y: float, z: float) -> None:
|
||||
async def _build_session_watcher() -> None:
|
||||
"""Background task that ends the build session after inactivity."""
|
||||
global _build_session_active, _build_session_origin
|
||||
global _build_bounds, _build_flying, _build_fly_xz
|
||||
|
||||
while True:
|
||||
await asyncio.sleep(1.0)
|
||||
@@ -136,9 +210,14 @@ async def _build_session_watcher() -> None:
|
||||
return
|
||||
elapsed = time.monotonic() - _last_build_time
|
||||
if elapsed >= _BUILD_SESSION_TIMEOUT:
|
||||
if _build_flying:
|
||||
await _set_bot_flying(False)
|
||||
await _set_bot_state("idle")
|
||||
_build_session_active = False
|
||||
_build_session_origin = None
|
||||
_build_bounds = None
|
||||
_build_flying = False
|
||||
_build_fly_xz = None
|
||||
return
|
||||
|
||||
|
||||
@@ -366,12 +445,13 @@ async def build_schematic(name: str, x: int, y: int, z: int) -> str:
|
||||
layers = info.get("layers", [])
|
||||
fmt = info.get("format", "unknown")
|
||||
num_layers = len(layers)
|
||||
bounds = info.get("bounds") # may be None for empty schematics
|
||||
|
||||
if num_layers == 0:
|
||||
return _fmt({"ok": True, "message": "Schematic is empty, nothing to build.", "placed": 0})
|
||||
|
||||
# 2. Start build session and announce
|
||||
await _ensure_build_session(x, y, z)
|
||||
# 2. Start build session with flying + bounds and announce
|
||||
await _ensure_build_session(x, y, z, bounds=bounds, flying=True)
|
||||
await _request("POST", "/chat", json={
|
||||
"message": f"Starting build of {name}! {total_blocks} blocks, {num_layers} layers."
|
||||
})
|
||||
@@ -384,8 +464,8 @@ async def build_schematic(name: str, x: int, y: int, z: int) -> str:
|
||||
layer_y = layer["y"]
|
||||
block_count = layer["block_count"]
|
||||
|
||||
# Keep build session alive and look at current layer
|
||||
await _ensure_build_session(x, layer_y, z)
|
||||
# Keep build session alive; flying mode rises with each layer
|
||||
await _ensure_build_session(x, layer_y, z, bounds=bounds, flying=True)
|
||||
|
||||
# Check if this layer crosses a milestone threshold
|
||||
progress = (i + 1) / num_layers
|
||||
@@ -408,6 +488,7 @@ async def build_schematic(name: str, x: int, y: int, z: int) -> str:
|
||||
}, timeout=15.0)
|
||||
|
||||
if isinstance(result, dict) and "error" in result:
|
||||
await _set_bot_flying(False)
|
||||
await _request("POST", "/chat", json={
|
||||
"message": f"Build failed at layer {i + 1}/{num_layers}! Error: {result['error']}"
|
||||
})
|
||||
@@ -426,7 +507,8 @@ async def build_schematic(name: str, x: int, y: int, z: int) -> str:
|
||||
if i < num_layers - 1:
|
||||
await asyncio.sleep(0.4)
|
||||
|
||||
# 4. Announce completion
|
||||
# 4. Land and announce completion
|
||||
await _set_bot_flying(False)
|
||||
await _request("POST", "/chat", json={
|
||||
"message": f"Build complete! Placed {total_placed} blocks in {num_layers} layers."
|
||||
})
|
||||
|
||||
@@ -48,6 +48,66 @@ public class ClodEntityRenderer extends MobRenderer<ClodEntity, ClodModel> {
|
||||
double y = entity.getY();
|
||||
double z = entity.getZ();
|
||||
|
||||
// Rocket leg particles when flying
|
||||
if (entity.isClodFlying()) {
|
||||
float yawRad = entity.yBodyRot * ((float) Math.PI / 180F);
|
||||
double perpX = -Mth.cos(yawRad);
|
||||
double perpZ = -Mth.sin(yawRad);
|
||||
|
||||
// Right foot: 0.16 blocks to the right
|
||||
double rfX = x + perpX * 0.16;
|
||||
double rfZ = z + perpZ * 0.16;
|
||||
// Left foot: 0.16 blocks to the left
|
||||
double lfX = x - perpX * 0.16;
|
||||
double lfZ = z - perpZ * 0.16;
|
||||
double footY = y + 0.05;
|
||||
|
||||
// Flame jets: 3 per foot
|
||||
for (int i = 0; i < 3; i++) {
|
||||
double velY = -0.15 - entity.getRandom().nextDouble() * 0.10;
|
||||
entity.level().addParticle(ParticleTypes.FLAME,
|
||||
rfX + (entity.getRandom().nextDouble() - 0.5) * 0.08,
|
||||
footY,
|
||||
rfZ + (entity.getRandom().nextDouble() - 0.5) * 0.08,
|
||||
0.0, velY, 0.0);
|
||||
entity.level().addParticle(ParticleTypes.FLAME,
|
||||
lfX + (entity.getRandom().nextDouble() - 0.5) * 0.08,
|
||||
footY,
|
||||
lfZ + (entity.getRandom().nextDouble() - 0.5) * 0.08,
|
||||
0.0, velY, 0.0);
|
||||
}
|
||||
|
||||
// Smoke puffs: 50% chance per foot
|
||||
if (entity.getRandom().nextFloat() < 0.5F) {
|
||||
entity.level().addParticle(ParticleTypes.SMOKE,
|
||||
rfX, footY - 0.1, rfZ,
|
||||
0.0, -0.05, 0.0);
|
||||
}
|
||||
if (entity.getRandom().nextFloat() < 0.5F) {
|
||||
entity.level().addParticle(ParticleTypes.SMOKE,
|
||||
lfX, footY - 0.1, lfZ,
|
||||
0.0, -0.05, 0.0);
|
||||
}
|
||||
|
||||
// Orange glow: 30% chance per foot
|
||||
if (entity.getRandom().nextFloat() < 0.3F) {
|
||||
entity.level().addParticle(
|
||||
new DustParticleOptions(new Vector3f(1.0F, 0.6F, 0.2F), 0.8F),
|
||||
rfX + (entity.getRandom().nextDouble() - 0.5) * 0.15,
|
||||
footY - 0.05,
|
||||
rfZ + (entity.getRandom().nextDouble() - 0.5) * 0.15,
|
||||
0.0, -0.02, 0.0);
|
||||
}
|
||||
if (entity.getRandom().nextFloat() < 0.3F) {
|
||||
entity.level().addParticle(
|
||||
new DustParticleOptions(new Vector3f(1.0F, 0.6F, 0.2F), 0.8F),
|
||||
lfX + (entity.getRandom().nextDouble() - 0.5) * 0.15,
|
||||
footY - 0.05,
|
||||
lfZ + (entity.getRandom().nextDouble() - 0.5) * 0.15,
|
||||
0.0, -0.02, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case "idle":
|
||||
// 10% chance per frame — warm dust drifting upward
|
||||
|
||||
@@ -127,5 +127,14 @@ public class ClodModel extends HumanoidModel<ClodEntity> {
|
||||
this.leftArm.zRot = -0.08F - Mth.sin(ageInTicks * 0.067F) * 0.02F;
|
||||
break;
|
||||
}
|
||||
|
||||
// Flying hover pose — overrides leg animation from state switch
|
||||
if (entity.isClodFlying()) {
|
||||
float hoverBob = Mth.sin(ageInTicks * 0.1F) * 0.04F;
|
||||
this.rightLeg.xRot = 0.2F + hoverBob;
|
||||
this.leftLeg.xRot = 0.2F + hoverBob;
|
||||
this.rightLeg.zRot = 0.1F;
|
||||
this.leftLeg.zRot = -0.1F;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ public class ClodEntity extends PathfinderMob {
|
||||
private static final EntityDataAccessor<String> STATE =
|
||||
SynchedEntityData.defineId(ClodEntity.class, EntityDataSerializers.STRING);
|
||||
|
||||
private static final EntityDataAccessor<Boolean> FLYING =
|
||||
SynchedEntityData.defineId(ClodEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
|
||||
private final SimpleContainer inventory = new SimpleContainer(36);
|
||||
|
||||
public ClodEntity(EntityType<? extends PathfinderMob> type, Level level) {
|
||||
@@ -45,6 +48,7 @@ public class ClodEntity extends PathfinderMob {
|
||||
protected void defineSynchedData(SynchedEntityData.Builder builder) {
|
||||
super.defineSynchedData(builder);
|
||||
builder.define(STATE, "idle");
|
||||
builder.define(FLYING, false);
|
||||
}
|
||||
|
||||
public String getClodState() {
|
||||
@@ -57,6 +61,27 @@ public class ClodEntity extends PathfinderMob {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isClodFlying() {
|
||||
return this.entityData.get(FLYING);
|
||||
}
|
||||
|
||||
public void setClodFlying(boolean flying) {
|
||||
this.entityData.set(FLYING, flying);
|
||||
this.setNoGravity(flying);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
if (isClodFlying()) {
|
||||
this.setDeltaMovement(0, 0, 0);
|
||||
this.fallDistance = 0;
|
||||
} else if (this.isNoGravity() && "idle".equals(this.getClodState())) {
|
||||
// Safety reset: if gravity is off but we're idle and not flying, re-enable gravity
|
||||
this.setNoGravity(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvulnerable() {
|
||||
return true;
|
||||
|
||||
@@ -91,6 +91,8 @@ public class ClodHttpServer {
|
||||
httpServer.createContext("/get_time", this::handleGetTime);
|
||||
httpServer.createContext("/get_weather", this::handleGetWeather);
|
||||
|
||||
httpServer.createContext("/set_flying", this::handleSetFlying);
|
||||
|
||||
// New POST endpoints
|
||||
httpServer.createContext("/place_structure", this::handlePlaceStructure);
|
||||
httpServer.createContext("/clear_area", this::handleClearArea);
|
||||
@@ -850,6 +852,31 @@ public class ClodHttpServer {
|
||||
|
||||
runOnServerThread(() -> {
|
||||
bot.setClodState(state);
|
||||
// Safety: disable flying when going idle
|
||||
if ("idle".equals(state) && bot.isClodFlying()) {
|
||||
bot.setClodFlying(false);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
sendJson(exchange, 200, okJson());
|
||||
} catch (TimeoutException e) {
|
||||
sendJson(exchange, 504, errorJson("Server thread timeout"));
|
||||
} catch (Exception e) {
|
||||
sendJson(exchange, 400, errorJson(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSetFlying(HttpExchange exchange) throws IOException {
|
||||
if (!requirePost(exchange)) return;
|
||||
try {
|
||||
ClodEntity bot = requireBot(exchange);
|
||||
if (bot == null) return;
|
||||
|
||||
JsonObject body = readJsonBody(exchange);
|
||||
boolean flying = body.get("flying").getAsBoolean();
|
||||
|
||||
runOnServerThread(() -> {
|
||||
bot.setClodFlying(flying);
|
||||
return null;
|
||||
});
|
||||
sendJson(exchange, 200, okJson());
|
||||
@@ -1302,6 +1329,15 @@ public class ClodHttpServer {
|
||||
size.addProperty("y", maxY - minY + 1);
|
||||
size.addProperty("z", maxZ - minZ + 1);
|
||||
result.add("size", size);
|
||||
|
||||
JsonObject bounds = new JsonObject();
|
||||
bounds.addProperty("min_x", minX);
|
||||
bounds.addProperty("max_x", maxX);
|
||||
bounds.addProperty("min_y", minY);
|
||||
bounds.addProperty("max_y", maxY);
|
||||
bounds.addProperty("min_z", minZ);
|
||||
bounds.addProperty("max_z", maxZ);
|
||||
result.add("bounds", bounds);
|
||||
} else {
|
||||
JsonObject size = new JsonObject();
|
||||
size.addProperty("x", 0);
|
||||
|
||||
Reference in New Issue
Block a user