This commit is contained in:
33333-33333 2026-07-18 13:06:02 +09:00
commit f657b6a4a4
94 changed files with 3970 additions and 1241 deletions

View file

@ -4,7 +4,68 @@
(function (global) {
const World = global.World;
if (!World) throw new Error("World is not available for mixin: world_combat_effects.js");
const EFFECT_POOL_TYPES = new Set(["ring", "fight", "flame", "fall"]);
const EFFECT_POOL_LIMIT_PER_TYPE = 96;
const effectPool = new Map();
function acquireEffect(type, x, y, options) {
const key = String(type || "");
const bucket = EFFECT_POOL_TYPES.has(key) ? effectPool.get(key) : null;
const effect = bucket?.length ? bucket.pop() : new Effect(key, x, y, options);
if (bucket?.length >= 0) effect.reset(key, x, y, options);
return effect;
}
function releaseEffect(effect) {
const key = String(effect?.type || "");
if (!effect || effect._effectPooled || !EFFECT_POOL_TYPES.has(key)) return false;
let bucket = effectPool.get(key);
if (!bucket) effectPool.set(key, bucket = []);
if (bucket.length >= EFFECT_POOL_LIMIT_PER_TYPE) return false;
effect._effectPooled = true;
bucket.push(effect);
return true;
}
Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({
spawnEffect(type, x, y, options = {}) {
const opts = options || {};
const importance = Math.max(0, Math.min(3, Number(opts.importance ?? (type === "explosion" || type === "electric_shock" || type === "bubble" || type === "heart" ? 3 : type === "ring" || type === "fall" ? 2 : 1))));
if (importance <= 1 && opts.allowOffscreen !== true) {
const margin = Math.max(80, Number(opts.offscreenMargin || 140));
const vw = typeof this.visibleWorldW === "function" ? this.visibleWorldW() : this.viewportW || this.w;
const vh = typeof this.visibleWorldH === "function" ? this.visibleWorldH() : this.viewportH || this.h;
const left = Number(this.cameraX || 0) - margin;
const top = Number(this.cameraY || 0) - margin;
if (x < left || y < top || x > left + vw + margin * 2 || y > top + vh + margin * 2) {
if (global.TarinaiPerf?.diagnosticsEnabled?.() === true) {
this._effectSpawnStats = this._effectSpawnStats || {};
this._effectSpawnStats.offscreenCulled = (this._effectSpawnStats.offscreenCulled || 0) + 1;
}
return null;
}
}
const effect = acquireEffect(type, x, y, opts);
effect._effectImportance = importance;
this.effects.push(effect);
if (global.TarinaiPerf?.diagnosticsEnabled?.() === true) {
this._effectSpawnStats = this._effectSpawnStats || {};
this._effectSpawnStats.requested = (this._effectSpawnStats.requested || 0) + 1;
if (EFFECT_POOL_TYPES.has(String(type || ""))) this._effectSpawnStats.poolEligible = (this._effectSpawnStats.poolEligible || 0) + 1;
}
return effect;
},
releaseEffect(effect) {
const released = releaseEffect(effect);
if (released) {
if (global.TarinaiPerf?.diagnosticsEnabled?.() === true) {
this._effectSpawnStats = this._effectSpawnStats || {};
this._effectSpawnStats.pooled = (this._effectSpawnStats.pooled || 0) + 1;
}
}
return released;
},
damageAntsInRadius(x, y, radius, damageFn, reason = "", opts = {}) {
let hit = 0;
const candidates = this.nearbyAnts?.(x, y, radius + 24, true) || this.ants || [];
@ -68,7 +129,7 @@
pin.spinVelocity = clamp((pin.spinVelocity || 0) + deterministicRange(this, `${salt}-pin-spin`, -18, 18, source || pin, pin), -38, 38);
pin.noStickUntil = (this.time || 0) + 0.22;
pin.lastPokedAt = this.time || 0;
this.effects.push(new Effect("ring", pin.x, pin.y, { size: Math.max(12, (pin.r || 8) * 1.8), life: 0.18, color: "rgba(255,230,116,0.46)" }));
this.spawnEffect("ring", pin.x, pin.y, { size: Math.max(12, (pin.r || 8) * 1.8), life: 0.18, color: "rgba(255,230,116,0.46)" });
blastedPins += 1;
}
return blastedPins;
@ -87,22 +148,22 @@
it.amount = 0;
const x = it.x, y = it.y;
const blastRadius = 240 * (it.blastScale || 1);
this.effects.push(new Effect("explosion", x, y, { size: 86, life: 1.05, color: "rgba(255,182,66,0.92)" }));
this.effects.push(new Effect("explosion", x, y, { size: 128, life: 0.82, color: "rgba(255,92,42,0.70)" }));
this.spawnEffect("explosion", x, y, { size: 86, life: 1.05, color: "rgba(255,182,66,0.92)" });
this.spawnEffect("explosion", x, y, { size: 128, life: 0.82, color: "rgba(255,92,42,0.70)" });
for (let r = 0; r < 4; r++) {
this.effects.push(new Effect("ring", x, y, { size: 30 + r * 24, life: 0.48 + r * 0.11, color: r % 2 ? "rgba(255,116,62,0.78)" : "rgba(255,240,124,0.88)" }));
this.spawnEffect("ring", x, y, { size: 30 + r * 24, life: 0.48 + r * 0.11, color: r % 2 ? "rgba(255,116,62,0.78)" : "rgba(255,240,124,0.88)" });
}
this.blastZunchiFrom(x, y, blastRadius * 0.92, it.blastScale || 1);
for (let i = 0; i < 34; i++) {
const a = Math.PI * 2 * i / 34 + deterministicRange(this, "firecracker-spark-angle", -0.10, 0.10, it, i);
const speed = deterministicRange(this, "firecracker-spark-speed", 90, 260, it, i);
this.effects.push(new Effect(i % 3 === 0 ? "explosion" : "fight", x + Math.cos(a) * deterministicRange(this, "firecracker-spark-x", 2, 22, it, i), y + Math.sin(a) * deterministicRange(this, "firecracker-spark-y", 2, 22, it, i), {
this.spawnEffect(i % 3 === 0 ? "explosion" : "fight", x + Math.cos(a) * deterministicRange(this, "firecracker-spark-x", 2, 22, it, i), y + Math.sin(a) * deterministicRange(this, "firecracker-spark-y", 2, 22, it, i), {
vx: Math.cos(a) * speed,
vy: Math.sin(a) * speed,
size: deterministicRange(this, "firecracker-spark-size", 8, i % 3 === 0 ? 24 : 20, it, i),
life: deterministicRange(this, "firecracker-spark-life", 0.26, 0.72, it, i),
color: i % 3 === 0 ? "rgba(255,218,70,0.78)" : "rgba(112,72,42,0.72)",
}));
});
}
for (const t of this.tarinai) {
if (t.dead) continue;
@ -177,7 +238,7 @@
ball.spinVelocity = clamp((ball.spinVelocity || 0) + deterministicRange(this, "firecracker-ball-spin", -24, 24, it, ball), -38, 38);
ball.lastPokedAt = this.time || 0;
ball.pokeCombo = Math.max(ball.pokeCombo || 0, 5);
this.effects.push(new Effect("ring", ball.x, ball.y, { size: Math.max(18, ball.r * 1.2), life: 0.24, color: "rgba(255,230,116,0.58)" }));
this.spawnEffect("ring", ball.x, ball.y, { size: Math.max(18, ball.r * 1.2), life: 0.24, color: "rgba(255,230,116,0.58)" });
blastedBalls += 1;
}
const blastedPins = this.blastLoosePinsFrom(x, y, blastRadius, it, it.blastScale || 1, "firecracker");
@ -193,22 +254,22 @@
const blastRadius = 240 * blastScale;
t.explosionDisease = false;
t.explosionDiseaseTimer = 0;
this.effects.push(new Effect("explosion", x, y, { size: 86 * blastScale, life: 1.05, color: "rgba(255,182,66,0.92)" }));
this.effects.push(new Effect("explosion", x, y, { size: 128 * blastScale, life: 0.82, color: "rgba(255,92,42,0.70)" }));
this.spawnEffect("explosion", x, y, { size: 86 * blastScale, life: 1.05, color: "rgba(255,182,66,0.92)" });
this.spawnEffect("explosion", x, y, { size: 128 * blastScale, life: 0.82, color: "rgba(255,92,42,0.70)" });
for (let r = 0; r < 4; r++) {
this.effects.push(new Effect("ring", x, y, { size: (30 + r * 24) * blastScale, life: 0.48 + r * 0.11, color: r % 2 ? "rgba(255,116,62,0.78)" : "rgba(255,240,124,0.88)" }));
this.spawnEffect("ring", x, y, { size: (30 + r * 24) * blastScale, life: 0.48 + r * 0.11, color: r % 2 ? "rgba(255,116,62,0.78)" : "rgba(255,240,124,0.88)" });
}
this.blastZunchiFrom(x, y, blastRadius * 0.92, blastScale);
for (let i = 0; i < 34; i++) {
const a = Math.PI * 2 * i / 34 + deterministicRange(this, "disease-explosion-spark-angle", -0.10, 0.10, t, i);
const speed = deterministicRange(this, "disease-explosion-spark-speed", 90, 260, t, i) * blastScale;
this.effects.push(new Effect(i % 3 === 0 ? "explosion" : "fight", x + Math.cos(a) * deterministicRange(this, "disease-explosion-spark-x", 2, 22, t, i) * blastScale, y + Math.sin(a) * deterministicRange(this, "disease-explosion-spark-y", 2, 22, t, i) * blastScale, {
this.spawnEffect(i % 3 === 0 ? "explosion" : "fight", x + Math.cos(a) * deterministicRange(this, "disease-explosion-spark-x", 2, 22, t, i) * blastScale, y + Math.sin(a) * deterministicRange(this, "disease-explosion-spark-y", 2, 22, t, i) * blastScale, {
vx: Math.cos(a) * speed,
vy: Math.sin(a) * speed,
size: deterministicRange(this, "disease-explosion-spark-size", 8, i % 3 === 0 ? 24 : 20, t, i) * blastScale,
life: deterministicRange(this, "disease-explosion-spark-life", 0.26, 0.72, t, i),
color: i % 3 === 0 ? "rgba(255,218,70,0.78)" : "rgba(112,72,42,0.72)",
}));
});
}
for (const o of this.tarinai) {
if (!o || o.dead || o === t) continue;
@ -272,7 +333,7 @@
ball.spinVelocity = clamp((ball.spinVelocity || 0) + deterministicRange(this, "disease-explosion-ball-spin", -24, 24, t, ball), -38, 38);
ball.lastPokedAt = this.time || 0;
ball.pokeCombo = Math.max(ball.pokeCombo || 0, 5);
this.effects.push(new Effect("ring", ball.x, ball.y, { size: Math.max(18, ball.r * 1.2), life: 0.24, color: "rgba(255,230,116,0.58)" }));
this.spawnEffect("ring", ball.x, ball.y, { size: Math.max(18, ball.r * 1.2), life: 0.24, color: "rgba(255,230,116,0.58)" });
}
this.blastLoosePinsFrom(x, y, blastRadius, t, blastScale, "disease-explosion");
t.die("\u7206\u767a\u75c5");
@ -358,7 +419,7 @@
}
}
if (deterministicChance(this, "water-hose-ring", 0.72, x, y)) {
this.effects.push(new Effect("ring", x + deterministicRange(this, "water-hose-ring-x", -18, 18, x, y), y + deterministicRange(this, "water-hose-ring-y", -12, 12, x, y), { size: deterministicRange(this, "water-hose-ring-size", 8, 22, x, y), life: deterministicRange(this, "water-hose-ring-life", 0.18, 0.32, x, y), color: "rgba(128,204,238,0.60)" }));
this.spawnEffect("ring", x + deterministicRange(this, "water-hose-ring-x", -18, 18, x, y), y + deterministicRange(this, "water-hose-ring-y", -12, 12, x, y), { size: deterministicRange(this, "water-hose-ring-size", 8, 22, x, y), life: deterministicRange(this, "water-hose-ring-life", 0.18, 0.32, x, y), color: "rgba(128,204,238,0.60)" });
}
if (cleaned > 0.25) {
this.drawListDirty = true;
@ -445,60 +506,60 @@
if ((this.effectCounts.bubble || 0) >= CONFIG.bubbleLimit) return;
if (text === "z") text = "Zzz...";
audio.bubble(text);
this.effects.push(new Effect("bubble", x, y, {
this.spawnEffect("bubble", x, y, {
vx: deterministicRange(this, "bubble-vx", -2, 2, x, y, text),
vy: deterministicRange(this, "bubble-vy", -5, -2, x, y, text),
life: deterministicRange(this, "bubble-life", 2.2, 3.2, x, y, text),
size: 8,
text,
color,
}));
});
this.effectCounts.bubble = (this.effectCounts.bubble || 0) + 1;
},
spawnHeadbuttEffect(x, y) {
this.effects.push(new Effect("fight", x, y, {
this.spawnEffect("fight", x, y, {
size: deterministicRange(this, "headbutt-effect-size", 10, 16, x, y),
life: deterministicRange(this, "headbutt-effect-life", 0.20, 0.34, x, y),
color: "rgba(105, 62, 34, 0.86)",
}));
});
if (deterministicChance(this, "headbutt-audio", 0.5, x, y)) audio.fight();
},
spawnFallEffect(x, y, scale = 1) {
if ((this.effectCounts.fall || 0) > 18) return;
this.effects.push(new Effect("fall", x, y, {
this.spawnEffect("fall", x, y, {
vx: deterministicRange(this, "fall-effect-vx", -12, 12, x, y),
vy: deterministicRange(this, "fall-effect-vy", -4, 3, x, y),
size: deterministicRange(this, "fall-effect-size", 20, 32, x, y) * scale,
life: deterministicRange(this, "fall-effect-life", 0.48, 0.72, x, y),
color: "rgba(154, 124, 80, 0.58)",
}));
});
},
spawnEatEffect(x, y, color = "#f1dfb7") {
if (deterministicChance(this, "eat-effect", 0.70, x, y, color)) {
this.effects.push(new Effect("eat", x + deterministicRange(this, "eat-effect-x", -4, 4, x, y, color), y + deterministicRange(this, "eat-effect-y", -4, 4, x, y, color), {
this.spawnEffect("eat", x + deterministicRange(this, "eat-effect-x", -4, 4, x, y, color), y + deterministicRange(this, "eat-effect-y", -4, 4, x, y, color), {
vx: deterministicRange(this, "eat-effect-vx", -10, 10, x, y, color),
vy: deterministicRange(this, "eat-effect-vy", -18, -3, x, y, color),
size: deterministicRange(this, "eat-effect-size", 2.2, 4.2, x, y, color),
life: deterministicRange(this, "eat-effect-life", 0.18, 0.32, x, y, color),
color,
}));
});
}
if (deterministicChance(this, "eat-ring", 0.08, x, y, color)) {
this.effects.push(new Effect("ring", x, y, { size: 5, life: 0.20, color }));
this.spawnEffect("ring", x, y, { size: 5, life: 0.20, color });
}
},
spawnBleedEffect(x, y) {
this.effects.push(new Effect("bleed", x + deterministicRange(this, "bleed-effect-x", -3, 3, x, y), y + deterministicRange(this, "bleed-effect-y", -2, 3, x, y), {
this.spawnEffect("bleed", x + deterministicRange(this, "bleed-effect-x", -3, 3, x, y), y + deterministicRange(this, "bleed-effect-y", -2, 3, x, y), {
vx: deterministicRange(this, "bleed-effect-vx", -8, 8, x, y),
vy: deterministicRange(this, "bleed-effect-vy", 3, 14, x, y),
size: deterministicRange(this, "bleed-effect-size", 2.0, 3.8, x, y),
life: deterministicRange(this, "bleed-effect-life", 0.34, 0.62, x, y),
color: "rgba(80, 172, 55, 0.78)",
}));
});
},
isSleepFurniture(it) {
@ -735,7 +796,7 @@
ball.spinVelocity = clamp((ball.spinVelocity || 0) + (nx >= 0 ? 1 : -1) * ((isBalloon ? 2.4 : 5.6) + ball.pokeCombo * (isBalloon ? 0.55 : 1.15)), -38, 38);
ball.amount = Math.max(ball.amount || 999, 999);
audio.ballHit?.();
this.effects.push(new Effect("ring", ball.x, ball.y, { size: Math.max(16, ball.r * (0.95 + ball.pokeCombo * 0.05)), life: 0.22, color: isBalloon ? "rgba(94,188,236,0.56)" : "rgba(255,255,255,0.58)" }));
this.spawnEffect("ring", ball.x, ball.y, { size: Math.max(16, ball.r * (0.95 + ball.pokeCombo * 0.05)), life: 0.22, color: isBalloon ? "rgba(94,188,236,0.56)" : "rgba(255,255,255,0.58)" });
if (!isBalloon) this.log("\u30dc\u30fc\u30eb\u3092\u3064\u3064\u3044\u3066\u8ee2\u304c\u3057\u305f\u3002", "observe", { eventType: "ball_poke" });
return true;
},