aaa
This commit is contained in:
parent
f1c3af2b89
commit
2636d580a8
75 changed files with 3890 additions and 1084 deletions
|
|
@ -5,9 +5,9 @@
|
|||
if (!World) throw new Error("World is not available for mixin: world_environment.js");
|
||||
|
||||
const Geometry = global.TarinaiGeometry;
|
||||
const BOUNCE_FENCE_RESTITUTION = 2.40;
|
||||
const BOUNCE_FENCE_MIN_SPEED = 520;
|
||||
const BOUNCE_FENCE_MAX_SPEED = 1180;
|
||||
const BOUNCE_FENCE_RESTITUTION = 4.20;
|
||||
const BOUNCE_FENCE_MIN_SPEED = 820;
|
||||
const BOUNCE_FENCE_MAX_SPEED = 1680;
|
||||
|
||||
function angleForItemLocal(it) {
|
||||
return itemAngleFor(it);
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
if (!it) return Infinity;
|
||||
const type = String(it.type || "");
|
||||
let reach = Math.max(32, Number(it.r || it.radius || 32) || 32);
|
||||
if (type === "fence" || type === "fence_v" || type === "glass_wall" || type === "bounce_fence" || type === "bounce_fence_v" || type === "gate_fence") reach = Math.max(120, reach * 3.9);
|
||||
if (type === "fence" || type === "fence_v" || type === "fence_h" || type === "glass_wall" || type === "bounce_fence" || type === "bounce_fence_v" || type === "gate_fence" || type === "one_way_fence") reach = Math.max(120, reach * 3.9);
|
||||
else if (type === "nest_box" || type === "pipe") reach = Math.max(92, reach * 1.9);
|
||||
else reach = global.TarinaiMechanicalShapeSystem.reach(it) || Math.max(60, reach * 1.6);
|
||||
return Math.max(0, distXY(x, y, Number(it.x) || 0, Number(it.y) || 0) - reach);
|
||||
|
|
@ -30,6 +30,14 @@
|
|||
return Math.hypot((Number(x) || 0) - px, (Number(y) || 0) - py);
|
||||
}
|
||||
|
||||
function nearestRectFaceDirection(left, right, top, bottom) {
|
||||
const depth = Math.min(left, right, top, bottom);
|
||||
if (depth === left) return { dx: -1, dy: 0, depth };
|
||||
if (depth === right) return { dx: 1, dy: 0, depth };
|
||||
if (depth === top) return { dx: 0, dy: -1, depth };
|
||||
return { dx: 0, dy: 1, depth };
|
||||
}
|
||||
|
||||
function visitNearestObstaclesLocal(x, y, candidates, limit, visitor, opts = {}) {
|
||||
if (!candidates || typeof visitor !== "function") return 0;
|
||||
const source = candidates;
|
||||
|
|
@ -152,6 +160,35 @@
|
|||
return hit ? { t: hit.t, nx: hit.nx, ny: hit.ny, rect: r } : null;
|
||||
}
|
||||
|
||||
function nearbyMechanicalObstacleRectsLocal(worldRef, x, y, radius, opts = {}) {
|
||||
if (!worldRef?.itemsOfType || !worldRef?.solidObstacleRects) return [];
|
||||
const maxChecks = Math.max(1, Math.min(32, Number(opts.maxChecks || 0) || 10));
|
||||
const maxRects = Math.max(12, Math.min(128, Number(opts.maxRects || 0) || 72));
|
||||
const rects = [];
|
||||
const candidates = [];
|
||||
for (const type of ["rotator", "poison_block", "reciprocator"]) {
|
||||
for (const it of worldRef.itemsOfType(type) || []) {
|
||||
if (!it || it.dead || it === opts.exclude) continue;
|
||||
const reach = global.TarinaiMechanicalShapeSystem.reach(it) || Math.max(60, Number(it.r || it.radius || 64) || 64);
|
||||
const d = Math.max(0, distXY(x, y, Number(it.x) || 0, Number(it.y) || 0) - reach);
|
||||
if (d > radius + 72) continue;
|
||||
candidates.push({ it, d });
|
||||
}
|
||||
}
|
||||
candidates.sort((a, b) => a.d - b.d);
|
||||
let checked = 0;
|
||||
for (const entry of candidates) {
|
||||
if (checked >= maxChecks || rects.length >= maxRects) break;
|
||||
checked += 1;
|
||||
for (const rect of worldRef.solidObstacleRects(entry.it) || []) {
|
||||
if (rectDistanceToPointLocal(rect, x, y) > radius + 48) continue;
|
||||
rects.push(rect);
|
||||
if (rects.length >= maxRects) break;
|
||||
}
|
||||
}
|
||||
return rects;
|
||||
}
|
||||
|
||||
function firstSweptCircleObstacleHitLocal(worldRef, t, x1, y1, x2, y2, radius, opts = {}) {
|
||||
if (!worldRef?.nearbySolidObstacleRects) return null;
|
||||
const midX = (x1 + x2) * 0.5;
|
||||
|
|
@ -159,12 +196,27 @@
|
|||
const travel = Math.hypot(x2 - x1, y2 - y1);
|
||||
const searchRadius = Math.max(Number(opts.searchRadius || 0) || 0, radius + travel * 0.5 + 180);
|
||||
const maxChecks = Math.max(4, Number(opts.maxChecks || 0) || 48);
|
||||
const maxRects = Math.max(18, Math.min(128, Number(opts.maxRects || 0) || Math.ceil(maxChecks * 2.5)));
|
||||
const mechanicalPadding = Math.max(1.75, Math.min(6.5, radius * 0.24));
|
||||
let best = null;
|
||||
for (const rect of worldRef.nearbySolidObstacleRects(midX, midY, searchRadius, { maxChecks })) {
|
||||
if (rect?.type === "nest_box" && worldRef.shouldIgnoreNestBoxCollisionFor?.(t, rect.item)) continue;
|
||||
const hit = sweptCircleRectHitLocal(x1, y1, x2, y2, radius, rect, 0.5);
|
||||
if (!hit) continue;
|
||||
const testRect = (rect) => {
|
||||
if (rect?.type === "nest_box" && worldRef.shouldIgnoreNestBoxCollisionFor?.(t, rect.item)) return;
|
||||
if (rect?.oneWay && worldRef.oneWayFenceAllowsPath?.(x1, y1, x2, y2, rect)) return;
|
||||
const hit = sweptCircleRectHitLocal(x1, y1, x2, y2, radius, rect, rect?.mechanical ? mechanicalPadding : 0.5);
|
||||
if (!hit) return;
|
||||
if (!best || hit.t < best.t) best = hit;
|
||||
};
|
||||
for (const rect of worldRef.nearbySolidObstacleRects(midX, midY, searchRadius, { maxChecks, maxRects })) {
|
||||
testRect(rect);
|
||||
}
|
||||
if (opts.mechanicalFallback !== false) {
|
||||
const mechanicalSearchRadius = Math.max(searchRadius, radius + travel + 140);
|
||||
for (const rect of nearbyMechanicalObstacleRectsLocal(worldRef, midX, midY, mechanicalSearchRadius, {
|
||||
maxChecks: Math.max(6, Math.min(18, Math.ceil(maxChecks * 0.55))),
|
||||
maxRects: Math.max(32, maxRects),
|
||||
})) {
|
||||
testRect(rect);
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
|
@ -266,6 +318,7 @@
|
|||
const bounce = type === "bounce_fence" || type === "bounce_fence_v";
|
||||
const gate = type === "gate_fence";
|
||||
const glassWall = type === "glass_wall";
|
||||
const oneWay = type === "one_way_fence";
|
||||
const len = Math.max(112, (it.r || 42) * 3.55);
|
||||
const thick = Math.max(10, (it.r || 42) * 0.31);
|
||||
const angle = angleForItemLocal(it);
|
||||
|
|
@ -276,7 +329,7 @@
|
|||
left: aabb.left, right: aabb.right, top: aabb.top, bottom: aabb.bottom,
|
||||
cx: it.x || 0, cy: it.y || 0, halfW, halfH, angle, cos: aabb.cos, sin: aabb.sin, oriented: true,
|
||||
vertical: Math.abs(Math.sin(angle)) > Math.abs(Math.cos(angle)), horizontal: Math.abs(Math.cos(angle)) >= Math.abs(Math.sin(angle)),
|
||||
bounce, gate, glassWall, gateOpen: Boolean(it.gateOpen),
|
||||
bounce, gate, glassWall, oneWay, oneWayNx: Math.sin(angle), oneWayNy: -Math.cos(angle), gateOpen: Boolean(it.gateOpen),
|
||||
restitution: bounce ? BOUNCE_FENCE_RESTITUTION : undefined, minBounceSpeed: bounce ? BOUNCE_FENCE_MIN_SPEED : undefined
|
||||
};
|
||||
},
|
||||
|
|
@ -506,7 +559,7 @@
|
|||
addRectsFor(it);
|
||||
return checked >= queryLimit;
|
||||
}, { include, exclude, stamp });
|
||||
if (checked < queryLimit && !this.nearbyObstacles) {
|
||||
if (checked < queryLimit) {
|
||||
const mech = global.TarinaiMechanicalSystem;
|
||||
const fallback = [];
|
||||
for (const type of ["rotator", "poison_block", "reciprocator"]) {
|
||||
|
|
@ -608,10 +661,16 @@
|
|||
return removed;
|
||||
},
|
||||
|
||||
oneWayFenceAllows(entity, rect, motionX = null, motionY = null) {
|
||||
return Geometry.oneWayFenceAllows(entity, rect, motionX, motionY);
|
||||
},
|
||||
|
||||
pushTarinaiOutOfRect(t, r, rr, opts = {}) {
|
||||
if (!t || !r) return false;
|
||||
if (this.oneWayFenceAllows?.(t, r, opts.oneWayDx, opts.oneWayDy)) return false;
|
||||
const preVx = Number(t.vx || 0) || 0;
|
||||
const preVy = Number(t.vy || 0) || 0;
|
||||
const hitRadius = Math.max(1, rr + (r.mechanical ? Math.max(2.5, Math.min(6.5, rr * 0.24)) : 0));
|
||||
let cx, cy, dx, dy, d, nx, ny;
|
||||
let insidePenetration = 0;
|
||||
if (r.oriented) {
|
||||
|
|
@ -621,18 +680,16 @@
|
|||
dx = local.x - clx;
|
||||
dy = local.y - cly;
|
||||
d = Math.hypot(dx, dy);
|
||||
if (d >= rr) return false;
|
||||
if (d >= hitRadius) return false;
|
||||
if (d < 0.001) {
|
||||
const left = Math.abs(local.x + (r.halfW || 0));
|
||||
const right = Math.abs((r.halfW || 0) - local.x);
|
||||
const top = Math.abs(local.y + (r.halfH || 0));
|
||||
const bottom = Math.abs((r.halfH || 0) - local.y);
|
||||
const m = Math.min(left, right, top, bottom);
|
||||
insidePenetration = m;
|
||||
if (m === left) { dx = -1; dy = 0; d = 1; }
|
||||
else if (m === right) { dx = 1; dy = 0; d = 1; }
|
||||
else if (m === top) { dx = 0; dy = -1; d = 1; }
|
||||
else { dx = 0; dy = 1; d = 1; }
|
||||
const face = nearestRectFaceDirection(
|
||||
Math.abs(local.x + (r.halfW || 0)),
|
||||
Math.abs((r.halfW || 0) - local.x),
|
||||
Math.abs(local.y + (r.halfH || 0)),
|
||||
Math.abs((r.halfH || 0) - local.y)
|
||||
);
|
||||
insidePenetration = face.depth;
|
||||
dx = face.dx; dy = face.dy; d = 1;
|
||||
}
|
||||
const n = Geometry.rectWorldNormal(r, dx / d, dy / d);
|
||||
nx = n.x; ny = n.y;
|
||||
|
|
@ -643,64 +700,82 @@
|
|||
dx = t.x - cx;
|
||||
dy = t.y - cy;
|
||||
d = Math.hypot(dx, dy);
|
||||
if (d >= rr) return false;
|
||||
if (d >= hitRadius) return false;
|
||||
if (d < 0.001) {
|
||||
const left = Math.abs(t.x - r.left);
|
||||
const right = Math.abs(r.right - t.x);
|
||||
const top = Math.abs(t.y - r.top);
|
||||
const bottom = Math.abs(r.bottom - t.y);
|
||||
const m = Math.min(left, right, top, bottom);
|
||||
if (m === left) { dx = -1; dy = 0; d = 1; }
|
||||
else if (m === right) { dx = 1; dy = 0; d = 1; }
|
||||
else if (m === top) { dx = 0; dy = -1; d = 1; }
|
||||
else { dx = 0; dy = 1; d = 1; }
|
||||
const face = nearestRectFaceDirection(
|
||||
Math.abs(t.x - r.left),
|
||||
Math.abs(r.right - t.x),
|
||||
Math.abs(t.y - r.top),
|
||||
Math.abs(r.bottom - t.y)
|
||||
);
|
||||
dx = face.dx; dy = face.dy; d = 1;
|
||||
}
|
||||
nx = dx / d;
|
||||
ny = dy / d;
|
||||
}
|
||||
const slop = Number.isFinite(opts.slop) ? opts.slop : 0.8;
|
||||
const defaultMaxPush = r.oriented && insidePenetration > 0 ? Math.max(18, rr * 2.8) : Math.max(10, rr * 0.92);
|
||||
const defaultMaxPush = r.oriented && insidePenetration > 0 ? Math.max(18, hitRadius * 2.8) : Math.max(10, hitRadius * 0.92);
|
||||
const maxPush = Math.max(1, Number.isFinite(opts.maxPush) ? opts.maxPush : defaultMaxPush);
|
||||
const push = r.oriented && insidePenetration > 0
|
||||
? Math.min(insidePenetration + rr + slop, maxPush)
|
||||
: Math.min(rr - d + slop, maxPush);
|
||||
? Math.min(insidePenetration + hitRadius + slop, maxPush)
|
||||
: Math.min(hitRadius - d + slop, maxPush);
|
||||
t.x += nx * push;
|
||||
t.y += ny * push;
|
||||
t.lastSolidObstacleCollisionSource = r.item || null;
|
||||
t.lastSolidObstacleCollisionAt = this.time || 0;
|
||||
if (r.bounce) {
|
||||
const restitution = Number.isFinite(r.restitution) ? r.restitution : BOUNCE_FENCE_RESTITUTION;
|
||||
const vx = t.vx || 0;
|
||||
const vy = t.vy || 0;
|
||||
const toward = vx * nx + vy * ny;
|
||||
if (toward < 0) {
|
||||
t.vx = vx - (1 + restitution) * toward * nx;
|
||||
t.vy = vy - (1 + restitution) * toward * ny;
|
||||
} else {
|
||||
const kick = Math.max(46, Math.min(120, rr * 3.2));
|
||||
t.vx = vx + nx * kick;
|
||||
t.vy = vy + ny * kick;
|
||||
}
|
||||
const minBounceSpeed = Math.max(0, Number(r.minBounceSpeed || 0) || 0);
|
||||
if (minBounceSpeed > 0) {
|
||||
const currentOut = (t.vx || 0) * nx + (t.vy || 0) * ny + (t.impulseVx || 0) * nx + (t.impulseVy || 0) * ny;
|
||||
const now = this.time || 0;
|
||||
if (currentOut < minBounceSpeed && ((t.lastBounceFenceImpulseAt || -999) + 0.16 <= now || toward < 0)) {
|
||||
const add = minBounceSpeed - currentOut;
|
||||
t.impulseVx = clamp((t.impulseVx || 0) + nx * add, -BOUNCE_FENCE_MAX_SPEED, BOUNCE_FENCE_MAX_SPEED);
|
||||
t.impulseVy = clamp((t.impulseVy || 0) + ny * add, -BOUNCE_FENCE_MAX_SPEED, BOUNCE_FENCE_MAX_SPEED);
|
||||
t.lastBounceFenceImpulseAt = now;
|
||||
}
|
||||
const now = this.time || 0;
|
||||
const source = r.item || null;
|
||||
const vx = Number(t.vx || 0) || 0;
|
||||
const vy = Number(t.vy || 0) || 0;
|
||||
const ivx = Number(t.impulseVx || 0) || 0;
|
||||
const ivy = Number(t.impulseVy || 0) || 0;
|
||||
const combinedX = vx + ivx;
|
||||
const combinedY = vy + ivy;
|
||||
const toward = combinedX * nx + combinedY * ny;
|
||||
const recentSameFence = source && t.lastBounceFenceSource === source
|
||||
&& now - Number(t.lastBounceFenceImpulseAt || -999) < 0.12
|
||||
&& toward > 0;
|
||||
|
||||
if (!recentSameFence) {
|
||||
const tangentX = combinedX - toward * nx;
|
||||
const tangentY = combinedY - toward * ny;
|
||||
const minBounceSpeed = Math.max(0, Number(r.minBounceSpeed || 0) || 0);
|
||||
const reflected = toward < 0 ? (-toward * restitution) : Math.max(180, minBounceSpeed * 0.72);
|
||||
const outward = clamp(Math.max(minBounceSpeed, reflected), 0, BOUNCE_FENCE_MAX_SPEED);
|
||||
// Keep a little ordinary velocity for visual continuity, but place most
|
||||
// of the launch in the external impulse channel so action states,
|
||||
// sleeping and AI steering cannot immediately cancel the blow-away.
|
||||
t.vx = clamp(tangentX * 0.22 + nx * outward * 0.22, -BOUNCE_FENCE_MAX_SPEED, BOUNCE_FENCE_MAX_SPEED);
|
||||
t.vy = clamp(tangentY * 0.22 + ny * outward * 0.22, -BOUNCE_FENCE_MAX_SPEED, BOUNCE_FENCE_MAX_SPEED);
|
||||
t.impulseVx = clamp(tangentX * 0.36 + nx * outward * 0.78, -BOUNCE_FENCE_MAX_SPEED, BOUNCE_FENCE_MAX_SPEED);
|
||||
t.impulseVy = clamp(tangentY * 0.36 + ny * outward * 0.78, -BOUNCE_FENCE_MAX_SPEED, BOUNCE_FENCE_MAX_SPEED);
|
||||
t.lastBounceFenceSource = source;
|
||||
t.lastBounceFenceImpulseAt = now;
|
||||
t._sleepExternalMotionUntil = Math.max(Number(t._sleepExternalMotionUntil || 0) || 0, now + 1.15);
|
||||
global.TarinaiMovementUpdateStep?.markSleepExternalMotion?.(t, 0.016, "bounce-fence-launch");
|
||||
if (t.sleeping || t.state === "sleep") global.TarinaiMovementUpdateStep?.wakeSleepingFromExternalMotion?.(t, "バウンス柵で跳ね飛ばされた");
|
||||
t.fallTimer = Math.max(Number(t.fallTimer || 0) || 0, 0.62);
|
||||
t.fallMax = Math.max(Number(t.fallMax || 0) || 0, Number(t.fallTimer || 0) || 0);
|
||||
// Clear the body beyond the contact skin so the multi-pass collision
|
||||
// solver cannot immediately reflect it back into the fence.
|
||||
const launchClearance = Math.min(16, Math.max(5, rr * 0.42));
|
||||
t.x += nx * launchClearance;
|
||||
t.y += ny * launchClearance;
|
||||
}
|
||||
|
||||
t.vx = clamp(t.vx || 0, -BOUNCE_FENCE_MAX_SPEED, BOUNCE_FENCE_MAX_SPEED);
|
||||
t.vy = clamp(t.vy || 0, -BOUNCE_FENCE_MAX_SPEED, BOUNCE_FENCE_MAX_SPEED);
|
||||
t.surpriseTimer = Math.max(t.surpriseTimer || 0, 0.18);
|
||||
if (t.bubble && (this.time || 0) >= (t.lastBounceFenceBubbleAt || -999) + 0.55) {
|
||||
t.lastBounceFenceBubbleAt = this.time || 0;
|
||||
t.impulseVx = clamp(t.impulseVx || 0, -BOUNCE_FENCE_MAX_SPEED, BOUNCE_FENCE_MAX_SPEED);
|
||||
t.impulseVy = clamp(t.impulseVy || 0, -BOUNCE_FENCE_MAX_SPEED, BOUNCE_FENCE_MAX_SPEED);
|
||||
t.surpriseTimer = Math.max(t.surpriseTimer || 0, 0.34);
|
||||
if (t.bubble && now >= (t.lastBounceFenceBubbleAt || -999) + 0.55) {
|
||||
t.lastBounceFenceBubbleAt = now;
|
||||
t.bubble("!", 0.45, "rgba(80,130,210,0.72)");
|
||||
}
|
||||
if ((this.time || 0) >= (t.lastBounceFenceEffectAt || -999) + 0.08) {
|
||||
t.lastBounceFenceEffectAt = this.time || 0;
|
||||
if (now >= (t.lastBounceFenceEffectAt || -999) + 0.08) {
|
||||
t.lastBounceFenceEffectAt = now;
|
||||
this.effects?.push(new Effect("ring", t.x, t.y, { size: Math.max(18, rr * 1.15), life: 0.18, color: "rgba(82,153,230,0.46)" }));
|
||||
}
|
||||
} else if (r.mechanical) {
|
||||
|
|
@ -736,6 +811,9 @@
|
|||
const steps = Math.max(1, Math.min(48, Math.ceil(dist / maxStep)));
|
||||
const stepX = moveX / steps;
|
||||
const stepY = moveY / steps;
|
||||
const fastSweep = dist > rr * 1.25 || Math.hypot(Number(t.vx || 0) || 0, Number(t.vy || 0) || 0) > 170;
|
||||
const sweepMaxChecks = Math.max(opts.light ? (fastSweep ? 18 : 10) : (fastSweep ? 44 : 32), Number(opts.maxChecks || 0) || CONFIG.maxFenceCollisionChecks || 24);
|
||||
const sweepMaxRects = fastSweep ? 108 : 72;
|
||||
let collided = false;
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const sx = t.x;
|
||||
|
|
@ -744,7 +822,8 @@
|
|||
const ey = sy + stepY;
|
||||
const hit = firstSweptCircleObstacleHitLocal(this, t, sx, sy, ex, ey, rr, {
|
||||
searchRadius: Math.max(rr + 110, rr + Math.hypot(stepX, stepY) + 170),
|
||||
maxChecks: Math.max(opts.light ? 10 : 32, Number(opts.maxChecks || 0) || CONFIG.maxFenceCollisionChecks || 24),
|
||||
maxChecks: sweepMaxChecks,
|
||||
maxRects: sweepMaxRects,
|
||||
});
|
||||
if (hit && hit.t <= 1) {
|
||||
const safeT = Math.max(0, hit.t - 0.035);
|
||||
|
|
@ -758,8 +837,11 @@
|
|||
collided = this.resolveSolidObstacleCollision(t, {
|
||||
maxPasses: 3,
|
||||
searchRadius: Math.max(rr + 110, rr + Math.hypot(stepX, stepY) + 170),
|
||||
maxChecks: Math.max(opts.light ? 10 : 32, Number(opts.maxChecks || 0) || CONFIG.maxFenceCollisionChecks || 24),
|
||||
maxChecks: sweepMaxChecks,
|
||||
maxRects: sweepMaxRects,
|
||||
slop: 0.30,
|
||||
oneWayDx: stepX,
|
||||
oneWayDy: stepY,
|
||||
reason: opts.reason || "tarinai-swept-hit",
|
||||
}) || true;
|
||||
continue;
|
||||
|
|
@ -769,8 +851,11 @@
|
|||
const passHit = this.resolveSolidObstacleCollision(t, {
|
||||
maxPasses: 2,
|
||||
searchRadius: Math.max(rr + 96, rr + Math.hypot(stepX, stepY) + 140),
|
||||
maxChecks: Math.max(opts.light ? 10 : 28, Number(opts.maxChecks || 0) || CONFIG.maxFenceCollisionChecks || 24),
|
||||
maxChecks: sweepMaxChecks,
|
||||
maxRects: sweepMaxRects,
|
||||
slop: 0.35,
|
||||
oneWayDx: stepX,
|
||||
oneWayDy: stepY,
|
||||
reason: opts.reason || "tarinai-swept-move",
|
||||
});
|
||||
collided = passHit || collided;
|
||||
|
|
@ -791,9 +876,10 @@
|
|||
const maxPasses = Math.max(1, Math.min(4, Number(opts.maxPasses ?? 2) || 2));
|
||||
const searchRadius = Math.max(rr + 40, Number(opts.searchRadius || rr + 150) || (rr + 150));
|
||||
const maxChecks = Math.max(1, Number(opts.maxChecks || CONFIG.maxFenceCollisionChecks || 24) || 24);
|
||||
const maxRects = Math.max(12, Math.min(128, Number(opts.maxRects || 0) || Math.ceil(maxChecks * 2.5)));
|
||||
for (let pass = 0; pass < maxPasses; pass++) {
|
||||
let passPushed = false;
|
||||
for (const rect of this.nearbySolidObstacleRects(t.x, t.y, searchRadius, { maxChecks })) {
|
||||
for (const rect of this.nearbySolidObstacleRects(t.x, t.y, searchRadius, { maxChecks, maxRects })) {
|
||||
if (rect?.type === "nest_box" && this.shouldIgnoreNestBoxCollisionFor(t, rect.item)) continue;
|
||||
if (this.pushTarinaiOutOfRect(t, rect, rr, opts)) {
|
||||
pushed = true;
|
||||
|
|
@ -807,7 +893,7 @@
|
|||
const pad = CONFIG.worldPadding + Math.max(2, (Number(t.radius) || 22) * 0.18);
|
||||
t.x = clamp(t.x, pad, this.w - pad);
|
||||
t.y = clamp(t.y, pad, this.h - pad);
|
||||
const maxV = bounced ? 520 : 130;
|
||||
const maxV = bounced ? 1280 : 130;
|
||||
t.vx = clamp(t.vx || 0, -maxV, maxV);
|
||||
t.vy = clamp(t.vy || 0, -maxV, maxV);
|
||||
}
|
||||
|
|
@ -836,6 +922,7 @@
|
|||
const type = rect?.item?.type || rect?.type || "";
|
||||
if (!type || type === "glass_wall") return false;
|
||||
if (type === "gate_fence" && rect?.item?.gateOpen) return false;
|
||||
if (type === "one_way_fence") return true;
|
||||
// Path routing uses this predicate even though the queried solid rects are
|
||||
// not limited to fences. Nest containers have real collision footprints;
|
||||
// include them here so tarinai route around a non-target nest instead of
|
||||
|
|
@ -846,6 +933,12 @@
|
|||
return Boolean(this.isFenceType?.(type));
|
||||
},
|
||||
|
||||
oneWayFenceAllowsPath(x1, y1, x2, y2, rect) {
|
||||
const dx = (Number(x2) || 0) - (Number(x1) || 0);
|
||||
const dy = (Number(y2) || 0) - (Number(y1) || 0);
|
||||
return Geometry.oneWayFenceAllowsMotion(rect, x1, y1, dx, dy);
|
||||
},
|
||||
|
||||
pathBlockedByFence(x1, y1, x2, y2, padding = 16, opts = {}) {
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
|
|
@ -854,6 +947,7 @@
|
|||
for (const rect of this.nearbySolidObstacleRects(cx, cy, maxD + padding + 170, { exclude })) {
|
||||
if (exclude && rect?.item === exclude) continue;
|
||||
if (!this.isFenceRoutingRect?.(rect)) continue;
|
||||
if (rect?.oneWay && this.oneWayFenceAllowsPath?.(x1, y1, x2, y2, rect)) continue;
|
||||
if (rect?.oriented) {
|
||||
const r = { ...rect, halfW: (rect.halfW || 0) + padding, halfH: (rect.halfH || 0) + padding };
|
||||
if (this.segmentIntersectsRect(x1, y1, x2, y2, r)) return true;
|
||||
|
|
@ -876,6 +970,9 @@
|
|||
const searchRadius = Math.max(48, Number(padding || 0) + 72);
|
||||
for (const rect of this.nearbySolidObstacleRects?.(x, y, searchRadius, { exclude, maxChecks: Math.max(10, Number(opts.maxChecks || 0) || 24) }) || []) {
|
||||
if (exclude && rect?.item === exclude) continue;
|
||||
// Directional pathfinding evaluates one-way fences on edges, not as
|
||||
// permanently blocked grid points. Other callers retain solid occupancy.
|
||||
if (rect?.oneWay && opts?.directionalOneWay === true) continue;
|
||||
if (this.pointInRect?.(x, y, rect, padding)) return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue