65 lines
3 KiB
JavaScript
65 lines
3 KiB
JavaScript
"use strict";
|
|
|
|
// Layer: world/grass-placement
|
|
// Grass placement clearance, crowding, and retry sampling helpers.
|
|
(function (global) {
|
|
const World = global.World;
|
|
if (!World) return;
|
|
|
|
Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({
|
|
grassBlockedAt(x, y, self = null, opts = {}) {
|
|
const ignoreSoft = Boolean(opts?.ignoreSoftPlacement);
|
|
for (const it of (this.nearbyNonGrassItems?.(x, y, 96) || this.nearbyItems(x, y, 96))) {
|
|
if (it === self || it.dead) continue;
|
|
if (ignoreSoft && global.TarinaiPhysicsSoftClear.isSoftPlacementType(it.type)) continue;
|
|
for (const r of this.solidObstacleRects(it)) {
|
|
if (this.pointInRect(x, y, r, 10)) return true;
|
|
}
|
|
if (it.type !== "zunchi") continue;
|
|
const dx = (x - it.x) / Math.max(22, it.r * 1.35);
|
|
const dy = (y - it.y) / Math.max(12, it.r * 0.82);
|
|
if (dx * dx + dy * dy < 1) return true;
|
|
}
|
|
return false;
|
|
},
|
|
grassCrowdedAt(x, y, minGap = 30, self = null) {
|
|
for (const it of (this.nearbyGrass?.(x, y, Math.max(42, minGap + 16)) || this.nearbyItems(x, y, Math.max(42, minGap + 16)))) {
|
|
if (it === self || it.dead || it.type !== "grass") continue;
|
|
if (distXY(x, y, it.x, it.y) < minGap) return true;
|
|
}
|
|
return false;
|
|
},
|
|
grassOnTarinaiAt(x, y, minGap = 24) {
|
|
for (const t of this.nearbyTarinai(x, y, minGap + 36)) {
|
|
if (t.dead || this.isTarinaiHiddenInNestBox(t)) continue;
|
|
if (distXY(x, y, t.x, t.y) < Math.max(minGap, t.radius * 1.15)) return true;
|
|
}
|
|
return false;
|
|
},
|
|
grassSpotOpen(x, y, { minGrassGap = 30, avoidTarinai = true, self = null } = {}) {
|
|
if (x < 44 || y < 44 || x > this.w - 44 || y > this.h - 44) return false;
|
|
if (this.grassBlockedAt(x, y, self)) return false;
|
|
if (this.grassCrowdedAt(x, y, minGrassGap, self)) return false;
|
|
if (avoidTarinai && this.grassOnTarinaiAt(x, y)) return false;
|
|
return true;
|
|
},
|
|
findGrassPlantingSpot(x, y, { allowOriginal = true, minRadius = 18, maxRadius = 160, attempts = 36, avoidTarinai = true } = {}) {
|
|
const cx = clamp(x, 44, this.w - 44);
|
|
const cy = clamp(y, 44, this.h - 44);
|
|
if (allowOriginal && this.grassSpotOpen(cx, cy, { avoidTarinai })) return { x: cx, y: cy };
|
|
const blockedAtCenter = this.grassBlockedAt(cx, cy);
|
|
const inner = blockedAtCenter ? Math.max(minRadius, 34) : minRadius;
|
|
const outer = Math.max(maxRadius, inner + 42);
|
|
const golden = Math.PI * (3 - Math.sqrt(5));
|
|
for (let i = 0; i < attempts; i++) {
|
|
const t = attempts <= 1 ? 1 : i / (attempts - 1);
|
|
const radius = lerp(inner, outer, Math.sqrt(t));
|
|
const angle = i * golden + stableUnit(`${cx},${cy}`, "grass-plant") * Math.PI * 2;
|
|
const px = clamp(cx + Math.cos(angle) * radius, 44, this.w - 44);
|
|
const py = clamp(cy + Math.sin(angle) * radius * 0.72, 44, this.h - 44);
|
|
if (this.grassSpotOpen(px, py, { avoidTarinai })) return { x: px, y: py };
|
|
}
|
|
return null;
|
|
},
|
|
}));
|
|
})(typeof window !== "undefined" ? window : globalThis);
|