This commit is contained in:
33333-33333 2026-06-24 18:49:17 +09:00
commit d875df8600
22 changed files with 625 additions and 496 deletions

View file

@ -10,9 +10,31 @@
const quality = this.world.performanceQuality?.() || {};
const effectDensity = clamp(Number(quality.effectDensity || 1) || 1, 0.12, 1);
const showDecor = effectDensity > 0.45 || this.world.selected === this;
if (this.state === "sunbath" || (this.sunbathTimer || 0) > 0.04) this.preloadSunbathSprites?.();
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 sunbathSprite = String(sprite || "").startsWith("sunbath");
const sunbathCandidates = sunbathSprite && this.sunbathSpriteCandidates ? this.sunbathSpriteCandidates() : [];
if (sunbathSprite) {
for (const id of sunbathCandidates) {
if (typeof ensureImage === "function") ensureImage(id);
else if (globalThis.TarinaiAssets?.ensureImage) globalThis.TarinaiAssets.ensureImage(id);
}
}
const spriteFallback = sunbathSprite ? "" : "smile";
let renderSprite = sprite;
let img = typeof getRenderableImage === "function" ? getRenderableImage(sprite, spriteFallback) : (images.get(sprite) || images.get(spriteFallback) || images.get(this.type));
if (sunbathSprite) {
const ready = (id) => typeof isImageReady === "function" ? isImageReady(id) : !!(images?.get?.(id)?.complete || images?.get?.(id)?.naturalWidth || images?.get?.(id)?.width);
if (!ready(sprite)) {
const fallbackSunbath = sunbathCandidates.find(id => ready(id));
if (fallbackSunbath) {
renderSprite = fallbackSunbath;
img = images.get(fallbackSunbath);
}
}
if (!img) img = typeof getRenderableImage === "function" ? getRenderableImage("smile", "smile") : (images.get("smile") || images.get(this.type));
}
const metrics = getImageMetrics(renderSprite) || 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);
@ -265,7 +287,19 @@
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;
let spriteCacheKey = sprite;
try {
if (typeof images !== "undefined" && images.get?.(sprite) !== img) {
let fallbackId = "fallback";
for (const [imageId, imageObj] of images) {
if (imageObj === img) { fallbackId = imageId; break; }
}
spriteCacheKey = `${sprite}:fallback:${fallbackId}`;
}
} catch (err) {
spriteCacheKey = sprite;
}
const spriteCanvas = sunbathSprite ? img : (typeof getCachedSpriteCanvas === "function" ? getCachedSpriteCanvas(spriteCacheKey, img, cacheW, cacheH) : img);
ctx.drawImage(spriteCanvas, -dw / 2, -dh / 2, dw, dh);
} else {
ctx.save();
@ -279,7 +313,7 @@
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);
const stainCanvas = getCachedStainOverlayCanvas(renderSprite || 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);
@ -364,6 +398,87 @@
}
},
drawSunbathSpriteDirect(ctx, t, lightState, showDecor = true) {
return false;
const ids = this.sunbathSpriteCandidates ? this.sunbathSpriteCandidates() : ["sunbath_1", "sunbath_2", "sunbath_3"];
const candidates = (Array.isArray(ids) && ids.length ? ids : ["sunbath_1", "sunbath_2", "sunbath_3"]).filter(id => String(id || "").startsWith("sunbath"));
const now = Number(this.world?.time ?? t ?? 0) || 0;
const frameKey = Math.floor(now / 0.5);
if (this._sunbathRenderFrameKey !== frameKey || !candidates.includes(this._sunbathRenderSpriteId)) {
const prev = this._sunbathRenderSpriteId || this.sunbathSpriteVariant || "";
const choices = candidates.length > 1 ? candidates.filter(id => id !== prev) : candidates;
this._sunbathRenderSpriteId = choices[Math.floor(Math.random() * choices.length)] || candidates[0] || "sunbath_1";
this._sunbathRenderFrameKey = frameKey;
}
const sprite = this._sunbathRenderSpriteId || this.sunbathSpriteVariant || candidates[0] || "sunbath_1";
this.sunbathSpriteVariant = sprite;
this.visibleSpriteId = sprite;
this.spriteLockUntil = now;
for (const id of candidates) {
if (typeof ensureImage === "function") ensureImage(id);
else if (globalThis.TarinaiAssets?.ensureImage) globalThis.TarinaiAssets.ensureImage(id);
}
let img = null;
const ordered = [sprite, this.sunbathSpriteVariant, ...candidates, "smile"];
const seen = new Set();
for (const id of ordered) {
if (!id || seen.has(id)) continue;
seen.add(id);
const next = typeof getRenderableImage === "function"
? getRenderableImage(id, id === "smile" ? "smile" : "smile")
: (typeof images !== "undefined" ? (images.get(id) || images.get("smile")) : null);
const ready = next && (next.complete || next.naturalWidth || next.width);
if (ready) { img = next; break; }
}
const metrics = (typeof getImageMetrics === "function" ? (getImageMetrics(sprite) || getImageMetrics(img) || getImageMetrics("smile")) : null) || {};
const w = metrics.w || img?.naturalWidth || img?.width || 512;
const h = metrics.h || img?.naturalHeight || img?.height || 468;
const visualScale = this.effectiveScale ? this.effectiveScale() : this.scale;
const breathing = Math.sin(t * 1.4 + this.age * 0.18);
const bob = breathing * 0.55;
const drawW = w * visualScale * 0.30;
const drawH = h * visualScale * 0.30;
const squish = 1 + breathing * 0.006;
const dw = drawW * squish;
const dh = drawH / squish;
const faceRight = this.facingDir ? this.facingDir() > 0 : (this.facing || 1) > 0;
const nestHideAlpha = this.nestFade ? clamp(1 - this.nestFade, 0, 1) : 1;
if (nestHideAlpha <= 0.01) return true;
const shadow = projectedShadowParams(lightState, 0.95 + visualScale * 0.8);
const shadowContactY = this.y + bob + dh * 0.30;
if (showDecor && img) {
drawImageProjectedShadow(ctx, img, this.x, shadowContactY, dw, dh, shadow, {
faceRight,
alpha: shadow.alpha * 0.92 * nestHideAlpha,
});
} else if (showDecor) {
drawProjectedShadow(ctx, this.x, shadowContactY, drawW * 0.52, drawH * 0.13, { ...shadow, alpha: shadow.alpha * 0.82 * nestHideAlpha });
}
if (showDecor) drawCreatureGlow(ctx, this, drawW, drawH, lightState);
ctx.save();
ctx.translate(this.x, this.y + bob);
ctx.rotate((faceRight ? 1 : -1) * (Math.PI / 18));
if (faceRight) ctx.scale(-1, 1);
ctx.globalAlpha = nestHideAlpha;
if (img) {
ctx.drawImage(img, -dw / 2, -dh / 2, dw, dh);
} else {
ctx.fillStyle = "#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();
return true;
},
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;