This commit is contained in:
33333-33333 2026-06-26 12:46:32 +09:00
commit 86226ccc94
97 changed files with 5790 additions and 2307 deletions

View file

@ -205,6 +205,14 @@ function placementPreviewFor(world) {
const influence = type === "fan" ? world.fanInfluenceShape?.(tmp) : world.magnetInfluenceShape?.(tmp);
return { type, x, y, r, rect: rawRect, influence, blocked: rectOutside(rawRect) || world.placementBlocked?.(tmp) };
}
if (type === "rotator" && world.rotatorObstacleRects) {
tmp.rotatorSpeed = Math.PI * 0.65;
tmp.rotatorThickness = 12;
tmp.rotatorSegments = [[-78, 0, 78, 0], [0, -52, 0, 52]];
const rects = world.rotatorObstacleRects(tmp) || [];
const bounds = world.rotatorBoundsAabb?.(tmp) || null;
return { type, x, y, r, rects, blocked: (bounds ? rectOutside(bounds) : circleOutside(x, y, r)) || world.placementBlocked?.(tmp) };
}
if (type === "nest_box" && world.nestBoxBaseRect) {
const rect = world.nestBoxBaseRect(tmp);
return { type, x, y, r, rect, blocked: rectOutside(rect) || world.placementBlocked?.(tmp) };
@ -219,7 +227,7 @@ function placementPreviewFor(world) {
function drawPlacementPreview(ctx, world) {
const preview = placementPreviewFor(world);
if (!preview) return;
const { type, x, y, r, rect, influence, blocked } = preview;
const { type, x, y, r, rect, rects, influence, blocked } = preview;
const pulse = 0.55 + Math.sin((world.time || 0) * 7.0) * 0.10;
ctx.save();
ctx.globalAlpha = blocked ? 0.44 : 0.72;
@ -228,20 +236,25 @@ function drawPlacementPreview(ctx, world) {
ctx.strokeStyle = blocked ? "rgba(194,70,58,0.86)" : "rgba(110,178,55,0.88)";
ctx.fillStyle = blocked ? "rgba(232,84,72,0.14)" : "rgba(190,236,92,0.15)";
if (influence) drawDeviceInfluenceShape(ctx, influence, blocked, 1.15);
if (rect) {
if (rect.oriented) {
const drawRectPreview = (rr) => {
if (!rr) return;
if (rr.oriented) {
ctx.save();
ctx.translate(rect.cx || x, rect.cy || y);
ctx.rotate(rect.angle || 0);
roundedRect(ctx, -(rect.halfW || (rect.right - rect.left) / 2), -(rect.halfH || (rect.bottom - rect.top) / 2), (rect.halfW || (rect.right - rect.left) / 2) * 2, (rect.halfH || (rect.bottom - rect.top) / 2) * 2, 8);
ctx.translate(rr.cx || x, rr.cy || y);
ctx.rotate(rr.angle || 0);
roundedRect(ctx, -(rr.halfW || (rr.right - rr.left) / 2), -(rr.halfH || (rr.bottom - rr.top) / 2), (rr.halfW || (rr.right - rr.left) / 2) * 2, (rr.halfH || (rr.bottom - rr.top) / 2) * 2, Math.min(8, Math.max(2, rr.halfH || 4)));
ctx.fill();
ctx.stroke();
ctx.restore();
} else {
roundedRect(ctx, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, 8);
roundedRect(ctx, rr.left, rr.top, rr.right - rr.left, rr.bottom - rr.top, 8);
ctx.fill();
ctx.stroke();
}
};
if (rect || (rects && rects.length)) {
if (rects && rects.length) for (const rr of rects) drawRectPreview(rr);
else drawRectPreview(rect);
if (type === "nest_box" && world.nestBoxSolidRects) {
ctx.setLineDash([3, 4]);
ctx.strokeStyle = blocked ? "rgba(194,70,58,0.38)" : "rgba(110,178,55,0.42)";
@ -271,6 +284,67 @@ function drawPlacementPreview(ctx, world) {
ctx.restore();
}
function drawOperationToolPreview(ctx, world) {
const p = world?.pointer;
if (!p?.inside) return;
if (world?.tool === "copy" && world.copyBuffer) {
const type = world.copyBuffer.type || "";
const item = world.copyPreviewItemAt?.(p.x, p.y);
if (item) {
ctx.save();
ctx.globalAlpha = 0.50;
ctx.setLineDash([7, 5]);
ctx.lineWidth = 2;
ctx.strokeStyle = "rgba(128,92,198,0.86)";
ctx.fillStyle = "rgba(150,110,220,0.12)";
const rects = world.solidObstacleRects?.(item) || [];
if (rects.length) {
for (const rr of rects) {
if (rr.oriented) {
ctx.save(); ctx.translate(rr.cx, rr.cy); ctx.rotate(rr.angle || 0);
roundedRect(ctx, -(rr.halfW || 10), -(rr.halfH || 5), (rr.halfW || 10) * 2, (rr.halfH || 5) * 2, 6);
ctx.fill(); ctx.stroke(); ctx.restore();
} else { roundedRect(ctx, rr.left, rr.top, rr.right - rr.left, rr.bottom - rr.top, 6); ctx.fill(); ctx.stroke(); }
}
} else {
const r = Math.max(10, item.r || itemRadiusFor?.(type, 16) || 16);
ctx.beginPath(); ctx.ellipse(p.x, p.y, r * 1.45, r * 0.95, 0, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
}
ctx.setLineDash([]);
ctx.beginPath(); ctx.moveTo(p.x - 9, p.y); ctx.lineTo(p.x + 9, p.y); ctx.moveTo(p.x, p.y - 9); ctx.lineTo(p.x, p.y + 9); ctx.stroke();
ctx.restore();
}
return;
}
if (world?.tool !== "area_delete") return;
const radius = world.areaDeleteRadius?.() || 138;
ctx.save();
ctx.globalAlpha = 0.42;
ctx.lineWidth = 2.2;
ctx.setLineDash([8, 7]);
ctx.strokeStyle = "rgba(218,82,70,0.82)";
ctx.fillStyle = "rgba(218,82,70,0.10)";
ctx.beginPath();
ctx.arc(p.x, p.y, radius, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.setLineDash([3, 8]);
ctx.globalAlpha = 0.34;
ctx.beginPath();
ctx.arc(p.x, p.y, radius * 0.58, 0, Math.PI * 2);
ctx.stroke();
ctx.setLineDash([]);
ctx.globalAlpha = 0.70;
ctx.beginPath();
ctx.moveTo(p.x - 10, p.y);
ctx.lineTo(p.x + 10, p.y);
ctx.moveTo(p.x, p.y - 10);
ctx.lineTo(p.x, p.y + 10);
ctx.stroke();
ctx.restore();
}
function drawCreatureGlow(ctx, tarinai, drawW, drawH, lighting) {
if (tarinai.dead || tarinai.world.selected !== tarinai) return;
ctx.save();
@ -723,8 +797,8 @@ function drawGardenBed(w, h, lighting) {
const groundType = world.groundType || "soil";
const x = 18, y = 22, bw = w - 36, bh = h - 44;
const border = activeCtx.createLinearGradient(0, y, 0, y + bh);
const borderTop = groundType === "ice" ? "#bfe6f4" : groundType === "concrete" ? "#8b8f98" : groundType === "blanket" ? "#d6a6bc" : groundType === "foot_massage" ? "#79a9cf" : fieldType === "park" ? "#6f8f55" : fieldType === "cage" ? "#8b8f98" : (lighting.goldenStrength > 0.25 ? "#bf8f5e" : "#a88c66");
const borderBottom = groundType === "ice" ? "#77b7d2" : groundType === "concrete" ? "#5f6670" : groundType === "blanket" ? "#b77d9f" : groundType === "foot_massage" ? "#4f79a3" : fieldType === "park" ? "#486d3d" : fieldType === "cage" ? "#5f6670" : (lighting.goldenStrength > 0.25 ? "#9c7043" : "#8a6f4b");
const borderTop = groundType === "ice" ? "#bfe6f4" : groundType === "concrete" ? "#8b8f98" : groundType === "blanket" ? "#d6a6bc" : groundType === "foot_massage" ? "#79a9cf" : groundType === "dirt" ? "#8fb06a" : fieldType === "park" ? "#6f8f55" : fieldType === "cage" ? "#8b8f98" : (lighting.goldenStrength > 0.25 ? "#bf8f5e" : "#a88c66");
const borderBottom = groundType === "ice" ? "#77b7d2" : groundType === "concrete" ? "#5f6670" : groundType === "blanket" ? "#b77d9f" : groundType === "foot_massage" ? "#4f79a3" : groundType === "dirt" ? "#5f7c43" : fieldType === "park" ? "#486d3d" : fieldType === "cage" ? "#5f6670" : (lighting.goldenStrength > 0.25 ? "#9c7043" : "#8a6f4b");
border.addColorStop(0, light > 0.42 ? borderTop : "#6f6c72");
border.addColorStop(1, light > 0.42 ? borderBottom : "#565a68");
activeCtx.fillStyle = border;
@ -748,6 +822,8 @@ function drawGardenBed(w, h, lighting) {
? ["#dff8ff", "#b8e7f5", "#8bcfe4"]
: groundType === "foot_massage"
? ["#d2e6f4", "#a8c8e6", "#7fa9cd"]
: groundType === "dirt"
? [lighting.goldenStrength > 0.25 ? "#d4b47d" : "#c9a46d", lighting.goldenStrength > 0.25 ? "#b89261" : "#a87948", lighting.goldenStrength > 0.25 ? "#927046" : "#7b5a38"]
: fieldType === "park"
? ["#cfe5a8", "#b8d990", "#91bd74"]
: fieldType === "cage"
@ -765,12 +841,12 @@ function drawGardenBed(w, h, lighting) {
roundedRect(activeCtx, x + 16, y + 14, bw - 32, bh - 30, 28);
activeCtx.clip();
const speckCount = groundType === "ice" ? 54 : groundType === "blanket" ? 28 : groundType === "foot_massage" ? 180 : fieldType === "park" ? 72 : fieldType === "cage" ? 34 : 46;
const speckCount = groundType === "ice" ? 54 : groundType === "blanket" ? 28 : groundType === "foot_massage" ? 180 : groundType === "dirt" ? 96 : fieldType === "park" ? 72 : fieldType === "cage" ? 34 : 46;
for (let i = 0; i < speckCount; i++) {
const px = randSeed(900 + i, x + 24, x + bw - 24);
const py = randSeed(1200 + i, y + 22, y + bh - 24);
const r = randSeed(1600 + i, 1.2, groundType === "foot_massage" ? 5.2 : 3.8);
const a = randSeed(1900 + i, groundType === "foot_massage" ? 0.065 : 0.03, groundType === "foot_massage" ? 0.18 : 0.09) * (light > 0.4 ? 1.05 + lighting.goldenStrength * 0.35 : 0.70);
const r = randSeed(1600 + i, 1.2, groundType === "foot_massage" ? 5.2 : (groundType === "dirt" ? 4.6 : 3.8));
const a = randSeed(1900 + i, groundType === "foot_massage" ? 0.065 : (groundType === "dirt" ? 0.045 : 0.03), groundType === "foot_massage" ? 0.18 : (groundType === "dirt" ? 0.13 : 0.09)) * (light > 0.4 ? 1.05 + lighting.goldenStrength * 0.35 : 0.70);
if (groundType === "foot_massage") {
const warm = i % 5 === 0;
activeCtx.fillStyle = warm
@ -780,7 +856,7 @@ function drawGardenBed(w, h, lighting) {
activeCtx.ellipse(px, py, r * randSeed(3100 + i, 1.1, 2.0), r * randSeed(3400 + i, 0.65, 1.25), randSeed(3700 + i, -1.2, 1.2), 0, Math.PI * 2);
activeCtx.fill();
} else {
activeCtx.fillStyle = groundType === "blanket" ? `rgba(255,255,255,${a * 1.35})` : (groundType === "ice" ? `rgba(255,255,255,${a * 1.65})` : (groundType === "foot_massage" ? `rgba(${Math.round(randSeed(2000 + i, 86, 128))}, ${Math.round(randSeed(2300 + i, 142, 196))}, ${Math.round(randSeed(2600 + i, 186, 235))}, ${a})` : `rgba(${Math.round(randSeed(2000 + i, 166, 196))}, ${Math.round(randSeed(2300 + i, 156, 182))}, ${Math.round(randSeed(2600 + i, 128, 150))}, ${a})`));
activeCtx.fillStyle = groundType === "blanket" ? `rgba(255,255,255,${a * 1.35})` : (groundType === "ice" ? `rgba(255,255,255,${a * 1.65})` : (groundType === "dirt" ? `rgba(${Math.round(randSeed(2000 + i, 94, 132))}, ${Math.round(randSeed(2300 + i, 66, 104))}, ${Math.round(randSeed(2600 + i, 38, 70))}, ${a})` : `rgba(${Math.round(randSeed(2000 + i, 166, 196))}, ${Math.round(randSeed(2300 + i, 156, 182))}, ${Math.round(randSeed(2600 + i, 128, 150))}, ${a})`));
activeCtx.beginPath();
activeCtx.ellipse(px, py, r * 1.5, r, randSeed(3000 + i, -0.8, 0.8), 0, Math.PI * 2);
activeCtx.fill();
@ -823,6 +899,24 @@ function drawGardenBed(w, h, lighting) {
activeCtx.restore();
}
if (groundType === "dirt") {
activeCtx.save();
activeCtx.globalAlpha = light > 0.42 ? 0.16 : 0.10;
activeCtx.strokeStyle = "rgba(72, 52, 32, 0.34)";
activeCtx.lineWidth = 1.0;
for (let i = 0; i < 34; i++) {
const px = randSeed(10100 + i, x + 30, x + bw - 30);
const py = randSeed(10400 + i, y + 30, y + bh - 32);
const len = randSeed(10700 + i, 8, 24);
const a = randSeed(11000 + i, -1.4, 1.4);
activeCtx.beginPath();
activeCtx.moveTo(px - Math.cos(a) * len * 0.45, py - Math.sin(a) * len * 0.45);
activeCtx.lineTo(px + Math.cos(a) * len * 0.55, py + Math.sin(a) * len * 0.55);
activeCtx.stroke();
}
activeCtx.restore();
}
if (groundType === "blanket") {
activeCtx.save();
activeCtx.strokeStyle = light > 0.42 ? "rgba(255,255,255,0.28)" : "rgba(255,255,255,0.16)";
@ -1099,6 +1193,7 @@ function render() {
beginFieldTransform();
drawPlacementPreview(ctx, world);
drawOperationToolPreview(ctx, world);
drawTerrainLayer(ctx, world, lighting, visibleRect);
// Render only entities that live in visible spatial cells; this keeps large offscreen colonies cheap.
@ -1270,7 +1365,7 @@ function drawSelectedCard(ctx, world, lighting) {
const zHint = t.digest > 4.9 ? " / \u305a\u3093\u3061" : "";
ctx.fillText(`${stateLabel(t.state)}${child}${zHint}`, x + 10, y + 35);
const genLabel = (t.displayGeneration && t.displayGeneration()) ? ` \u4e16\u4ee3 ${t.generation}` : "";
ctx.fillText(`\u7dcf\u5408\u7684\u6c17\u5206 ${fmt(t.mood)}${genLabel} \u4f53\u529b ${fmt(t.energy)}`, x + 10, y + 52);
ctx.fillText(`\u7dcf\u5408\u7684\u6c17\u5206 ${fmt(t.mood)}${genLabel} \u4f53\u529b ${fmt(t.energy)}/${fmt(typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(t) : (Number(t.maxEnergy) || 100))}`, x + 10, y + 52);
ctx.fillText(`\u30b9\u30c8\u30ec\u30b9 ${fmt(t.stress)} \u7a7a\u8179 ${fmt(t.hunger)}`, x + 10, y + 68);
ctx.restore();
}