tarinai/js/tarinai_item_effects.js
2026-07-18 13:06:02 +09:00

384 lines
20 KiB
JavaScript

"use strict";
(function (global) {
const Tarinai = global.Tarinai;
if (!Tarinai) throw new Error("Tarinai is not available for mixin: tarinai_item_effects.js");
const EFFECT_REGISTRY = global.TarinaiItemRegistry.effect || null;
const RANDOM_EFFECT_POOL = Object.freeze(EFFECT_REGISTRY?.randomPool?.() || [
"love_mochi", "fight_mochi", "sleep_drug", "laxative", "ammo", "protein", "niteropu", "mercury", "giant_drug", "dwarf_drug",
"disease_zunchi", "disease_sleep", "disease_explosion", "disease_fight",
]);
function effectLabel(id = "") {
return EFFECT_REGISTRY?.label?.(id) || id || "\u4e0d\u660e";
}
function effectColor(id = "", fallback = "rgba(255,242,160,0.85)") {
return EFFECT_REGISTRY?.color?.(id) || fallback;
}
function sizeDurationScale(item = null) {
const size = item?.toolSize || "medium";
if (size === "small") return 0.74;
if (size === "large") return 1.45;
return 1;
}
function recordItemChange(tarinai, id = "", reason = "\u30a2\u30a4\u30c6\u30e0\u52b9\u679c") {
tarinai?.recordChangeCause?.(reason, effectLabel(id));
}
function pickRandomEffects(count = 2) {
const pool = [...RANDOM_EFFECT_POOL];
const out = [];
while (pool.length && out.length < count) {
const idx = Math.floor(Math.random() * pool.length);
out.push(pool.splice(idx, 1)[0]);
}
return out;
}
Object.defineProperties(Tarinai.prototype, Object.getOwnPropertyDescriptors({
ensureItemEffectState() {
if (!this.itemEffectTimers || typeof this.itemEffectTimers !== "object") this.itemEffectTimers = { laxative: 0, ammo: 0, sedative: 0 };
if (!Number.isFinite(this.itemEffectTimers.laxative)) this.itemEffectTimers.laxative = 0;
if (!Number.isFinite(this.itemEffectTimers.ammo)) this.itemEffectTimers.ammo = 0;
if (!Number.isFinite(this.itemEffectTimers.sedative)) this.itemEffectTimers.sedative = 0;
if (!Number.isFinite(this.laxativePoopTimer)) this.laxativePoopTimer = 3;
if (!Number.isFinite(this.ammoBumpCooldown)) this.ammoBumpCooldown = 0;
if (this.powerItemMode !== "protein" && this.powerItemMode !== "niteropu") this.powerItemMode = "";
if (this.sizeItemMode !== "giant_drug" && this.sizeItemMode !== "dwarf_drug") this.sizeItemMode = "";
if (this.lifeItemMode !== "mercury") this.lifeItemMode = "";
if (!Number.isFinite(this.mercuryLifeSpanBase)) this.mercuryLifeSpanBase = null;
if (!Number.isFinite(this.mercuryLifeMultiplier)) this.mercuryLifeMultiplier = this.lifeItemMode === "mercury" ? 1.3 : 1;
this.mercuryLifeMultiplier = Math.max(1, this.mercuryLifeMultiplier || 1);
if (!Number.isFinite(this.itemDamageTimer)) this.itemDamageTimer = 1;
if (!Number.isFinite(this.powerEffectPulse)) this.powerEffectPulse = 0;
if (!Array.isArray(this.mysteryDrugHistory)) this.mysteryDrugHistory = [];
},
hasItemEffect(id = "") {
this.ensureItemEffectState();
if (id === "protein" || id === "niteropu") return this.powerItemMode === id;
if (id === "giant_drug" || id === "dwarf_drug") return this.sizeItemMode === id;
if (id === "mercury") return this.lifeItemMode === "mercury";
return (this.itemEffectTimers?.[id] || 0) > 0.04;
},
setTimedItemEffect(id = "", seconds = 0) {
this.ensureItemEffectState();
if (!(id in this.itemEffectTimers)) this.itemEffectTimers[id] = 0;
this.itemEffectTimers[id] = Math.max(this.itemEffectTimers[id] || 0, Number(seconds) || 0);
if (id === "laxative") this.laxativePoopTimer = Math.min(Math.max(this.laxativePoopTimer || 3, 0.2), 3);
recordItemChange(this, id, "\u6642\u9593\u52b9\u679c");
return true;
},
itemSizeMultiplier() {
this.ensureItemEffectState();
if (this.sizeItemMode === "giant_drug") return EFFECT_REGISTRY?.modifier?.("giant_drug", "sizeMultiplier", 3) ?? 3;
if (this.sizeItemMode === "dwarf_drug") return EFFECT_REGISTRY?.modifier?.("dwarf_drug", "sizeMultiplier", 0.25) ?? 0.25;
return 1;
},
effectiveScale() {
return (this.scale || 0.28) * (this.itemSizeMultiplier ? this.itemSizeMultiplier() : 1);
},
refreshEffectiveSize() {
const oldMax = Number.isFinite(this.maxEnergy) ? Math.max(1, this.maxEnergy) : 100;
const oldEnergy = Number.isFinite(this.energy) ? this.energy : oldMax;
const mult = this.itemSizeMultiplier ? this.itemSizeMultiplier() : 1;
this.radius = 56 * (this.scale || 0.28) * mult;
const nextMax = typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(this) : 100;
this.maxEnergy = nextMax;
const ratio = clamp(oldEnergy / oldMax, 0, 1.35);
this.energy = clamp(ratio * nextMax, 0, nextMax);
return this.radius;
},
energyRatio() {
return typeof tarinaiEnergyRatio === "function" ? tarinaiEnergyRatio(this) : clamp((this.energy || 0) / Math.max(1, this.maxEnergy || 100), 0, 1);
},
metabolicHungerMultiplier() {
return typeof tarinaiMetabolicHungerScale === "function" ? tarinaiMetabolicHungerScale(this) : 1;
},
setPowerItemMode(mode = "") {
this.ensureItemEffectState();
if (mode !== "protein" && mode !== "niteropu") return false;
this.powerItemMode = mode;
this.powerEffectKind = mode;
this.powerEffectPulse = Math.max(this.powerEffectPulse || 0, 3.2);
this.spawnPowerItemEffect(mode, 1.5);
recordItemChange(this, mode, "\u80fd\u529b\u5909\u5316");
return true;
},
setSizeItemMode(mode = "") {
this.ensureItemEffectState();
if (mode !== "giant_drug" && mode !== "dwarf_drug") return false;
this.sizeItemMode = mode;
this.itemDamageTimer = Math.min(Math.max(this.itemDamageTimer || 1, 0.08), 1);
this.refreshEffectiveSize();
this.powerEffectKind = mode;
this.powerEffectPulse = Math.max(this.powerEffectPulse || 0, 4.0);
this.spawnPowerItemEffect(mode, mode === "giant_drug" ? 1.9 : 1.2);
recordItemChange(this, mode, "\u4f53\u683c\u5909\u5316");
return true;
},
setMercuryEffect() {
this.ensureItemEffectState();
if (this.lifeItemMode !== "mercury" || !Number.isFinite(this.mercuryLifeSpanBase)) {
this.mercuryLifeSpanBase = Number.isFinite(this.lifeSpan) ? this.lifeSpan : null;
this.mercuryLifeMultiplier = 1;
}
this.lifeItemMode = "mercury";
this.mercuryLifeMultiplier = Math.max(1, (this.mercuryLifeMultiplier || 1) * 1.3);
if (Number.isFinite(this.mercuryLifeSpanBase)) this.lifeSpan = Math.max(this.age + 5, this.mercuryLifeSpanBase * this.mercuryLifeMultiplier);
this.powerEffectKind = "mercury";
this.powerEffectPulse = Math.max(this.powerEffectPulse || 0, 2.7);
this.spawnPowerItemEffect("mercury", 0.95);
recordItemChange(this, "mercury", "\u5bff\u547d\u5909\u5316");
return true;
},
clearAllItemEffects() {
this.ensureItemEffectState();
for (const key of Object.keys(this.itemEffectTimers || {})) this.itemEffectTimers[key] = 0;
this.loveMochiTimer = 0;
this.fightMochiTimer = 0;
this.powerItemMode = "";
this.sizeItemMode = "";
if (this.lifeItemMode === "mercury" && Number.isFinite(this.mercuryLifeSpanBase)) {
this.lifeSpan = Math.max(this.age + 5, this.mercuryLifeSpanBase);
}
this.lifeItemMode = "";
this.mercuryLifeSpanBase = null;
this.mercuryLifeMultiplier = 1;
this.laxativePoopTimer = 3;
this.ammoBumpCooldown = 0;
this.itemDamageTimer = 1;
this.powerEffectKind = "zunda_juice";
this.powerEffectPulse = Math.max(this.powerEffectPulse || 0, 2.2);
this.refreshEffectiveSize();
this.spawnPowerItemEffect("zunda_juice", 1.4);
recordItemChange(this, "zunda_juice", "\u52b9\u679c\u89e3\u9664");
return true;
},
applyParamItemEffect(type = "", item = null, options = {}) {
this.ensureItemEffectState();
const scale = sizeDurationScale(item);
let applied = [];
if (type === "mystery_drug") {
const picks = pickRandomEffects(2);
for (const effect of picks) this.applyRandomizedBehaviorEffect(effect, item);
this.mysteryDrugHistory = [...(this.mysteryDrugHistory || []), `${picks.map(effectLabel).join(" + ")}`].slice(-8);
this.bubble("???", 1.2, effectColor("mystery_drug"));
this.spawnPowerItemEffect("mystery_drug", 1.0);
applied = picks;
} else if (EFFECT_REGISTRY?.apply?.(type, this, { item, scale, source: options.source || "item", nutrition: options.nutrition })) {
applied.push(type);
if (type === "laxative") {
this.spawnPowerItemEffect("laxative", 0.75);
} else if (type === "ammo") {
this.spawnPowerItemEffect("ammo", 1.1);
}
} else {
return false;
}
if (!options.silentLog && this.world?.log) {
const label = type === "mystery_drug" ? `\u602a\u3057\u3044\u85ac(${applied.map(effectLabel).join(" + ")})` : effectLabel(type);
this.world.log(`${this.name}\u306b${label}\u306e\u52b9\u679c\u304c\u51fa\u305f\u3002`, "food", { participants: [this] });
}
this.recordChangeCause?.("\u30a2\u30a4\u30c6\u30e0\u6442\u53d6", applied.map(effectLabel).join(" + ") || effectLabel(type));
return true;
},
applyRandomizedBehaviorEffect(effect = "", item = null) {
const scale = sizeDurationScale(item);
return Boolean(EFFECT_REGISTRY?.apply?.(effect, this, { item, scale, source: "mystery_drug" }));
},
diseaseChance(base = 0) {
const b = Math.max(0, Number(base) || 0) / 3;
if (this.powerItemMode === "protein") return b * (EFFECT_REGISTRY?.modifier?.("protein", "diseaseChanceMultiplier", 0.5) ?? 0.5);
if (this.powerItemMode === "niteropu") return b * (EFFECT_REGISTRY?.modifier?.("niteropu", "diseaseChanceMultiplier", 2.0) ?? 2.0);
return b;
},
outgoingDamage(amount = 0) {
let value = (Number(amount) || 0) * (this.geneticStatRatio ? this.geneticStatRatio("attack") : 1);
if (this.powerItemMode === "protein") value *= EFFECT_REGISTRY?.modifier?.("protein", "damageMultiplier", 1.75) ?? 1.75;
if (this.powerItemMode === "niteropu") value *= EFFECT_REGISTRY?.modifier?.("niteropu", "damageMultiplier", 0.5) ?? 0.5;
if (this.sizeItemMode === "giant_drug") value *= EFFECT_REGISTRY?.modifier?.("giant_drug", "damageMultiplier", 2.0) ?? 2.0;
if (this.sizeItemMode === "dwarf_drug") value *= EFFECT_REGISTRY?.modifier?.("dwarf_drug", "damageMultiplier", 0.25) ?? 0.25;
return value;
},
itemSpeedMultiplier() {
let value = this.hasItemEffect("ammo") ? (EFFECT_REGISTRY?.modifier?.("ammo", "speedMultiplier", 10) ?? 10) : 1;
if (this.sizeItemMode === "giant_drug") value *= EFFECT_REGISTRY?.modifier?.("giant_drug", "speedMultiplier", 1.5) ?? 1.5;
if (this.sizeItemMode === "dwarf_drug") value *= EFFECT_REGISTRY?.modifier?.("dwarf_drug", "speedMultiplier", 0.5) ?? 0.5;
if (this.hasItemEffect("sedative")) value *= EFFECT_REGISTRY?.modifier?.("sedative", "speedMultiplier", 0.72) ?? 0.72;
return value;
},
applyFoodHungerRelief(amount = 0) {
const raw = Math.max(0, Number(amount) || 0);
const actual = this.hasItemEffect("laxative") ? raw * (EFFECT_REGISTRY?.modifier?.("laxative", "hungerReliefMultiplier", 0) ?? 0) : raw;
const now = this.world?.time || 0;
if (actual >= 3.5) {
const day = Number(this.world?.config?.dayLength || CONFIG?.dayLength || 120) || 120;
const base = day / 3.65;
const jitter = 0.82 + stableUnit(this.familyKey || this.id, "meal-cooldown") * 0.36;
this.mealCooldownUntil = Math.max(this.mealCooldownUntil || 0, now + base * jitter);
this.needSatisfaction = this.needSatisfaction || (typeof createDefaultNeeds === "function" ? createDefaultNeeds() : {});
this.needSatisfaction.food = Math.max(this.needSatisfaction.food || 0, Math.min(88, actual * 1.85));
this.actionCooldowns = this.actionCooldowns || {};
this.actionCooldowns.food = Math.max(this.actionCooldowns.food || 0, 5.2);
}
this.hunger -= actual;
if (actual >= 3.5 && this.hunger < 68 && (getTarinaiBehaviorNeed(this)) === "food" && !(typeof isTarinaiBehaviorForced === "function" ? isTarinaiBehaviorForced(this) : Boolean(this.behavior?.source === "forced" || this.behavior?.forcedId || this.behavior?.forcedRequest))) {
this.behaviorLockTimer = Math.min(this.behaviorLockTimer || 0, 0.25);
}
return actual;
},
updateItemEffects(dt = 0) {
this.ensureItemEffectState();
const step = Math.max(0, Number(dt) || 0);
this.refreshEffectiveSize();
for (const key of Object.keys(this.itemEffectTimers)) this.itemEffectTimers[key] = Math.max(0, (this.itemEffectTimers[key] || 0) - step);
this.ammoBumpCooldown = Math.max(0, (this.ammoBumpCooldown || 0) - step);
this.powerEffectPulse = Math.max(0, (this.powerEffectPulse || 0) - step);
if (this.hasItemEffect("laxative")) {
this.laxativePoopTimer = Math.max(0, (this.laxativePoopTimer || 3) - step);
while (this.laxativePoopTimer <= 0 && this.hasItemEffect("laxative")) {
this.laxativePoopTimer += EFFECT_REGISTRY?.get?.("laxative")?.tickInterval || 3;
EFFECT_REGISTRY?.tick?.("laxative", this, step);
}
} else {
this.laxativePoopTimer = Math.min(Math.max(this.laxativePoopTimer || 3, 0.2), 3);
}
if (this.sizeItemMode === "giant_drug" || this.sizeItemMode === "dwarf_drug") {
this.itemDamageTimer = Math.max(0, (this.itemDamageTimer || 1) - step);
while (this.itemDamageTimer <= 0 && !this.dead && (this.sizeItemMode === "giant_drug" || this.sizeItemMode === "dwarf_drug")) {
this.itemDamageTimer += 1;
EFFECT_REGISTRY?.tick?.(this.sizeItemMode, this, step);
}
} else {
this.itemDamageTimer = Math.min(Math.max(this.itemDamageTimer || 1, 0.08), 1);
}
if (this.powerEffectPulse > 0.04 && Math.random() < step * 2.4) this.spawnPowerItemEffect(this.powerEffectKind || this.powerItemMode || this.sizeItemMode || this.lifeItemMode, 0.38);
if (this.powerItemMode === "protein" || this.powerItemMode === "niteropu") {
this.powerEffectKind = this.powerItemMode;
if (Math.random() < step * 0.72) this.spawnPowerItemEffect(this.powerItemMode, this.powerItemMode === "protein" ? 0.32 : 0.28);
}
if (this.hasItemEffect("ammo") && Math.random() < step * 3.0) this.spawnPowerItemEffect("ammo", 0.32);
if (this.hasItemEffect("sedative") && Math.random() < step * 0.9) this.spawnPowerItemEffect("sedative", 0.22);
if ((this.sizeItemMode === "giant_drug" || this.sizeItemMode === "dwarf_drug") && Math.random() < step * 1.4) this.spawnPowerItemEffect(this.sizeItemMode, 0.24);
},
forceLaxativePoop() {
if (!this.world || this.dead) return;
if (this.hasLodgedPinEffect?.("blocksZunchi")) {
this.oshiriByoZunchiStock = Math.min(24, Math.max(0, this.oshiriByoZunchiStock || 0) + 1);
this.thought = (this.oshiriByoZunchiStock || 0) >= 6 ? "\u304a\u3057\u308a\u92f2\u3067\u305a\u3093\u3061\u304c\u6e9c\u307e\u3063\u3066\u3064\u3089\u3044" : "\u304a\u3057\u308a\u92f2\u3067\u305a\u3093\u3061\u3092\u6211\u6162\u3057\u3066\u3044\u308b";
return;
}
const side = this.facingDir ? this.facingDir() : (this.facing || 1);
const x = this.x - side * this.radius * rand(0.72, 0.96);
const y = this.y + this.radius * rand(0.25, 0.48);
this.poopCount = (this.poopCount || 0) + 1;
this.world.spawnZunchi?.(x, y);
this.digest = Math.max(0, (this.digest || 0) - 3.0);
const now = this.world?.time || 0;
this.bubble("\u3076\u308a\u3085\u3063", 1.2, effectColor("laxative"), { force: true });
if ((this.world.time || 0) > (this.nextLaxativeLogAt || -999)) {
this.nextLaxativeLogAt = (this.world.time || 0) + 9;
this.world.log?.(`${this.name}\u306f\u4e0b\u5264\u306e\u52b9\u679c\u3067\u305a\u3093\u3061\u3092\u653e\u51fa\u3057\u305f\u3002`, "accident", { participants: [this] });
}
},
spawnPowerItemEffect(kind = "", intensity = 1) {
if (!this.world?.effects) return;
const color = kind === "protein"
? "rgba(224,54,40,0.88)"
: (kind === "niteropu" ? "rgba(116,80,190,0.84)" : effectColor(kind));
const n = Math.max(1, Math.round((kind === "protein" || kind === "niteropu" ? 3 : 2) * intensity));
for (let i = 0; i < n; i++) {
const angle = rand(0, Math.PI * 2);
const r = this.radius * rand(0.18, 0.82);
const type = kind === "niteropu" ? "zunchi_miasma" : (kind === "ammo" ? "fall" : (kind === "protein" ? "fall" : "ring"));
const startY = kind === "protein" ? this.y + this.radius * rand(0.1, 0.55) : (kind === "niteropu" ? this.y - this.radius * rand(0.1, 0.70) : this.y + Math.sin(angle) * r * 0.55);
const vy = kind === "protein" ? -rand(38, 82) * intensity : (kind === "niteropu" ? rand(26, 68) * intensity : Math.sin(angle) * rand(8, 26) * intensity - rand(10, 28) * (kind === "giant_drug" || kind === "mercury" ? 1 : 0.35));
this.world.spawnEffect(type, this.x + Math.cos(angle) * r, startY, {
vx: (kind === "protein" || kind === "niteropu") ? rand(-18, 18) * intensity : Math.cos(angle) * rand(8, 34) * intensity,
vy,
size: rand(5, 11) * (0.75 + intensity * 0.30),
life: rand(0.42, 0.90),
color,
});
}
},
ammoCollisionKnockback(dt = 0) {
if (!this.hasItemEffect("ammo") || !this.world || this.dead || (this.ammoBumpCooldown || 0) > 0) return;
const speed = Math.hypot(this.vx || 0, this.vy || 0);
if (speed < 120) return;
const nx = (this.vx || 1) / Math.max(speed, 1);
const ny = (this.vy || 0) / Math.max(speed, 1);
const hitRadius = this.radius + 18;
for (const o of this.world.nearbyTarinai?.(this.x, this.y, hitRadius + 28, true) || []) {
if (!o || o === this || o.dead) continue;
const d = distXY(this.x, this.y, o.x, o.y);
if (d > this.radius + o.radius + 12) continue;
const px = d > 0.01 ? (o.x - this.x) / d : nx;
const py = d > 0.01 ? (o.y - this.y) / d : ny;
const push = clamp(speed * 2.2, 260, 820);
o.impulseVx = (o.impulseVx || 0) + px * push;
o.impulseVy = (o.impulseVy || 0) + py * push;
o.vx += px * 160;
o.vy += py * 160;
o.hurtTimer = Math.max(o.hurtTimer || 0, 1.1);
o.fallTimer = Math.max(o.fallTimer || 0, 0.72);
const bumpDamage = Math.max(0.6, Math.min(3.0, speed / 260));
o.damage?.(bumpDamage, "\u5f3e\u85ac\u885d\u7a81");
this.adjustPersonality?.("aggression", 0.012, "after ammo knockback");
this.world.spawnFallEffect?.(o.x, o.y + o.radius * 0.45, 1.05);
audio.damage?.(12);
this.ammoBumpCooldown = 0.12;
return;
}
for (const ant of this.world.nearbyAnts?.(this.x, this.y, hitRadius + 22, true) || []) {
if (!ant || ant.dead) continue;
const d = distXY(this.x, this.y, ant.x, ant.y);
if (d > this.radius + (ant.r || 4) + 12) continue;
const px = d > 0.01 ? (ant.x - this.x) / d : nx;
const py = d > 0.01 ? (ant.y - this.y) / d : ny;
const push = clamp(speed * 1.7, 180, 620);
ant.vx = (ant.vx || 0) + px * push;
ant.vy = (ant.vy || 0) + py * push;
globalThis.TarinaiAchievements?.recordAntDamageSource?.(ant, this, { world: this.world, reason: "tarinai_ammo_collision" });
ant.hp = Math.max(0, (ant.hp ?? ant.maxHp ?? 32) - 4);
ant.hpBarTimer = Math.max(ant.hpBarTimer || 0, 1.2);
this.world.spawnFallEffect?.(ant.x, ant.y, 0.65);
audio.antDie?.();
this.ammoBumpCooldown = 0.10;
return;
}
},
activeItemEffectSummary() {
this.ensureItemEffectState();
if (EFFECT_REGISTRY.activeSummary) return EFFECT_REGISTRY.activeSummary(this);
return [];
},
}));
})(typeof window !== "undefined" ? window : globalThis);