dee
This commit is contained in:
parent
33234f32ae
commit
46258a83eb
15 changed files with 1303 additions and 446 deletions
539
js/tarinai.js
539
js/tarinai.js
|
|
@ -1,5 +1,19 @@
|
|||
"use strict";
|
||||
|
||||
function tarinaiRoundRectPath(ctx, x, y, w, h, r) {
|
||||
const rr = Math.max(0, Math.min(r || 0, Math.abs(w) / 2, Math.abs(h) / 2));
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + rr, y);
|
||||
ctx.lineTo(x + w - rr, y);
|
||||
ctx.quadraticCurveTo(x + w, y, x + w, y + rr);
|
||||
ctx.lineTo(x + w, y + h - rr);
|
||||
ctx.quadraticCurveTo(x + w, y + h, x + w - rr, y + h);
|
||||
ctx.lineTo(x + rr, y + h);
|
||||
ctx.quadraticCurveTo(x, y + h, x, y + h - rr);
|
||||
ctx.lineTo(x, y + rr);
|
||||
ctx.quadraticCurveTo(x, y, x + rr, y);
|
||||
}
|
||||
|
||||
class Tarinai {
|
||||
constructor(world, opts = {}) {
|
||||
this.world = world;
|
||||
|
|
@ -86,6 +100,14 @@ class Tarinai {
|
|||
this.formerName = opts.formerName || null;
|
||||
this.loveMochiTimer = opts.loveMochiTimer ?? 0;
|
||||
this.fightMochiTimer = opts.fightMochiTimer ?? 0;
|
||||
this.sleepDisease = Boolean(opts.sleepDisease);
|
||||
this.sleepDiseaseCooldown = opts.sleepDiseaseCooldown ?? 0;
|
||||
this.explosionDisease = Boolean(opts.explosionDisease);
|
||||
this.explosionDiseaseTimer = opts.explosionDiseaseTimer ?? (this.explosionDisease ? (CONFIG.dayLength || 120) * 0.5 : 0);
|
||||
this.fightDisease = Boolean(opts.fightDisease);
|
||||
this.fightDiseaseCooldown = opts.fightDiseaseCooldown ?? 0;
|
||||
this.lastBleedAt = opts.lastBleedAt ?? -999;
|
||||
this.favorite = Boolean(opts.favorite);
|
||||
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;
|
||||
|
|
@ -144,6 +166,7 @@ class Tarinai {
|
|||
birthRitualRole: this.birthRitualRole, birthRitualLeader: this.birthRitualLeader,
|
||||
pokeFlashTimer: this.pokeFlashTimer, zunchiStain: this.zunchiStain, zunchiStainSeed: this.zunchiStainSeed,
|
||||
totalFightLosses: this.totalFightLosses, 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) : [],
|
||||
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,
|
||||
|
|
@ -200,6 +223,17 @@ class Tarinai {
|
|||
return sp ? sp.label : this.type;
|
||||
}
|
||||
|
||||
|
||||
addRecord(text, kind = "note") {
|
||||
if (!text) 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];
|
||||
if (head && head.text === entry.text && Math.abs((head.time || 0) - entry.time) < 0.05) return;
|
||||
this.records.unshift(entry);
|
||||
if (this.records.length > 42) this.records.length = 42;
|
||||
}
|
||||
|
||||
personalityProfile() {
|
||||
return PERSONALITIES[this.personality] || PERSONALITIES.calm;
|
||||
}
|
||||
|
|
@ -303,100 +337,13 @@ class Tarinai {
|
|||
return gained;
|
||||
}
|
||||
|
||||
damageCauseLabel(reason = "") {
|
||||
const text = String(reason || "");
|
||||
if (!text) return "";
|
||||
if (text.includes("\u3067\u8870\u5f31")) return text.replace(/\u3067\u8870\u5f31.*$/, "");
|
||||
if (/\u55a7\u5629|fight|headbutt/.test(text)) return "\u55a7\u5629";
|
||||
if (/\u7206\u7af9|firecracker|explosion/.test(text)) return "\u7206\u7af9";
|
||||
if (/\u3064\u3064\u304b\u308c|\u3064\u3064\u304d|poke/.test(text)) return "\u3064\u3064\u304b\u308c\u3059\u304e\u305f";
|
||||
if (/\u305a\u3093\u3061\u75c5|zunchi_sick|zunchi.*\u75c5/.test(text)) return "\u305a\u3093\u3061\u75c5";
|
||||
if (/\u75c5/.test(text)) {
|
||||
const disease = text.match(/([^\u3001\u3002\s]+\u75c5)/);
|
||||
return disease ? disease[1] : "\u305a\u3093\u3061\u75c5";
|
||||
}
|
||||
if (/\u98e2|\u7a7a\u8179|\u98df\u3079\u7269|hunger/.test(text)) return "\u98e2\u9913";
|
||||
if (/\u30b9\u30c8\u30ec\u30b9|stress/.test(text)) return "\u30b9\u30c8\u30ec\u30b9\u904e\u591a";
|
||||
if (/\u30dc\u30fc\u30eb|\u843d\u3061|\u843d\u4e0b|\u77f3|\u9053\u5177|\u4e8b\u6545|\u5f53\u305f|\u885d\u7a81|ball|drop|stone|accident/.test(text)) return "\u4e8b\u6545";
|
||||
if (/\u5bff\u547d/.test(text)) return "\u5bff\u547d";
|
||||
return "";
|
||||
}
|
||||
|
||||
recentDamageCause(maxAge = 10) {
|
||||
const now = this.world?.time || 0;
|
||||
if (this.lastDamageCause && now - (this.lastDamageAt || -999) <= maxAge) return this.lastDamageCause;
|
||||
return "";
|
||||
}
|
||||
|
||||
rememberDamage(amount, reason = "") {
|
||||
const cause = this.damageCauseLabel(reason) || "\u4e8b\u6545";
|
||||
const now = this.world?.time || 0;
|
||||
this.lastDamageCause = cause;
|
||||
this.lastDamageAmount = Math.max(0, amount || 0);
|
||||
this.lastDamageAt = now;
|
||||
if (this.lastDamageAmount >= 10 || this.lastDamageAmount >= Math.max(6, (this.energy || 0) * 0.36)) {
|
||||
this.lastMajorDamageCause = cause;
|
||||
this.lastMajorDamageAmount = this.lastDamageAmount;
|
||||
this.lastMajorDamageAt = now;
|
||||
}
|
||||
return cause;
|
||||
}
|
||||
|
||||
weakenedDeathReasonFor(cause = "") {
|
||||
const text = String(cause || "").trim();
|
||||
if (!text) return "";
|
||||
return text.includes("\u3067\u8870\u5f31") ? text : `${text}\u3067\u8870\u5f31`;
|
||||
}
|
||||
|
||||
dominantDeathCause(reason = "", opts = {}) {
|
||||
const text = String(reason || "").trim();
|
||||
const direct = this.damageCauseLabel(text);
|
||||
const now = this.world?.time || 0;
|
||||
const recentMajor = this.lastMajorDamageCause && now - (this.lastMajorDamageAt || -999) <= 10;
|
||||
const recent = this.recentDamageCause ? this.recentDamageCause(20) : "";
|
||||
const fighting = this.fightTimer > 0.04 || this.defeatedTimer > 0.04 || this.defeatedById || this.fightWinnerId || recent === "\u55a7\u5629";
|
||||
if (recentMajor && (!direct || direct === this.lastMajorDamageCause || opts.fromDamage || this.energy <= 22 || this.mood <= 20 || this.stress >= 118)) {
|
||||
return this.weakenedDeathReasonFor(this.lastMajorDamageCause);
|
||||
}
|
||||
if (direct && direct !== "\u30b9\u30c8\u30ec\u30b9\u904e\u591a") return direct;
|
||||
if (this.zunchiDisease && ((this.zunchiDiseaseSeverity || 0) >= 0.30 || /\u75c5|zunchi/.test(text))) return "\u305a\u3093\u3061\u75c5";
|
||||
if (this.hunger >= 108 || (this.hunger >= 94 && this.energy <= 26) || /\u98e2|\u7a7a\u8179|hunger/.test(text)) return "\u98e2\u9913";
|
||||
if (fighting && (this.energy <= 44 || this.stress >= 112 || this.mood <= 22 || opts.fromDamage)) return "\u55a7\u5629";
|
||||
if (recent && recent !== "\u30b9\u30c8\u30ec\u30b9\u904e\u591a" && (this.energy <= 34 || this.mood <= 20 || this.stress >= 118 || opts.fromDamage)) return recent;
|
||||
if (text.includes("\u5bff\u547d")) return "\u5bff\u547d";
|
||||
if (direct) return direct;
|
||||
if (this.stress >= 118 || /\u30b9\u30c8\u30ec\u30b9|stress|\u6c17\u5206/.test(text)) return "\u30b9\u30c8\u30ec\u30b9\u904e\u591a";
|
||||
if (this.energy <= 0.5) return "\u4e8b\u6545";
|
||||
return "";
|
||||
}
|
||||
|
||||
normalizeDeathReason(reason = "", opts = {}) {
|
||||
const text = String(reason || "").trim();
|
||||
if (text.includes("\u3067\u8870\u5f31")) return text;
|
||||
const cause = this.dominantDeathCause(text, opts) || "\u4e8b\u6545";
|
||||
if (cause.includes("\u3067\u8870\u5f31")) return cause;
|
||||
const now = this.world?.time || 0;
|
||||
const recentMajor = this.lastMajorDamageCause && now - (this.lastMajorDamageAt || -999) <= 10;
|
||||
const canWeaken = cause && !cause.endsWith("\u75c5") && cause !== "\u98e2\u9913" && cause !== "\u30b9\u30c8\u30ec\u30b9\u904e\u591a" && cause !== "\u5bff\u547d";
|
||||
if (canWeaken && (opts.weakened || (recentMajor && cause === this.lastMajorDamageCause))) return this.weakenedDeathReasonFor(cause);
|
||||
return cause;
|
||||
}
|
||||
|
||||
damage(amount, reason = "") {
|
||||
const before = this.energy;
|
||||
const loss = Math.max(0, amount || 0);
|
||||
this.energy = clamp(this.energy - loss, 0, 100);
|
||||
const actualLoss = Math.max(0, before - this.energy);
|
||||
let cause = "";
|
||||
if (actualLoss > 0.01) {
|
||||
cause = this.rememberDamage(actualLoss, reason);
|
||||
this.showHpBar();
|
||||
}
|
||||
if (this.energy <= 0.5) {
|
||||
const majorNow = actualLoss >= Math.max(10, before * 0.42);
|
||||
this.die(this.normalizeDeathReason(reason || cause, { fromDamage: true, weakened: majorNow }));
|
||||
}
|
||||
}
|
||||
damageCauseLabel(reason = "") { return HEALTH.causeLabel(reason); }
|
||||
recentDamageCause(maxAge = 10) { return HEALTH.recentDamageCause(this, maxAge); }
|
||||
rememberDamage(amount, reason = "") { return HEALTH.rememberDamage(this, amount, reason); }
|
||||
weakenedDeathReasonFor(cause = "") { return HEALTH.weakenedDeathReasonFor(cause); }
|
||||
dominantDeathCause(reason = "", opts = {}) { return HEALTH.dominantDeathCause(this, reason, opts); }
|
||||
normalizeDeathReason(reason = "", opts = {}) { return HEALTH.normalizeDeathReason(this, reason, opts); }
|
||||
damage(amount, reason = "") { return HEALTH.applyDamage(this, amount, reason); }
|
||||
|
||||
recoverHealth(amount) {
|
||||
if (this.dead) return;
|
||||
|
|
@ -526,12 +473,7 @@ class Tarinai {
|
|||
return null;
|
||||
}
|
||||
|
||||
briefDeathReason(reason = this.deathReason) {
|
||||
const text = String(reason || "");
|
||||
if (!text) return "";
|
||||
const normalized = this.normalizeDeathReason ? this.normalizeDeathReason(text) : text;
|
||||
return normalized.length > 12 ? `${normalized.slice(0, 12)}\u2026` : normalized;
|
||||
}
|
||||
briefDeathReason(reason = this.deathReason) { return HEALTH.briefDeathReason(this, reason); }
|
||||
|
||||
variantSprite(category) {
|
||||
const pools = {
|
||||
|
|
@ -609,6 +551,192 @@ class Tarinai {
|
|||
}
|
||||
}
|
||||
|
||||
hasWaterCurableDisease() {
|
||||
return Boolean(this.zunchiDisease || this.explosionDisease);
|
||||
}
|
||||
|
||||
hasZundaCurableDisease() {
|
||||
return Boolean(this.explosionDisease || this.fightDisease);
|
||||
}
|
||||
|
||||
infectSleepDisease(source = null) {
|
||||
if (this.dead || this.sleepDisease) return false;
|
||||
this.sleepDisease = true;
|
||||
this.sleepDiseaseCooldown = 8;
|
||||
this.sleepDiseaseAnchorX = this.x;
|
||||
this.sleepDiseaseAnchorY = this.y;
|
||||
this.state = "sleep";
|
||||
this.target = null;
|
||||
this.sleeping = true;
|
||||
this.fightTimer = 0;
|
||||
this.intimidateTimer = 0;
|
||||
this.fightTargetIds = [];
|
||||
this.thought = "\u306d\u3080\u308a\u75c5\u3067\u7720\u308a\u7d9a\u3051\u3066\u3044\u308b";
|
||||
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.18, "Zzz...", "rgba(72,76,120,0.76)");
|
||||
this.world?.log?.(`${this.name}\u306f\u306d\u3080\u308a\u75c5\u306b\u304b\u304b\u3063\u305f\u3002`, "accident");
|
||||
return true;
|
||||
}
|
||||
|
||||
recoverSleepDisease(reason = "") {
|
||||
if (!this.sleepDisease) return false;
|
||||
this.sleepDisease = false;
|
||||
this.sleepDiseaseCooldown = 14;
|
||||
this.sleepDiseaseAnchorX = null;
|
||||
this.sleepDiseaseAnchorY = null;
|
||||
this.state = "idle";
|
||||
this.sleeping = false;
|
||||
this.thought = reason || "\u306d\u3080\u308a\u75c5\u304c\u6cbb\u3063\u305f";
|
||||
this.surpriseTimer = Math.max(this.surpriseTimer || 0, 0.65);
|
||||
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.18, "!", "rgba(72,76,120,0.76)");
|
||||
this.world?.log?.(`${this.name}\u306e\u306d\u3080\u308a\u75c5\u304c\u6cbb\u3063\u305f\u3002`, "accident");
|
||||
return true;
|
||||
}
|
||||
|
||||
infectExplosionDisease(source = null) {
|
||||
if (this.dead || this.explosionDisease) return false;
|
||||
this.explosionDisease = true;
|
||||
this.explosionDiseaseTimer = Math.max(this.explosionDiseaseTimer || 0, (CONFIG.dayLength || 120) * 0.5);
|
||||
this.fearTimer = Math.max(this.fearTimer || 0, 0.55);
|
||||
this.thought = "\u7206\u767a\u75c5\u3067\u843d\u3061\u7740\u304b\u306a\u3044";
|
||||
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.2, "!", "rgba(178,78,42,0.82)");
|
||||
this.world?.log?.(`${this.name}\u306f\u7206\u767a\u75c5\u3092\u767a\u75c7\u3057\u305f\u3002`, "accident");
|
||||
return true;
|
||||
}
|
||||
|
||||
recoverExplosionDisease(reason = "") {
|
||||
if (!this.explosionDisease) return false;
|
||||
this.explosionDisease = false;
|
||||
this.explosionDiseaseTimer = 0;
|
||||
this.thought = reason || "\u7206\u767a\u75c5\u304c\u6cbb\u3063\u305f";
|
||||
this.stress = clamp(this.stress - 10, 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\u7206\u767a\u75c5\u304c\u6cbb\u3063\u305f\u3002`, "accident");
|
||||
return true;
|
||||
}
|
||||
|
||||
recordBleedExposure() {
|
||||
this.lastBleedAt = this.world?.time || 0;
|
||||
}
|
||||
|
||||
infectFightDisease(source = null) {
|
||||
if (this.dead || this.fightDisease) return false;
|
||||
this.fightDisease = true;
|
||||
this.fightDiseaseCooldown = 18;
|
||||
this.fearTimer = Math.max(this.fearTimer || 0, 0.35);
|
||||
this.thought = "\u55a7\u5629\u50b7\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\u55a7\u5629\u50b7\u75c5\u3092\u767a\u75c7\u3057\u305f\u3002`, "fight");
|
||||
return true;
|
||||
}
|
||||
|
||||
recoverFightDisease(reason = "") {
|
||||
if (!this.fightDisease) return false;
|
||||
this.fightDisease = false;
|
||||
this.fightDiseaseCooldown = 20;
|
||||
this.thought = reason || "\u55a7\u5629\u50b7\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\u55a7\u5629\u50b7\u75c5\u304c\u6cbb\u3063\u305f\u3002`, "fight");
|
||||
return true;
|
||||
}
|
||||
|
||||
freezeSleepDiseaseMotion(dt = 0) {
|
||||
if (!this.sleepDisease) return false;
|
||||
if (!Number.isFinite(this.sleepDiseaseAnchorX)) this.sleepDiseaseAnchorX = this.x;
|
||||
if (!Number.isFinite(this.sleepDiseaseAnchorY)) this.sleepDiseaseAnchorY = this.y;
|
||||
this.state = "sleep";
|
||||
this.sleeping = true;
|
||||
this.target = null;
|
||||
this.vx = 0;
|
||||
this.vy = 0;
|
||||
this.x = this.sleepDiseaseAnchorX;
|
||||
this.y = this.sleepDiseaseAnchorY;
|
||||
this.wanderAngle = this.wanderAngle || 0;
|
||||
this.thought = "\u306d\u3080\u308a\u75c5\u3067\u7720\u308a\u7d9a\u3051\u3066\u3044\u308b";
|
||||
if (this.world && this.world.time > (this.nextSleepBubbleAt || 0)) {
|
||||
this.nextSleepBubbleAt = this.world.time + 5.0;
|
||||
this.world.spawnBubble?.(this.x, this.y - this.radius * 1.18, "Zzz...", "rgba(65,70,92,0.72)");
|
||||
}
|
||||
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 canEnter = (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;
|
||||
}
|
||||
this.insideNestBoxId = null;
|
||||
this.nestFade = clamp((this.nestFade || 0) - dt * 1.8, 0, 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
applyWaterEffect(dt, source = "water") {
|
||||
const power = Math.max(0, dt || 0);
|
||||
this.stress = clamp(this.stress - power * 2.4, 0, 130);
|
||||
this.energy = clamp(this.energy + power * 0.5, 0, 100);
|
||||
this.hunger = clamp(this.hunger - power * 0.2, 0, 115);
|
||||
this.zunchiStain = clamp((this.zunchiStain || 0) - power * 10.5, 0, 100);
|
||||
if (this.zunchiDisease) {
|
||||
this.zunchiDiseaseSeverity = Math.max(0, (this.zunchiDiseaseSeverity || 1) - power * (this.energy > 70 ? 0.050 : 0.028));
|
||||
if (this.zunchiDiseaseSeverity <= 0.04 && this.recoverZunchiDisease("\u6c34\u3067\u305a\u3093\u3061\u75c5\u304c\u6cbb\u307e\u3063\u305f")) {
|
||||
this.world?.log?.(`${this.name}\u306f\u6c34\u3067\u305a\u3093\u3061\u75c5\u304c\u6cbb\u307e\u3063\u305f\u3002`, "accident");
|
||||
}
|
||||
}
|
||||
if (this.explosionDisease) this.recoverExplosionDisease("\u6c34\u3067\u7206\u767a\u75c5\u304c\u6cbb\u3063\u305f");
|
||||
}
|
||||
|
||||
updateSpecialDiseases(dt) {
|
||||
this.sleepDiseaseCooldown = Math.max(0, (this.sleepDiseaseCooldown || 0) - dt);
|
||||
this.fightDiseaseCooldown = Math.max(0, (this.fightDiseaseCooldown || 0) - dt);
|
||||
if (this.explosionDisease) {
|
||||
this.explosionDiseaseTimer = Math.max(0, (this.explosionDiseaseTimer || 0) - dt);
|
||||
this.addStress ? this.addStress(dt * 0.22, { threshold: 9, duration: 3.6 }) : (this.stress = clamp(this.stress + dt * 0.22, 0, 130));
|
||||
if (Math.random() < dt * 2.1) {
|
||||
const a = rand(0, Math.PI * 2);
|
||||
this.world?.effects?.push(new Effect("explosion", this.x + Math.cos(a) * this.radius * rand(0.2, 0.9), this.y + Math.sin(a) * this.radius * rand(0.1, 0.8), {
|
||||
vx: Math.cos(a) * rand(10, 38), vy: Math.sin(a) * rand(10, 38) - rand(8, 22), size: rand(5, 12), life: rand(0.18, 0.42), color: "rgba(255,64,48,0.86)"
|
||||
}));
|
||||
}
|
||||
if (this.explosionDiseaseTimer <= 0.01) {
|
||||
this.world?.explodeDiseaseTarinai?.(this);
|
||||
return;
|
||||
}
|
||||
if (this.explosionDiseaseTimer < 8 && Math.random() < dt * 0.45) this.bubble("!", 1.0, "rgba(178,78,42,0.82)");
|
||||
}
|
||||
const bleedAge = (this.world?.time || 0) - (this.lastBleedAt || -999);
|
||||
if (!this.fightDisease && this.fightDiseaseCooldown <= 0 && bleedAge >= (CONFIG.dayLength || 120) * 5 / 24 && bleedAge < (CONFIG.dayLength || 120) * 0.85) {
|
||||
if (Math.random() < dt * 0.000375) this.infectFightDisease();
|
||||
}
|
||||
if (this.zunchiDisease && Math.random() < dt * 0.00055 * (this.energy > 66 ? 1.8 : 1.0)) {
|
||||
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\u55a7\u5629\u50b7\u75c5\u304c\u6cbb\u3063\u305f");
|
||||
}
|
||||
if (this.fightDisease) {
|
||||
this.stress = clamp(this.stress + dt * 0.16, 0, 130);
|
||||
if (Math.random() < dt * 0.30) this.wanderAngle += rand(-2.8, 2.8);
|
||||
if (Math.random() < dt * 0.08) this.bubble("?", 2.6, "rgba(150,78,44,0.80)");
|
||||
}
|
||||
}
|
||||
|
||||
canEatItemType(type) {
|
||||
return this.isZunchiSlave ? type === "zunchi" : true;
|
||||
}
|
||||
|
|
@ -642,6 +770,9 @@ class Tarinai {
|
|||
|
||||
rawSpriteId() {
|
||||
if (this.dead) return this.variantSprite("fear");
|
||||
if (this.sleepDisease) return this.variantSprite("sleep");
|
||||
if (this.explosionDisease && Math.sin((this.world?.time || 0) * 8) > 0.15) return "pokan";
|
||||
if (this.fightDisease) return "stress_dizzy";
|
||||
if (this.isZunchiSlave) return "zunchi_slave";
|
||||
if (this.type === "cry" && (this.mood < 26 || this.stress > 78 || this.hunger > 96 || this.energy < 12)) return "cry";
|
||||
if ((this.state === "eat" || this.eatTimer > 0.04) && this.target?.type === "sweet") return "zunda_eat";
|
||||
|
|
@ -684,6 +815,37 @@ class Tarinai {
|
|||
return this.visibleSpriteId;
|
||||
}
|
||||
|
||||
|
||||
isAwakeCursorFriendly() {
|
||||
if (this.dead || this.sleepDisease || this.sleeping || this.state === "sleep" || this.state === "seek_bed") return false;
|
||||
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);
|
||||
}
|
||||
|
||||
applyCursorContactCare(dt) {
|
||||
const pointer = this.world?.pointer;
|
||||
if (!pointer?.inside || !this.isAwakeCursorFriendly()) return;
|
||||
const d = distXY(this.x, this.y, pointer.x, pointer.y);
|
||||
const touch = this.radius * 1.35;
|
||||
if (d > touch) return;
|
||||
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.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";
|
||||
if ((this.world.time || 0) >= (this.nextCursorHeartAt || 0)) {
|
||||
this.nextCursorHeartAt = (this.world.time || 0) + Math.max(0.08, 0.22 - p * 0.10);
|
||||
for (let i = 0; i < (p > 0.62 ? 3 : 2); i++) {
|
||||
this.world.effects?.push(new Effect("heart", this.x + rand(-this.radius * 0.58, this.radius * 0.58), this.y - this.radius * rand(0.68, 1.58), {
|
||||
vx: rand(-16, 16), vy: rand(-44, -20), size: rand(7, 13) * (0.8 + p * 0.45), life: rand(0.62, 0.95), color: "rgba(240,91,135,0.92)"
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update(dt) {
|
||||
if (this.dead) return;
|
||||
|
||||
|
|
@ -703,6 +865,8 @@ class Tarinai {
|
|||
this.hpBarTimer = Math.max(0, (this.hpBarTimer || 0) - dt);
|
||||
this.stressBarTimer = Math.max(0, (this.stressBarTimer || 0) - dt);
|
||||
this.updateZunchiDisease(dt);
|
||||
this.updateSpecialDiseases(dt);
|
||||
if (this.dead) return;
|
||||
this.loveMochiTimer = Math.max(0, (this.loveMochiTimer || 0) - dt);
|
||||
this.fightMochiTimer = Math.max(0, (this.fightMochiTimer || 0) - dt);
|
||||
if (this.fightMochiTimer > 0.04) {
|
||||
|
|
@ -721,6 +885,11 @@ class Tarinai {
|
|||
}
|
||||
this.pokeFlashTimer = Math.max(0, this.pokeFlashTimer - dt);
|
||||
this.cursorPetting = Math.max(0, this.cursorPetting - dt * 1.2);
|
||||
if (this.freezeSleepDiseaseMotion(dt)) {
|
||||
this.checkLife(dt);
|
||||
return;
|
||||
}
|
||||
this.applyCursorContactCare(dt);
|
||||
const wasFighting = this.fightTimer > 0.04;
|
||||
this.fightTimer = Math.max(0, this.fightTimer - dt);
|
||||
this.fightCooldown = Math.max(0, this.fightCooldown - dt);
|
||||
|
|
@ -792,6 +961,11 @@ class Tarinai {
|
|||
this.energy = clamp(this.energy, 0, 100);
|
||||
this.stress = clamp(this.stress, 0, 130);
|
||||
|
||||
if (this.freezeSleepDiseaseMotion(dt)) {
|
||||
this.checkLife(dt);
|
||||
return;
|
||||
}
|
||||
|
||||
this.aiTimer += dt;
|
||||
const urgentAi = this.fightTimer > 0.04 || this.intimidateTimer > 0.04 || this.intimidatedTimer > 0.04 || this.birthRitualTimer > 0.04 || this.defeatedTimer > 0.04 || this.eatTimer > 0.02 || this.hurtTimer > 0.02;
|
||||
const popPerf = this.world.performanceLevel ? this.world.performanceLevel() : 0;
|
||||
|
|
@ -821,6 +995,7 @@ class Tarinai {
|
|||
}
|
||||
this.maintainTargetProgress(dt);
|
||||
this.move(dt);
|
||||
this.updateNestBoxPresence(dt);
|
||||
if (this.world.resolveFenceCollision) this.world.resolveFenceCollision(this);
|
||||
this.checkLife(dt);
|
||||
}
|
||||
|
|
@ -840,6 +1015,12 @@ class Tarinai {
|
|||
const lush = clamp((it.growth ?? it.amount / 120) * (it.health ?? 1), 0, 1.2);
|
||||
grassComfort += clamp(1 - d / 105, 0, 1) * lush;
|
||||
}
|
||||
let nestComfort = 0;
|
||||
for (const it of this.world.nearbyItems(this.x, this.y, 120)) {
|
||||
if (!it || it.dead || it.type !== "nest_box") continue;
|
||||
nestComfort += clamp(1 - distXY(this.x, this.y, it.x, it.y) / 120, 0, 1);
|
||||
}
|
||||
if (nestComfort > 0) this.stress = clamp(this.stress - dt * Math.min(1.15, nestComfort * 0.62), 0, 130);
|
||||
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));
|
||||
|
|
@ -862,15 +1043,18 @@ class Tarinai {
|
|||
}
|
||||
|
||||
isSleepFurniture(target) {
|
||||
return Boolean(target && target.type === "bed");
|
||||
return Boolean(target && (target.type === "bed" || target.type === "nest_box"));
|
||||
}
|
||||
|
||||
sleepFurnitureLabel(target) {
|
||||
return "\u5e72\u8349\u5bdd\u5e8a";
|
||||
return target?.type === "nest_box" ? "\u5de3\u7bb1" : "\u5e72\u8349\u5bdd\u5e8a";
|
||||
}
|
||||
|
||||
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) };
|
||||
}
|
||||
const angle = stableUnit(this.id, `bed-angle-${bed.id || bed.seed || "bed"}`) * Math.PI * 2;
|
||||
const baseRing = bed.r * 0.52;
|
||||
const ring = baseRing + stableUnit(this.id, `bed-ring-${bed.id || bed.seed || "bed"}`) * bed.r * 0.95;
|
||||
|
|
@ -1012,28 +1196,49 @@ class Tarinai {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.zunchiDisease && this.fightTimer <= 0.04 && this.birthRitualTimer <= 0.04 && this.defeatedTimer <= 0.04) {
|
||||
const water = this.world.nearest(this, ["water"], 360);
|
||||
if (this.hasWaterCurableDisease?.() && this.fightTimer <= 0.04 && this.birthRitualTimer <= 0.04 && this.defeatedTimer <= 0.04) {
|
||||
const water = this.world.nearest(this, ["water", "water_bowl"], 760);
|
||||
if (water && !water.dead) {
|
||||
this.state = "seek_water";
|
||||
this.target = water;
|
||||
this.thought = "\u305a\u3093\u3061\u75c5\u3067\u6c34\u3092\u63a2\u3057\u3066\u3044\u308b";
|
||||
this.thought = this.explosionDisease ? "\u7206\u767a\u75c5\u3067\u6c34\u3092\u63a2\u3057\u3066\u3044\u308b" : "\u305a\u3093\u3061\u75c5\u3067\u6c34\u3092\u63a2\u3057\u3066\u3044\u308b";
|
||||
return;
|
||||
}
|
||||
this.state = "zunchi_sick";
|
||||
if (this.zunchiDisease) {
|
||||
this.state = "zunchi_sick";
|
||||
this.target = null;
|
||||
this.thought = "\u305a\u3093\u3061\u75c5\u3067\u5f53\u3066\u3082\u306a\u304f\u5f77\u5fa8\u3063\u3066\u3044\u308b";
|
||||
if (Math.random() < dt * 0.35) this.wanderAngle += rand(-2.4, 2.4);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
this.state = "seek_food";
|
||||
this.target = cureFood;
|
||||
this.foodReactTimer = Math.max(this.foodReactTimer, 0.42);
|
||||
this.thought = this.fightDisease ? "\u55a7\u5629\u50b7\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;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.fightDisease && this.fightTimer <= 0.04 && this.birthRitualTimer <= 0.04 && this.defeatedTimer <= 0.04) {
|
||||
this.state = "fight_sick";
|
||||
this.target = null;
|
||||
this.thought = "\u305a\u3093\u3061\u75c5\u3067\u5f53\u3066\u3082\u306a\u304f\u5f77\u5fa8\u3063\u3066\u3044\u308b";
|
||||
if (Math.random() < dt * 0.35) this.wanderAngle += rand(-2.4, 2.4);
|
||||
this.thought = "\u55a7\u5629\u50b7\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"], wasSleeping ? 520 : 390);
|
||||
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)) {
|
||||
this.state = "seek_food";
|
||||
this.target = specialMochi;
|
||||
this.foodReactTimer = Math.max(this.foodReactTimer, 0.34);
|
||||
this.thought = specialMochi.type === "love_mochi" ? "\u3078\u3053\u9905\u3092\u63a2\u3057\u3066\u3044\u308b" : "\u3051\u3093\u304b\u9905\u3092\u63a2\u3057\u3066\u3044\u308b";
|
||||
this.thought = specialMochi.type === "love_mochi" ? "\u3078\u3053\u9905\u3092\u63a2\u3057\u3066\u3044\u308b" : (specialMochi.type === "fight_mochi" ? "\u3051\u3093\u304b\u9905\u3092\u63a2\u3057\u3066\u3044\u308b" : "\u306d\u3080\u308a\u85ac\u3092\u63a2\u3057\u3066\u3044\u308b");
|
||||
return;
|
||||
}
|
||||
} else if (this.hunger > 20) {
|
||||
|
|
@ -1097,7 +1302,7 @@ class Tarinai {
|
|||
const pd = distXY(this.x, this.y, pointer.x, pointer.y);
|
||||
if (pd < 170) {
|
||||
const hostile = this.mood < 38 || this.stress > 58 || this.type === "angry";
|
||||
const friendly = !hostile && (this.mood > 52 || this.affection > 12 || this.goodMode === "smile");
|
||||
const friendly = !hostile && this.isAwakeCursorFriendly();
|
||||
if (hostile) {
|
||||
this.state = "cursor_enemy";
|
||||
this.target = { x: pointer.x, y: pointer.y, dead: false };
|
||||
|
|
@ -1149,7 +1354,7 @@ class Tarinai {
|
|||
}
|
||||
}
|
||||
|
||||
if (friendNearby && this.hunger > 44 && friendNearby.target && (this.isZunchiSlave ? ["zunchi"] : ["sweet", "love_mochi", "fight_mochi", "grass"]).includes(friendNearby.target.type) && !friendNearby.target.dead && !this.shouldAvoidTarget(friendNearby.target)) {
|
||||
if (friendNearby && this.hunger > 44 && friendNearby.target && (this.isZunchiSlave ? ["zunchi"] : ["sweet", "love_mochi", "fight_mochi", "sleep_drug", "grass"]).includes(friendNearby.target.type) && !friendNearby.target.dead && !this.shouldAvoidTarget(friendNearby.target)) {
|
||||
this.state = "seek_food";
|
||||
this.target = friendNearby.target;
|
||||
this.foodReactTimer = Math.max(this.foodReactTimer, 0.24);
|
||||
|
|
@ -1158,7 +1363,7 @@ class Tarinai {
|
|||
}
|
||||
|
||||
if (this.hunger > 62) {
|
||||
const itemTypes = this.isZunchiSlave ? ["zunchi"] : ["sweet", "love_mochi", "fight_mochi", "grass"];
|
||||
const itemTypes = this.isZunchiSlave ? ["zunchi"] : ["sweet", "love_mochi", "fight_mochi", "sleep_drug", "grass"];
|
||||
const item = this.world.nearest(this, itemTypes, 420);
|
||||
if (item) {
|
||||
this.state = "seek_food";
|
||||
|
|
@ -1225,6 +1430,8 @@ class Tarinai {
|
|||
if (it.type === "sweet") {
|
||||
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\u55a7\u5629\u50b7\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);
|
||||
|
|
@ -1245,7 +1452,7 @@ class Tarinai {
|
|||
}
|
||||
}
|
||||
|
||||
if (canSweetBite && (it.type === "love_mochi" || it.type === "fight_mochi") && it.amount > 1.2) {
|
||||
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);
|
||||
this.state = "eat";
|
||||
this.target = it;
|
||||
|
|
@ -1272,12 +1479,12 @@ class Tarinai {
|
|||
this.foodReactTimer = Math.max(this.foodReactTimer, 0.16);
|
||||
if (Math.random() < dt * 2.2) {
|
||||
const mouth = this.mouthPosition(it);
|
||||
this.world.spawnEatEffect(mouth.x, mouth.y, it.type === "love_mochi" ? "#ff7bab" : "#e07c43");
|
||||
this.world.spawnEatEffect(mouth.x, mouth.y, it.type === "love_mochi" ? "#ff7bab" : (it.type === "fight_mochi" ? "#e07c43" : "#b89cff"));
|
||||
}
|
||||
this.makePoop(eating * 0.20);
|
||||
if (this.world.time > this.nextEatSound) { audio.eat(); this.nextEatSound = this.world.time + 0.42; }
|
||||
if (this.world.time - this.lastLog > 9 && Math.random() < 0.02) {
|
||||
this.world.log(`${this.name}\u306f${it.type === "love_mochi" ? "\u3078\u3053\u9905" : "\u3051\u3093\u304b\u9905"}\u3092\u3057\u3070\u3089\u304f\u5473\u308f\u3063\u305f\u3002`, "food");
|
||||
this.world.log(`${this.name}\u306f${it.type === "love_mochi" ? "\u3078\u3053\u9905" : (it.type === "fight_mochi" ? "\u3051\u3093\u304b\u9905" : "\u306d\u3080\u308a\u85ac")}\u3092\u3057\u3070\u3089\u304f\u5473\u308f\u3063\u305f\u3002`, "food");
|
||||
this.lastLog = this.world.time;
|
||||
}
|
||||
break;
|
||||
|
|
@ -1335,18 +1542,9 @@ class Tarinai {
|
|||
}
|
||||
}
|
||||
|
||||
if (it.type === "water") {
|
||||
this.stress -= dt * 2.4;
|
||||
this.energy += dt * 0.5;
|
||||
this.hunger -= dt * 0.2;
|
||||
this.zunchiStain = clamp((this.zunchiStain || 0) - dt * 10.5, 0, 100);
|
||||
if (this.zunchiDisease) {
|
||||
this.zunchiDiseaseSeverity = Math.max(0, (this.zunchiDiseaseSeverity || 1) - dt * (this.energy > 70 ? 0.050 : 0.028));
|
||||
if (this.zunchiDiseaseSeverity <= 0.04 && this.recoverZunchiDisease("\u6c34\u3067\u305a\u3093\u3061\u75c5\u304c\u6cbb\u307e\u3063\u305f")) {
|
||||
this.world.log(`${this.name}\u306f\u6c34\u3067\u305a\u3093\u3061\u75c5\u304c\u6cbb\u307e\u3063\u305f\u3002`, "accident");
|
||||
}
|
||||
}
|
||||
it.amount -= dt * 2.4;
|
||||
if (it.type === "water" || it.type === "water_bowl") {
|
||||
this.applyWaterEffect(dt, it.type);
|
||||
if (it.type === "water") it.amount -= dt * 2.4;
|
||||
if (this.type === "teary" || this.type === "cry") this.affection += dt * 0.18;
|
||||
}
|
||||
|
||||
|
|
@ -1516,11 +1714,12 @@ class Tarinai {
|
|||
}
|
||||
|
||||
move(dt) {
|
||||
if (this.freezeSleepDiseaseMotion(dt)) return;
|
||||
let ax = 0, ay = 0;
|
||||
const baseSpeed = 46.5 * 1.5 * this.trait.speed * (this.zunchiDisease ? 0.5 : 1) * (this.energy < 20 ? 0.42 : 1) * (this.fallTimer > 0 ? 0.35 : 1);
|
||||
|
||||
if (this.state === "sleep") {
|
||||
const bedComfort = this.isSleepFurniture(this.target) ? this.world.bedComfort(this.target) : 0.72;
|
||||
const bedComfort = this.isSleepFurniture(this.target) ? this.world.bedComfort(this.target) * (this.target?.type === "nest_box" ? 1.55 : 1) : 0.72;
|
||||
const bedCrowd = this.isSleepFurniture(this.target) ? this.world.bedOccupancy(this.target) : 0;
|
||||
const crowdedSleep = bedCrowd > 5;
|
||||
this.energy = clamp(this.energy + dt * (this.target ? 1.8 + bedComfort * 1.35 : 2.1), 0, 100);
|
||||
|
|
@ -1540,6 +1739,7 @@ class Tarinai {
|
|||
this.y = spot.y;
|
||||
}
|
||||
}
|
||||
this.updateNestBoxPresence(dt);
|
||||
this.vx *= Math.pow(0.55, dt * 60);
|
||||
this.vy *= Math.pow(0.55, dt * 60);
|
||||
return;
|
||||
|
|
@ -1639,6 +1839,10 @@ class Tarinai {
|
|||
this.wanderAngle += rand(-2.4, 2.4) * dt + Math.sin(this.world.time * 1.7 + this.age) * dt * 0.8;
|
||||
ax += Math.cos(this.wanderAngle) * baseSpeed * 0.72;
|
||||
ay += Math.sin(this.wanderAngle) * baseSpeed * 0.72;
|
||||
} else if (this.state === "fight_sick") {
|
||||
this.wanderAngle += rand(-4.2, 4.2) * dt + Math.sin(this.world.time * 3.2 + this.age) * dt * 1.6;
|
||||
ax += Math.cos(this.wanderAngle) * baseSpeed * 0.88;
|
||||
ay += Math.sin(this.wanderAngle) * baseSpeed * 0.88;
|
||||
} else {
|
||||
ax += Math.cos(this.wanderAngle) * baseSpeed * 0.34;
|
||||
ay += Math.sin(this.wanderAngle) * baseSpeed * 0.34;
|
||||
|
|
@ -1649,7 +1853,7 @@ class Tarinai {
|
|||
this.vx *= Math.pow(0.84, dt * 8);
|
||||
this.vy *= Math.pow(0.84, dt * 8);
|
||||
|
||||
const maxV = this.state === "panic" ? 116 : (this.state === "zunchi_sick" ? 54 : 76);
|
||||
const maxV = this.state === "panic" ? 116 : (this.state === "zunchi_sick" ? 54 : (this.state === "fight_sick" ? 68 : 76));
|
||||
const v = Math.hypot(this.vx, this.vy);
|
||||
if (v > maxV) {
|
||||
this.vx = this.vx / v * maxV;
|
||||
|
|
@ -1734,7 +1938,8 @@ class Tarinai {
|
|||
const bob = breathing * (this.state === "sleep" ? 0.9 : 1.2 + gait * 1.6) + headbuttPulse * 0.9 + fallLift + birthShake * 0.25;
|
||||
const blastSpin = this.blastSpinTimer > 0.04 ? this.fallDir * ((this.blastSpinMax || 1) - this.blastSpinTimer) * Math.PI * 70.0 : 0;
|
||||
const fallSpin = this.blastSpinTimer > 0.04 ? blastSpin : (fallPose > 0 ? this.fallDir * (1 - fallPose) * Math.PI * 2 : 0);
|
||||
const motionTilt = clamp(this.vx / 240, -0.22, 0.22) + (this.state === "fight" ? headbuttPulse * 0.05 : 0) + eatingShake * 0.105 + this.fallDir * fallPose * 0.18 + fallSpin;
|
||||
const diseaseSpin = this.fightDisease ? (t * 3.6 + this.age) : 0;
|
||||
const motionTilt = clamp(this.vx / 240, -0.22, 0.22) + (this.state === "fight" ? headbuttPulse * 0.05 : 0) + eatingShake * 0.105 + this.fallDir * fallPose * 0.18 + fallSpin + diseaseSpin;
|
||||
const insideCardboard = false;
|
||||
let drawW = w * this.scale * 0.30;
|
||||
let drawH = h * this.scale * 0.30;
|
||||
|
|
@ -1765,18 +1970,19 @@ class Tarinai {
|
|||
? this.sleepFacing > 0
|
||||
: this.facingDir() > 0;
|
||||
const angle = (faceRight ? 1 : -1) * (Math.PI / 12) + motionTilt;
|
||||
const nestHideAlpha = this.nestFade ? clamp(1 - this.nestFade * 0.92, 0.08, 1) : 1;
|
||||
|
||||
const shadow = projectedShadowParams(lightState, 0.95 + this.scale * 0.8);
|
||||
const shadowContactY = this.y + bob + (drawH / squish) * 0.30;
|
||||
if (img) {
|
||||
drawImageProjectedShadow(ctx, img, this.x, shadowContactY, drawW * squish, drawH / squish, shadow, {
|
||||
faceRight,
|
||||
alpha: shadow.alpha * (this.state === "sleep" ? 1.10 : 0.92),
|
||||
alpha: shadow.alpha * (this.state === "sleep" ? 1.10 : 0.92) * nestHideAlpha,
|
||||
widthScale: this.state === "sleep" ? 1.12 : 1,
|
||||
heightScale: this.state === "sleep" ? 0.88 : 1,
|
||||
});
|
||||
} else {
|
||||
drawProjectedShadow(ctx, this.x, shadowContactY, drawW * 0.52, drawH * 0.13, { ...shadow, alpha: shadow.alpha * 0.82 });
|
||||
drawProjectedShadow(ctx, this.x, shadowContactY, drawW * 0.52, drawH * 0.13, { ...shadow, alpha: shadow.alpha * 0.82 * nestHideAlpha });
|
||||
}
|
||||
drawCreatureGlow(ctx, this, drawW, drawH, lightState);
|
||||
if (this.birthRitualTimer > 0.04 && this.birthRitualLeader) {
|
||||
|
|
@ -1793,6 +1999,49 @@ class Tarinai {
|
|||
ctx.rotate(angle);
|
||||
if (insideCardboard) ctx.globalAlpha *= 0.58;
|
||||
|
||||
const observeKind = this.world.observeHighlightKind ? this.world.observeHighlightKind(this) : "";
|
||||
if (observeKind && this.world.selected !== this) {
|
||||
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" },
|
||||
enemy: { color: "rgba(214,82,66,0.90)", label: "\u6575\u5bfe" },
|
||||
}[observeKind] || { color: "rgba(255,250,220,0.78)", label: "" };
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.96;
|
||||
ctx.shadowColor = style.color;
|
||||
ctx.shadowBlur = 16;
|
||||
ctx.strokeStyle = style.color;
|
||||
ctx.lineWidth = 5.2;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, drawH * 0.14, drawW * 0.78, drawH * 0.60, 0, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.strokeStyle = "rgba(255,255,255,0.92)";
|
||||
ctx.lineWidth = 2.0;
|
||||
ctx.setLineDash([8, 5]);
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, drawH * 0.14, drawW * 0.86, drawH * 0.66, 0, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
if (style.label) {
|
||||
const labelY = -drawH * 0.62;
|
||||
ctx.font = `${Math.max(11, this.radius * 0.52)}px Yomogi, ui-rounded, sans-serif`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
const labelW = ctx.measureText(style.label).width + 16;
|
||||
ctx.fillStyle = "rgba(255,252,238,0.92)";
|
||||
ctx.strokeStyle = style.color;
|
||||
ctx.lineWidth = 2.2;
|
||||
tarinaiRoundRectPath(ctx, -labelW / 2, labelY - 10, labelW, 20, 10);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = style.color;
|
||||
ctx.fillText(style.label, 0, labelY);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
if (this.world.selected === this) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.85;
|
||||
|
|
@ -1833,7 +2082,7 @@ class Tarinai {
|
|||
|
||||
if (this.pokeFlashTimer > 0.03) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = this.pokeFlashTimer * 0.9;
|
||||
ctx.globalAlpha = this.pokeFlashTimer * 0.9 * nestHideAlpha;
|
||||
ctx.fillStyle = "rgba(255, 120, 120, 0.22)";
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, 0, drawW * 0.60, drawH * 0.42, 0, 0, Math.PI * 2);
|
||||
|
|
@ -1846,7 +2095,7 @@ class Tarinai {
|
|||
const cursorLove = this.state === "cursor_friend" ? Math.max(0, this.cursorPetting || 0) : 0;
|
||||
ctx.globalAlpha = this.state === "cursor_friend" ? clamp(0.82 + cursorLove * 0.16, 0.82, 1) : 0.34;
|
||||
ctx.fillStyle = this.state === "cursor_friend" ? "#f05b87" : (lightState.nightStrength > 0.20 ? "#eef5ff" : (lightState.goldenStrength > 0.35 ? "#ffe398" : "#d6f5a9"));
|
||||
const count = this.state === "cursor_friend" ? (cursorLove > 0.18 ? 5 : 3) : 2;
|
||||
const count = this.state === "cursor_friend" ? (cursorLove > 1.0 ? 10 : (cursorLove > 0.45 ? 8 : (cursorLove > 0.18 ? 6 : 4))) : 2;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const orbit = 0.24 + (i % 3) * 0.08 + cursorLove * 0.05;
|
||||
const px = Math.cos(t * 2.2 + this.age + i * 1.42) * drawW * orbit;
|
||||
|
|
@ -1863,6 +2112,7 @@ class Tarinai {
|
|||
ctx.restore();
|
||||
}
|
||||
|
||||
ctx.globalAlpha *= nestHideAlpha;
|
||||
if (faceRight) ctx.scale(-1, 1);
|
||||
const dw = drawW * squish;
|
||||
const dh = drawH / squish;
|
||||
|
|
@ -1910,6 +2160,25 @@ class Tarinai {
|
|||
|
||||
ctx.restore();
|
||||
|
||||
if (this.favorite) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 1;
|
||||
const starSize = Math.max(18, this.radius * 0.88);
|
||||
const sx = this.x;
|
||||
const sy = this.y - drawH * 0.64 - 20;
|
||||
ctx.font = `${starSize}px "Apple Color Emoji", "Segoe UI Emoji", sans-serif`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.shadowColor = "rgba(255, 198, 28, 0.82)";
|
||||
ctx.shadowBlur = 14;
|
||||
ctx.lineWidth = Math.max(2, starSize * 0.12);
|
||||
ctx.strokeStyle = "rgba(92, 62, 0, 0.86)";
|
||||
ctx.fillStyle = "rgba(255, 218, 46, 1)";
|
||||
ctx.strokeText("\u2605", sx, sy);
|
||||
ctx.fillText("\u2605", sx, sy);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
const hpActive = (this.hpBarTimer || 0) > 0;
|
||||
const stressActive = (this.stressBarTimer || 0) > 0;
|
||||
if (hpActive || stressActive) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue