fixed issueeessss
This commit is contained in:
parent
aea08dc3fb
commit
874911821f
20 changed files with 1947 additions and 897 deletions
471
js/tarinai.js
471
js/tarinai.js
|
|
@ -19,19 +19,23 @@ class Tarinai {
|
|||
this.world = world;
|
||||
this.id = opts.id || (crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`);
|
||||
this.name = opts.name || makeName(world);
|
||||
this.personality = opts.personality || personalityForSeed(this.id);
|
||||
// Legacy string personality is intentionally ignored. The game now uses only the four-axis personality model.
|
||||
this.personality = "";
|
||||
this.type = opts.type || baseSpriteId();
|
||||
const typeMeta = SPRITES.find(s => s.id === this.type);
|
||||
if (typeMeta?.actionOnly || typeMeta?.displayOnly) this.type = baseSpriteId();
|
||||
this.birthPersonality = normalizePersonality(opts.birthPersonality || opts.personalityBirth);
|
||||
if (!opts.birthPersonality && !opts.personalityBirth) this.birthPersonality = randomBirthPersonality(this.id);
|
||||
this.currentPersonality = normalizePersonality(opts.currentPersonality || opts.personalityCurrent || this.birthPersonality);
|
||||
this.personalityDaily = opts.personalityDaily && typeof opts.personalityDaily === "object" ? JSON.parse(JSON.stringify(opts.personalityDaily)) : null;
|
||||
ensurePersonality(this);
|
||||
this.trait = makeTrait(this.type);
|
||||
const pdef = PERSONALITIES[this.personality] || PERSONALITIES.calm;
|
||||
this.trait.social *= pdef.social ?? 1;
|
||||
this.trait.stress /= pdef.fear ?? 1;
|
||||
this.trait.sleep *= pdef.sleep ?? 1;
|
||||
this.x = opts.x ?? rand(100, world.w - 100);
|
||||
this.y = opts.y ?? rand(100, world.h - 100);
|
||||
this.vx = opts.vx ?? rand(-12, 12);
|
||||
this.vy = opts.vy ?? rand(-12, 12);
|
||||
this.impulseVx = opts.impulseVx ?? 0;
|
||||
this.impulseVy = opts.impulseVy ?? 0;
|
||||
this.scale = opts.scale ?? rand(0.22, 0.33);
|
||||
this.radius = 56 * this.scale;
|
||||
this.age = opts.age ?? rand(0, 20);
|
||||
|
|
@ -109,6 +113,11 @@ class Tarinai {
|
|||
this.fightDiseaseCooldown = opts.fightDiseaseCooldown ?? 0;
|
||||
this.lastBleedAt = opts.lastBleedAt ?? -999;
|
||||
this.favorite = Boolean(opts.favorite);
|
||||
this.insideNestBoxId = opts.insideNestBoxId || null;
|
||||
this.nestFade = opts.nestFade ?? (this.insideNestBoxId ? 1 : 0);
|
||||
this.nestBoxEnteredAt = opts.nestBoxEnteredAt ?? (this.insideNestBoxId ? (world?.time || 0) : -Infinity);
|
||||
this.nestBoxStayUntil = opts.nestBoxStayUntil ?? (this.insideNestBoxId ? ((world?.time || 0) + 10) : -Infinity);
|
||||
this.focusPulseTimer = opts.focusPulseTimer ?? 0;
|
||||
if (this.isZunchiSlave) this.name = "\u305a\u3093\u3061\u3069\u308c\u3044";
|
||||
this.tempComfort = opts.tempComfort ?? 0.62;
|
||||
this.tempTimer = opts.tempTimer ?? stableUnit(this.id, "temp-start") * 0.55;
|
||||
|
|
@ -167,7 +176,7 @@ class Tarinai {
|
|||
birthRitualRole: this.birthRitualRole, birthRitualLeader: this.birthRitualLeader,
|
||||
pokeFlashTimer: this.pokeFlashTimer, zunchiStain: this.zunchiStain, zunchiStainSeed: this.zunchiStainSeed,
|
||||
totalFightLosses: this.totalFightLosses, totalFightWins: this.totalFightWins, isZunchiSlave: this.isZunchiSlave, formerName: this.formerName, loveMochiTimer: this.loveMochiTimer, fightMochiTimer: this.fightMochiTimer,
|
||||
sleepDisease: this.sleepDisease, sleepDiseaseCooldown: this.sleepDiseaseCooldown, explosionDisease: this.explosionDisease, explosionDiseaseTimer: this.explosionDiseaseTimer, fightDisease: this.fightDisease, fightDiseaseCooldown: this.fightDiseaseCooldown, lastBleedAt: this.lastBleedAt, favorite: this.favorite, insideNestBoxId: this.insideNestBoxId || null, nestFade: this.nestFade || 0, sleepDiseaseAnchorX: this.sleepDiseaseAnchorX, sleepDiseaseAnchorY: this.sleepDiseaseAnchorY, records: this.records ? this.records.slice(0, 42) : [],
|
||||
sleepDisease: this.sleepDisease, sleepDiseaseCooldown: this.sleepDiseaseCooldown, explosionDisease: this.explosionDisease, explosionDiseaseTimer: this.explosionDiseaseTimer, fightDisease: this.fightDisease, fightDiseaseCooldown: this.fightDiseaseCooldown, lastBleedAt: this.lastBleedAt, favorite: this.favorite, insideNestBoxId: this.insideNestBoxId || null, nestFade: this.nestFade || 0, nestBoxEnteredAt: this.nestBoxEnteredAt, nestBoxStayUntil: this.nestBoxStayUntil, focusPulseTimer: this.focusPulseTimer || 0, sleepDiseaseAnchorX: this.sleepDiseaseAnchorX, sleepDiseaseAnchorY: this.sleepDiseaseAnchorY, records: this.records ? this.records.slice(0, 42) : [],
|
||||
zunchiDisease: this.zunchiDisease, zunchiDiseaseSeverity: this.zunchiDiseaseSeverity, zunchiDiseaseCooldown: this.zunchiDiseaseCooldown,
|
||||
tempComfort: this.tempComfort, tempTimer: this.tempTimer,
|
||||
parents: this.parents, parentNames: this.parentNames, children: this.children, birthTime: this.birthTime,
|
||||
|
|
@ -176,7 +185,8 @@ class Tarinai {
|
|||
nextHeadbutt: this.nextHeadbutt, digest: this.digest, poopCount: this.poopCount, stretchTimer: this.stretchTimer,
|
||||
grassEatTimer: this.grassEatTimer, hurtTimer: this.hurtTimer,
|
||||
defeatedTimer: this.defeatedTimer, defeatedById: this.defeatedById, fightWinnerId: this.fightWinnerId,
|
||||
relationships: this.relationships, personality: this.personality, fallTimer: this.fallTimer, fallMax: this.fallMax, fallDir: this.fallDir,
|
||||
relationships: this.relationships, fallTimer: this.fallTimer, fallMax: this.fallMax, fallDir: this.fallDir,
|
||||
birthPersonality: normalizePersonality(this.birthPersonality), currentPersonality: normalizePersonality(this.currentPersonality), personalityDaily: this.personalityDaily || null,
|
||||
blastSpinTimer: this.blastSpinTimer, blastSpinMax: this.blastSpinMax, postBirthPeaceTimer: this.postBirthPeaceTimer,
|
||||
lastMealColor: this.lastMealColor, aiTimer: this.aiTimer, envTimer: this.envTimer, nextCursorHeartAt: this.nextCursorHeartAt, cursorPetting: this.cursorPetting,
|
||||
entryTimer: this.entryTimer || 0,
|
||||
|
|
@ -225,9 +235,9 @@ class Tarinai {
|
|||
}
|
||||
|
||||
|
||||
addRecord(text, kind = "note") {
|
||||
addRecord(text, kind = "note", options = {}) {
|
||||
if (!text) return;
|
||||
if (this.world?.shouldHideFromObservationRecords?.(text)) return;
|
||||
if (options.hiddenFromObservation) return;
|
||||
if (!Array.isArray(this.records)) this.records = [];
|
||||
const entry = { time: this.world?.time || 0, text: String(text), kind: kind || "note" };
|
||||
const head = this.records[0];
|
||||
|
|
@ -237,13 +247,33 @@ class Tarinai {
|
|||
}
|
||||
|
||||
personalityProfile() {
|
||||
return PERSONALITIES[this.personality] || PERSONALITIES.calm;
|
||||
const base = { label: "\u4e2d\u7acb", fear: 1, fight: 1, social: 1, relation: 1, sleep: 1, zunda: 1, play: 1 };
|
||||
const p = ensurePersonality(this).currentPersonality;
|
||||
const e = {
|
||||
neuroticism: personalityEffectValue(p.neuroticism),
|
||||
aggression: personalityEffectValue(p.aggression),
|
||||
sociability: personalityEffectValue(p.sociability),
|
||||
openness: personalityEffectValue(p.openness),
|
||||
};
|
||||
return {
|
||||
...base,
|
||||
fear: clamp((base.fear || 1) * (1 + e.neuroticism * 0.38), 0.55, 1.85),
|
||||
fight: clamp((base.fight || 1) * (1 + e.aggression * 0.55), 0.35, 2.35),
|
||||
social: clamp((base.social || 1) * (1 + e.sociability * 0.24), 0.62, 1.52),
|
||||
relation: clamp((base.relation || 1) * (1 + e.sociability * 0.24), 0.62, 1.60),
|
||||
play: clamp((base.play || 1) * (1 + e.openness * 0.45), 0.45, 2.10),
|
||||
};
|
||||
}
|
||||
|
||||
personalityLabel() {
|
||||
return this.personalityProfile().label || this.personality || "\u4e0d\u660e";
|
||||
const tags = getPersonalityTraitTags(this);
|
||||
return tags.length ? tags.join(" / ") : "\u4e2d\u7acb";
|
||||
}
|
||||
|
||||
personalityTags() { return getPersonalityTraitTags(this); }
|
||||
adjustPersonality(key, delta, reason = "") { return adjustPersonality(this, key, delta, reason); }
|
||||
shouldApplyPersonalityBehavior(key, direction = 1) { return shouldApplyPersonalityBehavior(this, key, direction); }
|
||||
|
||||
fightWinCount() {
|
||||
return this.totalFightWins || 0;
|
||||
}
|
||||
|
|
@ -253,17 +283,17 @@ class Tarinai {
|
|||
}
|
||||
|
||||
maybeBecomeTimidAfterDamage(cause = "") {
|
||||
if (this.dead || this.personality === "timid") return false;
|
||||
if (this.dead) return false;
|
||||
const losses = this.totalFightLosses || 0;
|
||||
const wins = this.totalFightWins || 0;
|
||||
if (losses <= wins) return false;
|
||||
if (Math.random() >= 0.05) return false;
|
||||
this.personality = "timid";
|
||||
const changedA = this.adjustPersonality?.("neuroticism", 0.025, "after being hurt") || 0;
|
||||
const changedB = this.adjustPersonality?.("aggression", -0.015, "after being hurt") || 0;
|
||||
this.fearTimer = Math.max(this.fearTimer || 0, 1.2);
|
||||
this.stress = clamp((this.stress || 0) + 5, 0, 130);
|
||||
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.35, "こわい…", "rgba(90,80,120,0.78)");
|
||||
this.addRecord?.(`${this.name}は痛みで臆病になった。`, "health");
|
||||
return true;
|
||||
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.35, "\u3053\u308f\u3044\u2026", "rgba(90,80,120,0.78)");
|
||||
return Boolean(changedA || changedB);
|
||||
}
|
||||
|
||||
relationTo(id) {
|
||||
|
|
@ -285,7 +315,7 @@ class Tarinai {
|
|||
|
||||
strongestRelation(kind = "friend") {
|
||||
let bestId = null;
|
||||
let bestScore = kind === "fear" ? 5 : 8;
|
||||
let bestScore = kind === "fear" ? 5 : FRIEND_AFFINITY_THRESHOLD;
|
||||
for (const [id, rel] of Object.entries(this.relationships || {})) {
|
||||
const score = kind === "fear" ? (rel.fear || 0) : (rel.affinity || 0);
|
||||
if (score > bestScore) { bestId = id; bestScore = score; }
|
||||
|
|
@ -299,7 +329,23 @@ class Tarinai {
|
|||
return { friend, fear };
|
||||
}
|
||||
|
||||
bestFriendLive(minScore = 10, maxDist = Infinity) {
|
||||
currentFriendCount(minScore = FRIEND_AFFINITY_THRESHOLD, liveOnly = true) {
|
||||
let count = 0;
|
||||
const liveIds = liveOnly ? new Set((this.world?.tarinai || []).filter(o => o && !o.dead && o !== this).map(o => o.id)) : null;
|
||||
for (const [id, rel] of Object.entries(this.relationships || {})) {
|
||||
if ((rel.affinity || 0) <= minScore) continue;
|
||||
if (liveIds && !liveIds.has(id)) continue;
|
||||
count += 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
sociabilityFriendScale() {
|
||||
const friends = this.currentFriendCount ? this.currentFriendCount(FRIEND_AFFINITY_THRESHOLD, true) : 0;
|
||||
return clamp(0.82 + Math.min(6, friends) * 0.16, 0.82, 1.78);
|
||||
}
|
||||
|
||||
bestFriendLive(minScore = FRIEND_AFFINITY_THRESHOLD, maxDist = Infinity) {
|
||||
let best = null, bestScore = minScore;
|
||||
for (const [id, rel] of Object.entries(this.relationships || {})) {
|
||||
const score = rel.affinity || 0;
|
||||
|
|
@ -369,6 +415,24 @@ class Tarinai {
|
|||
normalizeDeathReason(reason = "", opts = {}) { return HEALTH.normalizeDeathReason(this, reason, opts); }
|
||||
damage(amount, reason = "") { return HEALTH.applyDamage(this, amount, reason); }
|
||||
|
||||
wakeFromDamage(reason = "") {
|
||||
if (this.dead) return false;
|
||||
const wasSleeping = this.state === "sleep" || this.sleeping || this.insideNestBoxId;
|
||||
if (!wasSleeping) return false;
|
||||
const box = this.nestBoxById ? this.nestBoxById(this.insideNestBoxId) : null;
|
||||
this.sleeping = false;
|
||||
this.state = "panic";
|
||||
this.target = null;
|
||||
this.thought = "\u30c0\u30e1\u30fc\u30b8\u3067\u76ee\u304c\u899a\u3081\u305f";
|
||||
this.fearTimer = Math.max(this.fearTimer || 0, 0.85);
|
||||
this.hurtTimer = Math.max(this.hurtTimer || 0, 1.2);
|
||||
this.nestBoxStayUntil = -Infinity;
|
||||
if (this.insideNestBoxId) this.leaveNestBox(box);
|
||||
this.vx += rand(-34, 34);
|
||||
this.vy += rand(-26, 8);
|
||||
return true;
|
||||
}
|
||||
|
||||
recoverHealth(amount) {
|
||||
if (this.dead) return;
|
||||
this.energy = clamp(this.energy + Math.max(0, amount || 0), 0, 100);
|
||||
|
|
@ -406,7 +470,7 @@ class Tarinai {
|
|||
}
|
||||
|
||||
targetIgnoresFence(target = this.target) {
|
||||
return Boolean(target?.type === "sweet" || this.state === "panic" || this.state === "fight" || this.state === "intimidate" || this.state === "birth_ritual" || this.state === "cursor_enemy");
|
||||
return Boolean(target?.type === "sweet" || target?.type === "nest_box" || (target?.type === "bed" && (this.state === "seek_bed" || this.state === "sleep")) || this.state === "panic" || this.state === "fight" || this.state === "intimidate" || this.state === "birth_ritual" || this.state === "cursor_enemy");
|
||||
}
|
||||
|
||||
shouldAvoidTarget(target) {
|
||||
|
|
@ -647,9 +711,9 @@ class Tarinai {
|
|||
this.fightDisease = true;
|
||||
this.fightDiseaseCooldown = 18;
|
||||
this.fearTimer = Math.max(this.fearTimer || 0, 0.35);
|
||||
this.thought = "きずつき病\u3067\u5f53\u3066\u3082\u306a\u304f\u5f77\u5fa8\u3063\u3066\u3044\u308b";
|
||||
this.thought = "\u304d\u305a\u3064\u304d\u75c5\u3067\u5f53\u3066\u3082\u306a\u304f\u5f77\u5fa8\u3063\u3066\u3044\u308b";
|
||||
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.20, "?", "rgba(150,78,44,0.80)");
|
||||
this.world?.log?.(`${this.name}\u306fきずつき病\u3092\u767a\u75c7\u3057\u305f\u3002`, "fight");
|
||||
this.world?.log?.(`${this.name}\u306f\u304d\u305a\u3064\u304d\u75c5\u3092\u767a\u75c7\u3057\u305f\u3002`, "fight");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -657,10 +721,10 @@ class Tarinai {
|
|||
if (!this.fightDisease) return false;
|
||||
this.fightDisease = false;
|
||||
this.fightDiseaseCooldown = 20;
|
||||
this.thought = reason || "きずつき病\u304c\u6cbb\u3063\u305f";
|
||||
this.thought = reason || "\u304d\u305a\u3064\u304d\u75c5\u304c\u6cbb\u3063\u305f";
|
||||
this.stress = clamp(this.stress - 12, 0, 130);
|
||||
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.18, "\u306f\u3046", "rgba(82,126,88,0.76)");
|
||||
this.world?.log?.(`${this.name}\u306eきずつき病\u304c\u6cbb\u3063\u305f\u3002`, "fight");
|
||||
this.world?.log?.(`${this.name}\u306e\u304d\u305a\u3064\u304d\u75c5\u304c\u6cbb\u3063\u305f\u3002`, "fight");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -684,34 +748,112 @@ class Tarinai {
|
|||
return true;
|
||||
}
|
||||
|
||||
updateNestBoxPresence(dt = 0) {
|
||||
const previous = this.insideNestBoxId || null;
|
||||
let box = null;
|
||||
if (previous) box = this.world?.items?.find?.(it => it && !it.dead && it.id === previous && it.type === "nest_box") || null;
|
||||
if (!box && this.target && !this.target.dead && this.target.type === "nest_box") box = this.target;
|
||||
if (!box) {
|
||||
this.insideNestBoxId = null;
|
||||
this.nestFade = clamp((this.nestFade || 0) - dt * 1.8, 0, 1);
|
||||
return null;
|
||||
}
|
||||
const d = distXY(this.x, this.y, box.x, box.y);
|
||||
const occupants = this.world?.nestBoxOccupants ? this.world.nestBoxOccupants(box, Infinity) : [];
|
||||
nestBoxById(id) {
|
||||
if (!id) return null;
|
||||
return this.world?.items?.find?.(it => it && !it.dead && it.id === id && it.type === "nest_box") || null;
|
||||
}
|
||||
|
||||
isInsideNestBox() {
|
||||
return Boolean(this.insideNestBoxId);
|
||||
}
|
||||
|
||||
wantsNestBox(box = null) {
|
||||
if (!box || box.dead || box.type !== "nest_box") return false;
|
||||
if (this.insideNestBoxId === box.id && (this.world?.time || 0) < (this.nestBoxStayUntil || -Infinity)) return true;
|
||||
if (!(this.state === "sleep" || this.state === "seek_bed")) return false;
|
||||
return this.target === box || this.insideNestBoxId === box.id;
|
||||
}
|
||||
|
||||
enterNestBox(box, dt = 0.016) {
|
||||
if (!box || box.dead || box.type !== "nest_box" || this.dead) return false;
|
||||
const capacity = this.world?.nestBoxCapacity ? this.world.nestBoxCapacity(box) : 5;
|
||||
const occupantIndex = occupants.indexOf(this);
|
||||
const hasRoom = occupantIndex >= 0 ? occupantIndex < capacity : occupants.length < capacity;
|
||||
const canEnter = hasRoom && (this.state === "sleep" || this.state === "seek_bed") && d <= Math.max(18, box.r * 0.58);
|
||||
if (canEnter) {
|
||||
this.insideNestBoxId = box.id;
|
||||
this.nestFade = clamp((this.nestFade || 0) + dt * 2.4, 0, 1);
|
||||
const pull = clamp(dt * 4.2, 0, 1);
|
||||
this.x = lerp(this.x, box.x, pull);
|
||||
this.y = lerp(this.y, box.y, pull);
|
||||
this.vx *= Math.pow(0.45, dt * 60);
|
||||
this.vy *= Math.pow(0.45, dt * 60);
|
||||
return box;
|
||||
const occupants = this.world?.nestBoxOccupants ? this.world.nestBoxOccupants(box, Infinity) : [];
|
||||
const alreadyInside = this.insideNestBoxId === box.id;
|
||||
if (!alreadyInside && occupants.length >= capacity) return false;
|
||||
if (!alreadyInside) {
|
||||
this.nestBoxEnteredAt = this.world?.time || 0;
|
||||
this.nestBoxStayUntil = Math.max(this.nestBoxStayUntil || -Infinity, (this.world?.time || 0) + 14);
|
||||
}
|
||||
this.insideNestBoxId = box.id;
|
||||
this.target = box;
|
||||
if (this.state === "seek_bed") this.state = "sleep";
|
||||
if (this.state === "sleep") this.sleeping = true;
|
||||
const inner = this.world?.nestBoxInnerPoint ? this.world.nestBoxInnerPoint(box, this) : { x: box.x, y: box.y };
|
||||
const pull = clamp(dt * (alreadyInside ? 10.0 : 13.5), 0, 1);
|
||||
this.x = lerp(this.x, inner.x, pull);
|
||||
this.y = lerp(this.y, inner.y, pull);
|
||||
if (this.nestFade >= 0.88 || distXY(this.x, this.y, inner.x, inner.y) < 3.5) {
|
||||
this.x = inner.x;
|
||||
this.y = inner.y;
|
||||
}
|
||||
this.nestFade = clamp((this.nestFade || 0) + dt * 5.8, 0, 1);
|
||||
this.vx = 0;
|
||||
this.vy = 0;
|
||||
this.impulseVx = 0;
|
||||
this.impulseVy = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
leaveNestBox(box = null) {
|
||||
const b = box || this.nestBoxById(this.insideNestBoxId);
|
||||
if (b && this.world?.nestBoxEntryPoint) {
|
||||
const entry = this.world.nestBoxExitPoint ? this.world.nestBoxExitPoint(b, this) : this.world.nestBoxEntryPoint(b);
|
||||
this.x = clamp(entry.x, CONFIG.worldPadding, this.world.w - CONFIG.worldPadding);
|
||||
this.y = clamp(entry.y, CONFIG.worldPadding, this.world.h - CONFIG.worldPadding);
|
||||
this.vx = rand(-8, 8);
|
||||
this.vy = rand(6, 18);
|
||||
}
|
||||
this.insideNestBoxId = null;
|
||||
this.nestFade = clamp((this.nestFade || 0) - dt * 1.8, 0, 1);
|
||||
this.nestFade = 0;
|
||||
this.nestBoxEnteredAt = -Infinity;
|
||||
this.nestBoxStayUntil = -Infinity;
|
||||
return true;
|
||||
}
|
||||
|
||||
updateNestBoxPresence(dt = 0) {
|
||||
const currentBox = this.nestBoxById(this.insideNestBoxId);
|
||||
const targetBox = this.target && !this.target.dead && this.target.type === "nest_box" ? this.target : null;
|
||||
const box = currentBox || targetBox;
|
||||
if (!box) {
|
||||
this.insideNestBoxId = null;
|
||||
this.nestFade = clamp((this.nestFade || 0) - dt * 3.0, 0, 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (currentBox && !this.wantsNestBox(currentBox)) {
|
||||
this.leaveNestBox(currentBox);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (currentBox) {
|
||||
if ((this.world?.time || 0) < (this.nestBoxStayUntil || -Infinity)) {
|
||||
this.state = "sleep";
|
||||
this.sleeping = true;
|
||||
this.target = currentBox;
|
||||
}
|
||||
this.enterNestBox(currentBox, dt);
|
||||
return currentBox;
|
||||
}
|
||||
|
||||
if (!this.wantsNestBox(targetBox)) {
|
||||
this.nestFade = clamp((this.nestFade || 0) - dt * 3.0, 0, 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
const entry = this.world?.nestBoxEntryPoint ? this.world.nestBoxEntryPoint(targetBox) : { x: targetBox.x, y: targetBox.y };
|
||||
const base = this.world?.nestBoxBaseRect ? this.world.nestBoxBaseRect(targetBox) : null;
|
||||
const r = targetBox.r || 42;
|
||||
const entryD = distXY(this.x, this.y, entry.x, entry.y);
|
||||
const centerD = distXY(this.x, this.y, targetBox.x, targetBox.y);
|
||||
const inDoorColumn = base && this.x >= base.left + r * 0.18 && this.x <= base.right - r * 0.18 && this.y >= base.top + r * 0.06 && this.y <= base.bottom + r * 0.48;
|
||||
const captureRange = Math.max(118, r * 2.05, this.radius + 82);
|
||||
const nearEnough = entryD <= captureRange || centerD <= Math.max(104, r * 1.90) || inDoorColumn;
|
||||
if (nearEnough) {
|
||||
this.enterNestBox(targetBox, dt || 0.016);
|
||||
return targetBox;
|
||||
}
|
||||
|
||||
this.nestFade = clamp((this.nestFade || 0) - dt * 3.0, 0, 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -756,7 +898,7 @@ class Tarinai {
|
|||
if (this.recoverZunchiDisease("\u81ea\u7136\u306b\u305a\u3093\u3061\u75c5\u304c\u6cbb\u307e\u3063\u305f")) this.world?.log?.(`${this.name}\u306e\u305a\u3093\u3061\u75c5\u304c\u81ea\u7136\u306b\u6cbb\u3063\u305f\u3002`, "accident");
|
||||
}
|
||||
if (this.fightDisease && Math.random() < dt * 0.00070 * (this.energy > 58 ? 1.4 : 1.0)) {
|
||||
this.recoverFightDisease("\u81ea\u7136\u306bきずつき病\u304c\u6cbb\u3063\u305f");
|
||||
this.recoverFightDisease("\u81ea\u7136\u306b\u304d\u305a\u3064\u304d\u75c5\u304c\u6cbb\u3063\u305f");
|
||||
}
|
||||
if (this.fightDisease) {
|
||||
this.stress = clamp(this.stress + dt * 0.16, 0, 130);
|
||||
|
|
@ -849,7 +991,7 @@ class Tarinai {
|
|||
if (["fight", "panic", "cursor_enemy", "hurt", "defeated", "fight_sick"].includes(this.state)) return false;
|
||||
if ((this.fearTimer || 0) > 0.18 || (this.intimidatedTimer || 0) > 0.04 || (this.intimidateTimer || 0) > 0.04) return false;
|
||||
if (this.mood < 46 || this.stress > 64) return false;
|
||||
return this.affection > 12 || this.mood > 58 || this.goodMode === "smile" || ["lonely", "calm", "playful", "teary", "cry"].includes(this.personality);
|
||||
return this.affection > 12 || this.mood > 58 || this.goodMode === "smile" || this.shouldApplyPersonalityBehavior("sociability", 1);
|
||||
}
|
||||
|
||||
applyCursorContactCare(dt) {
|
||||
|
|
@ -861,6 +1003,7 @@ class Tarinai {
|
|||
const p = clamp(1 - d / touch, 0, 1);
|
||||
const relief = dt * (1.35 + 2.65 * p);
|
||||
this.stress = clamp((this.stress || 0) - relief, 0, 130);
|
||||
this.adjustPersonality("openness", dt * 0.006 * (0.4 + p), "after being petted.");
|
||||
this.moodLiftTimer = Math.max(this.moodLiftTimer || 0, 0.5);
|
||||
this.cursorPetting = clamp((this.cursorPetting || 0) + dt * (1.0 + p * 2.4), 0, 1.75);
|
||||
this.goodMode = "smile";
|
||||
|
|
@ -914,6 +1057,7 @@ class Tarinai {
|
|||
}
|
||||
}
|
||||
this.pokeFlashTimer = Math.max(0, this.pokeFlashTimer - dt);
|
||||
this.focusPulseTimer = Math.max(0, (this.focusPulseTimer || 0) - dt);
|
||||
this.cursorPetting = Math.max(0, this.cursorPetting - dt * 1.2);
|
||||
if (this.freezeSleepDiseaseMotion(dt)) {
|
||||
this.checkLife(dt);
|
||||
|
|
@ -1035,9 +1179,16 @@ class Tarinai {
|
|||
if (parent && dist(this, parent) < 96) {
|
||||
this.stress = clamp(this.stress - dt * 0.42, 0, 130);
|
||||
this.loneliness = clamp(this.loneliness - dt * 0.32, 0, 110);
|
||||
this.adjustPersonality("sociability", dt * 0.004 * this.sociabilityFriendScale(), "after staying near family.");
|
||||
}
|
||||
const friend = this.bestFriendLive ? this.bestFriendLive(FRIEND_AFFINITY_THRESHOLD, 92) : null;
|
||||
const friendCount = this.currentFriendCount ? this.currentFriendCount(FRIEND_AFFINITY_THRESHOLD, true) : 0;
|
||||
if (friend) {
|
||||
this.stress = clamp(this.stress - dt * 0.36, 0, 130);
|
||||
this.adjustPersonality("sociability", dt * 0.004 * this.sociabilityFriendScale(), "after staying near friends.");
|
||||
} else if (friendCount <= 0 && this.loneliness > 72 && this.age > 8) {
|
||||
this.adjustPersonality("sociability", -dt * 0.0018, "after having no friends.");
|
||||
}
|
||||
const friend = this.bestFriendLive ? this.bestFriendLive(12, 92) : null;
|
||||
if (friend) this.stress = clamp(this.stress - dt * 0.36, 0, 130);
|
||||
let grassComfort = 0;
|
||||
for (const it of this.world.nearbyItems(this.x, this.y, 105)) {
|
||||
if (it.type !== "grass" || it.dead) continue;
|
||||
|
|
@ -1054,7 +1205,7 @@ class Tarinai {
|
|||
if (grassComfort > 0) {
|
||||
this.stress = clamp(this.stress - dt * Math.min(0.32, grassComfort * 0.08), 0, 130);
|
||||
if (this.state === "panic") this.fearTimer = Math.max(0, this.fearTimer - dt * Math.min(0.22, grassComfort * 0.06));
|
||||
if (this.need === "\u9759\u3051\u3055" || this.personality === "timid") this.affection += dt * Math.min(0.06, grassComfort * 0.012);
|
||||
if (this.need === "\u9759\u3051\u3055" || this.shouldApplyPersonalityBehavior("neuroticism", 1)) this.affection += dt * Math.min(0.06, grassComfort * 0.012);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1080,10 +1231,44 @@ class Tarinai {
|
|||
return target?.type === "nest_box" ? "\u5de3\u7bb1" : "\u5e72\u8349\u5bdd\u5e8a";
|
||||
}
|
||||
|
||||
isServingFoodItem(it) {
|
||||
return Boolean(it && typeof isServingFoodType === "function" && isServingFoodType(it.type));
|
||||
}
|
||||
|
||||
foodItemHasServingLeft(it) {
|
||||
if (!it || it.dead) return false;
|
||||
if (!this.isServingFoodItem(it)) return (it.amount || 0) > 1.2;
|
||||
this.ensureFoodServings(it);
|
||||
return (it.foodServingsRemaining || 0) > 0;
|
||||
}
|
||||
|
||||
ensureFoodServings(it) {
|
||||
if (!this.isServingFoodItem(it)) return;
|
||||
const max = typeof foodServingsForSize === "function" ? foodServingsForSize(it.toolSize || "medium") : 5;
|
||||
if (!Number.isFinite(it.foodServingsMax) || it.foodServingsMax <= 0) it.foodServingsMax = max;
|
||||
if (!Number.isFinite(it.foodServingsRemaining)) {
|
||||
const original = { food: 85, sweet: 62, love_mochi: 62, fight_mochi: 62, sleep_drug: 58 }[it.type] || 62;
|
||||
const ratio = clamp((it.amount ?? original) / original, 0.05, 1);
|
||||
it.foodServingsRemaining = Math.max(1, Math.ceil(it.foodServingsMax * ratio));
|
||||
}
|
||||
it.foodServingsRemaining = clamp(it.foodServingsRemaining, 0, it.foodServingsMax);
|
||||
it.amount = it.foodServingsRemaining;
|
||||
}
|
||||
|
||||
consumeFoodServing(it, nutrition) {
|
||||
this.ensureFoodServings(it);
|
||||
if (!this.isServingFoodItem(it) || (it.foodServingsRemaining || 0) <= 0) return 0;
|
||||
it.foodServingsRemaining = Math.max(0, (it.foodServingsRemaining || 0) - 1);
|
||||
it.amount = it.foodServingsRemaining;
|
||||
return nutrition;
|
||||
}
|
||||
|
||||
sleepSpotFor(bed) {
|
||||
if (!bed) return { x: this.x, y: this.y };
|
||||
if (bed.type === "nest_box") {
|
||||
return { x: clamp(bed.x, CONFIG.worldPadding, this.world.w - CONFIG.worldPadding), y: clamp(bed.y, CONFIG.worldPadding, this.world.h - CONFIG.worldPadding) };
|
||||
if (this.insideNestBoxId === bed.id && this.world?.nestBoxInnerPoint) return this.world.nestBoxInnerPoint(bed, this);
|
||||
if (this.world?.nestBoxEntryPoint) return this.world.nestBoxEntryPoint(bed);
|
||||
return { x: clamp(bed.x, CONFIG.worldPadding, this.world.w - CONFIG.worldPadding), y: clamp(bed.y + (bed.r || 42) * 0.24, CONFIG.worldPadding, this.world.h - CONFIG.worldPadding) };
|
||||
}
|
||||
const angle = stableUnit(this.id, `bed-angle-${bed.id || bed.seed || "bed"}`) * Math.PI * 2;
|
||||
const baseRing = bed.r * 0.52;
|
||||
|
|
@ -1128,6 +1313,7 @@ class Tarinai {
|
|||
const d = dist(this, z);
|
||||
const push = clamp(1 - d / 90, 0, 1);
|
||||
this.stress = clamp(this.stress + push * dt * 1.2 * this.personalityProfile().fear, 0, 130);
|
||||
if (push > 0.2) this.adjustPersonality("neuroticism", -dt * push * 0.0035, "after repeated contact with poop.");
|
||||
this.vx += (this.x - z.x) / Math.max(d, 1) * push * dt * 40;
|
||||
this.vy += (this.y - z.y) / Math.max(d, 1) * push * dt * 40;
|
||||
}
|
||||
|
|
@ -1182,12 +1368,12 @@ class Tarinai {
|
|||
this.fightWinnerId = null;
|
||||
}
|
||||
|
||||
const friendNearby = this.bestFriendLive ? this.bestFriendLive(12, 170) : null;
|
||||
const friendNearby = this.bestFriendLive ? this.bestFriendLive(FRIEND_AFFINITY_THRESHOLD, 170) : null;
|
||||
if (friendNearby && friendNearby.state === "panic" && friendNearby.target) {
|
||||
this.state = "panic";
|
||||
this.target = this.panicDestination(friendNearby.target);
|
||||
this.fearTimer = Math.max(this.fearTimer, 0.32);
|
||||
this.thought = "\u4ef2\u826f\u3057\u3068\u4e00\u7dd2\u306b\u9003\u3052\u3066\u3044\u308b";
|
||||
this.thought = "\u53cb\u9054\u3068\u4e00\u7dd2\u306b\u9003\u3052\u3066\u3044\u308b";
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1245,11 +1431,11 @@ class Tarinai {
|
|||
|
||||
if (this.hasZundaCurableDisease?.() && this.fightTimer <= 0.04 && this.birthRitualTimer <= 0.04 && this.defeatedTimer <= 0.04) {
|
||||
const cureFood = this.world.nearest(this, ["sweet"], 700);
|
||||
if (cureFood && cureFood.amount > 1.2 && !this.shouldAvoidTarget(cureFood)) {
|
||||
if (cureFood && this.foodItemHasServingLeft(cureFood) && !this.shouldAvoidTarget(cureFood)) {
|
||||
this.state = "seek_food";
|
||||
this.target = cureFood;
|
||||
this.foodReactTimer = Math.max(this.foodReactTimer, 0.42);
|
||||
this.thought = this.fightDisease ? "きずつき病\u3067\u305a\u3093\u3060\u9905\u3092\u63a2\u3057\u3066\u3044\u308b" : "\u7206\u767a\u75c5\u3067\u305a\u3093\u3060\u9905\u3092\u63a2\u3057\u3066\u3044\u308b";
|
||||
this.thought = this.fightDisease ? "\u304d\u305a\u3064\u304d\u75c5\u3067\u305a\u3093\u3060\u9905\u3092\u63a2\u3057\u3066\u3044\u308b" : "\u7206\u767a\u75c5\u3067\u305a\u3093\u3060\u9905\u3092\u63a2\u3057\u3066\u3044\u308b";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1257,14 +1443,14 @@ class Tarinai {
|
|||
if (this.fightDisease && this.fightTimer <= 0.04 && this.birthRitualTimer <= 0.04 && this.defeatedTimer <= 0.04) {
|
||||
this.state = "fight_sick";
|
||||
this.target = null;
|
||||
this.thought = "きずつき病\u3067\u5f53\u3066\u3082\u306a\u304f\u5f77\u5fa8\u3063\u3066\u3044\u308b";
|
||||
this.thought = "\u304d\u305a\u3064\u304d\u75c5\u3067\u5f53\u3066\u3082\u306a\u304f\u5f77\u5fa8\u3063\u3066\u3044\u308b";
|
||||
if (Math.random() < dt * 0.45) this.wanderAngle += rand(-3.4, 3.4);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isZunchiSlave) {
|
||||
const specialMochi = this.world.nearest(this, ["love_mochi", "fight_mochi", "sleep_drug"], wasSleeping ? 520 : 390);
|
||||
if (specialMochi && specialMochi.amount > 1.2 && this.hunger > 10 && !this.shouldAvoidTarget(specialMochi)) {
|
||||
if (specialMochi && this.foodItemHasServingLeft(specialMochi) && this.hunger > 10 && !this.shouldAvoidTarget(specialMochi)) {
|
||||
this.state = "seek_food";
|
||||
this.target = specialMochi;
|
||||
this.foodReactTimer = Math.max(this.foodReactTimer, 0.34);
|
||||
|
|
@ -1286,7 +1472,7 @@ class Tarinai {
|
|||
const zunda = this.world.nearest(this, ["sweet"], zundaRange);
|
||||
const zundaHungry = this.hunger > Math.max(7, 18 - 10 * (this.personalityProfile().zunda - 1));
|
||||
const zundaReward = this.affection > 8 && this.hunger > 7;
|
||||
if (zunda && zunda.amount > 1.2 && ((wasSleeping && this.hunger > 7) || zundaHungry || zundaReward)) {
|
||||
if (zunda && this.foodItemHasServingLeft(zunda) && ((wasSleeping && this.hunger > 7) || zundaHungry || zundaReward)) {
|
||||
this.state = "seek_food";
|
||||
this.target = zunda;
|
||||
this.foodReactTimer = Math.max(this.foodReactTimer, 0.36);
|
||||
|
|
@ -1306,7 +1492,7 @@ class Tarinai {
|
|||
if (keepSleeping || (personalSleep && this.energy < 90 + 8 * sleepBias) || this.energy < 28 + 8 * sleepBias) {
|
||||
let bed = this.isSleepFurniture(previousTarget) ? previousTarget : null;
|
||||
if (!bed && friendNearby && this.isSleepFurniture(friendNearby.target) && (friendNearby.state === "sleep" || friendNearby.state === "seek_bed")) bed = friendNearby.target;
|
||||
if (!bed && !wasSleeping) bed = this.world.bestBedFor(this, 360);
|
||||
if (!bed) bed = this.world.bestBedFor(this, wasSleeping ? 480 : 420);
|
||||
if (bed && !wasSleeping && this.energy > 18) {
|
||||
const spot = this.sleepSpotFor(bed);
|
||||
if (distXY(this.x, this.y, spot.x, spot.y) > this.radius + 10) {
|
||||
|
|
@ -1323,7 +1509,7 @@ class Tarinai {
|
|||
this.nextSleepBubbleAt = this.world.time + 4.5;
|
||||
this.world.spawnBubble(this.x, this.y - this.radius * 1.18, "Zzz...", "rgba(65,70,92,0.72)");
|
||||
}
|
||||
this.thought = bed ? "\u30d9\u30c3\u30c9\u306e\u8fd1\u304f\u3067\u7720\u3063\u3066\u3044\u308b" : "\u7720\u3063\u3066\u3044\u308b";
|
||||
this.thought = bed ? (bed.type === "nest_box" ? "\u5de3\u7bb1\u306e\u4e2d\u3067\u7720\u3063\u3066\u3044\u308b" : "\u30d9\u30c3\u30c9\u306e\u8fd1\u304f\u3067\u7720\u3063\u3066\u3044\u308b") : "\u7720\u3063\u3066\u3044\u308b";
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1355,19 +1541,21 @@ class Tarinai {
|
|||
}
|
||||
|
||||
const playTrait = this.personalityProfile().play ?? 1;
|
||||
const ballRange = this.personality === "playful" ? 540 : 240 + 60 * playTrait;
|
||||
const openPlay = this.shouldApplyPersonalityBehavior("openness", 1);
|
||||
const closedPlay = this.shouldApplyPersonalityBehavior("openness", -1);
|
||||
const ballRange = openPlay ? 540 : (closedPlay ? 170 : 240 + 60 * playTrait);
|
||||
const ball = this.world.nearest(this, ["ball"], ballRange);
|
||||
if (ball && this.energy > 26 && this.hunger < 98 && !this.shouldAvoidTarget(ball)) {
|
||||
const ballSpeed = Math.hypot(ball.vx || 0, ball.vy || 0);
|
||||
const chasers = this.world.countBallChasers ? this.world.countBallChasers(ball, this) : 0;
|
||||
const baseChance = this.personality === "playful" ? 0.74 : 0.08;
|
||||
const baseChance = openPlay ? 0.74 : (closedPlay ? 0.025 : 0.08);
|
||||
const movingBonus = clamp(ballSpeed / 120, 0, 0.42);
|
||||
const socialBonus = this.loneliness > 48 ? 0.10 : 0;
|
||||
if (chasers < 5 && (this.personality === "playful" || ballSpeed > 7 || Math.random() < baseChance * playTrait + movingBonus + socialBonus)) {
|
||||
if (chasers < 5 && (openPlay || ballSpeed > 7 || Math.random() < baseChance * playTrait + movingBonus + socialBonus)) {
|
||||
this.state = "play_ball";
|
||||
this.target = ball;
|
||||
this.goodMode = "smile";
|
||||
this.thought = this.personality === "playful" ? "\u30dc\u30fc\u30eb\u306b\u5f15\u304d\u5bc4\u305b\u3089\u308c\u3066\u3044\u308b" : "\u8ee2\u304c\u308b\u3082\u306e\u304c\u6c17\u306b\u306a\u3063\u3066\u3044\u308b";
|
||||
this.thought = openPlay ? "\u30dc\u30fc\u30eb\u306b\u5f15\u304d\u5bc4\u305b\u3089\u308c\u3066\u3044\u308b" : "\u8ee2\u304c\u308b\u3082\u306e\u304c\u6c17\u306b\u306a\u3063\u3066\u3044\u308b";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1388,7 +1576,7 @@ class Tarinai {
|
|||
this.state = "seek_food";
|
||||
this.target = friendNearby.target;
|
||||
this.foodReactTimer = Math.max(this.foodReactTimer, 0.24);
|
||||
this.thought = "\u4ef2\u826f\u3057\u3068\u4e00\u7dd2\u306b\u98df\u3079\u306b\u884c\u304f";
|
||||
this.thought = "\u53cb\u9054\u3068\u4e00\u7dd2\u306b\u98df\u3079\u306b\u884c\u304f";
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1404,8 +1592,10 @@ class Tarinai {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.loneliness > 63 - (this.personality === "lonely" ? 16 : 0)) {
|
||||
const other = this.world.nearestOther(this, 300);
|
||||
const socialSeek = this.shouldApplyPersonalityBehavior("sociability", 1);
|
||||
const lonerSeek = this.shouldApplyPersonalityBehavior("sociability", -1);
|
||||
if (this.loneliness > 63 - (socialSeek ? 16 : 0) + (lonerSeek ? 10 : 0)) {
|
||||
const other = this.world.nearestOther(this, socialSeek ? 390 : (lonerSeek ? 190 : 300));
|
||||
if (other) {
|
||||
this.state = "seek_friend";
|
||||
this.target = other;
|
||||
|
|
@ -1446,14 +1636,13 @@ class Tarinai {
|
|||
const d = dist(this, it);
|
||||
if (d > this.radius + it.r + 12) continue;
|
||||
|
||||
if (((it.type === "sweet" && canSweetBite) || (it.type === "food" && canBite)) && it.amount > 1.2) {
|
||||
const eating = Math.min(it.amount, it.type === "sweet" ? 7.2 : 8.5);
|
||||
if (((it.type === "sweet" && canSweetBite) || (it.type === "food" && canBite)) && this.foodItemHasServingLeft(it)) {
|
||||
const eating = this.consumeFoodServing(it, it.type === "sweet" ? 7.2 : 8.5);
|
||||
if (eating > 0) {
|
||||
this.state = "eat";
|
||||
this.target = it;
|
||||
this.vx *= Math.pow(0.25, dt * 60);
|
||||
this.vy *= Math.pow(0.25, dt * 60);
|
||||
it.amount -= eating;
|
||||
this.hunger -= eating * (it.type === "sweet" ? 0.75 : 0.95);
|
||||
this.recoverHealth(eating * (it.type === "sweet" ? 0.28 : 0.46));
|
||||
this.affection += it.type === "sweet" ? eating * 0.03 : eating * 0.012;
|
||||
|
|
@ -1461,7 +1650,7 @@ class Tarinai {
|
|||
this.stress = clamp(this.stress - eating * 0.42, 0, 130);
|
||||
this.fearTimer = Math.max(0, this.fearTimer - eating * 0.035);
|
||||
if (this.explosionDisease) this.recoverExplosionDisease("\u305a\u3093\u3060\u9905\u3067\u7206\u767a\u75c5\u304c\u6cbb\u3063\u305f");
|
||||
if (this.fightDisease) this.recoverFightDisease("\u305a\u3093\u3060\u9905\u3067きずつき病\u304c\u6cbb\u3063\u305f");
|
||||
if (this.fightDisease) this.recoverFightDisease("\u305a\u3093\u3060\u9905\u3067\u304d\u305a\u3064\u304d\u75c5\u304c\u6cbb\u3063\u305f");
|
||||
}
|
||||
this.eatTimer = Math.max(this.eatTimer, 0.32);
|
||||
this.eatCooldown = it.type === "sweet" ? rand(0.34, 0.62) : rand(0.44, 0.78);
|
||||
|
|
@ -1482,13 +1671,13 @@ class Tarinai {
|
|||
}
|
||||
}
|
||||
|
||||
if (canSweetBite && (it.type === "love_mochi" || it.type === "fight_mochi" || it.type === "sleep_drug") && it.amount > 1.2) {
|
||||
const eating = Math.min(it.amount, 6.8);
|
||||
if (canSweetBite && (it.type === "love_mochi" || it.type === "fight_mochi" || it.type === "sleep_drug") && this.foodItemHasServingLeft(it)) {
|
||||
const eating = this.consumeFoodServing(it, 6.8);
|
||||
if (eating <= 0) continue;
|
||||
this.state = "eat";
|
||||
this.target = it;
|
||||
this.vx *= Math.pow(0.25, dt * 60);
|
||||
this.vy *= Math.pow(0.25, dt * 60);
|
||||
it.amount -= eating;
|
||||
this.hunger -= eating * 0.82;
|
||||
this.recoverHealth(eating * 0.24);
|
||||
this.affection += eating * 0.04;
|
||||
|
|
@ -1497,12 +1686,20 @@ class Tarinai {
|
|||
this.loveMochiTimer = Math.max(this.loveMochiTimer || 0, 42 + rand(4, 12));
|
||||
this.bubble("\u2661", 2.4, "rgba(190,82,132,0.78)");
|
||||
this.lastMealColor = "#ff7bab";
|
||||
} else {
|
||||
} else if (it.type === "fight_mochi") {
|
||||
this.stress = clamp(this.stress + eating * 0.22, 0, 130);
|
||||
this.fearTimer = Math.max(this.fearTimer || 0, 0.32);
|
||||
this.fightMochiTimer = Math.max(this.fightMochiTimer || 0, 52 + rand(10, 18));
|
||||
this.bubble("!", 2.0, "rgba(180,86,52,0.80)");
|
||||
this.lastMealColor = "#e07c43";
|
||||
} else {
|
||||
this.stress = clamp(this.stress - eating * 0.10, 0, 130);
|
||||
this.energy = clamp(this.energy - eating * 0.20, 0, 100);
|
||||
this.fearTimer = Math.max(0, (this.fearTimer || 0) - 0.18);
|
||||
this.fightMochiTimer = Math.max(0, (this.fightMochiTimer || 0) - 2.5);
|
||||
this.bubble("Zzz...", 2.3, "rgba(104,84,168,0.78)");
|
||||
this.lastMealColor = "#b89cff";
|
||||
if (!this.sleepDisease && Math.random() < Math.min(0.98, 0.35 + eating * 0.075)) this.infectSleepDisease(it);
|
||||
}
|
||||
this.eatTimer = Math.max(this.eatTimer, 0.30);
|
||||
this.eatCooldown = rand(0.38, 0.68);
|
||||
|
|
@ -1521,7 +1718,8 @@ class Tarinai {
|
|||
}
|
||||
|
||||
if (canBite && it.type === "grass") {
|
||||
const bite = Math.min(it.amount, rand(4.2, 7.2));
|
||||
const servingScale = Number.isFinite(it.foodServingScale) ? it.foodServingScale : (it.toolSize === "large" ? 1.18 : (it.toolSize === "small" ? 0.82 : 1));
|
||||
const bite = Math.min(it.amount, rand(4.2, 7.2) * servingScale);
|
||||
this.state = "eat";
|
||||
this.target = it;
|
||||
this.grassEatTimer = Math.max(this.grassEatTimer, 0.22);
|
||||
|
|
@ -1548,10 +1746,14 @@ class Tarinai {
|
|||
|
||||
if (it.type === "zunchi") {
|
||||
const touch = clamp(1 - d / (this.radius + it.r + 12), 0, 1);
|
||||
if (!this.isZunchiSlave) this.stress = clamp(this.stress + touch * dt * 3.4, 0, 130);
|
||||
if (!this.isZunchiSlave) {
|
||||
this.stress = clamp(this.stress + touch * dt * 3.4 * this.personalityProfile().fear, 0, 130);
|
||||
if (touch > 0.08) this.adjustPersonality("neuroticism", -dt * touch * 0.0045, "after repeated contact with poop.");
|
||||
}
|
||||
this.zunchiStain = clamp((this.zunchiStain || 0) + touch * dt * (this.isZunchiSlave ? 5.2 : 9.5), 0, 100);
|
||||
if (canZunchiBite) {
|
||||
const bite = Math.min(it.amount, this.isZunchiSlave ? rand(4.8, 7.4) : rand(3.4, 5.8));
|
||||
const servingScale = Number.isFinite(it.foodServingScale) ? it.foodServingScale : (it.toolSize === "large" ? 1.18 : (it.toolSize === "small" ? 0.82 : 1));
|
||||
const bite = Math.min(it.amount, (this.isZunchiSlave ? rand(4.8, 7.4) : rand(3.4, 5.8)) * servingScale);
|
||||
this.state = "eat";
|
||||
this.target = it;
|
||||
this.vx *= Math.pow(0.18, dt * 60);
|
||||
|
|
@ -1559,7 +1761,8 @@ class Tarinai {
|
|||
it.amount -= bite;
|
||||
this.hunger -= bite * (this.isZunchiSlave ? 0.66 : 0.18);
|
||||
this.recoverHealth(bite * (this.isZunchiSlave ? 0.10 : 0.02));
|
||||
this.stress = clamp(this.stress + bite * (this.isZunchiSlave ? 0.06 : 0.52), 0, 130);
|
||||
this.stress = clamp(this.stress + bite * (this.isZunchiSlave ? 0.06 : 0.52) * this.personalityProfile().fear, 0, 130);
|
||||
if (!this.isZunchiSlave) this.adjustPersonality("neuroticism", -bite * 0.0015, "after repeated contact with poop.");
|
||||
this.zunchiStain = clamp((this.zunchiStain || 0) + bite * (this.isZunchiSlave ? 0.75 : 1.75), 0, 100);
|
||||
this.lastMealColor = "#6b8d3f";
|
||||
this.eatTimer = Math.max(this.eatTimer, 0.24);
|
||||
|
|
@ -1604,7 +1807,7 @@ class Tarinai {
|
|||
|
||||
if (it.type === "ball") {
|
||||
const touch = clamp(1 - d / (this.radius + it.r + 12), 0, 1);
|
||||
if (this.state === "play_ball" || this.target === it || this.personality === "playful") {
|
||||
if (this.state === "play_ball" || this.target === it || this.shouldApplyPersonalityBehavior("openness", 1)) {
|
||||
this.goodMode = "smile";
|
||||
this.stress = clamp(this.stress - dt * (0.9 + touch * 1.8), 0, 130);
|
||||
this.loneliness = clamp(this.loneliness - dt * (0.4 + touch), 0, 110);
|
||||
|
|
@ -1612,9 +1815,17 @@ class Tarinai {
|
|||
}
|
||||
}
|
||||
|
||||
if (it.type === "splat") {
|
||||
const touch = clamp(1 - d / (this.radius + it.r + 16), 0, 1);
|
||||
if (touch > 0) {
|
||||
this.stress = clamp(this.stress + touch * dt * 2.8 * this.personalityProfile().fear, 0, 130);
|
||||
if (touch > 0.06) this.adjustPersonality("neuroticism", -dt * touch * 0.0042, "after repeated contact with corpses");
|
||||
}
|
||||
}
|
||||
|
||||
if (it.type === "trace") {
|
||||
this.loneliness -= dt * 0.25;
|
||||
this.stress += dt * 0.06;
|
||||
this.stress += dt * 0.06 * this.personalityProfile().fear;
|
||||
}
|
||||
}
|
||||
this.hunger = clamp(this.hunger, 0, 115);
|
||||
|
|
@ -1663,12 +1874,12 @@ class Tarinai {
|
|||
this.loneliness -= dt * 1.65 * this.trait.social;
|
||||
this.affection += dt * 0.28 * this.trait.social;
|
||||
this.adjustRelation(o, dt * 0.18 * pdef.relation * (rel.fear > 10 ? 0.35 : 1), -dt * 0.018, "nearby");
|
||||
if ((rel.affinity || 0) > 10) {
|
||||
if ((rel.affinity || 0) >= FRIEND_AFFINITY_THRESHOLD) {
|
||||
this.stress = clamp(this.stress - dt * 0.62 * this.trait.social, 0, 130);
|
||||
this.affection = clamp(this.affection + dt * 0.18, 0, 80);
|
||||
if (this.world.relationNotice(this.id, o.id, "friend-calm", 55)) this.world.log(`${this.name}\u306f${o.name}\u3068\u3044\u308b\u3068\u5c11\u3057\u843d\u3061\u7740\u304f\u3002`, "relation");
|
||||
}
|
||||
if ((rel.affinity || 0) > 8 && this.world.relationNotice(this.id, o.id, "friend", 32)) {
|
||||
if ((rel.affinity || 0) >= FRIEND_AFFINITY_THRESHOLD && this.world.relationNotice(this.id, o.id, "friend", 32)) {
|
||||
this.world.log(`${this.name}\u306f\u3088\u304f${o.name}\u306e\u8fd1\u304f\u306b\u3044\u308b\u3002`, "relation");
|
||||
}
|
||||
if (this.type === "angry") this.stress += dt * 0.08;
|
||||
|
|
@ -1711,10 +1922,14 @@ class Tarinai {
|
|||
const relB = o.relationTo(this.id);
|
||||
const battleDrug = this.hasFightMochiEffect() || o.hasFightMochiEffect();
|
||||
const loveDrug = this.hasLoveMochiEffect() || o.hasLoveMochiEffect();
|
||||
const fightRisk = (this.mood < 55 || o.mood < 55 || this.stress > 46 || o.stress > 46 || this.type === "angry" || o.type === "angry" || closeCount > 3 || this.personality === "irritable" || o.personality === "irritable" || battleDrug || o.isZunchiSlave || this.isZunchiSlave);
|
||||
const aggressiveThis = this.shouldApplyPersonalityBehavior("aggression", 1);
|
||||
const aggressiveOther = o.shouldApplyPersonalityBehavior?.("aggression", 1);
|
||||
const calmThis = this.shouldApplyPersonalityBehavior("aggression", -1);
|
||||
const calmOther = o.shouldApplyPersonalityBehavior?.("aggression", -1);
|
||||
const fightRisk = (this.mood < 55 || o.mood < 55 || this.stress > 46 || o.stress > 46 || this.type === "angry" || o.type === "angry" || closeCount > 3 || aggressiveThis || aggressiveOther || battleDrug || o.isZunchiSlave || this.isZunchiSlave);
|
||||
const fearBrake = battleDrug ? clamp(1 - ((relA.fear || 0) + (relB.fear || 0)) / 220, 0.74, 1.15) : clamp(1 - ((relA.fear || 0) + (relB.fear || 0)) / 120, 0.34, 1);
|
||||
const friendBrake = battleDrug ? clamp(1 - Math.max(relA.affinity || 0, relB.affinity || 0) / 180, 0.72, 1.12) : clamp(1 - Math.max(relA.affinity || 0, relB.affinity || 0) / 90, 0.48, 1);
|
||||
const personalityRisk = this.personalityProfile().fight * o.personalityProfile().fight;
|
||||
const personalityRisk = this.personalityProfile().fight * o.personalityProfile().fight * (calmThis ? 0.62 : 1) * (calmOther ? 0.62 : 1);
|
||||
const conflictBias = (battleDrug ? 2.9 : 1) * (o.isZunchiSlave ? 2.2 : 1) * (this.isZunchiSlave ? 1.15 : 1);
|
||||
const birthPeace = (this.postBirthPeaceTimer || 0) > 0 || (o.postBirthPeaceTimer || 0) > 0;
|
||||
const familyFightBlocked = this.world.areParentChild ? this.world.areParentChild(this, o) : false;
|
||||
|
|
@ -1723,7 +1938,7 @@ class Tarinai {
|
|||
this.world.startConflict(this, o);
|
||||
}
|
||||
|
||||
if (!this.isZunchiSlave && !o.isZunchiSlave && d < (loveDrug ? 44 : 36) && this.reproductionTimer <= 0 && o.reproductionTimer <= 0 && this.fightTimer <= 0 && o.fightTimer <= 0) {
|
||||
if (!this.isZunchiSlave && !o.isZunchiSlave && !this.sleepDisease && !o.sleepDisease && !this.fightDisease && !o.fightDisease && d < (loveDrug ? 44 : 36) && this.reproductionTimer <= 0 && o.reproductionTimer <= 0 && this.fightTimer <= 0 && o.fightTimer <= 0) {
|
||||
const enough = loveDrug ? (this.hunger < 90 && o.hunger < 92 && this.energy > 18 && o.energy > 18) : (this.hunger < 55 && o.hunger < 60 && this.energy > 44 && o.energy > 44);
|
||||
const lonelyBond = loveDrug ? (this.loneliness > 4 || o.loneliness > 4 || this.affection > 0 || o.affection > 0) : (this.loneliness > 24 || o.loneliness > 24);
|
||||
if (enough && lonelyBond && Math.random() < dt * (loveDrug ? 0.72 : 0.25)) {
|
||||
|
|
@ -1733,7 +1948,7 @@ class Tarinai {
|
|||
}
|
||||
|
||||
if (closeCount > 8) {
|
||||
this.stress += dt * 0.9;
|
||||
this.stress += dt * 0.9 * this.personalityProfile().fear;
|
||||
this.energy -= dt * 0.25;
|
||||
this.fearTimer = Math.max(this.fearTimer, 0.18);
|
||||
}
|
||||
|
|
@ -1755,6 +1970,12 @@ class Tarinai {
|
|||
this.energy = clamp(this.energy + dt * (this.target ? 1.8 + bedComfort * 1.35 : 2.1), 0, 100);
|
||||
this.stress = clamp(this.stress + dt * (crowdedSleep ? 0.18 * Math.max(1, bedCrowd - 5) : -0.12 * bedComfort), 0, 130);
|
||||
this.hunger = clamp(this.hunger + dt * 0.018, 0, 115);
|
||||
if (this.target && !this.target.dead && this.target.type === "nest_box") {
|
||||
this.updateNestBoxPresence(dt);
|
||||
this.vx = 0;
|
||||
this.vy = 0;
|
||||
return;
|
||||
}
|
||||
if (this.target && !this.target.dead && this.isSleepFurniture(this.target)) {
|
||||
const spot = this.sleepSpotFor(this.target);
|
||||
const dx = spot.x - this.x;
|
||||
|
|
@ -1828,9 +2049,13 @@ class Tarinai {
|
|||
const bedDx = this.target.x - this.x;
|
||||
const bedDy = this.target.y - this.y;
|
||||
const bedDist = Math.hypot(bedDx, bedDy);
|
||||
if (d <= Math.max(this.radius + 8, 26) || bedDist <= Math.max(this.target.r * 0.58, 20)) {
|
||||
const reachedSleepFurniture = this.target.type === "nest_box"
|
||||
? (this.insideNestBoxId === this.target.id || d <= Math.max(this.radius + 34, this.target.r * 1.08) || bedDist <= Math.max(this.target.r * 1.05, 62))
|
||||
: (d <= Math.max(this.radius + 8, 26) || bedDist <= Math.max(this.target.r * 0.58, 20));
|
||||
if (reachedSleepFurniture) {
|
||||
this.state = "sleep";
|
||||
this.sleeping = true;
|
||||
if (this.target.type === "nest_box") this.enterNestBox(this.target, dt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1890,8 +2115,13 @@ class Tarinai {
|
|||
this.vy = this.vy / v * maxV;
|
||||
}
|
||||
|
||||
this.x += this.vx * dt;
|
||||
this.y += this.vy * dt;
|
||||
const impulseVx = Number.isFinite(this.impulseVx) ? this.impulseVx : 0;
|
||||
const impulseVy = Number.isFinite(this.impulseVy) ? this.impulseVy : 0;
|
||||
this.x += (this.vx + impulseVx) * dt;
|
||||
this.y += (this.vy + impulseVy) * dt;
|
||||
const impulseDrag = Math.pow(0.58, dt * 3.2);
|
||||
this.impulseVx = Math.abs(impulseVx) < 0.35 ? 0 : impulseVx * impulseDrag;
|
||||
this.impulseVy = Math.abs(impulseVy) < 0.35 ? 0 : impulseVy * impulseDrag;
|
||||
|
||||
if (this.entryTimer > 0) {
|
||||
this.entryTimer = Math.max(0, this.entryTimer - dt);
|
||||
|
|
@ -1914,7 +2144,7 @@ class Tarinai {
|
|||
reason = this.dominantDeathCause("", { lowHealth: true }) || "\u4e8b\u6545";
|
||||
}
|
||||
else if (this.hunger >= 108) reason = "\u98e2\u9913";
|
||||
else if (this.age >= this.lifeSpan) reason = "天寿を全うした";
|
||||
else if (this.age >= this.lifeSpan) reason = "\u5929\u5bff\u3092\u5168\u3046\u3057\u305f";
|
||||
else if (this.stress >= 124) {
|
||||
reason = this.dominantDeathCause("\u30b9\u30c8\u30ec\u30b9\u904e\u591a", { stressDeath: true }) || "\u30b9\u30c8\u30ec\u30b9\u904e\u591a";
|
||||
}
|
||||
|
|
@ -2032,7 +2262,7 @@ class Tarinai {
|
|||
const style = {
|
||||
parent: { color: "rgba(86,142,232,0.86)", label: "\u89aa" },
|
||||
child: { color: "rgba(93,170,92,0.88)", label: "\u5b50" },
|
||||
friend: { color: "rgba(240,91,135,0.90)", label: "\u4ef2\u826f\u3057" },
|
||||
friend: { color: "rgba(240,91,135,0.90)", label: "\u53cb\u9054" },
|
||||
enemy: { color: "rgba(214,82,66,0.90)", label: "\u6575\u5bfe" },
|
||||
}[observeKind] || { color: "rgba(255,250,220,0.78)", label: "" };
|
||||
ctx.save();
|
||||
|
|
@ -2070,27 +2300,40 @@ class Tarinai {
|
|||
ctx.restore();
|
||||
}
|
||||
|
||||
|
||||
ctx.rotate(angle);
|
||||
ctx.globalAlpha *= nestHideAlpha;
|
||||
if (insideCardboard) ctx.globalAlpha *= 0.58;
|
||||
|
||||
|
||||
if (this.world.selected === this) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.85;
|
||||
ctx.strokeStyle = "rgba(165, 220, 98, 0.92)";
|
||||
ctx.lineWidth = 2;
|
||||
const pulse = 0.5 + 0.5 * Math.sin(this.world.time * 7.5);
|
||||
const jumpPulse = clamp(this.focusPulseTimer || 0, 0, 1);
|
||||
ctx.globalAlpha = (0.82 + pulse * 0.12) * nestHideAlpha;
|
||||
ctx.shadowColor = "rgba(124, 210, 76, 0.92)";
|
||||
ctx.shadowBlur = 14 + 18 * Math.max(pulse, jumpPulse);
|
||||
ctx.strokeStyle = "rgba(112, 198, 70, 0.98)";
|
||||
ctx.lineWidth = 4.2 + jumpPulse * 2.0;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, drawH * 0.14, drawW * 0.62, drawH * 0.48, 0, 0, Math.PI * 2);
|
||||
ctx.ellipse(0, drawH * 0.14, drawW * (0.74 + jumpPulse * 0.10), drawH * (0.56 + jumpPulse * 0.08), 0, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([5, 4]);
|
||||
ctx.lineWidth = 1.2;
|
||||
ctx.strokeStyle = "rgba(255,255,255,0.86)";
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.setLineDash([9, 5]);
|
||||
ctx.lineWidth = 2.0;
|
||||
ctx.strokeStyle = "rgba(255,255,255,0.96)";
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, drawH * 0.14, drawW * 0.72, drawH * 0.56, 0, 0, Math.PI * 2);
|
||||
ctx.ellipse(0, drawH * 0.14, drawW * 0.88, drawH * 0.68, 0, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
ctx.fillStyle = "rgba(255,252,232,0.94)";
|
||||
ctx.strokeStyle = "rgba(112,198,70,0.88)";
|
||||
ctx.lineWidth = 2;
|
||||
const focusLabel = "\u6ce8\u76ee";
|
||||
ctx.font = `${Math.max(11, this.radius * 0.54)}px Yomogi, ui-rounded, sans-serif`;
|
||||
const lw = ctx.measureText(focusLabel).width + 18;
|
||||
const ly = -drawH * 0.64;
|
||||
tarinaiRoundRectPath(ctx, -lw / 2, ly - 11, lw, 22, 11);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = "rgba(72,126,42,0.96)";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(focusLabel, 0, ly);
|
||||
ctx.restore();
|
||||
} else if ((this.world.performanceLevel ? this.world.performanceLevel() : 0) < 2 && this.world.pointer?.inside && distXY(this.x, this.y, this.world.pointer.x, this.world.pointer.y) < this.radius * 2.0) {
|
||||
ctx.save();
|
||||
|
|
@ -2103,6 +2346,11 @@ class Tarinai {
|
|||
ctx.restore();
|
||||
}
|
||||
|
||||
ctx.rotate(angle);
|
||||
ctx.globalAlpha *= nestHideAlpha;
|
||||
if (insideCardboard) ctx.globalAlpha *= 0.58;
|
||||
|
||||
|
||||
if (this.eatTimer > 0.02) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.46 * (this.eatTimer / 0.32);
|
||||
|
|
@ -2235,16 +2483,17 @@ class Tarinai {
|
|||
};
|
||||
if (stressActive) {
|
||||
const pct = clamp((this.stress || 0) / 130, 0, 1);
|
||||
drawMeter(baseY - (hpActive ? 11 : 0), pct, pct > 0.72 ? "rgba(178,74,110,0.92)" : "rgba(212,126,84,0.90)", clamp((this.stressBarTimer || 0) / 0.7, 0.35, 1));
|
||||
drawMeter(baseY, pct, pct > 0.72 ? "rgba(178,74,110,0.92)" : "rgba(212,126,84,0.90)", clamp((this.stressBarTimer || 0) / 0.7, 0.35, 1));
|
||||
}
|
||||
if (hpActive) {
|
||||
const pct = clamp(this.energy / 100, 0, 1);
|
||||
drawMeter(baseY, pct, pct > 0.45 ? "rgba(118,190,80,0.88)" : (pct > 0.20 ? "rgba(232,172,68,0.90)" : "rgba(218,82,70,0.92)"), clamp((this.hpBarTimer || 0) / 0.7, 0.35, 1));
|
||||
drawMeter(baseY - (stressActive ? 11 : 0), pct, pct > 0.45 ? "rgba(118,190,80,0.88)" : (pct > 0.20 ? "rgba(232,172,68,0.90)" : "rgba(218,82,70,0.92)"), clamp((this.hpBarTimer || 0) / 0.7, 0.35, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contains(x, y) {
|
||||
if (this.insideNestBoxId || (this.nestFade || 0) >= 0.92) return false;
|
||||
const hit = this.world.screenSizeToWorld ? this.world.screenSizeToWorld(this.radius * 1.9) : this.radius * 1.9;
|
||||
return distXY(this.x, this.y, x, y) < hit;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue