daaaa
This commit is contained in:
parent
8eb66f2e66
commit
33234f32ae
18 changed files with 1390 additions and 573 deletions
339
js/tarinai.js
339
js/tarinai.js
|
|
@ -30,11 +30,18 @@ class Tarinai {
|
|||
this.energy = opts.energy ?? rand(54, 98);
|
||||
this.stress = opts.stress ?? rand(0, 20);
|
||||
this.hpBarTimer = opts.hpBarTimer ?? 0;
|
||||
this.stressBarTimer = opts.stressBarTimer ?? 0;
|
||||
this.affection = opts.affection ?? rand(0, 30);
|
||||
this.need = opts.need || pick(NEEDS);
|
||||
this.state = opts.state || "idle";
|
||||
this.dead = !!opts.dead;
|
||||
this.deathReason = opts.deathReason || "";
|
||||
this.lastDamageCause = opts.lastDamageCause || "";
|
||||
this.lastDamageAmount = opts.lastDamageAmount ?? 0;
|
||||
this.lastDamageAt = opts.lastDamageAt ?? -999;
|
||||
this.lastMajorDamageCause = opts.lastMajorDamageCause || "";
|
||||
this.lastMajorDamageAmount = opts.lastMajorDamageAmount ?? 0;
|
||||
this.lastMajorDamageAt = opts.lastMajorDamageAt ?? -999;
|
||||
this.thought = "";
|
||||
this.target = null;
|
||||
this.targetKey = "";
|
||||
|
|
@ -79,7 +86,7 @@ class Tarinai {
|
|||
this.formerName = opts.formerName || null;
|
||||
this.loveMochiTimer = opts.loveMochiTimer ?? 0;
|
||||
this.fightMochiTimer = opts.fightMochiTimer ?? 0;
|
||||
if (this.isZunchiSlave) this.name = "ずんちどれい";
|
||||
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;
|
||||
this.parents = opts.parents || [];
|
||||
|
|
@ -127,8 +134,8 @@ class Tarinai {
|
|||
return {
|
||||
id: this.id, name: this.name, type: this.type, x: this.x, y: this.y, vx: this.vx, vy: this.vy,
|
||||
scale: this.scale, age: this.age, lifeSpan: this.lifeSpan, generation: this.generation, hasPaired: this.hasPaired,
|
||||
dead: this.dead, deathReason: this.deathReason,
|
||||
hunger: this.hunger, loneliness: this.loneliness, energy: this.energy, stress: this.stress, hpBarTimer: this.hpBarTimer,
|
||||
dead: this.dead, deathReason: this.deathReason, lastDamageCause: this.lastDamageCause, lastDamageAmount: this.lastDamageAmount, lastDamageAt: this.lastDamageAt, lastMajorDamageCause: this.lastMajorDamageCause, lastMajorDamageAmount: this.lastMajorDamageAmount, lastMajorDamageAt: this.lastMajorDamageAt,
|
||||
hunger: this.hunger, loneliness: this.loneliness, energy: this.energy, stress: this.stress, hpBarTimer: this.hpBarTimer, stressBarTimer: this.stressBarTimer,
|
||||
affection: this.affection, need: this.need, state: this.state,
|
||||
goodMode: this.goodMode, facing: this.facing, visualFacing: this.visualFacing, facingLockUntil: this.facingLockUntil, eatTimer: this.eatTimer,
|
||||
foodReactTimer: this.foodReactTimer, surpriseTimer: this.surpriseTimer, fearTimer: this.fearTimer,
|
||||
|
|
@ -266,7 +273,7 @@ class Tarinai {
|
|||
poke() {
|
||||
audio.poke();
|
||||
this.damage(rand(9, 16), "\u3064\u3064\u304b\u308c\u3059\u304e");
|
||||
this.stress = clamp(this.stress + 22, 0, 130);
|
||||
this.addStress(22, { threshold: 8 });
|
||||
this.surpriseTimer = 0.55;
|
||||
this.fearTimer = Math.max(this.fearTimer, 1.2);
|
||||
this.pokeFlashTimer = 0.55;
|
||||
|
|
@ -281,18 +288,119 @@ class Tarinai {
|
|||
this.hpBarTimer = Math.max(this.hpBarTimer || 0, duration);
|
||||
}
|
||||
|
||||
showStressBar(duration = 4.2) {
|
||||
this.stressBarTimer = Math.max(this.stressBarTimer || 0, duration);
|
||||
}
|
||||
|
||||
addStress(amount, opts = {}) {
|
||||
const delta = Math.max(0, amount || 0);
|
||||
if (delta <= 0) return 0;
|
||||
const before = this.stress || 0;
|
||||
this.stress = clamp(before + delta, 0, 130);
|
||||
const gained = Math.max(0, this.stress - before);
|
||||
const threshold = opts.threshold ?? 8;
|
||||
if (gained >= threshold) this.showStressBar(opts.duration ?? 4.2);
|
||||
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;
|
||||
this.energy = clamp(this.energy - Math.max(0, amount || 0), 0, 100);
|
||||
if (this.energy < before - 0.01) this.showHpBar();
|
||||
if (this.energy <= 0.5 && reason) this.die(reason);
|
||||
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 }));
|
||||
}
|
||||
}
|
||||
|
||||
recoverHealth(amount) {
|
||||
if (this.dead) return;
|
||||
const before = this.energy;
|
||||
this.energy = clamp(this.energy + Math.max(0, amount || 0), 0, 100);
|
||||
if (this.energy > before + 0.01 && before < 99.5) this.showHpBar(2.4);
|
||||
}
|
||||
|
||||
familyJoined() {
|
||||
|
|
@ -421,12 +529,8 @@ class Tarinai {
|
|||
briefDeathReason(reason = this.deathReason) {
|
||||
const text = String(reason || "");
|
||||
if (!text) return "";
|
||||
if (text.includes("食べ物がたりない")) return "飢え";
|
||||
if (text.includes("寿命")) return "寿命";
|
||||
if (text.includes("ストレス")) return "過ストレス";
|
||||
if (text.includes("疲れ")) return "衰弱";
|
||||
if (text.includes("つつかれ")) return "つつき";
|
||||
return text.length > 10 ? `${text.slice(0, 10)}…` : text;
|
||||
const normalized = this.normalizeDeathReason ? this.normalizeDeathReason(text) : text;
|
||||
return normalized.length > 12 ? `${normalized.slice(0, 12)}\u2026` : normalized;
|
||||
}
|
||||
|
||||
variantSprite(category) {
|
||||
|
|
@ -452,8 +556,8 @@ class Tarinai {
|
|||
this.zunchiDiseaseCooldown = 12;
|
||||
this.fearTimer = Math.max(this.fearTimer || 0, 0.35);
|
||||
this.surpriseTimer = Math.max(this.surpriseTimer || 0, 0.45);
|
||||
this.thought = "ずんち病にかかった";
|
||||
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.20, "うぅ…", "rgba(38,82,38,0.82)");
|
||||
this.thought = "\u305a\u3093\u3061\u75c5\u306b\u304b\u304b\u3063\u305f";
|
||||
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.20, "\u3046\u3045\u2026", "rgba(38,82,38,0.82)");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -464,8 +568,8 @@ class Tarinai {
|
|||
this.zunchiDiseaseCooldown = 18;
|
||||
this.zunchiStain = clamp((this.zunchiStain || 0) - 35, 0, 100);
|
||||
this.stress = clamp(this.stress - 8, 0, 130);
|
||||
this.thought = reason || "ずんち病が治まった";
|
||||
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.18, "はう", "rgba(62,104,62,0.78)");
|
||||
this.thought = reason || "\u305a\u3093\u3061\u75c5\u304c\u6cbb\u307e\u3063\u305f";
|
||||
this.world?.spawnBubble?.(this.x, this.y - this.radius * 1.18, "\u306f\u3046", "rgba(62,104,62,0.78)");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -491,16 +595,16 @@ class Tarinai {
|
|||
}
|
||||
if (!this.zunchiDisease && this.zunchiStain > 82 && Math.random() < dt * clamp((this.zunchiStain - 82) / 18, 0, 1) * 0.045) {
|
||||
this.infectZunchiDisease(null, true);
|
||||
this.world?.log?.(`${this.name}はずんち病を発症した。`, "accident");
|
||||
this.world?.log?.(`${this.name}\u306f\u305a\u3093\u3061\u75c5\u3092\u767a\u75c7\u3057\u305f\u3002`, "accident");
|
||||
}
|
||||
if (this.zunchiDisease) {
|
||||
this.zunchiDiseaseSeverity = clamp((this.zunchiDiseaseSeverity || 0.7) + dt * 0.002, 0, 1.25);
|
||||
this.stress = clamp(this.stress + dt * 0.22 * this.zunchiDiseaseSeverity, 0, 130);
|
||||
this.zunchiDiseaseSeverity = clamp((this.zunchiDiseaseSeverity || 0.7) + dt * 0.004, 0, 1.25);
|
||||
this.stress = clamp(this.stress + dt * (0.28 + 0.18 * this.zunchiDiseaseSeverity), 0, 130);
|
||||
this.energy = clamp(this.energy - dt * 0.035 * this.zunchiDiseaseSeverity, 0, 100);
|
||||
if (this.energy > 82) this.zunchiDiseaseSeverity = Math.max(0, this.zunchiDiseaseSeverity - dt * (this.energy > 94 ? 0.020 : 0.010));
|
||||
this.emitZunchiDiseaseEffect(dt);
|
||||
if (this.zunchiDiseaseSeverity <= 0.04) {
|
||||
if (this.recoverZunchiDisease("体力が戻り、ずんち病が治まった")) this.world?.log?.(`${this.name}のずんち病が治まった。`, "accident");
|
||||
if (this.recoverZunchiDisease("\u4f53\u529b\u304c\u623b\u308a\u3001\u305a\u3093\u3061\u75c5\u304c\u6cbb\u307e\u3063\u305f")) this.world?.log?.(`${this.name}\u306e\u305a\u3093\u3061\u75c5\u304c\u6cbb\u307e\u3063\u305f\u3002`, "accident");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -521,7 +625,7 @@ class Tarinai {
|
|||
if (this.isZunchiSlave) return false;
|
||||
this.formerName = this.formerName || this.name;
|
||||
this.isZunchiSlave = true;
|
||||
this.name = "ずんちどれい";
|
||||
this.name = "\u305a\u3093\u3061\u3069\u308c\u3044";
|
||||
this.birthRitualTimer = 0;
|
||||
this.birthPartnerId = null;
|
||||
this.birthRitualLeader = false;
|
||||
|
|
@ -529,7 +633,7 @@ class Tarinai {
|
|||
this.target = null;
|
||||
this.reproductionTimer = Math.max(this.reproductionTimer || 0, CONFIG.reproductionCooldown + rand(8, 18));
|
||||
this.loveMochiTimer = 0;
|
||||
this.stress = clamp(this.stress + 14, 0, 130);
|
||||
this.addStress(14, { threshold: 8 });
|
||||
this.hunger = Math.max(this.hunger, 40);
|
||||
this.visibleSpriteId = "zunchi_slave";
|
||||
this.spriteLockUntil = 0;
|
||||
|
|
@ -597,9 +701,14 @@ class Tarinai {
|
|||
this.blastSpinTimer = Math.max(0, (this.blastSpinTimer || 0) - dt);
|
||||
this.postBirthPeaceTimer = Math.max(0, (this.postBirthPeaceTimer || 0) - dt);
|
||||
this.hpBarTimer = Math.max(0, (this.hpBarTimer || 0) - dt);
|
||||
this.stressBarTimer = Math.max(0, (this.stressBarTimer || 0) - dt);
|
||||
this.updateZunchiDisease(dt);
|
||||
this.loveMochiTimer = Math.max(0, (this.loveMochiTimer || 0) - dt);
|
||||
this.fightMochiTimer = Math.max(0, (this.fightMochiTimer || 0) - dt);
|
||||
if (this.fightMochiTimer > 0.04) {
|
||||
this.stress = clamp(this.stress + dt * 0.11, 0, 130);
|
||||
if (this.state !== "fight") this.fearTimer = Math.max(this.fearTimer || 0, 0.10);
|
||||
}
|
||||
if (this.intimidateTimer <= 0.04) this.intimidateTargetId = null;
|
||||
if (this.birthRitualTimer > 0) {
|
||||
const beforeBirthTimer = this.birthRitualTimer;
|
||||
|
|
@ -713,7 +822,7 @@ class Tarinai {
|
|||
this.maintainTargetProgress(dt);
|
||||
this.move(dt);
|
||||
if (this.world.resolveFenceCollision) this.world.resolveFenceCollision(this);
|
||||
this.checkLife();
|
||||
this.checkLife(dt);
|
||||
}
|
||||
|
||||
applyEnvironment(dt) {
|
||||
|
|
@ -757,7 +866,7 @@ class Tarinai {
|
|||
}
|
||||
|
||||
sleepFurnitureLabel(target) {
|
||||
return "干草寝床";
|
||||
return "\u5e72\u8349\u5bdd\u5e8a";
|
||||
}
|
||||
|
||||
sleepSpotFor(bed) {
|
||||
|
|
@ -873,6 +982,22 @@ class Tarinai {
|
|||
? dayP >= this.sleepStart && dayP < this.sleepEnd
|
||||
: dayP >= this.sleepStart || dayP < this.sleepEnd;
|
||||
|
||||
if (this.hasFightMochiEffect() && this.fightTimer <= 0.04 && this.intimidateTimer <= 0.04 && this.defeatedTimer <= 0.04) {
|
||||
const opponent = this.world.nearestOther(this, 320);
|
||||
if (opponent) {
|
||||
const d = dist(this, opponent);
|
||||
if (d <= Math.max(48, this.radius + opponent.radius + 18)) {
|
||||
if (this.world.startForcedFight?.(this, opponent)) return;
|
||||
}
|
||||
this.state = "seek_friend";
|
||||
this.target = opponent;
|
||||
this.goodMode = "angry";
|
||||
this.thought = "\u3051\u3093\u304b\u9905\u3067\u76f8\u624b\u3092\u63a2\u3057\u3066\u3044\u308b";
|
||||
if (Math.random() < dt * 0.24) this.bubble("!", 1.4, "rgba(160,74,45,0.78)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const recentRival = this.recentFightRival ? this.recentFightRival(60, 170) : null;
|
||||
const feared = this.strongestRelation("fear");
|
||||
if (!wasSleeping && (recentRival || (feared && feared.score > 12))) {
|
||||
|
|
@ -892,12 +1017,12 @@ class Tarinai {
|
|||
if (water && !water.dead) {
|
||||
this.state = "seek_water";
|
||||
this.target = water;
|
||||
this.thought = "ずんち病で水を探している";
|
||||
this.thought = "\u305a\u3093\u3061\u75c5\u3067\u6c34\u3092\u63a2\u3057\u3066\u3044\u308b";
|
||||
return;
|
||||
}
|
||||
this.state = "zunchi_sick";
|
||||
this.target = null;
|
||||
this.thought = "ずんち病で当てもなく彷徨っている";
|
||||
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;
|
||||
}
|
||||
|
|
@ -908,7 +1033,7 @@ class Tarinai {
|
|||
this.state = "seek_food";
|
||||
this.target = specialMochi;
|
||||
this.foodReactTimer = Math.max(this.foodReactTimer, 0.34);
|
||||
this.thought = specialMochi.type === "love_mochi" ? "媚薬餅を探している" : "喧嘩餅を探している";
|
||||
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";
|
||||
return;
|
||||
}
|
||||
} else if (this.hunger > 20) {
|
||||
|
|
@ -917,7 +1042,7 @@ class Tarinai {
|
|||
this.state = "seek_food";
|
||||
this.target = zOnly;
|
||||
this.foodReactTimer = Math.max(this.foodReactTimer, 0.30);
|
||||
this.thought = "ずんちを探している";
|
||||
this.thought = "\u305a\u3093\u3061\u3092\u63a2\u3057\u3066\u3044\u308b";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -952,7 +1077,7 @@ class Tarinai {
|
|||
if (distXY(this.x, this.y, spot.x, spot.y) > this.radius + 10) {
|
||||
this.state = "seek_bed";
|
||||
this.target = bed;
|
||||
this.thought = `${this.sleepFurnitureLabel(bed)}へ向かっている`;
|
||||
this.thought = `${this.sleepFurnitureLabel(bed)}\u3078\u5411\u304b\u3063\u3066\u3044\u308b`;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -963,7 +1088,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 ? "ベッドの近くで眠っている" : "眠っている";
|
||||
this.thought = bed ? "\u30d9\u30c3\u30c9\u306e\u8fd1\u304f\u3067\u7720\u3063\u3066\u3044\u308b" : "\u7720\u3063\u3066\u3044\u308b";
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1007,7 +1132,7 @@ class Tarinai {
|
|||
this.state = "play_ball";
|
||||
this.target = ball;
|
||||
this.goodMode = "smile";
|
||||
this.thought = this.personality === "playful" ? "ボールに引き寄せられている" : "転がるものが気になっている";
|
||||
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";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1130,13 +1255,15 @@ class Tarinai {
|
|||
this.hunger -= eating * 0.82;
|
||||
this.recoverHealth(eating * 0.24);
|
||||
this.affection += eating * 0.04;
|
||||
this.stress = clamp(this.stress - eating * 0.25, 0, 130);
|
||||
if (it.type === "love_mochi") {
|
||||
this.stress = clamp(this.stress - eating * 0.25, 0, 130);
|
||||
this.loveMochiTimer = Math.max(this.loveMochiTimer || 0, 42 + rand(4, 12));
|
||||
this.bubble("♡", 2.4, "rgba(190,82,132,0.78)");
|
||||
this.bubble("\u2661", 2.4, "rgba(190,82,132,0.78)");
|
||||
this.lastMealColor = "#ff7bab";
|
||||
} else {
|
||||
this.fightMochiTimer = Math.max(this.fightMochiTimer || 0, 36 + rand(4, 10));
|
||||
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";
|
||||
}
|
||||
|
|
@ -1150,7 +1277,7 @@ class Tarinai {
|
|||
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}は${it.type === "love_mochi" ? "媚薬餅" : "喧嘩餅"}をしばらく味わった。`, "food");
|
||||
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.lastLog = this.world.time;
|
||||
}
|
||||
break;
|
||||
|
|
@ -1200,7 +1327,7 @@ class Tarinai {
|
|||
this.lastMealColor = "#6b8d3f";
|
||||
this.eatTimer = Math.max(this.eatTimer, 0.24);
|
||||
this.eatCooldown = rand(0.9, 1.4);
|
||||
this.bubble(this.isZunchiSlave ? "もぐ" : "...", 2.2, "rgba(74,91,50,0.78)");
|
||||
this.bubble(this.isZunchiSlave ? "\u3082\u3050" : "...", 2.2, "rgba(74,91,50,0.78)");
|
||||
const mouth = this.mouthPosition(it);
|
||||
this.world.spawnEatEffect(mouth.x, mouth.y, "#6b8d3f");
|
||||
if (this.world.time > this.nextEatSound) { audio.eat(); this.nextEatSound = this.world.time + 0.7; }
|
||||
|
|
@ -1215,8 +1342,8 @@ class Tarinai {
|
|||
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("水でずんち病が治まった")) {
|
||||
this.world.log(`${this.name}は水でずんち病が治まった。`, "accident");
|
||||
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;
|
||||
|
|
@ -1277,11 +1404,16 @@ class Tarinai {
|
|||
if (d < 0.01 || d > 74) continue;
|
||||
closeCount += 1;
|
||||
|
||||
const fightMochiContact = this.hasFightMochiEffect() || o.hasFightMochiEffect();
|
||||
if (fightMochiContact && d <= Math.max(42, this.radius + o.radius + 16)) {
|
||||
if (this.world.startForcedFight?.(this, o)) continue;
|
||||
}
|
||||
|
||||
if (this.zunchiDisease && !o.zunchiDisease && (o.zunchiDiseaseCooldown || 0) <= 0 && d < 54 && Math.random() < dt * 0.065 * clamp(1 - d / 58, 0.12, 1)) {
|
||||
if (o.infectZunchiDisease(this)) this.world.log(`${this.name}のずんち病が${o.name}にうつった。`, "accident");
|
||||
if (o.infectZunchiDisease(this)) this.world.log(`${this.name}\u306e\u305a\u3093\u3061\u75c5\u304c${o.name}\u306b\u3046\u3064\u3063\u305f\u3002`, "accident");
|
||||
}
|
||||
if (o.zunchiDisease && !this.zunchiDisease && (this.zunchiDiseaseCooldown || 0) <= 0 && d < 54 && Math.random() < dt * 0.065 * clamp(1 - d / 58, 0.12, 1)) {
|
||||
if (this.infectZunchiDisease(o)) this.world.log(`${o.name}のずんち病が${this.name}にうつった。`, "accident");
|
||||
if (this.infectZunchiDisease(o)) this.world.log(`${o.name}\u306e\u305a\u3093\u3061\u75c5\u304c${this.name}\u306b\u3046\u3064\u3063\u305f\u3002`, "accident");
|
||||
}
|
||||
|
||||
const pairBond = this.world.areCoParents?.(this, o);
|
||||
|
|
@ -1326,8 +1458,8 @@ class Tarinai {
|
|||
o.vy += ny * rand(34, 64);
|
||||
this.stress = clamp(this.stress + 1.6, 0, 130);
|
||||
o.stress = clamp(o.stress + 2.8, 0, 130);
|
||||
this.damage(1.2);
|
||||
o.damage(1.6);
|
||||
this.damage(1.2, "\u55a7\u5629");
|
||||
o.damage(1.6, "\u55a7\u5629");
|
||||
this.hurtTimer = Math.max(this.hurtTimer, 1.4);
|
||||
o.hurtTimer = Math.max(o.hurtTimer, 1.8);
|
||||
this.adjustRelation(o, -0.16, 0.12, "fight");
|
||||
|
|
@ -1385,7 +1517,7 @@ class Tarinai {
|
|||
|
||||
move(dt) {
|
||||
let ax = 0, ay = 0;
|
||||
const baseSpeed = 46.5 * 1.5 * this.trait.speed * (this.energy < 20 ? 0.42 : 1) * (this.fallTimer > 0 ? 0.35 : 1);
|
||||
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;
|
||||
|
|
@ -1542,19 +1674,32 @@ class Tarinai {
|
|||
if (this.y > maxY) { this.y = maxY; this.vy = -Math.abs(this.vy) * 0.5; this.wanderAngle = rand(-2.2, -0.7); this.surpriseTimer = 0.15; }
|
||||
}
|
||||
|
||||
checkLife() {
|
||||
checkLife(dt = 0.016) {
|
||||
let reason = "";
|
||||
if (this.hunger >= 108) reason = "\u98df\u3079\u7269\u304c\u305f\u308a\u306a\u3044";
|
||||
if (this.energy <= 0) {
|
||||
reason = this.dominantDeathCause("", { lowHealth: true }) || "\u4e8b\u6545";
|
||||
}
|
||||
else if (this.hunger >= 108) reason = "\u98e2\u9913";
|
||||
else if (this.age >= this.lifeSpan) reason = "\u5bff\u547d";
|
||||
else if (this.stress >= 124) reason = "\u30b9\u30c8\u30ec\u30b9\u304c\u591a\u3059\u304e\u308b";
|
||||
else if (this.energy <= 0 && this.hunger > 90) reason = "\u75b2\u308c\u3068\u7a7a\u8179\u304c\u91cd\u306a\u3063\u305f";
|
||||
else if (this.stress >= 124) {
|
||||
reason = this.dominantDeathCause("\u30b9\u30c8\u30ec\u30b9\u904e\u591a", { stressDeath: true }) || "\u30b9\u30c8\u30ec\u30b9\u904e\u591a";
|
||||
}
|
||||
else if (this.mood <= 20) {
|
||||
const lowMood = clamp((20 - this.mood) / 20, 0, 1);
|
||||
const bodyLoad = clamp((this.hunger - 72) / 42, 0, 1) * 0.018 + clamp((28 - this.energy) / 28, 0, 1) * 0.020 + clamp((this.stress - 86) / 44, 0, 1) * 0.022;
|
||||
const deathRisk = 0.010 + lowMood * 0.060 + bodyLoad;
|
||||
if (Math.random() < Math.max(0, dt) * deathRisk) {
|
||||
reason = this.dominantDeathCause("", { lowMood: true }) || "\u30b9\u30c8\u30ec\u30b9\u904e\u591a";
|
||||
}
|
||||
}
|
||||
if (reason) this.die(reason);
|
||||
}
|
||||
|
||||
die(reason) {
|
||||
if (this.dead) return;
|
||||
const finalReason = this.normalizeDeathReason ? this.normalizeDeathReason(reason) : (reason || "\u4e8b\u6545");
|
||||
this.dead = true;
|
||||
this.deathReason = reason;
|
||||
this.deathReason = finalReason;
|
||||
this.world.deadCount += 1;
|
||||
this.world.items.push(new Item("trace", this.x, this.y));
|
||||
this.world.items.push(new Item("splat", this.x, this.y));
|
||||
|
|
@ -1562,9 +1707,9 @@ class Tarinai {
|
|||
const spot = this.world.findGrassPlantingSpot(this.x, this.y, { allowOriginal: false, minRadius: 26, maxRadius: 76 });
|
||||
if (spot) this.world.items.push(new Item("grass", spot.x, spot.y));
|
||||
}
|
||||
this.world.markDead(this, reason);
|
||||
this.world.markDead(this, finalReason);
|
||||
audio.death();
|
||||
this.world.log(`${this.name}\u306f\u9759\u304b\u306a\u75d5\u8de1\u3092\u6b8b\u3057\u305f\u3002\u7406\u7531: ${reason}`, "death");
|
||||
this.world.log(`${this.name}\u306f\u9759\u304b\u306a\u75d5\u8de1\u3092\u6b8b\u3057\u305f\u3002\u6b7b\u56e0: ${finalReason}`, "death");
|
||||
if (this.world.selected === this) this.world.selected = null;
|
||||
}
|
||||
|
||||
|
|
@ -1572,8 +1717,8 @@ class Tarinai {
|
|||
if (this.dead) return;
|
||||
const lightState = lighting || getLightingState(this.world);
|
||||
const sprite = this.spriteId();
|
||||
const img = images.get(sprite) || images.get(this.type);
|
||||
const metrics = getImageMetrics(sprite) || getImageMetrics(this.type);
|
||||
const img = typeof getRenderableImage === "function" ? getRenderableImage(sprite, "smile") : (images.get(sprite) || images.get(this.type));
|
||||
const metrics = getImageMetrics(sprite) || getImageMetrics(this.type) || getImageMetrics("smile");
|
||||
const w = metrics?.w || 512;
|
||||
const h = metrics?.h || 512;
|
||||
const moveSpeed = Math.hypot(this.vx, this.vy);
|
||||
|
|
@ -1623,12 +1768,16 @@ class Tarinai {
|
|||
|
||||
const shadow = projectedShadowParams(lightState, 0.95 + this.scale * 0.8);
|
||||
const shadowContactY = this.y + bob + (drawH / squish) * 0.30;
|
||||
drawImageProjectedShadow(ctx, img, this.x, shadowContactY, drawW * squish, drawH / squish, shadow, {
|
||||
faceRight,
|
||||
alpha: shadow.alpha * (this.state === "sleep" ? 1.10 : 0.92),
|
||||
widthScale: this.state === "sleep" ? 1.12 : 1,
|
||||
heightScale: this.state === "sleep" ? 0.88 : 1,
|
||||
});
|
||||
if (img) {
|
||||
drawImageProjectedShadow(ctx, img, this.x, shadowContactY, drawW * squish, drawH / squish, shadow, {
|
||||
faceRight,
|
||||
alpha: shadow.alpha * (this.state === "sleep" ? 1.10 : 0.92),
|
||||
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 });
|
||||
}
|
||||
drawCreatureGlow(ctx, this, drawW, drawH, lightState);
|
||||
if (this.birthRitualTimer > 0.04 && this.birthRitualLeader) {
|
||||
const partner = this.world.tarinai.find(o => o.id === this.birthPartnerId && !o.dead);
|
||||
|
|
@ -1722,9 +1871,21 @@ class Tarinai {
|
|||
const renderScale = typeof SPRITE_CACHE_SCALE === "number" ? SPRITE_CACHE_SCALE : 2.0;
|
||||
const cacheW = Math.max(1, Math.round((dw * renderScale) / 8) * 8);
|
||||
const cacheH = Math.max(1, Math.round((dh * renderScale) / 8) * 8);
|
||||
const spriteCanvas = typeof getCachedSpriteCanvas === "function" ? getCachedSpriteCanvas(sprite, img, cacheW, cacheH) : img;
|
||||
ctx.drawImage(spriteCanvas, -dw / 2, -dh / 2, dw, dh);
|
||||
if ((this.zunchiStain || 0) > 3 && typeof getCachedStainOverlayCanvas === "function") {
|
||||
if (img) {
|
||||
const spriteCanvas = typeof getCachedSpriteCanvas === "function" ? getCachedSpriteCanvas(sprite, img, cacheW, cacheH) : img;
|
||||
ctx.drawImage(spriteCanvas, -dw / 2, -dh / 2, dw, dh);
|
||||
} else {
|
||||
ctx.save();
|
||||
ctx.fillStyle = this.stress > 62 ? "#f1c3a8" : "#f4ead6";
|
||||
ctx.strokeStyle = "rgba(92,71,49,0.48)";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, 0, dw * 0.42, dh * 0.36, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
if (img && (this.zunchiStain || 0) > 3 && typeof getCachedStainOverlayCanvas === "function") {
|
||||
const stainCanvas = getCachedStainOverlayCanvas(sprite, img, cacheW, cacheH, clamp((this.zunchiStain || 0) / 100, 0, 1), this.zunchiStainSeed || 0.5);
|
||||
if (stainCanvas) {
|
||||
ctx.save();
|
||||
|
|
@ -1733,15 +1894,6 @@ class Tarinai {
|
|||
ctx.restore();
|
||||
}
|
||||
}
|
||||
if (this.zunchiDisease) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.12 + clamp(this.zunchiDiseaseSeverity || 0, 0, 1) * 0.10;
|
||||
ctx.fillStyle = "rgba(9, 74, 23, 0.75)";
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, drawH * 0.05, drawW * 0.56, drawH * 0.38, Math.sin(t + this.age) * 0.12, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
drawDawnRimLight(ctx, dw, dh, lightState);
|
||||
if (faceRight) ctx.scale(-1, 1);
|
||||
|
||||
|
|
@ -1758,24 +1910,35 @@ class Tarinai {
|
|||
|
||||
ctx.restore();
|
||||
|
||||
if ((this.hpBarTimer || 0) > 0) {
|
||||
const hpActive = (this.hpBarTimer || 0) > 0;
|
||||
const stressActive = (this.stressBarTimer || 0) > 0;
|
||||
if (hpActive || stressActive) {
|
||||
const bw = Math.max(28, this.radius * 2.35);
|
||||
const bh = 5;
|
||||
const x = this.x - bw / 2;
|
||||
const y = this.y - drawH * 0.55 - 16;
|
||||
const pct = clamp(this.energy / 100, 0, 1);
|
||||
ctx.save();
|
||||
ctx.globalAlpha = clamp((this.hpBarTimer || 0) / 0.7, 0.35, 1);
|
||||
ctx.fillStyle = "rgba(255,252,242,0.86)";
|
||||
roundedRect(ctx, x - 2, y - 2, bw + 4, bh + 4, 5);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = "rgba(72,58,45,0.22)";
|
||||
roundedRect(ctx, x, y, bw, bh, 4);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = pct > 0.45 ? "rgba(118,190,80,0.88)" : (pct > 0.20 ? "rgba(232,172,68,0.90)" : "rgba(218,82,70,0.92)");
|
||||
roundedRect(ctx, x, y, bw * pct, bh, 4);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
const baseY = this.y - drawH * 0.55 - 16;
|
||||
const drawMeter = (y, pct, fill, alpha) => {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = alpha;
|
||||
ctx.fillStyle = "rgba(255,252,242,0.86)";
|
||||
roundedRect(ctx, x - 2, y - 2, bw + 4, bh + 4, 5);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = "rgba(72,58,45,0.22)";
|
||||
roundedRect(ctx, x, y, bw, bh, 4);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = fill;
|
||||
roundedRect(ctx, x, y, bw * pct, bh, 4);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
};
|
||||
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));
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue