hm
This commit is contained in:
parent
cefa2ace5a
commit
0e432c62ae
87 changed files with 4282 additions and 3066 deletions
151
js/tarinai_nest_sleep_system.js
Normal file
151
js/tarinai_nest_sleep_system.js
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
"use strict";
|
||||
|
||||
// Layer: tarinai/nest-sleep
|
||||
// Centralizes sleep furniture, nest occupancy/reservation, and sleep-target
|
||||
// selection policy so nest-box entrance logic and bed targeting do not drift.
|
||||
(function (global) {
|
||||
const math = global.TarinaiMath || {};
|
||||
const distFn = math.dist || ((a, b) => Math.hypot((a?.x || 0) - (b?.x || 0), (a?.y || 0) - (b?.y || 0)));
|
||||
const distXYFn = math.distXY || ((ax, ay, bx, by) => Math.hypot((ax || 0) - (bx || 0), (ay || 0) - (by || 0)));
|
||||
const clampFn = math.clamp || ((v, a, b) => Math.max(a, Math.min(b, v)));
|
||||
|
||||
function isSleepFurniture(item) {
|
||||
return Boolean(item && (item.roles?.sleepPlace || (typeof global.isSleepFurnitureType === "function" ? global.isSleepFurnitureType(item.type) : (item.type === "bed" || item.type === "nest_box" || item.type === "grass_bed"))));
|
||||
}
|
||||
|
||||
function nestBoxCapacity(worldRef, box = null) {
|
||||
if (worldRef?.nestBoxCapacity && worldRef.nestBoxCapacity !== nestBoxCapacityFacade) return worldRef.nestBoxCapacity(box);
|
||||
return 5;
|
||||
}
|
||||
|
||||
function nestBoxOccupants(worldRef, box, limit = 5) {
|
||||
if (!worldRef || !box || box.dead || box.type !== "nest_box") return [];
|
||||
const occupants = (worldRef.tarinai || []).filter(t => t && !t.dead && t.insideNestBoxId === box.id);
|
||||
return Number.isFinite(limit) ? occupants.slice(0, Math.max(0, limit)) : occupants;
|
||||
}
|
||||
|
||||
function isTarinaiHiddenInNestBox(worldRef, t) {
|
||||
if (!t || t.dead || !t.insideNestBoxId) return false;
|
||||
if (typeof t.nestBoxEntryActive === "function" && t.nestBoxEntryActive()) return false;
|
||||
const now = Number(worldRef?.time ?? t.world?.time ?? 0) || 0;
|
||||
if (now < (t._nestBoxEntrySmoothUntil || -Infinity) || ((t.nestFade || 0) > 0.01 && (t.nestFade || 0) < 0.985)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function tarinaiIsReservingNest(t, box, now = 0) {
|
||||
if (!t || t.dead || !box || t.insideNestBoxId === box.id) return false;
|
||||
if (t.target !== box) return false;
|
||||
if (t._nestBoxAvoidId === box.id && now < (t._nestBoxAvoidUntil || -Infinity)) return false;
|
||||
const seekingNest = t.state === "seek_bed" || t.state === "sleep" || t.sleeping || t.behavior?.actionId === "sleep_in_bed";
|
||||
if (!seekingNest) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function nestBoxReservationCount(worldRef, box) {
|
||||
if (!worldRef || !box || box.dead || box.type !== "nest_box") return 0;
|
||||
const now = Number(worldRef.time || 0) || 0;
|
||||
let n = 0;
|
||||
for (const t of worldRef.tarinai || []) if (tarinaiIsReservingNest(t, box, now)) n += 1;
|
||||
return n;
|
||||
}
|
||||
|
||||
function bedOccupancy(worldRef, bed) {
|
||||
if (!worldRef || !isSleepFurniture(bed)) return 0;
|
||||
if (bed.type === "nest_box") {
|
||||
return nestBoxOccupants(worldRef, bed, Infinity).length + nestBoxReservationCount(worldRef, bed);
|
||||
}
|
||||
let n = 0;
|
||||
const radius = Math.max(86, (bed.r || 24) * 2.45);
|
||||
const near = typeof worldRef.nearbyTarinai === "function" ? worldRef.nearbyTarinai(bed.x, bed.y, radius) : (worldRef.tarinai || []);
|
||||
for (const t of near || []) {
|
||||
if (!t || t.dead || isTarinaiHiddenInNestBox(worldRef, t)) continue;
|
||||
if (t.state === "sleep" || t.state === "seek_bed" || t.target === bed) n += 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function bedComfort(worldRef, bed) {
|
||||
if (!isSleepFurniture(bed)) return 0.7;
|
||||
const occ = bedOccupancy(worldRef, bed);
|
||||
return clampFn((bed.comfort ?? 1) - Math.max(0, occ - 3) * 0.075 - (bed.wear || 0) * 0.18, 0.42, 1.18);
|
||||
}
|
||||
|
||||
function sleepPlaceAvailableFor(worldRef, tarinai, item) {
|
||||
if (!worldRef || !tarinai || !item || item.dead || !isSleepFurniture(item)) return false;
|
||||
if (tarinai.shouldAvoidTarget?.(item)) return false;
|
||||
const capacity = item.type === "nest_box" ? nestBoxCapacity(worldRef, item) : 1;
|
||||
const occupancy = bedOccupancy(worldRef, item);
|
||||
return occupancy < capacity || (tarinai.insideNestBoxId && tarinai.insideNestBoxId === item.id);
|
||||
}
|
||||
|
||||
function findOwnedStructure(worldRef, tarinai, type = "", maxDist = Infinity) {
|
||||
let best = null, bestD = maxDist;
|
||||
for (const item of worldRef?.items || []) {
|
||||
if (!item?.isStructure || item.dead || item.ownerId !== tarinai?.id) continue;
|
||||
if (type && item.type !== type) continue;
|
||||
const d = distFn(tarinai, item);
|
||||
if (d < bestD) { best = item; bestD = d; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
function findUnownedStructure(worldRef, tarinai, type = "", maxDist = Infinity) {
|
||||
let best = null, bestD = maxDist;
|
||||
for (const item of worldRef?.items || []) {
|
||||
if (!item?.isStructure || item.dead || item.ownerId) continue;
|
||||
if (type && item.type !== type) continue;
|
||||
if (tarinai?.shouldAvoidTarget?.(item)) continue;
|
||||
const d = distFn(tarinai, item);
|
||||
if (d < bestD) { best = item; bestD = d; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
function nearestSleepPlaceOfType(worldRef, tarinai, type, maxDist = 520) {
|
||||
let best = null;
|
||||
let bestScore = Infinity;
|
||||
const list = worldRef?.nearbyItems?.(tarinai.x, tarinai.y, maxDist) || worldRef?.items || [];
|
||||
for (const item of list) {
|
||||
if (!item || item.dead || item.type !== type) continue;
|
||||
if (!sleepPlaceAvailableFor(worldRef, tarinai, item)) continue;
|
||||
const d = distFn(tarinai, item);
|
||||
if (d > maxDist) continue;
|
||||
const comfort = bedComfort(worldRef, item);
|
||||
const score = d - comfort * 18;
|
||||
if (score < bestScore) { best = item; bestScore = score; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
function findPrioritySleepPlaceBeforeGrassBedBuild(worldRef, tarinai, maxDist = 520) {
|
||||
return findOwnedStructure(worldRef, tarinai, "grass_bed", maxDist)
|
||||
|| nearestSleepPlaceOfType(worldRef, tarinai, "nest_box", maxDist)
|
||||
|| nearestSleepPlaceOfType(worldRef, tarinai, "bed", maxDist)
|
||||
|| null;
|
||||
}
|
||||
|
||||
function findNearestSleepPlace(worldRef, tarinai, maxDist = 520) {
|
||||
return findPrioritySleepPlaceBeforeGrassBedBuild(worldRef, tarinai, maxDist)
|
||||
|| findUnownedStructure(worldRef, tarinai, "grass_bed", maxDist)
|
||||
|| null;
|
||||
}
|
||||
|
||||
// Named separately so nestBoxCapacity() can detect and avoid recursive facade calls.
|
||||
function nestBoxCapacityFacade(box) { return nestBoxCapacity(this, box); }
|
||||
|
||||
global.TarinaiNestSleepSystem = Object.freeze({
|
||||
isSleepFurniture,
|
||||
isTarinaiHiddenInNestBox,
|
||||
nestBoxOccupants,
|
||||
nestBoxReservationCount,
|
||||
bedOccupancy,
|
||||
bedComfort,
|
||||
sleepPlaceAvailableFor,
|
||||
nearestSleepPlaceOfType,
|
||||
findOwnedStructure,
|
||||
findUnownedStructure,
|
||||
findPrioritySleepPlaceBeforeGrassBedBuild,
|
||||
findNearestSleepPlace,
|
||||
distXY: distXYFn,
|
||||
});
|
||||
})(typeof window !== "undefined" ? window : globalThis);
|
||||
Loading…
Add table
Add a link
Reference in a new issue