tarinai/js/world_ants_system.js
2026-07-15 14:44:29 +09:00

130 lines
6.2 KiB
JavaScript

"use strict";
(function (global) {
const World = global.World;
if (!World) throw new Error("World is not available for mixin: world_ants_system.js");
Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({
updateAnts(dt) {
if (!this.ants) this.ants = [];
this.antNestUpdateAccum = (this.antNestUpdateAccum || 0) + dt;
if (this.antNestUpdateAccum >= 3.0) {
const nestDt = this.antNestUpdateAccum;
this.antNestUpdateAccum = 0;
const nests = this.itemsOfType?.("ant_nest") || (this.items || []).filter(it => it && !it.dead && it.type === "ant_nest");
for (const nest of nests) {
if (global.TarinaiSignalSystem?.powerOverride?.(nest) === false) continue;
if (typeof ensureAntNestWorkerPool === "function") ensureAntNestWorkerPool(nest);
if (!Number.isFinite(nest.antCount)) nest.antCount = ANT_NEST_START_COUNT || 6;
if (typeof syncAntNestCount === "function") syncAntNestCount(this, nest);
nest.antSpawnTimer = Math.max(0, (nest.antSpawnTimer || 0) - nestDt);
let outside = 0;
for (const a of this.ants) if (a && !a.dead && a.kind === "worker" && a.homeId === nest.id) outside += 1;
const available = Array.isArray(nest.antWorkers) ? nest.antWorkers.length : Math.max(0, (nest.antCount || 0) - outside);
const maxOutside = Math.min(ANT_NEST_MAX_OUTSIDE || 3, nest.antCount || 0);
if (available > 0 && outside < maxOutside && (nest.antSpawnTimer || 0) <= 0 && (this.tarinai || []).some(t => t && !t.dead && !t.insideNestBoxId)) {
const hp = Array.isArray(nest.antWorkers) && nest.antWorkers.length ? clamp(nest.antWorkers.pop(), 0, ANT_WORKER_HP || 32) : (ANT_WORKER_HP || 32);
const a = new AntActor(this, {
kind: "worker",
homeId: nest.id,
x: nest.x + rand(-nest.r * 0.18, nest.r * 0.18),
y: nest.y - nest.r * 1.00 + rand(-3, 3),
hp,
maxHp: ANT_WORKER_HP || 32,
});
this.ants.push(a);
global.TarinaiAchievements?.recordAntPopulation?.(this);
if (typeof syncAntNestCount === "function") syncAntNestCount(this, nest);
nest.antSpawnTimer = rand(2.5, 5.5) + outside * 0.65;
this.drawListDirty = true;
}
}
}
const aliveAntCount = (this.ants || []).reduce((n, a) => n + (a && !a.dead ? 1 : 0), 0);
const stride = aliveAntCount >= 28 ? 3 : 2;
this.antUpdatePhase = ((this.antUpdatePhase || 0) + 1) % stride;
for (let i = 0; i < this.ants.length; i++) {
const a = this.ants[i];
if (!a || a.dead) continue;
a.updateAccum = (a.updateAccum || 0) + dt;
// Keep active hauling responsive, but stagger search/return/wander updates.
const force = a.state === "drag" || a.kind === "queen";
if (stride <= 1 || force || ((i + this.antUpdatePhase) % stride === 0)) {
const antDt = Math.min(a.updateAccum, 0.18);
a.updateAccum = 0;
a.update(antDt);
}
}
},
completeAntHaul(home, target) {
if (!home || home.dead || home.type !== "ant_nest" || !target || target.dead) return;
if (typeof ensureAntNestWorkerPool === "function") ensureAntNestWorkerPool(home);
const before = typeof syncAntNestCount === "function"
? syncAntNestCount(this, home)
: clamp(Math.round(home.antCount ?? ANT_NEST_START_COUNT ?? 6), 0, ANT_NEST_MAX_COUNT ?? 10);
if (before >= (ANT_NEST_MAX_COUNT || 10)) {
this.spawnQueenAnt(home);
} else {
if (Array.isArray(home.antWorkers)) home.antWorkers.push(ANT_WORKER_HP || 32);
home.antCount = clamp(before + 1, 0, ANT_NEST_MAX_COUNT || 10);
if (typeof syncAntNestCount === "function") syncAntNestCount(this, home);
}
target.x = home.x;
target.y = home.y;
audio.antNest?.();
global.TarinaiAchievements?.recordDamageSource?.(target, home, { world: this, reason: "\u30a2\u30ea\u306b\u98df\u3079\u3089\u308c\u305f" });
target.die("\u30a2\u30ea\u306b\u98df\u3079\u3089\u308c\u305f", { achievementDangerKill: true });
this.drawListDirty = true;
},
spawnQueenAnt(home) {
if (!home || home.dead || home.type !== "ant_nest") return null;
if ((home.queenSpawnAt || -999) + (CONFIG.dayLength || 120) > (this.time || 0)) return null;
if ((this.ants || []).some(a => a && !a.dead && a.kind === "queen" && a.homeId === home.id)) return null;
const spot = this.findAntNestFoundingSpot(home) || {
x: clamp((home.x || this.w * 0.5) + rand(-220, 220), 56, this.w - 56),
y: clamp((home.y || this.h * 0.5) + rand(-160, 160), 56, this.h - 56),
};
home.queenSpawnAt = this.time || 0;
const queen = new AntActor(this, {
kind: "queen",
homeId: home.id,
x: home.x,
y: home.y - (home.r || 34) * 0.9,
foundingX: spot.x,
foundingY: spot.y,
foundingDelayUntil: (this.time || 0) + 0.35,
});
this.ants.push(queen);
global.TarinaiAchievements?.recordAntPopulation?.(this);
audio.queenAnt?.();
this.drawListDirty = true;
return queen;
},
findAntNestFoundingSpot(source) {
const baseX = Number.isFinite(source?.x) ? source.x : this.w * 0.5;
const baseY = Number.isFinite(source?.y) ? source.y : this.h * 0.5;
let best = null;
let bestD = -Infinity;
for (let i = 0; i < 70; i++) {
const a = rand(0, Math.PI * 2);
const r = rand(150, Math.min(430, Math.max(this.w, this.h) * 0.52));
const x = clamp(baseX + Math.cos(a) * r, 56, this.w - 56);
const y = clamp(baseY + Math.sin(a) * r * 0.74, 56, this.h - 56);
const nest = new Item("ant_nest", x, y);
const spot = this.findPlacementSpot ? this.findPlacementSpot(nest, { allowOriginal: true, maxRadius: 80, attempts: 10 }) : { x, y };
if (!spot) continue;
let nearest = Infinity;
const nests = this.itemsOfType ? this.itemsOfType("ant_nest") : (this.items || []);
for (const it of nests) {
if (!it || it.dead || it.type !== "ant_nest") continue;
nearest = Math.min(nearest, distXY(spot.x, spot.y, it.x, it.y));
}
if (nearest > bestD) { best = spot; bestD = nearest; }
if (nearest > 280) break;
}
return best;
}
}));
})(typeof window !== "undefined" ? window : globalThis);