This commit is contained in:
33333-33333 2026-06-22 02:03:19 +09:00
commit e8f1d1db1e
60 changed files with 2417 additions and 612 deletions

View file

@ -30,6 +30,9 @@ const SPRITES = [
{ id: "zunchi_slave", label: "\u305a\u3093\u3061\u3069\u308c\u3044", path: "assets/sprites/tarinai_28_zunchi_slave.webp", displayOnly: true },
{ id: "hungry_70", label: "\u7a7a\u817970\u4ee5\u4e0a\u305f\u308a\u306a\u3044", path: "assets/sprites/tarinai_29_hungry_70.webp", displayOnly: true },
{ id: "zunchi_slave_alt", label: "\u305a\u3093\u3061\u3069\u308c\u3044 2", path: "assets/sprites/tarinai_30_zunchi_slave_alt.webp", displayOnly: true },
{ id: "sunbath_1", label: "\u65e5\u5149\u6d74\u305f\u308a\u306a\u3044 1", path: "assets/sprites/tarinai_31_sunbath_01.webp", actionOnly: true },
{ id: "sunbath_2", label: "\u65e5\u5149\u6d74\u305f\u308a\u306a\u3044 2", path: "assets/sprites/tarinai_32_sunbath_02.webp", actionOnly: true },
{ id: "sunbath_3", label: "\u65e5\u5149\u6d74\u305f\u308a\u306a\u3044 3", path: "assets/sprites/tarinai_33_sunbath_03.webp", actionOnly: true },
];
const DECOR_ASSETS = [
{ id: "zunchi", path: "assets/objects/zunchi.webp" },
@ -39,6 +42,8 @@ const DECOR_ASSETS = [
{ id: "genkotsu", path: "assets/objects/genkotsu.webp" },
{ id: "pushpin", path: "assets/objects/pushpin.webp" },
{ id: "pushpin_stuck", path: "assets/objects/pushpin_stuck.webp" },
{ id: "oshibyo", path: "assets/objects/oshibyo.webp" },
{ id: "oshibyo_stuck", path: "assets/objects/oshibyo_stuck.webp" },
];
const TOOL_DEFINITIONS = Object.freeze({
@ -48,6 +53,7 @@ const TOOL_DEFINITIONS = Object.freeze({
pinch: { id: "pinch", label: "\u3064\u307e\u3080", placeable: false, scalable: false, iconText: "\u{1F90F}", tooltip: "\u305f\u308a\u306a\u3044\u3084\u7269\u3092\u3064\u307e\u3093\u3067\u79fb\u52d5\u3055\u305b\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" },
freeze: { id: "freeze", label: "冷凍", placeable: false, scalable: false, iconText: "\u{1F9CA}", tooltip: "つまむでたりないを冷凍個体UIまで運ぶと冷凍保存する。" },
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" },
@ -71,8 +77,10 @@ const TOOL_DEFINITIONS = Object.freeze({
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, icon: "assets/ui/tool_ball.webp", 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: 24, amount: 999, iconText: "\u{1FAA7}", collisionShape: "circle", tooltip: "\u77ed\u3044\u30c6\u30ad\u30b9\u30c8\u3092\u66f8\u304d\u8fbc\u3081\u308b\u770b\u677f\u3002\u305f\u308a\u306a\u3044\u306b\u5bfe\u3059\u308b\u52b9\u679c\u306f\u7121\u3057" },
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: "おしり鋲", placeable: true, scalable: false, radius: 9, amount: 999, icon: "assets/objects/oshibyo.webp", tooltip: "刺さるとずんちが出なくなる。つまんで抜くと溜まったずんちが弾け飛ぶ。" },
fence_v: { id: "fence_v", itemType: "fence_v", label: "\u7e26\u306e\u67f5", placeable: true, scalable: true, radius: 42, amount: 999, icon: "assets/ui/tool_fence_v.webp", collisionShape: "rect", tooltip: "\u305f\u308a\u306a\u3044\u3092\u901a\u305b\u3093\u307c\u3059\u308b\u3002" },
fence_h: { id: "fence_h", itemType: "fence_h", label: "\u6a2a\u306e\u67f5", placeable: true, scalable: true, radius: 42, amount: 999, icon: "assets/ui/tool_fence_h.webp", collisionShape: "rect", tooltip: "\u305f\u308a\u306a\u3044\u3092\u901a\u305b\u3093\u307c\u3059\u308b\u3002" },
water: { id: "water", itemType: "water", label: "\u6c34", placeable: false, scalable: false, radius: 12, amount: 64, simulationOnly: true },
@ -82,12 +90,55 @@ const TOOL_DEFINITIONS = Object.freeze({
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", "fence_v", "fence_h"]),
food_interest: new Set(["sweet", "love_mochi", "fight_mochi", "grass", "water", "water_bowl", "ant_corpse", "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", "pushpin", "oshibyo"]),
});
function itemHasTrait(type = "", trait = "") {
return Boolean(ITEM_TRAITS[trait]?.has?.(String(type || "")));
}
function itemTraitsFor(type = "") {
const out = [];
for (const [trait, set] of Object.entries(ITEM_TRAITS)) if (set.has(String(type || ""))) out.push(trait);
return out;
}
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;
}
const TOOL_CATEGORIES = Object.freeze([
{ id: "operate", label: "\u64cd\u4f5c\u30fb\u89b3\u5bdf", themeClass: "tool-category-operate", order: 10, toolIds: ["observe", "delete", "poke", "pinch", "new", "water_hose"] },
{ id: "food", label: "\u98df\u3079\u7269\u30fb\u6c34\u30fb\u8349", themeClass: "tool-category-food", order: 20, toolIds: ["sweet", "love_mochi", "fight_mochi", "grass", "water_bowl", "zunda_juice"] },
{ id: "medicine", label: "\u85ac\u30fb\u500b\u4f53\u52b9\u679c", 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\u30fb\u751f\u304d\u7269", themeClass: "tool-category-habitat", order: 40, toolIds: ["zunchi", "bed", "nest_box", "ant_nest", "ball"] },
{ id: "hazard", label: "\u5371\u967a\u30fb\u969c\u5bb3\u7269", themeClass: "tool-category-hazard", order: 50, toolIds: ["stone", "genkotsu", "firecracker", "pushpin", "fence_v", "fence_h"] },
{ id: "habitat", label: "\u751f\u6d3b\u30fb\u751f\u304d\u7269", themeClass: "tool-category-habitat", order: 40, toolIds: ["zunchi", "bed", "nest_box", "ant_nest", "ball", "signboard"] },
{ id: "hazard", label: "\u5371\u967a\u30fb\u969c\u5bb3\u7269", themeClass: "tool-category-hazard", order: 50, toolIds: ["stone", "genkotsu", "firecracker", "pushpin", "oshibyo", "fence_v", "fence_h"] },
{ id: "freeze", label: "冷凍", themeClass: "tool-category-freeze", order: 99, toolIds: ["freeze"] },
]);
function toolCategories() {
@ -120,6 +171,18 @@ 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: "小", medium: "中", large: "大" });
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);
}
@ -163,6 +226,9 @@ const ALPHA_BOUNDS = {
ant_queen: { x: 0, y: 0, w: 300, h: 155, imageW: 300, imageH: 155 },
ant_worker: { x: 0, y: 0, w: 140, h: 72, imageW: 140, imageH: 72 },
genkotsu: { x: 8, y: 8, w: 220, h: 283, imageW: 236, imageH: 299 },
sunbath_1: { x: 71, y: 18, w: 369, h: 432, imageW: 512, imageH: 468 },
sunbath_2: { x: 69, y: 18, w: 374, h: 432, imageW: 512, imageH: 468 },
sunbath_3: { x: 71, y: 18, w: 369, h: 432, imageW: 512, imageH: 468 },
};
const NEEDS = ["\u98df\u3079\u7269", "\u4ef2\u9593", "\u7720\u308a", "\u52c7\u6c17", "\u6c34", "\u9759\u3051\u3055", "\u3042\u305f\u305f\u304b\u3055", "\u610f\u5473", "\u7a7a\u9593", "\u81ea\u4fe1"];