rr
This commit is contained in:
parent
2636d580a8
commit
d14550dad6
78 changed files with 2827 additions and 563 deletions
162
js/tarinai_direct_feeding_system.js
Normal file
162
js/tarinai_direct_feeding_system.js
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
"use strict";
|
||||
|
||||
// Direct player-to-Tarinai feeding. Placement clicks and pinched-item drops
|
||||
// share this path so item effects, feedback, and consumption stay consistent.
|
||||
(function (global) {
|
||||
const FOOD_TOOL_TYPES = new Set(["food", "sweet", "love_mochi", "fight_mochi", "grass", "water", "zunda_juice"]);
|
||||
const MEDICINE_TOOL_TYPES = new Set(["first_aid", "sedative", "sleep_drug", "laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug"]);
|
||||
const HEART_FOOD_TYPES = new Set(["food", "sweet", "love_mochi", "fight_mochi", "grass", "zunda_juice"]);
|
||||
|
||||
function itemType(itemOrType = "") {
|
||||
return String(typeof itemOrType === "object" ? itemOrType?.type || "" : itemOrType || "");
|
||||
}
|
||||
|
||||
function isDirectFeedType(itemOrType = "") {
|
||||
const type = itemType(itemOrType);
|
||||
return FOOD_TOOL_TYPES.has(type) || MEDICINE_TOOL_TYPES.has(type);
|
||||
}
|
||||
|
||||
function isFoodGift(itemOrType = "") {
|
||||
return HEART_FOOD_TYPES.has(itemType(itemOrType));
|
||||
}
|
||||
|
||||
function hasConsumableAmount(item = null) {
|
||||
if (!item || item.dead || !isDirectFeedType(item)) return false;
|
||||
const type = itemType(item);
|
||||
if (typeof global.isServingFoodType === "function" && global.isServingFoodType(type)) {
|
||||
if (Number.isFinite(Number(item.foodServingsRemaining))) return Number(item.foodServingsRemaining) > 0;
|
||||
return Number(item.amount ?? 1) > 0;
|
||||
}
|
||||
return Number(item.amount ?? 1) > 0;
|
||||
}
|
||||
|
||||
function findTargetAt(worldRef, x, y) {
|
||||
if (!worldRef || !Number.isFinite(Number(x)) || !Number.isFinite(Number(y))) return null;
|
||||
const px = Number(x);
|
||||
const py = Number(y);
|
||||
const list = worldRef.tarinai || [];
|
||||
for (let i = list.length - 1; i >= 0; i -= 1) {
|
||||
const t = list[i];
|
||||
if (!t || t.dead || worldRef.isTarinaiHiddenInNestBox?.(t)) continue;
|
||||
const hit = typeof t.contains === "function"
|
||||
? t.contains(px, py)
|
||||
: (typeof global.distXY === "function" && global.distXY(px, py, t.x, t.y) <= Math.max(20, Number(t.radius || 22) * 1.35));
|
||||
if (hit) return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function roleFor(item = null) {
|
||||
const type = itemType(item);
|
||||
if (type === "water") return "drink";
|
||||
if (MEDICINE_TOOL_TYPES.has(type) || type === "zunda_juice") return "medicine";
|
||||
return "food";
|
||||
}
|
||||
|
||||
function spawnFoodGiftHearts(tarinai, nutrition = 0) {
|
||||
const worldRef = tarinai?.world;
|
||||
if (!worldRef?.effects || typeof global.TarinaiEffect !== "function") return false;
|
||||
const count = 10 + Math.min(4, Math.max(0, Math.round((Number(nutrition) || 0) / 7)));
|
||||
const radius = Math.max(18, Number(tarinai.radius || 22) || 22);
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
const side = i % 2 ? 1 : -1;
|
||||
worldRef.effects.push(new global.TarinaiEffect("heart",
|
||||
tarinai.x + side * global.rand(radius * 0.06, radius * 0.82),
|
||||
tarinai.y - global.rand(radius * 0.38, radius * 1.72), {
|
||||
vx: side * global.rand(9, 30) + global.rand(-9, 9),
|
||||
vy: global.rand(-76, -30),
|
||||
size: global.rand(9, 17),
|
||||
life: global.rand(0.82, 1.36),
|
||||
color: "rgba(240,91,135,0.94)",
|
||||
}));
|
||||
}
|
||||
worldRef.effects.push(new global.TarinaiEffect("ring", tarinai.x, tarinai.y, {
|
||||
size: Math.max(24, radius * 1.35), life: 0.28, color: "rgba(240,91,135,0.42)",
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
function applyFoodGiftPersonality(tarinai, nutrition = 0) {
|
||||
if (!tarinai || typeof tarinai.adjustPersonality !== "function") return false;
|
||||
const amount = Math.max(0, Number(nutrition) || 0);
|
||||
// A single gift intentionally outweighs ordinary pointer contact, which
|
||||
// changes openness gradually per second while the cursor remains nearby.
|
||||
const openness = 0.018 + Math.min(0.010, amount * 0.0004);
|
||||
const sociability = 0.010 + Math.min(0.006, amount * 0.00024);
|
||||
const changedOpen = tarinai.adjustPersonality("openness", openness, "\u30ab\u30fc\u30bd\u30eb\u304b\u3089\u76f4\u63a5\u98df\u3079\u7269\u3092\u98df\u3079\u305f") || 0;
|
||||
const changedSocial = tarinai.adjustPersonality("sociability", sociability, "\u30ab\u30fc\u30bd\u30eb\u304b\u3089\u76f4\u63a5\u98df\u3079\u7269\u3092\u98df\u3079\u305f") || 0;
|
||||
return Math.abs(changedOpen) + Math.abs(changedSocial) > 0;
|
||||
}
|
||||
|
||||
function removeGivenWorldItem(worldRef, item, label) {
|
||||
if (!worldRef || !item || !(worldRef.items || []).includes(item)) return false;
|
||||
if (global.TarinaiStructureLifecycle?.deleteItem) {
|
||||
return Boolean(global.TarinaiStructureLifecycle.deleteItem(worldRef, item, {
|
||||
reason: "direct-feed-consumed",
|
||||
userReason: `${label}\u304c\u76f4\u63a5\u4e0e\u3048\u3089\u308c\u305f`,
|
||||
wake: false,
|
||||
panicOwner: false,
|
||||
}));
|
||||
}
|
||||
item.amount = 0;
|
||||
item.dead = true;
|
||||
worldRef.markItemBucketsDirty?.("direct-feed-consumed");
|
||||
worldRef.markSpatialDirty?.("direct-feed-consumed");
|
||||
return true;
|
||||
}
|
||||
|
||||
function give(worldRef, tarinai, item, options = {}) {
|
||||
if (!worldRef || !tarinai || tarinai.dead || !hasConsumableAmount(item)) return null;
|
||||
if (typeof global.consumeBehaviorTarget !== "function") return null;
|
||||
const type = itemType(item);
|
||||
const label = typeof global.toolLabel === "function" ? global.toolLabel(type) : type;
|
||||
const consumed = global.consumeBehaviorTarget(tarinai, worldRef, item, roleFor(item), 0.18);
|
||||
if (!consumed) return null;
|
||||
const nutrition = global.TarinaiItemRegistry?.food?.nutrition?.(type, 0) ?? 0;
|
||||
|
||||
tarinai.target = null;
|
||||
tarinai.targetKey = "";
|
||||
tarinai.sleeping = false;
|
||||
tarinai.goodMode = "smile";
|
||||
tarinai.moodLiftTimer = Math.max(Number(tarinai.moodLiftTimer || 0) || 0, 1.2);
|
||||
tarinai.foodReactTimer = Math.max(Number(tarinai.foodReactTimer || 0) || 0, 0.55);
|
||||
tarinai.surpriseTimer = Math.max(Number(tarinai.surpriseTimer || 0) || 0, 0.28);
|
||||
tarinai.thought = `${label}\u3092\u3082\u3089\u3063\u305f`;
|
||||
|
||||
const foodGift = isFoodGift(type);
|
||||
if (foodGift) {
|
||||
tarinai.affection = Number(tarinai.affection || 0) + 1.8 + Math.max(0, Number(nutrition) || 0) * 0.035;
|
||||
applyFoodGiftPersonality(tarinai, nutrition);
|
||||
spawnFoodGiftHearts(tarinai, nutrition);
|
||||
tarinai.bubble?.("\u2661", 2.2, "rgba(190,82,132,0.80)");
|
||||
} else {
|
||||
tarinai.focusPulseTimer = Math.max(Number(tarinai.focusPulseTimer || 0) || 0, 0.72);
|
||||
}
|
||||
|
||||
const sourceLabel = options.source === "pinch" ? "\u624b\u6e21\u3057\u3067" : "\u76f4\u63a5";
|
||||
worldRef.log?.(`${tarinai.name}\u306b${label}\u3092${sourceLabel}\u4e0e\u3048\u305f\u3002`, "food", {
|
||||
participants: [tarinai], eventType: "direct_feed", sound: false,
|
||||
});
|
||||
|
||||
const removed = options.removeItem !== false ? removeGivenWorldItem(worldRef, item, label) : false;
|
||||
if (!removed && (worldRef.items || []).includes(item)) {
|
||||
item.vx = 0;
|
||||
item.vy = 0;
|
||||
item.prevX = item.x;
|
||||
item.prevY = item.y;
|
||||
}
|
||||
worldRef.hoverGiveTarget = null;
|
||||
worldRef.drawListDirty = true;
|
||||
worldRef.recordFamily?.(tarinai);
|
||||
if (global.uiCache) global.uiCache.selectedSnapshot = "";
|
||||
return { tarinai, item, type, label, foodGift, removed };
|
||||
}
|
||||
|
||||
global.TarinaiDirectFeedingSystem = Object.freeze({
|
||||
isDirectFeedType,
|
||||
isFoodGift,
|
||||
hasConsumableAmount,
|
||||
findTargetAt,
|
||||
give,
|
||||
});
|
||||
})(typeof window !== "undefined" ? window : globalThis);
|
||||
Loading…
Add table
Add a link
Reference in a new issue