739 lines
48 KiB
JavaScript
739 lines
48 KiB
JavaScript
"use strict";
|
|
|
|
// Unified item definition source: tools, item traits, visuals, food metadata, and item effects.
|
|
|
|
const TOOL_DEFINITIONS = Object.freeze({
|
|
observe: { id: "observe", label: "\u89b3\u5bdf", placeable: false, scalable: false, icon: "assets/ui/tool_observe.webp", tooltip: "\u500b\u4f53\u3092\u9078\u629e\u3057\u3001\u72b6\u614b\u30fb\u6027\u683c\u30fb\u75c5\u6c17\u30fb\u304a\u6c17\u306b\u5165\u308a\u3092\u78ba\u8a8d\u3059\u308b\u3002" },
|
|
delete: { id: "delete", label: "\u524a\u9664", placeable: false, scalable: false, icon: "assets/ui/tool_delete.webp", tooltip: "\u7f6e\u3044\u305f\u9053\u5177\u3084\u6c5a\u308c\u3092\u6d88\u3059\u3002" },
|
|
area_delete: { id: "area_delete", label: "範囲削除", placeable: false, scalable: false, icon: "assets/ui/tool_delete.webp", tooltip: "カーソル周囲の広い範囲にある置きものや汚れをまとめて消す。" },
|
|
copy: { id: "copy", label: "コピー", placeable: false, scalable: false, iconText: "⧉", tooltip: "置いたアイテムをクリックしてコピーし、次のクリックで設定ごと貼り付ける。" },
|
|
undo: { id: "undo", label: "戻す", placeable: false, scalable: false, iconText: "↶", tooltip: "直前の操作を取り消す。" },
|
|
redo: { id: "redo", label: "進む", placeable: false, scalable: false, iconText: "↷", tooltip: "取り消した操作をやり直す。" },
|
|
poke: { id: "poke", label: "\u3064\u3064\u304f", placeable: false, scalable: false, iconText: "\u{1F448}", tooltip: "\u305f\u308a\u306a\u3044\u3092\u3064\u3064\u304f\u3002\u30dc\u30fc\u30eb\u3082\u3064\u3064\u3044\u3066\u8ee2\u304c\u305b\u308b\u3002" },
|
|
pinch: { id: "pinch", label: "\u3064\u307e\u3080", placeable: false, scalable: false, iconText: "\u{1F90F}", tooltip: "\u305f\u308a\u306a\u3044\u3084\u7269\u3092\u79fb\u52d5\u3055\u305b\u308b\u3002\u51b7\u51cd\u5eab\u306b\u3082\u904b\u3079\u308b\u3002" },
|
|
new: { id: "new", label: "\u8ffd\u52a0", placeable: false, scalable: false, icon: "assets/ui/tool_new.webp", tooltip: "\u753b\u9762\u5916\u304b\u3089\u65b0\u3057\u3044\u305f\u308a\u306a\u3044\u3092\u547c\u3076\u3002" },
|
|
water_hose: { id: "water_hose", label: "\u6d17\u6d44", placeable: false, scalable: false, iconText: "\u{1F6BF}", tooltip: "\u30c9\u30e9\u30c3\u30b0\u3067\u6c5a\u308c\u3092\u304d\u308c\u3044\u306b\u3059\u308b\u3002" },
|
|
rope: { id: "rope", label: "ヒモ", placeable: false, scalable: false, iconText: "╰", tooltip: "2つの対象を順にクリックして、伸び縮みしないヒモで接続する。機械体・柵・ヒモ・棒はクリック地点、それ以外は中心につなぐ。" },
|
|
rod: { id: "rod", label: "棒", placeable: false, scalable: false, iconText: "━", tooltip: "2つの対象を順にクリックして、曲がらない棒で接続する。距離を常に一定に保つ。" },
|
|
zunchi: { id: "zunchi", itemType: "zunchi", label: "\u305a\u3093\u3061", placeable: true, scalable: true, radius: 14, amount: 240, icon: "assets/ui/tool_zunchi.webp", tooltip: "\u305f\u308a\u306a\u3044\u306e\u305a\u3093\u3061\u3002\u75c5\u6c17\u3084\u8349\u306e\u80a5\u6599\u306b\u95a2\u308f\u308b\u3002" },
|
|
sweet: { id: "sweet", itemType: "sweet", label: "\u305a\u3093\u3060\u9905", placeable: true, scalable: true, radius: 13, amount: 62, icon: "assets/ui/tool_sweet.webp", tooltip: "\u305f\u308a\u306a\u3044\u306e\u5927\u597d\u7269\u3002\u4e00\u90e8\u306e\u75c5\u6c17\u3092\u6cbb\u305b\u308b\u3002" },
|
|
love_mochi: { id: "love_mochi", itemType: "love_mochi", label: "\u3078\u3053\u9905", placeable: true, scalable: true, radius: 13, amount: 62, icon: "assets/ui/tool_love_mochi.webp", tooltip: "\u98df\u5f8c\u3057\u3070\u3089\u304f\u7e41\u6b96\u884c\u52d5\u304c\u8d77\u304d\u3084\u3059\u304f\u306a\u308b\u3002" },
|
|
fight_mochi: { id: "fight_mochi", itemType: "fight_mochi", label: "\u3051\u3093\u304b\u9905", placeable: true, scalable: true, radius: 13, amount: 62, icon: "assets/ui/tool_fight_mochi.webp", tooltip: "\u98df\u5f8c\u3057\u3070\u3089\u304f\u3051\u3093\u304b\u304c\u8d77\u304d\u3084\u3059\u304f\u306a\u308b\u3002" },
|
|
sleep_drug: { id: "sleep_drug", itemType: "sleep_drug", label: "\u306d\u3080\u308a\u85ac", placeable: true, scalable: true, radius: 13, amount: 58, icon: "assets/ui/tool_sleep_drug.webp", tooltip: "\u98df\u3079\u308b\u3068\u306d\u3080\u308a\u75c5\u306b\u306a\u308b\u3002\u30c0\u30e1\u30fc\u30b8\u3067\u5b8c\u6cbb\u3059\u308b\u3002" },
|
|
|
|
laxative: { id: "laxative", itemType: "laxative", label: "\u4e0b\u5264", placeable: true, scalable: true, radius: 13, amount: 58, icon: "assets/ui/tool_laxative.webp", tooltip: "\u305a\u3093\u3061\u3092\u5927\u91cf\u306b\u6392\u6cc4\u3055\u305b\u308b\u3002\u4f55\u304b\u3092\u98df\u3079\u3066\u3082\u3059\u3050\u306b\u51fa\u3066\u884c\u3063\u3066\u3057\u307e\u3046\u3002" },
|
|
protein: { id: "protein", itemType: "protein", label: "\u30d7\u30ed\u30c6\u30a4\u30f3", placeable: true, scalable: true, radius: 14, amount: 58, icon: "assets/ui/tool_protein.webp", tooltip: "\u75c5\u6c17\u306b\u5f37\u304f\u3001\u305f\u304f\u307e\u3057\u304f\u3002" },
|
|
niteropu: { id: "niteropu", itemType: "niteropu", label: "\u30f3\u30a4\u30c6\u30ed\u30d7", placeable: true, scalable: true, radius: 14, amount: 58, icon: "assets/ui/tool_niteropu.webp", tooltip: "\u75c5\u6c17\u306b\u5f31\u304f\u3001\u305f\u3088\u308a\u306a\u304f\u3002" },
|
|
ammo: { id: "ammo", itemType: "ammo", label: "\u5f3e\u85ac", placeable: true, scalable: true, radius: 12, amount: 48, icon: "assets/ui/tool_ammo.webp", tooltip: "\u5168\u3066\u3092\u52a0\u901f\u3055\u305b\u308b\u3002" },
|
|
mystery_drug: { id: "mystery_drug", itemType: "mystery_drug", label: "\u602a\u3057\u3044\u85ac", placeable: true, scalable: true, radius: 13, amount: 52, icon: "assets/ui/tool_mystery_drug.webp", tooltip: "\u4f55\u304b\u304c\u8d77\u3053\u308b\u3002" },
|
|
mercury: { id: "mercury", itemType: "mercury", label: "\u6c34\u9280", placeable: true, scalable: true, radius: 13, amount: 44, icon: "assets/ui/tool_mercury.webp", tooltip: "\u9577\u751f\u304d\u306e\u79d8\u8a23\u3002" },
|
|
giant_drug: { id: "giant_drug", itemType: "giant_drug", label: "\u5de8\u5927\u85ac", placeable: true, scalable: true, radius: 22, amount: 52, icon: "assets/ui/tool_giant_drug.webp", tooltip: "\u4f53\u3092\u5927\u304d\u304f\u5f37\u304f\u3059\u308b\u304c\u3001\u8ca0\u62c5\u304c\u5927\u304d\u3044\u3002" },
|
|
dwarf_drug: { id: "dwarf_drug", itemType: "dwarf_drug", label: "\u77ee\u5c0f\u85ac", placeable: true, scalable: true, radius: 8, amount: 52, icon: "assets/ui/tool_dwarf_drug.webp", tooltip: "\u4f53\u3092\u5c0f\u3055\u304f\u5f31\u304f\u3059\u308b\u304c\u3001\u8ca0\u62c5\u3082\u5927\u304d\u3044\u3002" },
|
|
zunda_juice: { id: "zunda_juice", itemType: "zunda_juice", label: "\u305a\u3093\u3060\u6c41", placeable: true, scalable: true, radius: 15, amount: 70, icon: "assets/ui/tool_zunda_juice.webp", tooltip: "\u98df\u3079\u308b\u3068\u5168\u3066\u306e\u30a2\u30a4\u30c6\u30e0\u52b9\u679c\u3092\u9664\u53bb\u3059\u308b\u3002" },
|
|
grass: { id: "grass", itemType: "grass", label: "\u8349", placeable: true, scalable: true, radius: 17, amount: 120, icon: "assets/ui/tool_grass.webp", tooltip: "\u98df\u3079\u7269\u3002\u305f\u308a\u306a\u3044\u304c\u5b89\u5fc3\u3059\u308b\u3002" },
|
|
stone: { id: "stone", itemType: "stone", label: "\u77f3", placeable: true, scalable: true, radius: 20, amount: 999, icon: "assets/ui/tool_stone.webp", tooltip: "\u91cd\u3044\u77f3\u3002\u843d\u3068\u3059\u3068\u5371\u306a\u3044\u3002" },
|
|
genkotsu: { id: "genkotsu", itemType: "genkotsu", label: "\u3052\u3093\u3053\u3064", placeable: true, scalable: false, radius: 88, amount: 100, icon: "assets/ui/tool_genkotsu.webp", tooltip: "\u62f3\u3092\u5730\u9762\u306b\u305f\u305f\u304d\u3064\u3051\u308b\u3002" },
|
|
bed: { id: "bed", itemType: "bed", label: "\u5e72\u8349\u5bdd\u5e8a", placeable: true, scalable: false, radius: 37, amount: 999, icon: "assets/ui/tool_bed.webp", tooltip: "\u8fd1\u304f\u3067\u7720\u308b\u3002\u4f53\u529b\u304c\u56de\u5fa9\u3059\u308b\u3002" },
|
|
nest_box: { id: "nest_box", itemType: "nest_box", label: "\u5de3\u7bb1", placeable: true, scalable: false, radius: 42, amount: 999, icon: "assets/ui/tool_nest_box.webp", collisionShape: "nest_box_3x3", tooltip: "\u3044\u308b\u3060\u3051\u3067\u30b9\u30c8\u30ec\u30b9\u4f4e\u6e1b\u3002\u7720\u308b\u3068\u7761\u7720\u306e\u8cea\u304c\u4e0a\u304c\u308b\u3002" },
|
|
ant_nest: { id: "ant_nest", itemType: "ant_nest", label: "\u30a2\u30ea\u306e\u5de3", placeable: true, scalable: false, radius: 17, amount: 999, icon: "assets/ui/tool_ant_nest.webp", collisionShape: "circle", tooltip: "\u50cd\u304d\u30a2\u30ea\u304c\u305f\u308a\u306a\u3044\u3092\u63a2\u3057\u3001\u5de3\u3078\u904b\u3076\u3002" },
|
|
ball: { id: "ball", itemType: "ball", label: "\u30dc\u30fc\u30eb", placeable: true, scalable: false, radius: 18, amount: 999, iconText: "\u26bd", collisionShape: "circle", tooltip: "\u305f\u308a\u306a\u3044\u304c\u904a\u3076\u305f\u3081\u306e\u304a\u3082\u3061\u3083\u3002" },
|
|
signboard: { id: "signboard", itemType: "signboard", label: "\u770b\u677f", placeable: true, scalable: false, radius: 30, amount: 999, icon: "assets/ui/tool_signboard.webp", collisionShape: "circle", tooltip: "\u8a2d\u7f6e\u5f8c\u306b\u89b3\u5bdf\u30c4\u30fc\u30eb\u3067\u30af\u30ea\u30c3\u30af\u3059\u308b\u3068\u6587\u5b57\u3092\u66f8\u304d\u8fbc\u3081\u308b\u770b\u677f\u30026\u6587\u5b57\u00d73\u884c\u307e\u3067\u3002" },
|
|
duplicator: { id: "duplicator", itemType: "duplicator", label: "\u8907\u88fd\u6a5f", placeable: true, scalable: false, radius: 34, amount: 999, icon: "assets/ui/tool_duplicator.webp", collisionShape: "circle", tooltip: "\u98df\u3079\u7269\u30fb\u85ac\u30fb\u753b\u92f2\u30fb\u304a\u3057\u308a\u92f2\u3092\u30bb\u30c3\u30c8\u3067\u304d\u308b\u3002\u92f2\u7cfb\u306f\u89e6\u308c\u305f\u305f\u308a\u306a\u3044\u306b\u523a\u3055\u308b\u3002" },
|
|
firecracker: { id: "firecracker", itemType: "firecracker", label: "\u7206\u7af9", placeable: true, scalable: true, radius: 15, amount: 999, icon: "assets/ui/tool_firecracker.webp", tooltip: "\u7206\u767a\u3092\u8d77\u3053\u3059\u3002" },
|
|
pushpin: { id: "pushpin", itemType: "pushpin", label: "\u753b\u92f2", placeable: true, scalable: false, radius: 8, amount: 999, icon: "assets/ui/tool_pushpin.webp", tooltip: "\u843d\u3068\u3059\u3068\u3053\u308d\u304c\u308a\u3001\u305f\u308a\u306a\u3044\u306b\u523a\u3055\u308b\u3068\u30d1\u30cb\u30c3\u30af\u3068\u7d99\u7d9a\u30c0\u30e1\u30fc\u30b8\u3092\u4e0e\u3048\u308b\u3002" },
|
|
oshibyo: { id: "oshibyo", itemType: "oshibyo", label: "\u304a\u3057\u308a\u92f2", placeable: true, scalable: false, radius: 9, amount: 999, icon: "assets/ui/tool_oshibyo.webp", tooltip: "\u523a\u3055\u308b\u3068\u305a\u3093\u3061\u304c\u51fa\u306a\u304f\u306a\u308b\u3002\u3064\u307e\u3093\u3067\u629c\u304f\u3068\u6e9c\u307e\u3063\u305f\u305a\u3093\u3061\u304c\u5f3e\u3051\u98db\u3076\u3002" },
|
|
fence_v: { id: "fence_v", itemType: "fence_v", label: "\u67f5", placeable: true, scalable: true, radius: 42, amount: 999, icon: "assets/ui/tool_fence_v.webp", collisionShape: "oriented_rect", tooltip: "\u305f\u308a\u306a\u3044\u3092\u901a\u305b\u3093\u307c\u3059\u308b\u67f5\u3002Q/E\u306745\u5ea6\u3001Shift+Q/E\u30675\u5ea6\u305a\u3064\u5411\u304d\u3092\u5909\u3048\u308b\u3002" },
|
|
fence_h: { id: "fence_h", itemType: "fence_h", label: "\u67f5", placeable: true, scalable: true, radius: 42, amount: 999, icon: "assets/ui/tool_fence_h.webp", collisionShape: "oriented_rect", tooltip: "\u305f\u308a\u306a\u3044\u3092\u901a\u305b\u3093\u307c\u3059\u67f5\u3002Q/E\u306745\u5ea6\u3001Shift+Q/E\u30675\u5ea6\u305a\u3064\u5411\u304d\u3092\u5909\u3048\u308b\u3002" },
|
|
bounce_fence: { id: "bounce_fence", itemType: "bounce_fence", label: "\u30d0\u30a6\u30f3\u30b9\u67f5", placeable: true, scalable: true, radius: 42, amount: 999, collisionShape: "oriented_rect", tooltip: "\u4f55\u304b\u304c\u5f53\u305f\u308b\u3068\u901f\u3081\u306b\u8df3\u306d\u8fd4\u3059\u67f5\u3002Q/E\u306745\u5ea6\u3001Shift+Q/E\u30675\u5ea6\u305a\u3064\u5411\u304d\u3092\u5909\u3048\u308b\u3002" },
|
|
bounce_fence_v: { id: "bounce_fence_v", itemType: "bounce_fence_v", label: "\u30d0\u30a6\u30f3\u30b9\u67f5", placeable: true, scalable: true, radius: 42, amount: 999, collisionShape: "oriented_rect", tooltip: "\u4f55\u304b\u304c\u5f53\u305f\u308b\u3068\u901f\u3081\u306b\u8df3\u306d\u8fd4\u3059\u67f5\u3002Q/E\u306745\u5ea6\u3001Shift+Q/E\u30675\u5ea6\u305a\u3064\u5411\u304d\u3092\u5909\u3048\u308b\u3002" },
|
|
gate_fence: { id: "gate_fence", itemType: "gate_fence", label: "\u30b2\u30fc\u30c8\u67f5", placeable: true, scalable: true, radius: 42, amount: 999, collisionShape: "oriented_rect", tooltip: "\u30af\u30ea\u30c3\u30af\u3059\u308b\u3068\u958b\u9589\u3059\u308b\u67f5\u3002Q/E\u306745\u5ea6\u3001Shift+Q/E\u30675\u5ea6\u305a\u3064\u5411\u304d\u3092\u5909\u3048\u308b\u3002" },
|
|
rotator: { id: "rotator", itemType: "rotator", label: "回転体", placeable: true, scalable: false, radius: 64, amount: 999, iconText: "↻", collisionShape: "custom_rotator", tooltip: "回転する機械。クリックすると形状や速度を設定できる。" },
|
|
reciprocator: { id: "reciprocator", itemType: "reciprocator", label: "往復体", placeable: true, scalable: false, radius: 64, amount: 999, iconText: "⇄", collisionShape: "custom_reciprocator", tooltip: "往復する機械。クリックすると形状や速度を設定できる。" },
|
|
water: { id: "water", itemType: "water", label: "水滴", placeable: true, scalable: true, radius: 12, amount: 64, iconText: "💧", tooltip: "雨の日にも現れる水滴。たりないが飲める。複製機にもセットできる。" },
|
|
trace: { id: "trace", itemType: "trace", label: "死骸", placeable: false, scalable: false, radius: 16, amount: 220, simulationOnly: true },
|
|
splat: { id: "splat", itemType: "splat", label: "\u3057\u3076\u304d", placeable: false, scalable: false, radius: 26, amount: 260, simulationOnly: true },
|
|
food: { id: "food", itemType: "food", label: "\u98df\u3079\u7269", placeable: false, scalable: false, radius: 14, amount: 85, simulationOnly: true },
|
|
ant_corpse: { id: "ant_corpse", itemType: "ant_corpse", label: "\u30a2\u30ea\u306e\u6b7b\u9ab8", placeable: false, scalable: false, radius: 4, amount: 24, simulationOnly: true },
|
|
});
|
|
|
|
|
|
const ITEM_TRAITS = Object.freeze({
|
|
obstacle: new Set(["stone", "ball", "nest_box", "bed", "duplicator", "fence_v", "fence_h", "bounce_fence", "bounce_fence_v", "gate_fence", "rotator", "reciprocator"]),
|
|
food_interest: new Set(["sweet", "love_mochi", "fight_mochi", "grass", "zunchi", "water", "ant_corpse", "duplicator", "laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice", "sleep_drug"]),
|
|
hazard: new Set(["firecracker", "genkotsu", "pushpin", "oshibyo", "sleep_drug", "zunchi", "splat", "mystery_drug", "laxative", "niteropu", "mercury", "giant_drug", "dwarf_drug"]),
|
|
pin: new Set(["pushpin", "oshibyo"]),
|
|
kinematic: new Set(["ball", "pushpin", "oshibyo", "zunchi"]),
|
|
sleepFurniture: new Set(["bed", "nest_box"]),
|
|
draggable: new Set(["ball", "stone", "bed", "nest_box", "signboard", "duplicator", "pushpin", "oshibyo", "fence_v", "fence_h", "bounce_fence", "bounce_fence_v", "gate_fence", "rotator", "reciprocator"]),
|
|
});
|
|
|
|
|
|
const FENCE_ITEM_TYPES = Object.freeze(new Set(["fence_v", "fence_h", "bounce_fence", "bounce_fence_v", "gate_fence"]));
|
|
const MECHANICAL_ITEM_TYPES = Object.freeze(new Set(["rotator", "reciprocator"]));
|
|
const LINK_ITEM_TYPES = Object.freeze(new Set(["rope", "rod"]));
|
|
const PHYSICS_TOOL_IDS = Object.freeze(["rope", "rod", "fence_h", "bounce_fence", "gate_fence", "rotator", "reciprocator"]);
|
|
const ROTATABLE_ITEM_TYPES = Object.freeze(new Set([...FENCE_ITEM_TYPES, ...MECHANICAL_ITEM_TYPES]));
|
|
function isFenceItemType(type = "") { return FENCE_ITEM_TYPES.has(String(type || "")); }
|
|
function isMechanicalItemType(type = "") { return MECHANICAL_ITEM_TYPES.has(String(type || "")); }
|
|
function isLinkItemType(type = "") { return LINK_ITEM_TYPES.has(String(type || "")); }
|
|
function isPhysicsToolId(id = "") { return PHYSICS_TOOL_IDS.includes(String(id || "")); }
|
|
function isRotatableItemType(type = "") { return ROTATABLE_ITEM_TYPES.has(String(type || "")); }
|
|
function defaultItemAngle(type = "") {
|
|
const t = String(type || "");
|
|
if (t === "fence_v" || t === "bounce_fence_v") return Math.PI / 2;
|
|
return 0;
|
|
}
|
|
function normalizedItemAngle(angle = 0, fallback = 0) {
|
|
const twoPi = Math.PI * 2;
|
|
const n = Number(angle);
|
|
const base = Number.isFinite(n) ? n : (Number.isFinite(Number(fallback)) ? Number(fallback) : 0);
|
|
return ((base % twoPi) + twoPi) % twoPi;
|
|
}
|
|
function itemAngleFor(itemOrType = "") {
|
|
if (itemOrType && typeof itemOrType === "object") return normalizedItemAngle(itemOrType.angle, defaultItemAngle(itemOrType.type));
|
|
return defaultItemAngle(String(itemOrType || ""));
|
|
}
|
|
function rotatableToolIds() {
|
|
return Object.values(TOOL_DEFINITIONS).filter(def => isRotatableItemType(def.itemType || def.id)).map(def => def.id);
|
|
}
|
|
|
|
function itemHasTrait(type = "", trait = "") {
|
|
return Boolean(ITEM_TRAITS[trait]?.has?.(String(type || "")));
|
|
}
|
|
|
|
function isPinType(type = "") { return itemHasTrait(type, "pin"); }
|
|
function isSleepFurnitureType(type = "") { return itemHasTrait(type, "sleepFurniture"); }
|
|
function isLodgedPin(item = null) { return Boolean(item && !item.dead && isPinType(item.type) && item.pinState === "lodged"); }
|
|
function lodgedPinFor(tarinai = null, worldRef = null) {
|
|
if (!tarinai?.stuckPushpinId) return null;
|
|
const items = worldRef?.items || tarinai.world?.items || [];
|
|
return items.find(it => isLodgedPin(it) && it.id === tarinai.stuckPushpinId) || null;
|
|
}
|
|
function lodgedPinBehaviorFor(tarinai = null, worldRef = null) { return pinBehaviorFor(lodgedPinFor(tarinai, worldRef)?.type); }
|
|
function hasLodgedPinEffect(tarinai = null, effectKey = "") { return Boolean(lodgedPinBehaviorFor(tarinai)?.[effectKey]); }
|
|
|
|
const PIN_BEHAVIORS = Object.freeze({
|
|
pushpin: { type: "pushpin", looseAsset: "pushpin", lodgedAsset: "pushpin_stuck", attachMode: "impact", panicOnAttach: true, damageOnAttach: 6, damagePerTick: 1.4, stressOnAttach: 18, stressPerTick: 2.6, blocksZunchi: false, burstZunchiOnDetach: false },
|
|
oshibyo: { type: "oshibyo", looseAsset: "oshibyo", lodgedAsset: "oshibyo_stuck", attachMode: "butt", panicOnAttach: false, damageOnAttach: 0, damagePerTick: 0, stressOnAttach: 0, stressPerTick: 0, blocksZunchi: true, burstZunchiOnDetach: true },
|
|
});
|
|
|
|
function pinBehaviorFor(type = "") {
|
|
return PIN_BEHAVIORS[String(type || "")] || null;
|
|
}
|
|
|
|
|
|
let ITEM_VISUAL_DEFINITIONS = null;
|
|
let FOOD_REGISTRY = null;
|
|
let FOOD_DEFINITIONS = null;
|
|
let EFFECT_REGISTRY = null;
|
|
let EFFECT_DEFINITIONS = null;
|
|
|
|
const TOOL_CATEGORIES = Object.freeze([
|
|
{ id: "operate", label: "\u64cd\u4f5c", themeClass: "tool-category-operate", order: 10, toolIds: ["observe", "new", "poke", "pinch", "copy", "undo", "redo", "delete", "area_delete", "water_hose"] },
|
|
{ id: "food", label: "\u98df\u3079\u7269", themeClass: "tool-category-food", order: 20, toolIds: ["sweet", "love_mochi", "fight_mochi", "grass", "water", "zunda_juice", "duplicator"] },
|
|
{ id: "medicine", label: "\u85ac", themeClass: "tool-category-medicine", order: 30, toolIds: ["sleep_drug", "laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug"] },
|
|
{ id: "habitat", label: "\u751f\u6d3b", themeClass: "tool-category-habitat", order: 40, toolIds: ["zunchi", "bed", "nest_box", "ball", "signboard"] },
|
|
{ id: "physics", label: "物理", themeClass: "tool-category-physics", order: 45, toolIds: PHYSICS_TOOL_IDS },
|
|
{ id: "hazard", label: "\u5371\u967a", themeClass: "tool-category-hazard", order: 50, toolIds: ["stone", "genkotsu", "firecracker", "pushpin", "oshibyo", "ant_nest"] },
|
|
]);
|
|
|
|
function toolCategories() {
|
|
return [...TOOL_CATEGORIES].sort((a, b) => (a.order || 0) - (b.order || 0));
|
|
}
|
|
|
|
function toolDefinition(id = "") {
|
|
return TOOL_DEFINITIONS[id] || null;
|
|
}
|
|
|
|
function toolLabel(id = "") {
|
|
const def = toolDefinition(id);
|
|
return def?.label || id || "";
|
|
}
|
|
|
|
function toolItemType(id = "") {
|
|
const def = toolDefinition(id);
|
|
return def?.placeable ? (def.itemType || def.id) : null;
|
|
}
|
|
|
|
function itemDefinition(type = "") {
|
|
return Object.values(TOOL_DEFINITIONS).find(def => (def.itemType || def.id) === type) || null;
|
|
}
|
|
|
|
function itemRadiusFor(type = "", fallback = 12) {
|
|
return itemDefinition(type)?.radius ?? fallback;
|
|
}
|
|
|
|
function itemAmountFor(type = "", fallback = 80) {
|
|
return itemDefinition(type)?.amount ?? fallback;
|
|
}
|
|
|
|
const TOOL_SIZE_ORDER = Object.freeze(["small", "medium", "large"]);
|
|
const TOOL_SIZE_LABELS = Object.freeze({ small: "\u5c0f", medium: "\u4e2d", large: "\u5927" });
|
|
const TOOL_SIZE_SCALES = Object.freeze({ small: 0.68, medium: 1.0, large: 1.48 });
|
|
function normalizedToolSizeFor(worldRef = null, type = "") {
|
|
const def = itemDefinition(type);
|
|
if (!def?.scalable) return "medium";
|
|
const value = worldRef?.toolSizes?.[type] || worldRef?.toolSize || "medium";
|
|
return TOOL_SIZE_ORDER.includes(value) ? value : "medium";
|
|
}
|
|
function toolSizeScaleForValue(size = "medium") { return TOOL_SIZE_SCALES[size] || TOOL_SIZE_SCALES.medium; }
|
|
function toolSizeScaleFor(worldRef = null, type = "") { return itemDefinition(type)?.scalable ? toolSizeScaleForValue(normalizedToolSizeFor(worldRef, type)) : 1; }
|
|
|
|
function scalableToolIds() {
|
|
return Object.values(TOOL_DEFINITIONS).filter(def => def.scalable).map(def => def.id);
|
|
}
|
|
|
|
function toolTipsFromDefinitions() {
|
|
return Object.fromEntries(Object.entries(TOOL_DEFINITIONS).filter(([, def]) => def.tooltip).map(([id, def]) => [id, def.tooltip]));
|
|
}
|
|
|
|
(function (global) {
|
|
const raw = String.raw`{
|
|
"water": {
|
|
"renderer": "oval_droplet",
|
|
"shape": "horizontal_oval_liquid",
|
|
"palette": { "fill": "rgba(86,157,224,0.82)", "stroke": "rgba(45,101,168,0.52)", "highlight": "rgba(255,255,255,0.64)" },
|
|
"scale": 1,
|
|
"uiPreviewScale": 1,
|
|
"fieldScale": 1,
|
|
"accessibleShapeTraits": ["horizontal", "oval", "liquid", "blue"]
|
|
},
|
|
"zunda_juice": {
|
|
"renderer": "oval_droplet",
|
|
"shape": "horizontal_oval_liquid",
|
|
"palette": { "fill": "rgba(125,218,82,0.94)", "stroke": "rgba(63,150,50,0.62)", "highlight": "rgba(238,255,226,0.72)" },
|
|
"scale": 1,
|
|
"uiPreviewScale": 1,
|
|
"fieldScale": 1,
|
|
"accessibleShapeTraits": ["horizontal", "oval", "liquid", "green"]
|
|
},
|
|
"mercury": {
|
|
"renderer": "oval_droplet",
|
|
"shape": "horizontal_oval_liquid",
|
|
"palette": { "fill": "rgba(210,216,222,0.94)", "stroke": "rgba(112,122,132,0.62)", "highlight": "rgba(255,255,255,0.72)" },
|
|
"scale": 1,
|
|
"uiPreviewScale": 1,
|
|
"fieldScale": 1,
|
|
"accessibleShapeTraits": ["horizontal", "oval", "liquid", "silver"]
|
|
},
|
|
"water_bowl": {
|
|
"renderer": "water_bowl",
|
|
"shape": "bowl_with_horizontal_oval_water",
|
|
"palette": {
|
|
"bowlFill": "rgba(171,120,76,0.72)",
|
|
"bowlStroke": "rgba(94,69,48,0.55)",
|
|
"waterFill": "rgba(86,157,224,0.58)",
|
|
"waterStroke": "rgba(62,113,178,0.36)",
|
|
"highlight": "rgba(255,255,255,0.54)"
|
|
},
|
|
"scale": 1,
|
|
"uiPreviewScale": 1,
|
|
"fieldScale": 1,
|
|
"accessibleShapeTraits": ["bowl", "horizontal", "oval", "water", "blue"]
|
|
},
|
|
"sleep_drug": {
|
|
"renderer": "sleep_tablet",
|
|
"shape": "moon_tablet",
|
|
"palette": { "fill": "#ebe4ff", "stroke": "rgba(74,60,144,0.72)", "accent": "rgba(112,91,188,0.82)" },
|
|
"scale": 1,
|
|
"uiPreviewScale": 1,
|
|
"fieldScale": 1,
|
|
"accessibleShapeTraits": ["tablet", "moon", "purple"]
|
|
},
|
|
"laxative": {
|
|
"renderer": "laxative_tablet",
|
|
"shape": "rounded_tablet_zunchi_motif",
|
|
"palette": { "fill": "#f4d49a", "stroke": "rgba(98,61,29,0.82)", "accent": "rgba(96,70,38,0.88)" },
|
|
"scale": 1,
|
|
"uiPreviewScale": 1,
|
|
"fieldScale": 1,
|
|
"accessibleShapeTraits": ["tablet", "zunchi motif", "tan"]
|
|
},
|
|
"mystery_drug": {
|
|
"renderer": "mystery_tablet",
|
|
"shape": "question_mark_tablet",
|
|
"palette": { "fill": "#f3ecff", "stroke": "rgba(91,55,150,0.84)", "accent": "rgba(91,55,150,0.92)" },
|
|
"scale": 1,
|
|
"uiPreviewScale": 1,
|
|
"fieldScale": 1,
|
|
"accessibleShapeTraits": ["tablet", "question mark", "purple"]
|
|
},
|
|
"ammo": {
|
|
"renderer": "bullet",
|
|
"shape": "bullet",
|
|
"palette": { "fill": "#f4c46a", "stroke": "rgba(87,51,22,0.78)", "highlight": "rgba(255,237,176,0.62)" },
|
|
"scale": 1,
|
|
"uiPreviewScale": 1,
|
|
"fieldScale": 1,
|
|
"accessibleShapeTraits": ["bullet", "gold"]
|
|
},
|
|
"protein": {
|
|
"renderer": "powder_pile",
|
|
"shape": "powder_pile",
|
|
"palette": { "fill": "#f4a24f", "stroke": "#b45b27", "highlight": "#ffe2b9" },
|
|
"scale": 1,
|
|
"uiPreviewScale": 1,
|
|
"fieldScale": 1,
|
|
"accessibleShapeTraits": ["powder", "orange"]
|
|
},
|
|
"niteropu": {
|
|
"renderer": "powder_pile",
|
|
"shape": "powder_pile",
|
|
"palette": { "fill": "#8a79d0", "stroke": "#4c3f91", "highlight": "#d8d1ff" },
|
|
"scale": 1,
|
|
"uiPreviewScale": 1,
|
|
"fieldScale": 1,
|
|
"accessibleShapeTraits": ["powder", "purple"]
|
|
},
|
|
"giant_drug": {
|
|
"renderer": "split_pill",
|
|
"shape": "split_pill",
|
|
"palette": { "fill": "#ffe3cf", "stroke": "#c9632f", "highlight": "#ffb47b" },
|
|
"scale": 1.08,
|
|
"uiPreviewScale": 1.08,
|
|
"fieldScale": 1.08,
|
|
"accessibleShapeTraits": ["pill", "large", "orange"]
|
|
},
|
|
"dwarf_drug": {
|
|
"renderer": "split_pill",
|
|
"shape": "split_pill",
|
|
"palette": { "fill": "#e9e9ff", "stroke": "#6967ac", "highlight": "#b8b5f4" },
|
|
"scale": 0.92,
|
|
"uiPreviewScale": 0.92,
|
|
"fieldScale": 0.92,
|
|
"accessibleShapeTraits": ["pill", "small", "purple"]
|
|
},
|
|
"ball": {
|
|
"renderer": "emoji",
|
|
"shape": "soccer_ball",
|
|
"palette": {},
|
|
"scale": 1,
|
|
"uiPreviewScale": 1,
|
|
"fieldScale": 1,
|
|
"accessibleShapeTraits": ["round", "soccer ball"],
|
|
"emojiFallback": "\u26bd"
|
|
}
|
|
}`;
|
|
|
|
const defs = Object.freeze(JSON.parse(raw));
|
|
|
|
function itemVisualDefinition(type = "") {
|
|
return defs[String(type || "")] || null;
|
|
}
|
|
|
|
function itemVisualPalette(type = "") {
|
|
return itemVisualDefinition(type)?.palette || null;
|
|
}
|
|
|
|
ITEM_VISUAL_DEFINITIONS = defs;
|
|
global.itemVisualDefinition = itemVisualDefinition;
|
|
global.itemVisualPalette = itemVisualPalette;
|
|
})(typeof window !== "undefined" ? window : globalThis);
|
|
|
|
(function (global) {
|
|
const rawDefinitions = {
|
|
food: { label: "\u98df\u3079\u7269", nutrition: 18.0, hungerRelief: 1.0, interest: "normal", decayRateMultiplier: 1.0, hygienePenalty: 0.055 },
|
|
sweet: { label: "\u305a\u3093\u3060\u9905", nutrition: 16.5, hungerRelief: 0.95, interest: "zunda_high", decayRateMultiplier: 0.82, hygienePenalty: 0.055, cures: ["disease_explosion", "disease_fight"] },
|
|
love_mochi: { label: "\u3078\u3053\u9905", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "love_mochi" },
|
|
fight_mochi: { label: "\u3051\u3093\u304b\u9905", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "fight_mochi" },
|
|
sleep_drug: { label: "\u306d\u3080\u308a\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "sleep_drug" },
|
|
laxative: { label: "\u4e0b\u5264", nutrition: 6.8, hungerRelief: 0, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "laxative", passThrough: true },
|
|
protein: { label: "\u30d7\u30ed\u30c6\u30a4\u30f3", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "protein" },
|
|
niteropu: { label: "\u30f3\u30a4\u30c6\u30ed\u30d7", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "niteropu" },
|
|
ammo: { label: "\u5f3e\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "ammo" },
|
|
mystery_drug: { label: "\u602a\u3057\u3044\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "mystery_drug" },
|
|
mercury: { label: "\u6c34\u9280", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "mercury" },
|
|
giant_drug: { label: "\u5de8\u5927\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "giant_drug" },
|
|
dwarf_drug: { label: "\u77ee\u5c0f\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "dwarf_drug" },
|
|
zunda_juice: { label: "\u305a\u3093\u3060\u6c41", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.12, hygienePenalty: 0.055, effectId: "zunda_juice", removesItemEffects: true },
|
|
grass: { label: "\u8349", nutrition: 2.6, hungerRelief: 0.55, interest: "grass_like", decayRateMultiplier: 1.0, hygienePenalty: 0 },
|
|
water: { label: "水滴", nutrition: 0, hungerRelief: 0, interest: "water", decayRateMultiplier: 1.0, hygienePenalty: 0 },
|
|
water_bowl: { label: "水の皿", nutrition: 0, hungerRelief: 0, interest: "water", decayRateMultiplier: 1.0, hygienePenalty: 0 },
|
|
ant_corpse: { label: "\u30a2\u30ea\u306e\u6b7b\u9ab8", nutrition: 4.2, hungerRelief: 0.62, interest: "corpse", decayRateMultiplier: 1.0, hygienePenalty: 0.065 }
|
|
};
|
|
|
|
class FoodRegistry extends global.TarinaiRegistryBase {
|
|
servingTypes() {
|
|
return [...this.defs.entries()]
|
|
.filter(([, def]) => def.interest && !["grass_like", "water", "corpse"].includes(def.interest))
|
|
.map(([id]) => id);
|
|
}
|
|
typesByInterest(...interests) {
|
|
const wanted = new Set(interests.flat().filter(Boolean));
|
|
return [...this.defs.entries()]
|
|
.filter(([, def]) => wanted.has(def.interest))
|
|
.map(([id]) => id);
|
|
}
|
|
paramEffectTypes() {
|
|
return [...this.defs.entries()].filter(([, def]) => def.effectId && !def.effectId.endsWith("_mochi") && def.effectId !== "sleep_drug").map(([id]) => id);
|
|
}
|
|
nutrition(type = "", fallback = 0) {
|
|
const value = this.get(type)?.nutrition;
|
|
return Number.isFinite(value) ? value : fallback;
|
|
}
|
|
hungerRelief(type = "", fallback = 0) {
|
|
const value = this.get(type)?.hungerRelief;
|
|
return Number.isFinite(value) ? value : fallback;
|
|
}
|
|
effectId(type = "") { return this.get(type)?.effectId || ""; }
|
|
passiveDecayMultiplier(type = "") { return this.get(type)?.decayRateMultiplier ?? 1; }
|
|
hygienePenalty(type = "") { return this.get(type)?.hygienePenalty ?? 0.055; }
|
|
debugSnapshot() { return super.debugSnapshot((id, def) => ({ id, label: def.label, interest: def.interest, effectId: def.effectId || "" })); }
|
|
}
|
|
|
|
const registry = new FoodRegistry(rawDefinitions);
|
|
FOOD_REGISTRY = registry;
|
|
FOOD_DEFINITIONS = Object.freeze(rawDefinitions);
|
|
})(typeof window !== "undefined" ? window : globalThis);
|
|
|
|
(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", stack: "multiply",
|
|
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"],
|
|
},
|
|
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.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 + rand(4, 12));
|
|
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 + rand(10, 18));
|
|
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 && Math.random() < Math.min(0.98, sleepChance)) return tarinai.infectSleepDisease?.(context.item || null) || true;
|
|
return true;
|
|
}
|
|
return tarinai.infectSleepDisease?.(context.item || null) || 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", "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),
|
|
}));
|
|
}
|
|
labels() { return super.labels(); }
|
|
colors() { return super.colors(); }
|
|
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])];
|
|
}
|
|
permanentIds() { return [...this.defs.entries()].filter(([, def]) => def.kind === "permanent").map(([id]) => id); }
|
|
timedIds() { return [...this.defs.entries()].filter(([, def]) => def.kind === "timed" || def.kind === "timer").map(([id]) => id); }
|
|
debugSnapshot() {
|
|
return [...this.defs.entries()].map(([id, def]) => ({ id, label: def.label, kind: def.kind, group: def.exclusiveGroup || "" }));
|
|
}
|
|
}
|
|
|
|
const registry = new EffectRegistry(rawDefinitions);
|
|
EFFECT_REGISTRY = registry;
|
|
EFFECT_DEFINITIONS = Object.freeze(rawDefinitions);
|
|
})(typeof window !== "undefined" ? window : globalThis);
|
|
|
|
|
|
(function (global) {
|
|
function itemTypeDefinition(type = "") {
|
|
const id = String(type || "");
|
|
const tool = typeof itemDefinition === "function" ? itemDefinition(id) : null;
|
|
const food = FOOD_DEFINITIONS?.[id] || null;
|
|
const effectId = food?.effectId || (EFFECT_DEFINITIONS?.[id] ? id : "");
|
|
const effect = effectId ? (EFFECT_DEFINITIONS?.[effectId] || null) : null;
|
|
const visual = ITEM_VISUAL_DEFINITIONS?.[id] || null;
|
|
return {
|
|
id,
|
|
tool,
|
|
food,
|
|
effect,
|
|
effectId,
|
|
visual,
|
|
label: tool?.label || food?.label || effect?.label || id,
|
|
radius: tool?.radius,
|
|
amount: tool?.amount,
|
|
placeable: Boolean(tool?.placeable),
|
|
scalable: Boolean(tool?.scalable),
|
|
traits: Object.fromEntries(Object.keys(ITEM_TRAITS).map(trait => [trait, itemHasTrait(id, trait)])),
|
|
};
|
|
}
|
|
|
|
function itemTypeLabel(type = "") {
|
|
return itemTypeDefinition(type).label;
|
|
}
|
|
|
|
function itemTypeDebugSnapshot() {
|
|
const ids = new Set([
|
|
...Object.values(TOOL_DEFINITIONS).map(def => def.itemType || def.id),
|
|
...Object.keys(FOOD_DEFINITIONS || {}),
|
|
...Object.keys(EFFECT_DEFINITIONS || {}),
|
|
...Object.keys(ITEM_VISUAL_DEFINITIONS || {}),
|
|
]);
|
|
return [...ids].sort().map(id => itemTypeDefinition(id));
|
|
}
|
|
|
|
global.TarinaiItemRegistry = Object.freeze({
|
|
definition: itemTypeDefinition,
|
|
label: itemTypeLabel,
|
|
debugSnapshot: itemTypeDebugSnapshot,
|
|
tool: toolDefinition,
|
|
item: itemDefinition,
|
|
food: FOOD_REGISTRY,
|
|
effect: EFFECT_REGISTRY,
|
|
visual: itemVisualDefinition,
|
|
hasTrait: itemHasTrait,
|
|
radius: itemRadiusFor,
|
|
amount: itemAmountFor,
|
|
categories: toolCategories,
|
|
physicsToolIds: () => [...PHYSICS_TOOL_IDS],
|
|
isFence: isFenceItemType,
|
|
isMechanical: isMechanicalItemType,
|
|
isLink: isLinkItemType,
|
|
scalableToolIds,
|
|
pinBehavior: pinBehaviorFor,
|
|
});
|
|
})(typeof window !== "undefined" ? window : globalThis);
|
|
|
|
if (typeof window !== "undefined") {
|
|
window.isFenceItemType = isFenceItemType;
|
|
window.isMechanicalItemType = isMechanicalItemType;
|
|
window.isLinkItemType = isLinkItemType;
|
|
window.isPhysicsToolId = isPhysicsToolId;
|
|
window.isRotatableItemType = isRotatableItemType;
|
|
window.defaultItemAngle = defaultItemAngle;
|
|
window.normalizedItemAngle = normalizedItemAngle;
|
|
window.itemAngleFor = itemAngleFor;
|
|
window.rotatableToolIds = rotatableToolIds;
|
|
}
|