lag r
This commit is contained in:
parent
d875df8600
commit
2741b93dea
31 changed files with 751 additions and 546 deletions
|
|
@ -4,14 +4,145 @@
|
|||
const Tarinai = global.Tarinai;
|
||||
if (!Tarinai) throw new Error("Tarinai is not available for mixin: tarinai_render.js");
|
||||
Object.defineProperties(Tarinai.prototype, Object.getOwnPropertyDescriptors({
|
||||
drawSunbathPose(ctx, t, lighting = null) {
|
||||
if (this.dead) return true;
|
||||
const active = this.state === "sunbath" || (this.sunbathTimer || 0) > 0.04;
|
||||
if (!active) return false;
|
||||
|
||||
this.preloadSunbathSprites?.();
|
||||
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 ready = (id) => typeof isImageReady === "function"
|
||||
? isImageReady(id)
|
||||
: !!(typeof images !== "undefined" && images?.get?.(id)?.complete && (images?.get?.(id)?.naturalWidth || images?.get?.(id)?.width));
|
||||
const imageFor = (id) => {
|
||||
if (!id) return null;
|
||||
if (typeof getRenderableImage === "function") return getRenderableImage(id, "");
|
||||
return (typeof images !== "undefined" ? images.get?.(id) : globalThis.TarinaiAssets?.images?.get?.(id)) || null;
|
||||
};
|
||||
|
||||
let requested = this.sunbathSpriteId ? this.sunbathSpriteId() : this.normalizeSunbathSpriteId?.(this.sunbathSpriteVariant);
|
||||
if (!requested || !candidates.includes(requested)) requested = candidates[0] || "sunbath_1";
|
||||
|
||||
const locked = this._activeSunbathSpriteId && candidates.includes(this._activeSunbathSpriteId) && ready(this._activeSunbathSpriteId)
|
||||
? this._activeSunbathSpriteId
|
||||
: "";
|
||||
const ordered = [locked, requested, this._lastRenderableSunbathSpriteId, ...candidates].filter(Boolean);
|
||||
let renderSprite = ordered.find(id => candidates.includes(id) && ready(id));
|
||||
let img = renderSprite ? imageFor(renderSprite) : null;
|
||||
|
||||
if (img && ready(renderSprite)) {
|
||||
this._activeSunbathSpriteId = renderSprite;
|
||||
this._lastRenderableSunbathSpriteId = renderSprite;
|
||||
this.sunbathSpriteVariant = renderSprite;
|
||||
this.visibleSpriteId = renderSprite;
|
||||
} else {
|
||||
const fallbackId = this._lastNonSunbathSpriteId || this.goodMode || this.type || "smile";
|
||||
renderSprite = fallbackId;
|
||||
img = typeof getRenderableImage === "function"
|
||||
? getRenderableImage(fallbackId, "smile")
|
||||
: (typeof images !== "undefined" ? (images.get?.(fallbackId) || images.get?.("smile")) : null);
|
||||
if (!img) {
|
||||
renderSprite = "smile";
|
||||
img = typeof getRenderableImage === "function"
|
||||
? getRenderableImage("smile", "smile")
|
||||
: (typeof images !== "undefined" ? images.get?.("smile") : null);
|
||||
}
|
||||
}
|
||||
|
||||
const worldW = Math.max(1, Number(this.world?.w || 1000) || 1000);
|
||||
const worldH = Math.max(1, Number(this.world?.h || 720) || 720);
|
||||
const pad = Math.max(28, CONFIG.worldPadding || 30);
|
||||
const lastX = Number.isFinite(this._lastSunbathDrawX) ? this._lastSunbathDrawX : (Number.isFinite(this._lastValidX) ? this._lastValidX : NaN);
|
||||
const lastY = Number.isFinite(this._lastSunbathDrawY) ? this._lastSunbathDrawY : (Number.isFinite(this._lastValidY) ? this._lastValidY : NaN);
|
||||
if (!Number.isFinite(this.sunbathAnchorX)) this.sunbathAnchorX = Number.isFinite(lastX) ? lastX : (Number.isFinite(this.x) ? this.x : pad);
|
||||
if (!Number.isFinite(this.sunbathAnchorY)) this.sunbathAnchorY = Number.isFinite(lastY) ? lastY : (Number.isFinite(this.y) ? this.y : pad);
|
||||
let drawX = this.sunbathAnchorX;
|
||||
let drawY = this.sunbathAnchorY;
|
||||
if (!Number.isFinite(drawX)) drawX = Number.isFinite(lastX) ? lastX : pad;
|
||||
if (!Number.isFinite(drawY)) drawY = Number.isFinite(lastY) ? lastY : pad;
|
||||
drawX = clamp(drawX, pad, Math.max(pad, worldW - pad));
|
||||
drawY = clamp(drawY, pad, Math.max(pad, worldH - pad));
|
||||
this.sunbathAnchorX = drawX;
|
||||
this.sunbathAnchorY = drawY;
|
||||
this._lastSunbathDrawX = drawX;
|
||||
this._lastSunbathDrawY = drawY;
|
||||
this._lastValidX = drawX;
|
||||
this._lastValidY = drawY;
|
||||
if (!Number.isFinite(this.x) || Math.abs(this.x - drawX) > 1.5) this.x = drawX;
|
||||
if (!Number.isFinite(this.y) || Math.abs(this.y - drawY) > 1.5) this.y = drawY;
|
||||
|
||||
const metrics = getImageMetrics(renderSprite) || getImageMetrics(img) || getImageMetrics("smile") || {};
|
||||
const w = metrics?.w || img?.naturalWidth || img?.width || 512;
|
||||
const h = metrics?.h || img?.naturalHeight || img?.height || 512;
|
||||
const visualScale = this.effectiveScale ? this.effectiveScale() : this.scale;
|
||||
const drawW = w * visualScale * 0.30;
|
||||
const drawH = h * visualScale * 0.30;
|
||||
const breathing = Math.sin(t * 1.25 + this.age * 0.18);
|
||||
const bob = breathing * 0.55;
|
||||
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 lightState = lighting || getLightingState(this.world);
|
||||
const showDecor = true || this.world.selected === this;
|
||||
if (showDecor) {
|
||||
const shadow = projectedShadowParams(lightState, 0.95 + visualScale * 0.8);
|
||||
drawProjectedShadow(ctx, drawX, drawY + drawH * 0.30, drawW * 0.58, drawH * 0.13, { ...shadow, alpha: shadow.alpha * 0.90 * nestHideAlpha });
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
ctx.globalAlpha = nestHideAlpha;
|
||||
ctx.translate(drawX, drawY + bob);
|
||||
ctx.rotate((faceRight ? 1 : -1) * Math.PI / 28);
|
||||
if (faceRight) ctx.scale(-1, 1);
|
||||
if (img) {
|
||||
ctx.drawImage(img, -drawW / 2, -drawH / 2, drawW, drawH);
|
||||
} else {
|
||||
ctx.fillStyle = "#f4ead6";
|
||||
ctx.strokeStyle = "rgba(92,71,49,0.48)";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, 0, drawW * 0.42, drawH * 0.36, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
|
||||
if (showDecor) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.18 * nestHideAlpha;
|
||||
ctx.fillStyle = lightState.goldenStrength > 0.25 ? "rgba(255, 223, 137, 0.95)" : "rgba(255, 244, 172, 0.82)";
|
||||
ctx.beginPath();
|
||||
ctx.arc(drawX - drawW * 0.28, drawY - drawH * 0.35, Math.max(3, drawW * 0.06), 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
draw(ctx, t, lighting = null) {
|
||||
if (this.dead) return;
|
||||
const sunbathActive = this.state === "sunbath" || (this.sunbathTimer || 0) > 0.04;
|
||||
if (!sunbathActive && Number.isFinite(this.x) && Number.isFinite(this.y)) {
|
||||
this._lastValidX = this.x;
|
||||
this._lastValidY = this.y;
|
||||
}
|
||||
const lightState = lighting || getLightingState(this.world);
|
||||
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();
|
||||
if (sunbathActive && this.drawSunbathPose?.(ctx, t, lightState)) return;
|
||||
if (!sunbathActive && String(this.visibleSpriteId || "").startsWith("sunbath")) {
|
||||
this.visibleSpriteId = "";
|
||||
this.spriteLockUntil = 0;
|
||||
}
|
||||
const showDecor = true || this.world.selected === this;
|
||||
if (sunbathActive) this.preloadSunbathSprites?.();
|
||||
let sprite = this.spriteId();
|
||||
if (!sunbathActive && String(sprite || "").startsWith("sunbath")) {
|
||||
this.visibleSpriteId = "";
|
||||
this.spriteLockUntil = 0;
|
||||
sprite = this.rawSpriteId ? this.rawSpriteId() : (this.goodMode || this.type || "smile");
|
||||
}
|
||||
if (!String(sprite || "").startsWith("sunbath")) this._lastNonSunbathSpriteId = sprite;
|
||||
const sunbathSprite = String(sprite || "").startsWith("sunbath");
|
||||
const sunbathCandidates = sunbathSprite && this.sunbathSpriteCandidates ? this.sunbathSpriteCandidates() : [];
|
||||
if (sunbathSprite) {
|
||||
|
|
@ -20,18 +151,28 @@
|
|||
else if (globalThis.TarinaiAssets?.ensureImage) globalThis.TarinaiAssets.ensureImage(id);
|
||||
}
|
||||
}
|
||||
const spriteFallback = sunbathSprite ? "" : "smile";
|
||||
const spriteFallback = "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));
|
||||
const imageFor = (id) => (typeof images !== "undefined" ? images.get?.(id) : globalThis.TarinaiAssets?.images?.get?.(id)) || null;
|
||||
if (ready(sprite)) {
|
||||
this._lastRenderableSunbathSpriteId = sprite;
|
||||
} else {
|
||||
const orderedFallbacks = [this._lastRenderableSunbathSpriteId, ...sunbathCandidates].filter(Boolean);
|
||||
const fallbackSunbath = orderedFallbacks.find(id => ready(id));
|
||||
if (fallbackSunbath) {
|
||||
renderSprite = fallbackSunbath;
|
||||
img = images.get(fallbackSunbath);
|
||||
img = imageFor(fallbackSunbath);
|
||||
this._lastRenderableSunbathSpriteId = fallbackSunbath;
|
||||
this.sunbathSpriteVariant = fallbackSunbath;
|
||||
}
|
||||
}
|
||||
if (!img && this._lastRenderableSunbathSpriteId && ready(this._lastRenderableSunbathSpriteId)) {
|
||||
renderSprite = this._lastRenderableSunbathSpriteId;
|
||||
img = imageFor(this._lastRenderableSunbathSpriteId);
|
||||
}
|
||||
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");
|
||||
|
|
@ -131,7 +272,7 @@
|
|||
ctx.restore();
|
||||
}
|
||||
if (this.birthRitualTimer > 0.04 && this.birthRitualLeader) {
|
||||
const partner = this.world.tarinai.find(o => o.id === this.birthPartnerId && !o.dead);
|
||||
const partner = this.world.liveTarinaiById?.(this.birthPartnerId);
|
||||
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;
|
||||
|
|
@ -287,19 +428,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) {
|
||||
let spriteCacheKey = sprite;
|
||||
let spriteCacheKey = renderSprite || sprite;
|
||||
try {
|
||||
if (typeof images !== "undefined" && images.get?.(sprite) !== img) {
|
||||
if (typeof images !== "undefined" && images.get?.(renderSprite || sprite) !== img) {
|
||||
let fallbackId = "fallback";
|
||||
for (const [imageId, imageObj] of images) {
|
||||
if (imageObj === img) { fallbackId = imageId; break; }
|
||||
}
|
||||
spriteCacheKey = `${sprite}:fallback:${fallbackId}`;
|
||||
spriteCacheKey = `${renderSprite || sprite}:fallback:${fallbackId}`;
|
||||
}
|
||||
} catch (err) {
|
||||
spriteCacheKey = sprite;
|
||||
spriteCacheKey = renderSprite || sprite;
|
||||
}
|
||||
const spriteCanvas = sunbathSprite ? img : (typeof getCachedSpriteCanvas === "function" ? getCachedSpriteCanvas(spriteCacheKey, img, cacheW, cacheH) : img);
|
||||
const spriteCanvas = typeof getCachedSpriteCanvas === "function" ? getCachedSpriteCanvas(spriteCacheKey, img, cacheW, cacheH) : img;
|
||||
ctx.drawImage(spriteCanvas, -dw / 2, -dh / 2, dw, dh);
|
||||
} else {
|
||||
ctx.save();
|
||||
|
|
@ -398,87 +539,6 @@
|
|||
}
|
||||
},
|
||||
|
||||
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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue