"use strict"; (function (global) { const Tarinai = global.Tarinai; if (!Tarinai) throw new Error("Tarinai is not available for mixin: tarinai_render.js"); Object.defineProperties(Tarinai.prototype, Object.getOwnPropertyDescriptors({ draw(ctx, t, lighting = null) { if (this.dead) return; const lightState = lighting || getLightingState(this.world); const sprite = this.spriteId(); 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); const gait = this.state === "sleep" ? 0.8 : clamp(moveSpeed / 42, 0.2, 1.8); const breathing = Math.sin(t * (this.state === "sleep" ? 1.1 : 2.3) + this.age * 0.18); const headbuttPulse = this.state === "fight" ? Math.max(0, Math.sin(t * 18 + this.age * 0.37)) : 0; const eatingShake = (this.eatTimer > 0.02 || this.grassEatTimer > 0) ? Math.sin(t * 54 + this.age) * clamp(Math.max(this.eatTimer / 0.32, this.grassEatTimer / 0.22), 0, 1) : 0; const fallPose = this.fallTimer > 0 ? clamp(this.fallTimer / Math.max(this.fallMax || 1, 0.01), 0, 1) : 0; const fallLift = Math.sin(fallPose * Math.PI) * this.radius * 0.20; const intimidateShake = this.intimidateTimer > 0.04 ? Math.sin(t * 44 + this.age) * this.radius * 0.09 : 0; const fearShakeY = this.intimidatedTimer > 0.04 ? Math.sin(t * 52 + this.age * 0.7) * this.radius * 0.11 : 0; const birthShake = this.birthRitualTimer > 0.04 ? Math.sin(t * 34 + this.age) * this.radius * 0.07 : 0; 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 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; const visualScale = this.effectiveScale ? this.effectiveScale() : this.scale; let drawW = w * visualScale * 0.30; let drawH = h * visualScale * 0.30; if (insideCardboard) { drawW *= 0.72; drawH *= 0.72; } if (sprite === "stretch") { drawW *= 1.16; drawH *= 1.24; } if (sprite === "intimidate") { drawW *= 1.32; drawH *= 1.32; } if (sprite === "birth_ritual") { drawW *= 0.64; drawH *= 0.64; } if (fallPose > 0) { drawW *= 1 + fallPose * 0.08; drawH *= 1 - fallPose * 0.07; } const squish = this.state === "sleep" ? 1 + breathing * 0.018 : 1 + Math.sin(t * (4.5 + gait * 1.6) + this.age) * (0.012 + gait * 0.018) + headbuttPulse * 0.03; const faceRight = this.state === "sleep" ? this.sleepFacing > 0 : this.facingDir() > 0; const angle = (faceRight ? 1 : -1) * (Math.PI / 12) + motionTilt; const nestHideAlpha = this.nestFade ? clamp(1 - this.nestFade, 0, 1) : 1; if (nestHideAlpha <= 0.01) return; const shadow = projectedShadowParams(lightState, 0.95 + visualScale * 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) * 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 * nestHideAlpha }); } drawCreatureGlow(ctx, this, drawW, drawH, lightState); if (this.powerItemMode === "protein" || this.powerItemMode === "niteropu") { ctx.save(); const up = this.powerItemMode === "protein"; const baseColor = up ? "rgba(224,54,40,0.92)" : "rgba(116,80,190,0.88)"; const glowColor = up ? "rgba(255,72,44,0.45)" : "rgba(118,80,190,0.38)"; ctx.shadowColor = glowColor; ctx.shadowBlur = up ? 10 : 7; const particleCount = up ? 10 : 12; for (let i = 0; i < particleCount; i++) { const phase = (t * (up ? 0.92 : 0.78) + stableUnit(`${this.id || this.familyKey}:power:${i}`, "phase") * 9.7) % 1; const side = stableUnit(`${this.id || this.familyKey}:power:${i}`, "side") * 2 - 1; const drift = Math.sin(t * 1.7 + i * 0.91) * drawW * 0.06; const px = this.x + side * drawW * randSeed(i + this.seed, 0.12, 0.45) + drift; const py = up ? this.y + drawH * 0.38 - phase * drawH * 0.94 : this.y - drawH * 0.46 + phase * drawH * 0.98; const fade = up ? Math.sin(phase * Math.PI) : (0.42 + 0.58 * Math.sin(phase * Math.PI)); ctx.globalAlpha = fade * (up ? 0.78 : 0.70); ctx.fillStyle = baseColor; ctx.beginPath(); ctx.ellipse(px, py, Math.max(2, drawW * randSeed(i + 83, 0.025, 0.045)), Math.max(2, drawW * randSeed(i + 137, 0.020, 0.040)), randSeed(i + 223, -0.6, 0.6), 0, Math.PI * 2); ctx.fill(); } ctx.globalAlpha = up ? 0.10 : 0.08; ctx.strokeStyle = baseColor; ctx.lineWidth = up ? 1.8 : 1.4; ctx.beginPath(); ctx.ellipse(this.x, this.y + drawH * 0.08, drawW * 0.58, drawH * 0.44, 0, 0, Math.PI * 2); ctx.stroke(); ctx.restore(); } if (this.birthRitualTimer > 0.04 && this.birthRitualLeader) { const partner = this.world.tarinai.find(o => o.id === this.birthPartnerId && !o.dead); const hx = partner ? (this.x + partner.x) / 2 : this.x; const hy = partner ? Math.min(this.y, partner.y) - Math.max(drawH, partner.radius * 2.6) * 0.50 : this.y - drawH * 0.60; const pulse = 1 + Math.sin(t * 5.5 + this.age) * 0.10; const size = Math.max(10, this.radius * 0.62) * pulse; drawHeartShape(ctx, hx, hy, size, { fill: "rgba(240,91,135,0.90)", stroke: "rgba(255,252,246,0.92)" }); } ctx.save(); ctx.translate(this.x + intimidateShake, this.y + bob + fearShakeY); 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: "\u53cb\u9054" }, 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 * nestHideAlpha; 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(); 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.74 + jumpPulse * 0.10), drawH * (0.56 + jumpPulse * 0.08), 0, 0, Math.PI * 2); ctx.stroke(); 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.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(); ctx.globalAlpha = 0.28; ctx.strokeStyle = this.state === "cursor_enemy" ? "rgba(180, 92, 92, 0.70)" : "rgba(255, 250, 220, 0.78)"; ctx.lineWidth = 1.4; ctx.beginPath(); ctx.ellipse(0, drawH * 0.14, drawW * 0.58, drawH * 0.43, 0, 0, Math.PI * 2); ctx.stroke(); 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); ctx.strokeStyle = this.lastMealColor || "#f7cf67"; ctx.lineWidth = 1.6; ctx.beginPath(); ctx.arc(0, -drawH * 0.02, drawW * 0.22 + (1 - this.eatTimer / 0.32) * 5, 0, Math.PI * 2); ctx.stroke(); ctx.restore(); } if (this.pokeFlashTimer > 0.03) { ctx.save(); 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); ctx.fill(); ctx.restore(); } if ((this.world.performanceLevel ? this.world.performanceLevel() : 0) < 2 && (this.goodMode === "smile" || this.state === "cursor_friend") && this.mood > 58 && this.stress < 44 && this.state !== "sleep") { ctx.save(); 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 > 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; const py = -drawH * (0.22 + (i % 2) * 0.06) + Math.sin(t * 2.9 + this.age * 0.2 + i) * drawH * (0.08 + cursorLove * 0.02); if (this.state === "cursor_friend") { const s = Math.max(5, drawW * (0.070 + (i % 3) * 0.012 + cursorLove * 0.01)); drawHeartShape(ctx, px, py, s, { fill: "rgba(240,91,135,0.90)", stroke: "rgba(255,252,246,0.92)", alpha: 1, rotation: Math.sin(t * 3 + i) * 0.18 }); } else { ctx.beginPath(); ctx.arc(px, py, drawW * 0.045, 0, Math.PI * 2); ctx.fill(); } } ctx.restore(); } if (faceRight) ctx.scale(-1, 1); const dw = drawW * squish; const dh = drawH / squish; const brightness = clamp(0.82 + lightState.light * 0.30 + lightState.dawnStrength * 0.08 + lightState.goldenStrength * 0.06 + lightState.noonStrength * 0.04, 0.80, 1.17); const saturation = clamp(0.86 + lightState.light * 0.22 + lightState.goldenStrength * 0.10 - lightState.nightStrength * 0.04, 0.82, 1.14); 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); 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(); ctx.globalAlpha = clamp((this.zunchiStain || 0) / 100, 0.10, 0.88); ctx.drawImage(stainCanvas, -dw / 2, -dh / 2, dw, dh); ctx.restore(); } } if (sprite === "zunchi_slave") { const zImg = typeof getRenderableImage === "function" ? getRenderableImage("zunchi", "zunchi") : (images && images.get ? images.get("zunchi") : null); if (zImg) { ctx.save(); ctx.shadowColor = "rgba(46, 36, 24, 0.28)"; ctx.shadowBlur = 5; ctx.shadowOffsetY = Math.max(1, drawH * 0.012); const zw = Math.max(12, drawW * 0.30); const zh = zw * 0.74; ctx.drawImage(zImg, -zw * 0.50, -dh * 0.48 - zh * 0.25, zw, zh); ctx.restore(); } } drawDawnRimLight(ctx, dw, dh, lightState); if (faceRight) ctx.scale(-1, 1); if ((this.world.performanceLevel ? this.world.performanceLevel() : 0) < 3 && (this.hunger > 90 || this.loneliness > 88 || this.energy < 16)) { ctx.save(); ctx.globalAlpha = 0.80; ctx.font = `${Math.max(10, drawW * 0.14)}px ui-rounded, sans-serif`; ctx.textAlign = "center"; ctx.fillStyle = "rgba(42,36,29,0.72)"; const mark = this.hunger > 90 ? "..." : (this.energy < 16 ? "Z" : "?"); ctx.fillText(mark, 0, -drawH * 0.72); ctx.restore(); } ctx.restore(); if (this.favorite) { ctx.save(); ctx.globalAlpha = nestHideAlpha; 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) { const bw = Math.max(28, this.radius * 2.35); const bh = 5; const x = this.x - bw / 2; 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, 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 - (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; } })); })(typeof window !== "undefined" ? window : globalThis);