"use strict"; // Reset preset definitions and world population generators. (function (global) { const RESET_PRESETS = Object.freeze({ empty: { label: "\u7A7A\u767D" }, default: { label: "\u30C7\u30D5\u30A9\u30EB\u30C8" }, athletic: { label: "\u30A2\u30B9\u30EC\u30C1\u30C3\u30AF" }, crowded: { label: "\u904E\u5BC6" }, family: { label: "\u5BB6\u65CF" }, war: { label: "\u6226\u4E89" }, happy: { label: "\u5E78\u798F" }, hair_trigger: { label: "\u4E00\u89E6\u5373\u767A" }, }); function normalizeResetPresetId(presetId = "default") { const id = String(presetId || "default"); return Object.prototype.hasOwnProperty.call(RESET_PRESETS, id) ? id : "default"; } function presetScaleForField(field) { const id = field?.id || "garden"; if (id === "cage") return 0.65; if (id === "park") return 1.45; return 1; } function presetCount(field, base, min = 0, max = Infinity) { const value = Math.round((Number(base) || 0) * presetScaleForField(field)); return Math.max(min, Math.min(Number.isFinite(max) ? max : value, value)); } function randPoint(worldRef, pad = 78, zone = null) { const x0 = Math.max(pad, Number(zone?.x0 ?? pad)); const x1 = Math.min(worldRef.w - pad, Number(zone?.x1 ?? (worldRef.w - pad))); const y0 = Math.max(pad, Number(zone?.y0 ?? pad)); const y1 = Math.min(worldRef.h - pad, Number(zone?.y1 ?? (worldRef.h - pad))); return { x: rand(Math.min(x0, x1), Math.max(x0, x1)), y: rand(Math.min(y0, y1), Math.max(y0, y1)) }; } function randomPresetAngle(step = Math.PI / 4) { const n = Math.max(1, Math.round((Math.PI * 2) / Math.max(0.001, step))); return Math.floor(rand(0, n)) * step; } function presetShapeExtent(segments) { let max = 0; for (const seg of segments || []) { for (let i = 0; i < 4; i += 2) max = Math.max(max, Math.hypot(Number(seg[i]) || 0, Number(seg[i + 1]) || 0)); } return max; } function configurePresetMechanicalItem(item, opts = {}) { const body = item?.physicsBody; if (!body || (item.type !== "rotator" && item.type !== "reciprocator")) return item; body.pose = body.pose || { x: item.x, y: item.y, angle: Number(item.angle) || 0 }; body.pose.x = item.x; body.pose.y = item.y; body.pose.angle = Number(item.angle) || 0; body.velocity = body.velocity || { x: 0, y: 0, angular: 0, linear: 0 }; body.velocity.x = 0; body.velocity.y = 0; body.velocity.angular = 0; body.velocity.linear = 0; body.shape = body.shape || { model: "segments", thickness: 12, segments: [], version: 0 }; if (item.type === "rotator") { const segments = opts.segments || [ [-92, 0, 92, 0], [0, -58, 0, 58], [-58, -42, 58, 42], [-58, 42, 58, -42], ]; body.motor = body.motor || { powered: true, speed: Math.PI * 0.18, direction: 1 }; body.motor.powered = opts.motorOn !== false; body.motor.speed = Number.isFinite(Number(opts.motorSpeed)) ? Number(opts.motorSpeed) : Math.PI * 0.18; body.motor.direction = Number(opts.direction) < 0 ? -1 : 1; body.shape.model = "segments"; body.shape.thickness = Number.isFinite(Number(opts.thickness)) ? Number(opts.thickness) : 14; body.shape.segments = segments.map(seg => seg.slice(0, 4)); body.shape.version = (Number(body.shape.version) || 0) + 1; item.spin = 0; item.r = Math.max(item.r || 64, presetShapeExtent(segments) + body.shape.thickness + 8); } else if (item.type === "reciprocator") { const railAxis = Number.isFinite(Number(opts.railAxis)) ? Number(opts.railAxis) : (Number(item.angle) || 0); const segments = opts.segments || [ [-92, -24, -36, -24], [-36, -24, -36, 24], [-36, 24, 36, 24], [36, 24, 36, -24], [36, -24, 92, -24], ]; body.motor = body.motor || { powered: true, speed: 36, direction: 1 }; body.motor.powered = opts.railOn !== false; body.motor.speed = Number.isFinite(Number(opts.railMotorSpeed)) ? Number(opts.railMotorSpeed) : 36; body.motor.direction = Number(opts.direction) < 0 ? -1 : 1; body.rail = body.rail || { axisAngle: railAxis, travel: 120, phase: 0, anchorX: item.x, anchorY: item.y }; body.rail.axisAngle = railAxis; body.rail.travel = Number.isFinite(Number(opts.railTravel)) ? Number(opts.railTravel) : 120; body.rail.phase = Number.isFinite(Number(opts.railPhase)) ? Number(opts.railPhase) : 0; body.rail.anchorX = item.x; body.rail.anchorY = item.y; body.shape.model = "segments"; body.shape.thickness = Number.isFinite(Number(opts.thickness)) ? Number(opts.thickness) : 14; body.shape.segments = segments.map(seg => seg.slice(0, 4)); body.shape.version = (Number(body.shape.version) || 0) + 1; item.r = Math.max(item.r || 64, presetShapeExtent(segments) + body.shape.thickness + 8); } globalThis.TarinaiPhysicsBodySystem?.invalidateItem?.(item, "preset-mechanical-shape"); globalThis.TarinaiMechanicalSystem?.invalidateGeometry?.(item); return item; } function syncPresetMechanicalPose(item) { const body = item?.physicsBody; if (!body) return; body.pose = body.pose || { x: item.x, y: item.y, angle: Number(item.angle) || 0 }; body.pose.x = item.x; body.pose.y = item.y; body.pose.angle = Number(item.angle) || 0; if (body.rail) { body.rail.anchorX = item.x; body.rail.anchorY = item.y; } globalThis.TarinaiPhysicsBodySystem?.invalidateItem?.(item, "preset-placement-pose"); globalThis.TarinaiMechanicalSystem?.invalidateGeometry?.(item); } function presetItemFootprintRadius(worldRef, item) { const radius = globalThis.TarinaiCollisionFootprints?.itemFootprintRadius?.(worldRef, item); if (Number.isFinite(Number(radius))) { if (item?.type === "ball" || item?.type === "balloon") return Math.max(34, Number(radius) * 1.35); return Math.max(14, Number(radius)); } return Math.max(14, Number(item?.r || 16) || 16); } function presetItemPlacementBlocked(worldRef, item, opts = {}) { if (!worldRef || !item || item.dead) return true; if (typeof worldRef.placementBlocked === "function" && worldRef.placementBlocked(item)) return true; const itemRects = worldRef.solidObstacleRects?.(item) || globalThis.TarinaiCollisionFootprints?.placementRectsFor?.(worldRef, item) || []; const itemRadius = presetItemFootprintRadius(worldRef, item); const search = Math.max(120, itemRadius * 3.2); for (const other of worldRef.nearbyItems?.(item.x, item.y, search) || worldRef.items || []) { if (!other || other === item || other.dead) continue; const sameType = other.type === item.type; const otherIsFence = worldRef.isFenceType?.(other.type) || false; const itemIsFence = worldRef.isFenceType?.(item.type) || false; if (!opts.avoidAllObstacles && !sameType && !otherIsFence && !itemIsFence) continue; const otherRects = worldRef.solidObstacleRects?.(other) || globalThis.TarinaiCollisionFootprints?.placementRectsFor?.(worldRef, other) || []; const otherRadius = presetItemFootprintRadius(worldRef, other); const margin = (itemIsFence || otherIsFence) ? 4 : Math.max(8, Math.min(itemRadius, otherRadius) * 0.28); if (opts.rotationEnvelope && item.type === "rotator" && otherIsFence && otherRects.length) { const envelope = Math.max(itemRadius, globalThis.TarinaiMechanicalSystem?.reach?.(item) || 0) + Math.max(8, Number(opts.rotationMargin || 12) || 12); if (otherRects.some(rect => globalThis.TarinaiCollisionFootprints?.rectCircleOverlap?.(rect, item.x, item.y, envelope, 0))) return true; } if (itemRects.length && otherRects.length) { if (itemRects.some(a => otherRects.some(b => globalThis.TarinaiCollisionFootprints?.rectsOverlap?.(a, b, margin)))) return true; continue; } if (itemRects.length) { if (itemRects.some(r => globalThis.TarinaiCollisionFootprints?.rectCircleOverlap?.(r, other.x, other.y, otherRadius, margin))) return true; continue; } if (otherRects.length) { if (otherRects.some(r => globalThis.TarinaiCollisionFootprints?.rectCircleOverlap?.(r, item.x, item.y, itemRadius, margin))) return true; continue; } if (Math.hypot((item.x || 0) - (other.x || 0), (item.y || 0) - (other.y || 0)) < itemRadius + otherRadius + margin) return true; } return false; } function findPresetItemSpot(worldRef, item, opts = {}) { const allowOriginal = opts.allowOriginal !== false; if (allowOriginal && !presetItemPlacementBlocked(worldRef, item, opts)) return { x: item.x, y: item.y }; const solid = Boolean(worldRef?.isFenceType?.(item.type) || item.type === "rotator" || item.type === "reciprocator" || item.type === "nest_box" || item.type === "pipe"); const count = Math.max(12, Number(opts.attempts || 0) || (solid ? 96 : 58)); const maxRadius = Math.max(70, Number(opts.maxRadius || 0) || (solid ? 220 : 170)); const golden = Math.PI * (3 - Math.sqrt(5)); let best = null; let bestD = Infinity; const seed = `${item.type}:${Math.round(item.x)},${Math.round(item.y)}`; for (let i = 0; i < count; i++) { const t = count <= 1 ? 1 : i / (count - 1); const radius = Math.max(10, maxRadius * Math.sqrt(t)); const angle = i * golden + (globalThis.stableUnit ? globalThis.stableUnit(seed, "preset-place") : Math.random()) * Math.PI * 2; const rawX = item.x + Math.cos(angle) * radius; const rawY = item.y + Math.sin(angle) * radius * 0.74; const p = worldRef.placementClampPointFor?.(item, rawX, rawY) || { x: clamp(rawX, 42, worldRef.w - 42), y: clamp(rawY, 42, worldRef.h - 42) }; const oldX = item.x, oldY = item.y; item.x = p.x; item.y = p.y; syncPresetMechanicalPose(item); const blocked = presetItemPlacementBlocked(worldRef, item, opts); item.x = oldX; item.y = oldY; syncPresetMechanicalPose(item); if (blocked) continue; const d = Math.hypot(p.x - oldX, p.y - oldY); if (d < bestD) { best = p; bestD = d; } } return best; } function addPresetItem(worldRef, type, x, y, opts = {}) { const item = new Item(type, x, y); if (Number.isFinite(Number(opts.r))) item.r = Number(opts.r); if (opts.randomAngle) item.angle = randomPresetAngle(Number(opts.angleStep) || Math.PI / 4); else if (Number.isFinite(Number(opts.angle))) item.angle = Number(opts.angle); if (opts.toolSize) item.toolSize = opts.toolSize; if (type === "fan") { const center = Number.isFinite(Number(opts.fanSwingCenter)) ? Number(opts.fanSwingCenter) : (Number(item.angle) || 0); item.angle = center; item.fanBodyAngle = Number.isFinite(Number(opts.fanBodyAngle)) ? Number(opts.fanBodyAngle) : center; item.fanSwingCenter = center; item.fanSwingRange = Number.isFinite(Number(opts.fanSwingRange)) ? Number(opts.fanSwingRange) : (35 * Math.PI / 180); item.fanSwingSpeed = Number.isFinite(Number(opts.fanSwingSpeed)) ? Number(opts.fanSwingSpeed) : (48 * Math.PI / 180); item.fanSwingPhase = Number.isFinite(Number(opts.fanSwingPhase)) ? Number(opts.fanSwingPhase) : rand(0, Math.PI * 2); item.fanSwingOn = opts.fanSwingOn === true && item.fanSwingRange > 0 && item.fanSwingSpeed > 0; } if (type === "duplicator") { item.storedFoodType = opts.storedFoodType || "sweet"; item.storedFoodLabel = typeof toolLabel === "function" ? toolLabel(item.storedFoodType) : item.storedFoodType; if (item.roles) item.roles.food = true; } if (type === "rotator" || type === "reciprocator") configurePresetMechanicalItem(item, opts); if (opts.avoidOverlap) { const spot = findPresetItemSpot(worldRef, item, opts); if (!spot) return null; item.x = spot.x; item.y = spot.y; syncPresetMechanicalPose(item); } return worldRef.addItem?.(item, opts.reason || `preset-${type}`) || null; } function addPresetGrass(worldRef, count, zone = null) { for (let i = 0; i < count; i++) { const p = randPoint(worldRef, 70, zone); const grass = addPresetItem(worldRef, "grass", p.x, p.y, { reason: "preset-grass" }); globalThis.TarinaiGrass?.setStage?.(grass, Math.floor(rand(0, 2.999)), { force: true }); } } function addPresetTarinai(worldRef, count, zone = null, opts = {}) { for (let i = 0; i < count; i++) { const p = randPoint(worldRef, 82, zone); worldRef.addTarinai({ x: p.x, y: p.y, generation: opts.generation || 1 }); } } function addPresetItemsScattered(worldRef, type, count, zone = null, opts = {}) { let placed = 0; const placedItems = []; const n = Math.max(0, Number(count) | 0); const attempts = opts.avoidOverlap ? Math.max(n * 4, n + 8) : n; for (let i = 0; i < attempts && placed < n; i++) { const p = randPoint(worldRef, opts.pad || 82, zone); const item = addPresetItem(worldRef, type, p.x, p.y, opts); if (item) { placed++; placedItems.push(item); } } return placedItems; } function addPresetFenceLine(worldRef, count, yRatio, angle = 0, type = "fence_h", opts = {}) { const n = Math.max(1, Number(count) | 0); const gap = worldRef.w / (n + 1); for (let i = 1; i <= n; i++) { addPresetItem(worldRef, type, gap * i, worldRef.h * yRatio + rand(-24, 24), { angle, reason: opts.reason || "preset-fence", avoidOverlap: !!opts.avoidOverlap, avoidAllObstacles: !!opts.avoidAllObstacles, attempts: opts.attempts || 96, maxRadius: opts.maxRadius || 190 }); } } function addPresetFencesScattered(worldRef, count, zone = null, opts = {}) { const n = Math.max(0, Number(count) | 0); let placed = 0; const attempts = opts.avoidOverlap ? Math.max(n * 4, n + 8) : n; for (let i = 0; i < attempts && placed < n; i++) { const p = randPoint(worldRef, opts.pad || 78, zone); const vertical = Math.random() < 0.5; const item = addPresetItem(worldRef, vertical ? "fence_v" : "fence_h", p.x, p.y, { angle: randomPresetAngle(Math.PI / 4), reason: opts.reason || "preset-fence-scatter", avoidOverlap: !!opts.avoidOverlap, attempts: opts.attempts || 80, maxRadius: opts.maxRadius || 180 }); if (item) placed++; } } function addPresetBounceFenceBox(worldRef, cx, cy, halfW, halfH) { const fenceLength = 142; const horizontalCount = Math.max(2, Math.ceil((halfW * 2) / fenceLength)); const verticalCount = Math.max(2, Math.ceil((halfH * 2) / fenceLength)); const horizontalStep = (halfW * 2) / horizontalCount; const verticalStep = (halfH * 2) / verticalCount; for (let i = 0; i < horizontalCount; i++) { const x = cx - halfW + horizontalStep * (i + 0.5); addPresetItem(worldRef, "bounce_fence", x, cy - halfH, { angle: 0, reason: "preset-hair-trigger-boundary" }); addPresetItem(worldRef, "bounce_fence", x, cy + halfH, { angle: 0, reason: "preset-hair-trigger-boundary" }); } for (let i = 0; i < verticalCount; i++) { const y = cy - halfH + verticalStep * (i + 0.5); addPresetItem(worldRef, "bounce_fence", cx - halfW, y, { angle: Math.PI / 2, reason: "preset-hair-trigger-boundary" }); addPresetItem(worldRef, "bounce_fence", cx + halfW, y, { angle: Math.PI / 2, reason: "preset-hair-trigger-boundary" }); } return { cx, cy, halfW, halfH }; } function pointOutsidePresetBox(point, box, margin = 0) { return Math.abs((point?.x || 0) - box.cx) > box.halfW + margin || Math.abs((point?.y || 0) - box.cy) > box.halfH + margin; } function addPresetTarinaiOutsideBox(worldRef, count, box) { const n = Math.max(0, Number(count) | 0); const zones = [ { x0: 82, x1: Math.max(82, box.cx - box.halfW - 70), y0: 82, y1: worldRef.h - 82 }, { x0: Math.min(worldRef.w - 82, box.cx + box.halfW + 70), x1: worldRef.w - 82, y0: 82, y1: worldRef.h - 82 }, { x0: box.cx - box.halfW, x1: box.cx + box.halfW, y0: 82, y1: Math.max(82, box.cy - box.halfH - 70) }, { x0: box.cx - box.halfW, x1: box.cx + box.halfW, y0: Math.min(worldRef.h - 82, box.cy + box.halfH + 70), y1: worldRef.h - 82 }, ].filter(zone => zone.x1 >= zone.x0 && zone.y1 >= zone.y0); for (let i = 0; i < n; i++) { let p = randPoint(worldRef, 82, zones[i % Math.max(1, zones.length)] || null); if (!pointOutsidePresetBox(p, box, 28)) { const side = i % 2 === 0 ? -1 : 1; p = { x: clamp(box.cx + side * (box.halfW + 96), 82, worldRef.w - 82), y: clamp(box.cy + rand(-box.halfH, box.halfH), 82, worldRef.h - 82) }; } worldRef.addTarinai({ x: p.x, y: p.y, generation: 1 }); } } function generateDefaultPreset(worldRef, field, seedPopulation) { const population = seedPopulation ?? field.population ?? CONFIG.initialPopulation; const grassCount = field.grass ?? 18; addPresetTarinai(worldRef, population); addPresetGrass(worldRef, grassCount); addPresetItem(worldRef, "stone", worldRef.w * 0.68, worldRef.h * 0.32, { reason: "reset-stone" }); } function generateAthleticPreset(worldRef, field) { const factor = presetScaleForField(field); const fieldId = field?.id || "garden"; const compact = fieldId === "cage" || fieldId === "garden"; const center = { x0: worldRef.w * 0.10, x1: worldRef.w * 0.90, y0: worldRef.h * 0.14, y1: worldRef.h * 0.86 }; addPresetTarinai(worldRef, presetCount(field, 14, 7, 24), { x0: worldRef.w * 0.08, x1: worldRef.w * 0.30, y0: worldRef.h * 0.18, y1: worldRef.h * 0.82 }); // \u7269\u7406\u30D6\u30ED\u30C3\u30AF\u306F\u67F5\u3088\u308A\u5148\u306B\u7F6E\u304F\u3002\u72ED\u3044\u4E2D\u5C0F\u30D5\u30A3\u30FC\u30EB\u30C9\u3067\u306F\u67F5\u5074\u3092\u9593\u5F15\u304D\u3001\u30D6\u30ED\u30C3\u30AF\u3092\u512A\u5148\u3059\u308B\u3002 addPresetItemsScattered(worldRef, "stone", presetCount(field, compact ? 4 : 6, 3, 9), center, { pad: compact ? 118 : 132, reason: "preset-athletic-physical-block", avoidOverlap: true, avoidAllObstacles: true, attempts: 140, maxRadius: compact ? 260 : 220 }); addPresetItemsScattered(worldRef, "stove", presetCount(field, 2, 1, 3), center, { pad: 118, reason: "preset-athletic-stove", avoidOverlap: true, avoidAllObstacles: true, attempts: 100, maxRadius: 210 }); addPresetItemsScattered(worldRef, "ball", presetCount(field, compact ? 8 : 10, 4, 18), null, { pad: 96, reason: "preset-ball", avoidOverlap: true, avoidAllObstacles: true, attempts: 90, maxRadius: compact ? 230 : 190 }); addPresetItemsScattered(worldRef, "balloon", presetCount(field, compact ? 5 : 7, 3, 12), center, { pad: 92, reason: "preset-athletic-water-balloon", avoidOverlap: true, avoidAllObstacles: true, attempts: 100, maxRadius: compact ? 240 : 200 }); addPresetFenceLine(worldRef, Math.max(2, Math.round((compact ? 3 : 4) * factor)), 0.36, 0, "fence_h", { avoidOverlap: true, avoidAllObstacles: true, attempts: 120, maxRadius: compact ? 250 : 210, reason: "preset-athletic-fence-line" }); addPresetFenceLine(worldRef, Math.max(1, Math.round((compact ? 2 : 3) * factor)), 0.62, Math.PI / 2, "fence_v", { avoidOverlap: true, avoidAllObstacles: true, attempts: 120, maxRadius: compact ? 250 : 210, reason: "preset-athletic-fence-line" }); addPresetFencesScattered(worldRef, Math.max(1, Math.round((compact ? 2 : 4) * factor)), null, { pad: 108, reason: "preset-athletic-fence", avoidOverlap: true, avoidAllObstacles: true, attempts: 120, maxRadius: compact ? 250 : 210 }); addPresetItemsScattered(worldRef, "rotator", Math.max(1, Math.round(2 * factor)), null, { pad: 148, reason: "preset-rotator-simple", segments: [[-66, 0, 66, 0]], motorSpeed: Math.PI * 0.18, thickness: 13, rotationEnvelope: true, rotationMargin: 14, avoidOverlap: true, avoidAllObstacles: true, attempts: 180, maxRadius: compact ? 320 : 280 }); addPresetItemsScattered(worldRef, "reciprocator", Math.max(1, Math.round(2 * factor)), null, { pad: 148, reason: "preset-reciprocator-slow-custom", railMotorSpeed: 36, railTravel: 120, thickness: 14, avoidOverlap: true, avoidAllObstacles: true, attempts: 140, maxRadius: compact ? 280 : 240 }); } function generateCrowdedPreset(worldRef, field) { const center = { x0: worldRef.w * 0.18, x1: worldRef.w * 0.82, y0: worldRef.h * 0.18, y1: worldRef.h * 0.82 }; addPresetTarinai(worldRef, presetCount(field, 36, 18, 58), center); addPresetItemsScattered(worldRef, "nest_box", presetCount(field, 13, 7, 22), center, { pad: 96, reason: "preset-nest-box" }); addPresetItemsScattered(worldRef, "duplicator", presetCount(field, 3, 2, 5), center, { pad: 110, storedFoodType: "sweet", reason: "preset-duplicator-zunda" }); addPresetGrass(worldRef, presetCount(field, 18, 8, 34), center); addPresetItemsScattered(worldRef, "water", presetCount(field, 8, 4, 16), center, { pad: 84, reason: "preset-water" }); } function generateFamilyPreset(worldRef, field) { const cx = worldRef.w * 0.5; const cy = worldRef.h * 0.52; worldRef.addTarinai({ x: cx - 52, y: cy + 20, generation: 1 }); worldRef.addTarinai({ x: cx + 52, y: cy + 20, generation: 1 }); addPresetItem(worldRef, "nest_box", cx, cy - 58, { reason: "preset-family-nest" }); addPresetItem(worldRef, "duplicator", cx, cy + 112, { storedFoodType: "love_mochi", reason: "preset-family-duplicator" }); } function generateWarPreset(worldRef, field) { const left = { x0: worldRef.w * 0.08, x1: worldRef.w * 0.45, y0: worldRef.h * 0.16, y1: worldRef.h * 0.84 }; const right = { x0: worldRef.w * 0.55, x1: worldRef.w * 0.92, y0: worldRef.h * 0.16, y1: worldRef.h * 0.84 }; addPresetTarinai(worldRef, Math.ceil(presetCount(field, 28, 14, 48) / 2), left); addPresetTarinai(worldRef, Math.floor(presetCount(field, 28, 14, 48) / 2), right); addPresetItemsScattered(worldRef, "duplicator", presetCount(field, 3, 2, 5), { x0: worldRef.w * 0.38, x1: worldRef.w * 0.62, y0: worldRef.h * 0.22, y1: worldRef.h * 0.78 }, { pad: 96, storedFoodType: "fight_mochi", reason: "preset-war-duplicator" }); addPresetItemsScattered(worldRef, "ball", presetCount(field, 8, 4, 14), null, { pad: 90, reason: "preset-war-ball" }); addPresetItemsScattered(worldRef, "ant_nest", presetCount(field, 1.25, 1, 2), null, { pad: 116, reason: "preset-war-ant-nest", avoidOverlap: true, attempts: 80, maxRadius: 190 }); addPresetItemsScattered(worldRef, "pushpin", presetCount(field, 5, 2, 9), null, { pad: 88, reason: "preset-war-pushpin-reduced" }); addPresetItemsScattered(worldRef, "oshibyo", presetCount(field, 8, 4, 16), null, { pad: 88, reason: "preset-war-oshibyo" }); addPresetItemsScattered(worldRef, "fan", presetCount(field, 2, 1, 4), null, { pad: 118, reason: "preset-war-rotating-fan", randomAngle: true, fanSwingOn: true, fanSwingRange: 110 * Math.PI / 180, fanSwingSpeed: 72 * Math.PI / 180, avoidOverlap: true, avoidAllObstacles: true, attempts: 120, maxRadius: 230 }); addPresetItemsScattered(worldRef, "bounce_fence", presetCount(field, 3, 2, 5), null, { pad: 104, reason: "preset-war-bounce-fence", randomAngle: true, avoidOverlap: true, avoidAllObstacles: true, attempts: 120, maxRadius: 230 }); addPresetFencesScattered(worldRef, presetCount(field, 8, 4, 14), null, { pad: 94, reason: "preset-war-fence" }); } function generateHappyPreset(worldRef, field) { const center = { x0: worldRef.w * 0.16, x1: worldRef.w * 0.84, y0: worldRef.h * 0.16, y1: worldRef.h * 0.84 }; addPresetTarinai(worldRef, presetCount(field, 16, 8, 28), center); addPresetItemsScattered(worldRef, "nest_box", presetCount(field, 8, 4, 14), center, { pad: 96, reason: "preset-happy-nest-box" }); addPresetItemsScattered(worldRef, "robot_cleaner", presetCount(field, 2, 1, 3), center, { pad: 110, reason: "preset-happy-robot-cleaner" }); const happyDuplicators = addPresetItemsScattered(worldRef, "duplicator", presetCount(field, 2, 1, 4), center, { pad: 110, storedFoodType: "sweet", reason: "preset-happy-duplicator-zunda" }); for (const duplicator of happyDuplicators) addPresetItem(worldRef, "toilet", duplicator.x, duplicator.y, { reason: "preset-happy-toilet-sand" }); addPresetItemsScattered(worldRef, "rain_shelter", presetCount(field, 3, 2, 5), center, { pad: 132, reason: "preset-happy-parasol", avoidOverlap: true, avoidAllObstacles: true, attempts: 120, maxRadius: 240 }); addPresetItemsScattered(worldRef, "balloon", presetCount(field, 6, 3, 10), center, { pad: 88, reason: "preset-happy-water-balloon", avoidOverlap: true, attempts: 90, maxRadius: 190 }); addPresetItemsScattered(worldRef, "ball", presetCount(field, 6, 3, 10), center, { pad: 88, reason: "preset-happy-ball" }); } function generateHairTriggerPreset(worldRef, field) { const cx = worldRef.w * 0.5; const cy = worldRef.h * 0.5; const halfW = clamp(worldRef.w * 0.18, 132, 238); const halfH = clamp(worldRef.h * 0.18, 104, 182); const box = addPresetBounceFenceBox(worldRef, cx, cy, halfW, halfH); const inside = { x0: cx - halfW + 52, x1: cx + halfW - 52, y0: cy - halfH + 52, y1: cy + halfH - 52, }; addPresetItemsScattered(worldRef, "ball", presetCount(field, 7, 4, 12), inside, { pad: 0, reason: "preset-hair-trigger-ball" }); addPresetTarinaiOutsideBox(worldRef, presetCount(field, 14, 7, 24), box); const duplicatorCount = presetCount(field, 2, 1, 4); for (let i = 0; i < duplicatorCount; i++) { const side = i % 2 === 0 ? -1 : 1; const x = clamp(cx + side * (halfW + 112), 84, worldRef.w - 84); const spread = duplicatorCount <= 2 ? 0 : ((i / Math.max(1, duplicatorCount - 1)) - 0.5) * Math.min(worldRef.h * 0.42, 260); const y = clamp(cy + spread, 84, worldRef.h - 84); addPresetItem(worldRef, "duplicator", x, y, { storedFoodType: "food", reason: "preset-hair-trigger-pet-food-duplicator" }); } } function addDefaultPresetRobotCleaner(worldRef, presetId = "default") { if (!worldRef || normalizeResetPresetId(presetId) === "empty") return; if ((worldRef.items || []).some(item => item && !item.dead && item.type === "robot_cleaner")) return; const center = { x0: worldRef.w * 0.16, x1: worldRef.w * 0.84, y0: worldRef.h * 0.16, y1: worldRef.h * 0.84 }; addPresetItemsScattered(worldRef, "robot_cleaner", 1, center, { pad: 112, reason: `preset-${normalizeResetPresetId(presetId)}-default-robot-cleaner`, avoidOverlap: true, avoidAllObstacles: true, attempts: 140, maxRadius: 260, }); if ((worldRef.items || []).some(item => item && !item.dead && item.type === "robot_cleaner")) return; const p = randPoint(worldRef, 88, center); addPresetItem(worldRef, "robot_cleaner", p.x, p.y, { reason: `preset-${normalizeResetPresetId(presetId)}-default-robot-cleaner` }); } function generatePresetWorld(worldRef, field, presetId, seedPopulation) { const preset = normalizeResetPresetId(presetId); switch (preset) { case "empty": return; case "athletic": generateAthleticPreset(worldRef, field); break; case "crowded": generateCrowdedPreset(worldRef, field); break; case "family": generateFamilyPreset(worldRef, field); break; case "war": generateWarPreset(worldRef, field); break; case "happy": generateHappyPreset(worldRef, field); break; case "hair_trigger": generateHairTriggerPreset(worldRef, field); break; case "default": default: generateDefaultPreset(worldRef, field, seedPopulation); break; } addDefaultPresetRobotCleaner(worldRef, preset); } function groundForResetPreset(presetId = "default") { const id = normalizeResetPresetId(presetId); if (id === "athletic") return "ice"; if (id === "war") return "foot_massage"; if (id === "happy") return "blanket"; return "soil"; } function presetLabel(presetId = "default") { return RESET_PRESETS[normalizeResetPresetId(presetId)]?.label || "\u30C7\u30D5\u30A9\u30EB\u30C8"; } const api = Object.freeze({ ids: Object.freeze(Object.keys(RESET_PRESETS)), definitions: RESET_PRESETS, normalize: normalizeResetPresetId, generatePresetWorld, groundForResetPreset, label: presetLabel, }); global.TarinaiResetPresets = api; global.TarinaiWorldResetPresets = api; })(typeof window !== "undefined" ? window : globalThis);