tarinai/js/world_grass_placement_system.js
2026-07-18 13:06:02 +09:00

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);