130 lines
6.2 KiB
JavaScript
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);
|