← Pirates and sea3D assetfogon/botella-con-mensaje
Botella con mensaje
Drag to turn it// «Botella con mensaje» · gpt-6-sol (medium)
// A big green glass bottle lying on its side half in the sand, a cork in the neck and a rolled beige paper scroll tied with a red string visible inside.
// A message in a bottle, half-buried on a sunny pirate beach.
const glassDark = "#126453";
const glassMid = "#188c70";
const glassLight = "#30b793";
const glassShine = "#84e5bc";
const sand = ["#dfae62", "#edc47d", "#f5d594"];
const sandShadow = "#c99551";
const paper = "#f3dfad";
const paperEdge = "#cda773";
const red = "#c73836";
function radiusAt(x) {
if (x < 10) return 5.8 + (x - 7) * 1.15; // rounded base
if (x <= 27) return 9.3;
if (x <= 33) return 9.3 - (x - 27) * 0.85; // shoulder
if (x <= 40) return 4.2;
return 0;
}
// Low, irregular oval patch of beach.
for (let x = 4; x <= 45; x++)
for (let y = 8; y <= 40; y++) {
const d = ((x - 24) / 21) ** 2 + ((y - 24) / 16) ** 2;
if (d > 1 || (d > 0.88 && rand() < 0.35)) continue;
let h = 1;
if (y < 18) h += Math.max(0, Math.floor((18 - y) / 3));
if (y > 31) h += Math.max(0, Math.floor((y - 31) / 5));
if (rand() < 0.15) h++;
for (let z = 0; z <= h; z++)
set(x, y, z, z === h ? sand[Math.floor(rand() * 3)] : sandShadow);
}
// Bottle body: solid colored glass, with its broad front face opened up
// to show the letter through the green glass.
for (let x = 7; x <= 40; x++) {
const r = radiusAt(x);
for (let y = 13; y <= 35; y++)
for (let z = 1; z <= 21; z++) {
const dy = (y - 24) / r, dz = (z - 11) / (r * 0.94);
const distance = dy * dy + dz * dz;
if (distance > 1) continue;
// A dark green interior behind the scroll.
let color = glassDark;
if (z >= 13) color = glassMid;
if (z >= 17 && y <= 26) color = glassLight;
if (distance > 0.78 && y < 24) color = glassMid;
if (distance > 0.88 && z >= 15 && y < 23) color = glassShine;
if (rand() < 0.035) color = z > 11 ? glassLight : glassDark;
set(x, y, z, color);
}
}
// Clear viewing area in the near side. Retain a chunky glass border.
for (let x = 13; x <= 30; x++)
for (let y = 13; y <= 20; y++)
for (let z = 7; z <= 15; z++) {
if (get(x, y, z)) set(x, y, z, null);
}
// Dark inner edge makes the glass wall read as thick.
for (let x = 13; x <= 30; x++) {
for (const z of [6, 16])
for (let y = 15; y <= 19; y++)
if (get(x, y, z)) set(x, y, z, glassDark);
}
// Rolled parchment inside, running along the bottle.
for (let x = 15; x <= 29; x++)
for (let y = 16; y <= 23; y++)
for (let z = 7; z <= 15; z++) {
const d = ((y - 19.7) / 3.1) ** 2 + ((z - 11) / 3.1) ** 2;
if (d <= 1) {
let c = z >= 12 ? "#fff0c9" : paper;
if (d > 0.7 || z <= 8) c = paperEdge;
if (rand() < 0.035) c = "#dfc390";
set(x, y, z, c);
}
}
// Pale curled end of the rolled sheet.
for (let y = 17; y <= 23; y++)
for (let z = 8; z <= 14; z++) {
const d = ((y - 19.7) / 3.1) ** 2 + ((z - 11) / 3.1) ** 2;
if (d <= 1) set(30, y, z, d > 0.58 ? paperEdge : "#fff0c9");
}
set(30, 17, 11, paperEdge);
set(30, 18, 12, paperEdge);
set(30, 19, 12, paperEdge);
set(30, 20, 11, paperEdge);
set(30, 19, 10, paperEdge);
// Red string bound around the middle, including its front-facing knot.
for (let x = 22; x <= 23; x++)
for (let y = 16; y <= 23; y++)
for (let z = 7; z <= 15; z++) {
const d = ((y - 19.7) / 3.2) ** 2 + ((z - 11) / 3.2) ** 2;
if (d > 0.66 && d < 1.13) set(x, y, z, x === 22 ? red : "#ed5a43");
}
box(21, 15, 11, 23, 16, 12, "#a82730");
set(22, 14, 10, red);
set(21, 15, 9, "#ed5a43");
set(23, 15, 9, "#ed5a43");
// Narrow neck, raised lip, and the chunky cork in its mouth.
for (let x = 39; x <= 41; x++)
for (let y = 19; y <= 29; y++)
for (let z = 6; z <= 16; z++) {
const d = ((y - 24) / 5.2) ** 2 + ((z - 11) / 5.2) ** 2;
if (d <= 1 && d >= 0.49)
set(x, y, z, z >= 13 ? glassLight : glassDark);
}
for (let x = 41; x <= 45; x++)
for (let y = 20; y <= 28; y++)
for (let z = 7; z <= 15; z++) {
const d = ((y - 24) / 3.6) ** 2 + ((z - 11) / 3.6) ** 2;
if (d <= 1) {
let c = z > 11 ? "#dfaa6b" : "#b97946";
if (d > 0.72 || rand() < 0.1) c = "#995d39";
set(x, y, z, c);
}
}
box(44, 22, 10, 45, 26, 12, "#dca16a");
// Sparkling glass streaks across the rounded upper surface.
for (let x = 12; x <= 26; x++)
if (x % 5 !== 0) set(x, 19, 19, glassShine);
for (let x = 10; x <= 16; x++) set(x, 16, 15, glassLight);
for (let x = 32; x <= 37; x++) set(x, 21, 15, glassShine);
set(10, 17, 18, "#d0ffe0");
set(11, 17, 18, "#d0ffe0");
set(10, 17, 19, "#d0ffe0");
// Fresh sand bank against the lower front of the bottle.
for (let x = 8; x <= 37; x++)
for (let y = 12; y <= 18; y++) {
let h = Math.floor(2 + (18 - y) * 0.45 + Math.sin(x * 0.42) * 0.8);
if (rand() < 0.12) h++;
for (let z = 1; z <= h; z++) {
if (z <= 3 || y <= 16 || (x < 13 && z <= 5))
set(x, y, z, z === h ? sand[Math.floor(rand() * 3)] : "#deb06b");
}
}
// A couple of little gold glints washed up nearby.
for (const [x, y] of [[33, 12], [35, 13]]) {
box(x, y, 3, x + 2, y + 1, 3, "#b47b20");
box(x, y, 4, x + 1, y + 1, 4, "#ffd456");
set(x, y, 5, "#ffe98b");
}import { draw } from "./assets/voxel";
import { botellaConMensaje } from "./assets/botella-con-mensaje";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, botellaConMensaje, x, y, { size: 64, angle: 0, t: performance.now() / 1000 });
// size: its longest side, in px
// angle: turn on the floor, in radians
// t: seconds; if it moves, that animates it# In a terminal, in the game's folder
fogon assets add botella-con-mensaje
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["botella-con-mensaje"] })
# It lands in client/assets/botella-con-mensaje.ts
# https://fogon.page/object/botella-con-mensaje