This commit is contained in:
33333-33333 2026-07-18 22:15:26 +09:00
commit d163ceb291
76 changed files with 1385 additions and 524 deletions

View file

@ -4,16 +4,15 @@
(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_TYPES = new Set(["ring", "fight", "flame", "fall", "bleed", "bubble", "eat", "explosion", "heart", "shoot_impact", "zunchi_miasma", "electric_shock", "wind"]);
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;
if (bucket?.length) return bucket.pop().reset(key, x, y, options);
return new Effect(key, x, y, options);
}
function releaseEffect(effect) {
@ -28,7 +27,25 @@
}
Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({
queueEffect(type, x, y, options = {}) {
// The visual-effects toggle is a true simulation gate: when disabled,
// do not allocate, enqueue, update, or draw transient Effect objects.
if (global.TarinaiPerf?.visualLevel?.("effects", "high") === "off") return null;
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))));
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;
},
spawnEffect(type, x, y, options = {}) {
if (global.TarinaiPerf?.visualLevel?.("effects", "high") === "off") return null;
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) {
@ -45,15 +62,7 @@
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;
return this.queueEffect(type, x, y, opts);
},
releaseEffect(effect) {
@ -463,7 +472,7 @@
if (this.addItem?.(it, "zunchi-burst")) spawned += 1;
}
if (!spawned) return 0;
this.effects?.push(new Effect("zunchi_miasma", sx, sy, { size: 28, life: 0.65, color: "rgba(74,91,50,0.48)" }));
this.queueEffect?.("zunchi_miasma", sx, sy, { size: 28, life: 0.65, color: "rgba(74,91,50,0.48)" });
this.log?.(`${source?.name || "\u305f\u308a\u306a\u3044"}\u304b\u3089\u6e9c\u307e\u3063\u3066\u3044\u305f\u305a\u3093\u3061\u304c\u5f3e\u3051\u98db\u3093\u3060\u3002`, "accident", { participants: source?.name ? [source] : [] });
audio.place?.("zunchi");
this.ensureSpatial?.("zunchi-burst");
@ -677,10 +686,13 @@
resolveFightOutcome(winner, loser) {
if (!winner || !loser || winner.dead || loser.dead) return;
const key = `${winner.id}:${loser.id}:${Math.floor(this.time / 2)}`;
// Keep only one timestamp per directional pair. The old time-bucket key
// created a new permanent property every two seconds for recurring fights.
const key = `${winner.id}:${loser.id}`;
if (!this.resolvedFightIds) this.resolvedFightIds = {};
if (this.resolvedFightIds[key]) return;
this.resolvedFightIds[key] = true;
const resolvedAt = Number(this.resolvedFightIds[key]);
if (Number.isFinite(resolvedAt) && this.time - resolvedAt < 2) return;
this.resolvedFightIds[key] = this.time;
winner.defeatedById = null;
winner.defeatedTimer = 0;
winner.fearTimer = Math.max(0, winner.fearTimer - 0.75);
@ -797,7 +809,6 @@
ball.amount = Math.max(ball.amount || 999, 999);
audio.ballHit?.();
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;
},