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

318 lines
17 KiB
JavaScript

"use strict";
// Item effect behavior, timers, and display metadata.
(function (global) {
const rawDefinitions = {
laxative: {
label: "\u4e0b\u5264", color: "rgba(119, 136, 58, 0.78)", kind: "timed", defaultDuration: 60,
tags: ["item", "behavior", "food"], display: timer => `${Math.ceil(timer || 0)}\u79d2 / 3\u79d2\u3054\u3068\u306b\u305a\u3093\u3061 / \u98df\u4e8b\u306e\u7a7a\u8179\u56de\u5fa9\u306a\u3057`,
},
ammo: {
label: "\u5f3e\u85ac", color: "rgba(216, 142, 38, 0.82)", kind: "timed", defaultDuration: 18,
tags: ["item", "behavior", "movement"], display: timer => `${Math.ceil(timer || 0)}\u79d2 / \u6b69\u884c\u901f\u5ea610\u500d / \u885d\u7a81\u3067\u5f3e\u304d\u98db\u3070\u3059`,
},
protein: {
label: "\u30d7\u30ed\u30c6\u30a4\u30f3", color: "rgba(239, 122, 51, 0.86)", kind: "permanent", exclusiveGroup: "power",
tags: ["item", "behavior", "power"], display: () => "\u6c38\u7d9a / \u4e0e\u30c0\u30e1\u30fc\u30b81.75\u500d / \u75c5\u6c17\u78ba\u73870.5\u500d",
},
niteropu: {
label: "\u30f3\u30a4\u30c6\u30ed\u30d7", color: "rgba(88, 76, 166, 0.78)", kind: "permanent", exclusiveGroup: "power",
tags: ["item", "behavior", "power"], display: () => "\u6c38\u7d9a / \u4e0e\u30c0\u30e1\u30fc\u30b80.5\u500d / \u75c5\u6c17\u78ba\u73872\u500d",
},
mercury: {
label: "\u6c34\u9280", color: "rgba(96, 146, 166, 0.82)", kind: "permanent",
tags: ["item", "life"], display: (_timer, t) => `\u6c38\u7d9a / \u5bff\u547dx${((t && t.mercuryLifeMultiplier) || 1.3).toFixed(2)}\uff08\u98df\u3079\u308b\u305f\u3073\u4e57\u7b97\uff09`,
},
giant_drug: {
label: "\u5de8\u5927\u85ac", color: "rgba(228, 123, 58, 0.86)", kind: "permanent", exclusiveGroup: "body_size",
tags: ["item", "body", "behavior"], display: () => "\u6c38\u7d9a / \u30b5\u30a4\u30ba3\u500d / \u653b\u64832\u500d / \u901f\u5ea61.5\u500d / \u6bce\u79d21\u30c0\u30e1\u30fc\u30b8",
},
dwarf_drug: {
label: "\u77ee\u5c0f\u85ac", color: "rgba(109, 107, 190, 0.82)", kind: "permanent", exclusiveGroup: "body_size",
tags: ["item", "body", "behavior"], display: () => "\u6c38\u7d9a / \u30b5\u30a4\u30ba0.25\u500d / \u653b\u64830.25\u500d / \u901f\u5ea60.5\u500d / \u6bce\u79d21\u30c0\u30e1\u30fc\u30b8",
},
love_mochi: {
label: "\u3078\u3053\u9905", color: "rgba(190,82,132,0.78)", kind: "timer", timerProp: "loveMochiTimer",
tags: ["item", "mochi", "behavior"], display: timer => `${Math.ceil(timer || 0)}\u79d2 / \u7e41\u6b96\u884c\u52d5\u304c\u8d77\u304d\u3084\u3059\u3044`,
},
fight_mochi: {
label: "\u3051\u3093\u304b\u9905", color: "rgba(180,86,52,0.80)", kind: "timer", timerProp: "fightMochiTimer",
tags: ["item", "mochi", "behavior"], display: timer => `${Math.ceil(timer || 0)}\u79d2 / \u55a7\u5629\u304c\u8d77\u304d\u3084\u3059\u3044`,
},
sleep_drug: {
label: "\u306d\u3080\u308a\u85ac", color: "rgba(104,84,168,0.78)", kind: "disease", tags: ["item", "disease", "behavior"],
},
first_aid: {
label: "\u6551\u6025\u85ac", color: "rgba(210, 72, 82, 0.82)", kind: "instant",
tags: ["item", "heal"], display: () => "\u5373\u6642 / HP\u56de\u5fa9\u306e\u307f",
},
sedative: {
label: "\u93ae\u9759\u85ac", color: "rgba(92, 142, 174, 0.82)", kind: "timed", defaultDuration: 42,
tags: ["item", "behavior"], display: timer => `${Math.ceil(timer || 0)}\u79d2 / \u6050\u6016\u3068\u3051\u3093\u304b\u6c17\u5206\u3092\u6291\u3048\u308b`,
},
mystery_drug: {
label: "\u602a\u3057\u3044\u85ac", color: "rgba(60, 154, 87, 0.80)", kind: "instant", tags: ["item", "behavior"],
},
zunda_juice: {
label: "\u305a\u3093\u3060\u6c41", color: "rgba(87, 180, 79, 0.86)", kind: "cleanse", tags: ["item", "cleanse"],
},
disease_zunchi: { label: "\u305a\u3093\u3061\u75c5", color: "rgba(113, 93, 59, 0.74)", kind: "disease", tags: ["disease", "behavior"] },
disease_sleep: { label: "\u306d\u3080\u308a\u75c5", color: "rgba(104,84,168,0.78)", kind: "disease", tags: ["disease", "behavior"] },
disease_explosion: { label: "\u7206\u767a\u75c5", color: "rgba(210, 90, 44, 0.78)", kind: "disease", tags: ["disease", "behavior"] },
disease_fight: { label: "\u304d\u305a\u3064\u304d\u75c5", color: "rgba(180,86,52,0.80)", kind: "disease", tags: ["disease", "behavior"] },
};
Object.assign(rawDefinitions.laxative, {
modifiers: { hungerReliefMultiplier: 0 },
tickInterval: 3,
bubblePolicy: "none",
mysteryEligible: true,
zundaJuiceRemovable: true,
});
Object.assign(rawDefinitions.ammo, {
modifiers: { speedMultiplier: 10 },
visualEffectId: "fall",
bubblePolicy: "none",
mysteryEligible: true,
zundaJuiceRemovable: true,
});
Object.assign(rawDefinitions.protein, {
modifiers: { damageMultiplier: 1.75, diseaseChanceMultiplier: 0.5 },
visualEffectId: "fall_up_red",
bubblePolicy: "none",
mysteryEligible: true,
zundaJuiceRemovable: true,
});
Object.assign(rawDefinitions.niteropu, {
modifiers: { damageMultiplier: 0.5, diseaseChanceMultiplier: 2 },
visualEffectId: "fall_down_purple",
bubblePolicy: "none",
mysteryEligible: true,
zundaJuiceRemovable: true,
});
Object.assign(rawDefinitions.mercury, {
modifiers: { lifeMultiplierPerUse: 1.3 },
bubblePolicy: "none",
mysteryEligible: true,
zundaJuiceRemovable: true,
});
Object.assign(rawDefinitions.giant_drug, {
modifiers: { sizeMultiplier: 3, damageMultiplier: 2, speedMultiplier: 1.5, damagePerSecond: 1 },
bubblePolicy: "none",
mysteryEligible: true,
zundaJuiceRemovable: true,
});
Object.assign(rawDefinitions.dwarf_drug, {
modifiers: { sizeMultiplier: 0.25, damageMultiplier: 0.25, speedMultiplier: 0.5, damagePerSecond: 1 },
bubblePolicy: "none",
mysteryEligible: true,
zundaJuiceRemovable: true,
});
for (const id of ["love_mochi", "fight_mochi", "sleep_drug"]) {
Object.assign(rawDefinitions[id], { mysteryEligible: true, zundaJuiceRemovable: id !== "sleep_drug" });
}
Object.assign(rawDefinitions.first_aid, { mysteryEligible: false, zundaJuiceRemovable: false });
Object.assign(rawDefinitions.sedative, {
modifiers: { fearMultiplier: 0.45, fightWeightMultiplier: 0.28, speedMultiplier: 0.72 },
visualEffectId: "sedative",
bubblePolicy: "none",
mysteryEligible: true,
zundaJuiceRemovable: true,
});
Object.assign(rawDefinitions.zunda_juice, { removesItemEffects: true, mysteryEligible: false });
Object.assign(rawDefinitions.love_mochi, {
onApply(tarinai, context = {}) {
const scale = context.scale ?? 1;
tarinai.loveMochiTimer = Math.max(tarinai.loveMochiTimer || 0, 42 * scale + deterministicRange(tarinai.world, "love-mochi-duration", 4, 12, tarinai, context.item));
const nutrition = Number(context.nutrition) || 0;
if (typeof applyNeedRelief === "function") applyNeedRelief(tarinai, { fulfill: -(context.source === "eating" ? nutrition * 0.5 : 12), social: -4 });
if (context.source === "eating") {
if (tarinai.forceBehavior) tarinai.forceBehavior("approach_mate", { source: "love_mochi", priority: 150, ttl: 24, searchRange: 820, causeText: "\u3078\u3053\u9905\u306e\u52b9\u679c" });
else if (typeof queueForcedTarinaiBehavior === "function") queueForcedTarinaiBehavior(tarinai, "approach_mate", { source: "love_mochi", priority: 150, ttl: 24, searchRange: 820, causeText: "\u3078\u3053\u9905\u306e\u52b9\u679c" });
}
return true;
},
});
Object.assign(rawDefinitions.fight_mochi, {
onApply(tarinai, context = {}) {
const scale = context.scale ?? 1;
tarinai.fightMochiTimer = Math.max(tarinai.fightMochiTimer || 0, 52 * scale + deterministicRange(tarinai.world, "fight-mochi-duration", 10, 18, tarinai, context.item));
const nutrition = Number(context.nutrition) || 0;
if (typeof applyNeedShock === "function") applyNeedShock(tarinai, { safety: context.source === "eating" ? nutrition * 0.5 : 16 });
if (context.source === "eating") {
tarinai.fearTimer = Math.max(tarinai.fearTimer || 0, 0.32);
if (tarinai.forceBehavior) tarinai.forceBehavior("fight_rival", { source: "fight_mochi", priority: 180, ttl: 24, searchRange: 820, causeText: "\u3051\u3093\u304b\u9905\u306e\u52b9\u679c" });
else if (typeof queueForcedTarinaiBehavior === "function") queueForcedTarinaiBehavior(tarinai, "fight_rival", { source: "fight_mochi", priority: 180, ttl: 24, searchRange: 820, causeText: "\u3051\u3093\u304b\u9905\u306e\u52b9\u679c" });
}
return true;
},
});
Object.assign(rawDefinitions.sleep_drug, {
onApply(tarinai, context = {}) {
if (context.source === "eating") {
const nutrition = Number(context.nutrition) || 0;
if (typeof applyNeedRelief === "function") applyNeedRelief(tarinai, { safety: -nutrition * 0.25, sleep: -nutrition * 0.15 });
tarinai.energy = clamp(tarinai.energy - nutrition * 0.20, 0, typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(tarinai) : (Number(tarinai.maxEnergy) || 100));
tarinai.fearTimer = Math.max(0, (tarinai.fearTimer || 0) - 0.18);
tarinai.fightMochiTimer = Math.max(0, (tarinai.fightMochiTimer || 0) - 2.5);
const baseChance = Math.min(0.98, 0.35 + nutrition * 0.075);
const sleepChance = tarinai.diseaseChance ? tarinai.diseaseChance(baseChance) : baseChance;
if (tarinai.forceBehavior) tarinai.forceBehavior("sleep_anywhere", { source: "sleep_drug", priority: 145, ttl: 14, duration: 8, minDuration: 8, causeText: "\u306d\u3080\u308a\u85ac\u306e\u52b9\u679c" });
else if (typeof queueForcedTarinaiBehavior === "function") queueForcedTarinaiBehavior(tarinai, "sleep_anywhere", { source: "sleep_drug", priority: 145, ttl: 14, duration: 8, minDuration: 8, causeText: "\u306d\u3080\u308a\u85ac\u306e\u52b9\u679c" });
if (!tarinai.sleepDisease && deterministicChance(tarinai.world, "sleep-drug-disease", Math.min(0.98, sleepChance), tarinai, context.item)) return tarinai.infectSleepDisease?.(context.item || null) || true;
return true;
}
return tarinai.infectSleepDisease?.(context.item || null) || true;
},
});
Object.assign(rawDefinitions.first_aid, {
onApply(tarinai, context = {}) {
const max = typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(tarinai) : (Number(tarinai.maxEnergy) || 100);
const scale = Math.max(0.35, Number(context.scale ?? 1) || 1);
const amount = Math.max(18, Math.round(max * 0.38 * scale));
const before = Number(tarinai.energy || 0) || 0;
tarinai.recoverHealth?.(amount);
const recovered = Math.max(0, (Number(tarinai.energy || 0) || 0) - before);
if (recovered > 0.0001) global.TarinaiAchievements?.recordFirstAidRecovery?.({ world: tarinai.world, tarinai, item: context.item || null, source: context.source || "", recovered });
tarinai.hpBarTimer = Math.max(tarinai.hpBarTimer || 0, 2.4);
tarinai.spawnPowerItemEffect?.("first_aid", 0.42);
tarinai.bubble?.("HP+", 2.2, "rgba(210,72,82,0.82)", { force: true });
return true;
},
});
Object.assign(rawDefinitions.sedative, {
onApply(tarinai, context = {}) {
const duration = Math.round((this.defaultDuration || 42) * Math.max(0.35, Number(context.scale ?? 1) || 1));
tarinai.setTimedItemEffect?.("sedative", duration);
tarinai.fearTimer = Math.max(0, (tarinai.fearTimer || 0) - 2.4);
tarinai.intimidatedTimer = Math.max(0, (tarinai.intimidatedTimer || 0) - 1.8);
tarinai.fightMochiTimer = Math.max(0, (tarinai.fightMochiTimer || 0) - 10);
tarinai.postConflictPeaceTimer = Math.max(tarinai.postConflictPeaceTimer || 0, 14);
if (typeof applyNeedRelief === "function") applyNeedRelief(tarinai, { safety: -18, social: -2 });
tarinai.spawnPowerItemEffect?.("sedative", 0.36);
return true;
},
});
Object.assign(rawDefinitions.laxative, {
onApply(tarinai, context = {}) {
return tarinai.setTimedItemEffect?.("laxative", Math.round((this.defaultDuration || 60) * (context.scale ?? 1)));
},
onTick(tarinai) {
tarinai.forceLaxativePoop?.();
return true;
},
});
Object.assign(rawDefinitions.ammo, {
onApply(tarinai, context = {}) {
return tarinai.setTimedItemEffect?.("ammo", Math.round((this.defaultDuration || 18) * (context.scale ?? 1)));
},
});
Object.assign(rawDefinitions.protein, {
onApply(tarinai) { return tarinai.setPowerItemMode?.("protein"); },
});
Object.assign(rawDefinitions.niteropu, {
onApply(tarinai) { return tarinai.setPowerItemMode?.("niteropu"); },
});
Object.assign(rawDefinitions.mercury, {
onApply(tarinai) { return tarinai.setMercuryEffect?.(); },
});
Object.assign(rawDefinitions.giant_drug, {
onApply(tarinai) { return tarinai.setSizeItemMode?.("giant_drug"); },
onTick(tarinai) {
tarinai.damage?.(this.modifiers?.damagePerSecond || 1, this.label);
if (tarinai.world?.effects) tarinai.spawnPowerItemEffect?.("giant_drug", 0.25);
return true;
},
});
Object.assign(rawDefinitions.dwarf_drug, {
onApply(tarinai) { return tarinai.setSizeItemMode?.("dwarf_drug"); },
onTick(tarinai) {
tarinai.damage?.(this.modifiers?.damagePerSecond || 1, this.label);
if (tarinai.world?.effects) tarinai.spawnPowerItemEffect?.("dwarf_drug", 0.25);
return true;
},
});
Object.assign(rawDefinitions.zunda_juice, {
onApply(tarinai) { return tarinai.clearAllItemEffects?.(); },
});
Object.assign(rawDefinitions.disease_zunchi, {
onApply(tarinai, context = {}) { return tarinai.infectZunchiDisease?.(context.item || null, true) || true; },
});
Object.assign(rawDefinitions.disease_sleep, {
onApply(tarinai, context = {}) { return tarinai.infectSleepDisease?.(context.item || null) || true; },
});
Object.assign(rawDefinitions.disease_explosion, {
onApply(tarinai, context = {}) { return tarinai.infectExplosionDisease?.(context.item || null) || true; },
});
Object.assign(rawDefinitions.disease_fight, {
onApply(tarinai, context = {}) { return tarinai.infectFightDisease?.(context.item || null) || true; },
});
class EffectRegistry extends global.TarinaiRegistryBase {
label(id = "") { return super.label(id, id || "\u4e0d\u660e"); }
color(id = "") { return super.color(id, "rgba(255,242,160,0.85)"); }
display(id = "", timer = 0, tarinai = null) {
const def = this.get(id);
if (!def) return "";
return typeof def.display === "function" ? def.display(timer, tarinai, def) : (def.display || "");
}
apply(id = "", tarinai = null, context = {}) {
const def = this.get(id);
if (!def || !tarinai || typeof def.onApply !== "function") return false;
return Boolean(def.onApply.call(def, tarinai, { ...context, effectId: id, registry: this }));
}
tick(id = "", tarinai = null, dt = 0, context = {}) {
const def = this.get(id);
if (!def || !tarinai || typeof def.onTick !== "function") return false;
return Boolean(def.onTick.call(def, tarinai, Math.max(0, Number(dt) || 0), { ...context, effectId: id, registry: this }));
}
remove(id = "", tarinai = null, context = {}) {
const def = this.get(id);
if (!def || !tarinai || typeof def.onRemove !== "function") return false;
return Boolean(def.onRemove.call(def, tarinai, { ...context, effectId: id, registry: this }));
}
modifier(id = "", key = "", fallback = 1) {
const value = this.get(id)?.modifiers?.[key];
return Number.isFinite(value) ? value : fallback;
}
timerValue(tarinai = null, id = "") {
const def = this.get(id);
if (!def || !tarinai) return 0;
if (def.timerProp) return Number(tarinai[def.timerProp] || 0);
if (def.kind === "timed") return Number(tarinai.itemEffectTimers?.[id] || 0);
return 0;
}
isActive(tarinai = null, id = "") {
if (!tarinai) return false;
const def = this.get(id);
if (!def) return false;
if (id === "protein" || id === "niteropu") return tarinai.powerItemMode === id;
if (id === "giant_drug" || id === "dwarf_drug") return tarinai.sizeItemMode === id;
if (id === "mercury") return tarinai.lifeItemMode === "mercury";
return this.timerValue(tarinai, id) > 0.04;
}
activeSummary(tarinai = null) {
const order = ["protein", "niteropu", "mercury", "giant_drug", "dwarf_drug", "laxative", "ammo", "sedative", "love_mochi", "fight_mochi"];
return order
.filter(id => this.isActive(tarinai, id))
.map(id => ({
id,
label: this.label(id),
detail: this.display(id, this.timerValue(tarinai, id), tarinai),
}));
}
randomPool() {
const itemEffects = [...this.defs.entries()]
.filter(([id, def]) => Boolean(def.mysteryEligible && id !== "mystery_drug" && id !== "zunda_juice" && !(def.tags || []).includes("disease")))
.map(([id]) => id)
.filter(id => id !== "mystery_drug" && id !== "zunda_juice");
const diseases = global.TarinaiDiseaseRegistry.mysteryPool() || [];
return [...new Set([...itemEffects, ...diseases])];
}
}
const registry = new EffectRegistry(rawDefinitions);
global.TarinaiEffectRegistry = registry;
})(typeof window !== "undefined" ? window : globalThis);