From 874c6258ab21e90291e89fa9c8140f97deb2e605 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Wed, 1 Jul 2026 16:05:02 -0500 Subject: [PATCH] New ramp types --- assets/chunks/0_0_0.dcf | Bin 4194 -> 4194 bytes assets/meshes/chunks/chunk_0_0_0_0.dmf | Bin 28932 -> 28932 bytes assetsraw/chunks/chunk_0_0_0.json | 2 +- editor/client/public/common/chunkterrain.js | 14 ++- editor/client/public/map/index.html | 6 +- editor/client/public/map/map.js | 106 ++++++++++++++++++-- src/dusk/rpg/entity/entity.c | 46 +++++++-- src/dusk/rpg/overworld/tile.c | 4 + src/dusk/rpg/overworld/tile.h | 6 +- tools/asset/chunk/__main__.py | 12 ++- 10 files changed, 168 insertions(+), 28 deletions(-) diff --git a/assets/chunks/0_0_0.dcf b/assets/chunks/0_0_0.dcf index 8c91ef16e2178de87b46214d67e4b89abfa1f919..74c1f7851da9e875882ab013b2905ec02ca606bb 100644 GIT binary patch delta 14 VcmaE)@JL}p4ih8K=3J()yZ|gc1yle4 delta 14 VcmaE)@JL}p4ih8C=3J()yZ|gE1yBG0 diff --git a/assets/meshes/chunks/chunk_0_0_0_0.dmf b/assets/meshes/chunks/chunk_0_0_0_0.dmf index 0454d8ab68f41118bd98b533a89a0e5de835103f..d443df1b7c4d6a49f035f0a44a83d6af115468ba 100644 GIT binary patch delta 34 ncmZp9#MttPaYLNN { const TILE_SHAPE_RAMP_NORTHWEST = 7; const TILE_SHAPE_RAMP_SOUTHEAST = 8; const TILE_SHAPE_RAMP_SOUTHWEST = 9; + const TILE_SHAPE_RAMP_NORTHEAST_INNER = 10; + const TILE_SHAPE_RAMP_NORTHWEST_INNER = 11; + const TILE_SHAPE_RAMP_SOUTHEAST_INNER = 12; + const TILE_SHAPE_RAMP_SOUTHWEST_INNER = 13; // Per-shape corner heights as [sw, se, ne, nw] offsets from tile base Z. // NORTH=+Y, EAST=+X. Cardinal ramps: low face at 0, high face at 1. - // Diagonal ramps: opposite corner at 0/1, adjacent corners at 0.5. + // Diagonal outer-corner ramps: only the named corner raised, rest at 0. + // Diagonal inner-corner ramps: only the corner opposite the named one is + // lowered to 0, the rest (including the named corner) stay raised at 1. const RAMP_CORNERS = { [TILE_SHAPE_GROUND]: [0.0, 0.0, 0.0, 0.0], [TILE_SHAPE_RAMP_NORTH]: [0.0, 0.0, 1.0, 1.0], @@ -41,6 +47,10 @@ const ChunkTerrain = (() => { [TILE_SHAPE_RAMP_NORTHWEST]: [0.0, 0.0, 0.0, 1.0], [TILE_SHAPE_RAMP_SOUTHEAST]: [0.0, 1.0, 0.0, 0.0], [TILE_SHAPE_RAMP_SOUTHWEST]: [1.0, 0.0, 0.0, 0.0], + [TILE_SHAPE_RAMP_NORTHEAST_INNER]: [0.0, 1.0, 1.0, 1.0], + [TILE_SHAPE_RAMP_NORTHWEST_INNER]: [1.0, 0.0, 1.0, 1.0], + [TILE_SHAPE_RAMP_SOUTHEAST_INNER]: [1.0, 1.0, 1.0, 0.0], + [TILE_SHAPE_RAMP_SOUTHWEST_INNER]: [1.0, 1.0, 0.0, 1.0], }; function tileIndex(x, y, z) { @@ -92,6 +102,8 @@ const ChunkTerrain = (() => { TILE_SHAPE_RAMP_SOUTH, TILE_SHAPE_RAMP_WEST, TILE_SHAPE_RAMP_NORTHEAST, TILE_SHAPE_RAMP_NORTHWEST, TILE_SHAPE_RAMP_SOUTHEAST, TILE_SHAPE_RAMP_SOUTHWEST, + TILE_SHAPE_RAMP_NORTHEAST_INNER, TILE_SHAPE_RAMP_NORTHWEST_INNER, + TILE_SHAPE_RAMP_SOUTHEAST_INNER, TILE_SHAPE_RAMP_SOUTHWEST_INNER, RAMP_CORNERS, tileIndex, buildTerrainVerts, }); })(); diff --git a/editor/client/public/map/index.html b/editor/client/public/map/index.html index 29761658..5e491709 100644 --- a/editor/client/public/map/index.html +++ b/editor/client/public/map/index.html @@ -52,10 +52,8 @@ 0
-
- -
-
+
+
diff --git a/editor/client/public/map/map.js b/editor/client/public/map/map.js index 43cce2da..62177869 100644 --- a/editor/client/public/map/map.js +++ b/editor/client/public/map/map.js @@ -17,6 +17,10 @@ { type: ChunkTerrain.TILE_SHAPE_RAMP_NORTHWEST, name: "Ramp NW", color: "#ff5722" }, { type: ChunkTerrain.TILE_SHAPE_RAMP_SOUTHEAST, name: "Ramp SE", color: "#9c27b0" }, { type: ChunkTerrain.TILE_SHAPE_RAMP_SOUTHWEST, name: "Ramp SW", color: "#e91e63" }, + { type: ChunkTerrain.TILE_SHAPE_RAMP_NORTHEAST_INNER, name: "Ramp NE Inner", color: "#795548" }, + { type: ChunkTerrain.TILE_SHAPE_RAMP_NORTHWEST_INNER, name: "Ramp NW Inner", color: "#607d8b" }, + { type: ChunkTerrain.TILE_SHAPE_RAMP_SOUTHEAST_INNER, name: "Ramp SE Inner", color: "#8bc34a" }, + { type: ChunkTerrain.TILE_SHAPE_RAMP_SOUTHWEST_INNER, name: "Ramp SW Inner", color: "#ffc107" }, ]; const SHAPE_COLORS = Object.fromEntries(SHAPES.map((s) => [s.type, s.color])); @@ -31,6 +35,10 @@ [ChunkTerrain.TILE_SHAPE_RAMP_NORTHWEST]: { dx: -1, dy: -1 }, [ChunkTerrain.TILE_SHAPE_RAMP_SOUTHEAST]: { dx: 1, dy: 1 }, [ChunkTerrain.TILE_SHAPE_RAMP_SOUTHWEST]: { dx: -1, dy: 1 }, + [ChunkTerrain.TILE_SHAPE_RAMP_NORTHEAST_INNER]: { dx: 1, dy: -1 }, + [ChunkTerrain.TILE_SHAPE_RAMP_NORTHWEST_INNER]: { dx: -1, dy: -1 }, + [ChunkTerrain.TILE_SHAPE_RAMP_SOUTHEAST_INNER]: { dx: 1, dy: 1 }, + [ChunkTerrain.TILE_SHAPE_RAMP_SOUTHWEST_INNER]: { dx: -1, dy: 1 }, }; // Draws an arrow centered at (cx, cy) pointing toward the ramp's high @@ -65,6 +73,66 @@ ctx.fill(); } + // Draws a small pencil icon centered in a `size`x`size` canvas. + function drawPencilIcon(ctx, size) { + ctx.save(); + ctx.translate(size / 2, size / 2); + ctx.rotate(-Math.PI / 4); + + const shaftW = size * 0.18; + const shaftH = size * 0.62; + + ctx.fillStyle = "#e0e0e0"; + ctx.fillRect(-shaftW / 2, shaftH * 0.28, shaftW, shaftH * 0.22); + + ctx.fillStyle = "#f0c419"; + ctx.fillRect(-shaftW / 2, -shaftH * 0.5, shaftW, shaftH * 0.78); + + ctx.fillStyle = "#8d6e63"; + ctx.beginPath(); + ctx.moveTo(-shaftW / 2, -shaftH * 0.5); + ctx.lineTo(shaftW / 2, -shaftH * 0.5); + ctx.lineTo(0, -shaftH * 0.5 - shaftW); + ctx.closePath(); + ctx.fill(); + + ctx.restore(); + } + + // Draws a small paint bucket icon centered in a `size`x`size` canvas. + function drawBucketIcon(ctx, size) { + ctx.save(); + ctx.translate(size / 2, size / 2); + + const w = size * 0.5, h = size * 0.34; + + ctx.strokeStyle = "#e0e0e0"; + ctx.lineWidth = Math.max(1, size * 0.06); + ctx.beginPath(); + ctx.arc(0, -h * 0.4, w * 0.4, Math.PI, 0); + ctx.stroke(); + + ctx.fillStyle = "#03a9f4"; + ctx.beginPath(); + ctx.moveTo(-w / 2, -h * 0.2); + ctx.lineTo(w / 2, -h * 0.2); + ctx.lineTo(w * 0.32, h * 0.6); + ctx.lineTo(-w * 0.32, h * 0.6); + ctx.closePath(); + ctx.fill(); + + ctx.beginPath(); + ctx.arc(-w * 0.55, h * 0.75, size * 0.06, 0, Math.PI * 2); + ctx.fill(); + + ctx.restore(); + } + + const TOOLS = [ + { id: "pencil", name: "Pencil", draw: drawPencilIcon }, + { id: "bucket", name: "Paint Bucket", draw: drawBucketIcon }, + ]; + let coord = { x: 0, y: 0, z: 0 }; let tiles = new Int32Array(ChunkTerrain.TILE_COUNT); let meshes = []; @@ -74,7 +142,7 @@ let zoomWorldH = 20; let dirty = false; let hoverTile = null; - let fillMode = false; + let activeTool = "pencil"; let mapRenderer = null; let terrainMesh = null; @@ -372,6 +440,31 @@ } } + function buildToolPalette() { + const el = document.getElementById("toolPalette"); + el.innerHTML = ""; + for(const tool of TOOLS) { + const btn = document.createElement("button"); + btn.type = "button"; + btn.className = "tile-swatch" + (tool.id === activeTool ? " active" : ""); + btn.style.background = "#2a2d33"; + btn.title = tool.name; + btn.addEventListener("click", () => { + activeTool = tool.id; + buildToolPalette(); + }); + + const icon = document.createElement("canvas"); + icon.width = 32; + icon.height = 32; + icon.className = "tile-swatch-icon"; + tool.draw(icon.getContext("2d"), 32); + btn.appendChild(icon); + + el.appendChild(btn); + } + } + function canvasToTile(e, canvas) { const rect = canvas.getBoundingClientRect(); const cell = canvas.width / ChunkTerrain.CHUNK_WIDTH; @@ -655,7 +748,7 @@ let draggingMeshIndex = -1; gridCanvas.addEventListener("mousedown", (e) => { - if(fillMode) { + if(activeTool === "bucket") { fillAt(e); return; } @@ -719,14 +812,6 @@ document.getElementById("btnSave").addEventListener("click", runSave); - document.getElementById("fillToggle").addEventListener("click", () => { - fillMode = !fillMode; - const btn = document.getElementById("fillToggle"); - btn.textContent = fillMode ? "Fill: On" : "Fill: Off"; - btn.classList.toggle("btn-secondary", fillMode); - btn.classList.toggle("btn-outline-secondary", !fillMode); - }); - document.getElementById("btnAddMesh").addEventListener("click", () => { const base = document.getElementById("meshPicker").value; if(!base) return; @@ -766,6 +851,7 @@ document.addEventListener("DOMContentLoaded", async () => { buildPalette(); + buildToolPalette(); mapRenderer = MapRenderer.create(document.getElementById("previewCanvas")); bindEvents(); await Promise.all([buildChunkBrowseList(), buildMeshPicker()]); diff --git a/src/dusk/rpg/entity/entity.c b/src/dusk/rpg/entity/entity.c index 4988e2f0..f2f25d1e 100644 --- a/src/dusk/rpg/entity/entity.c +++ b/src/dusk/rpg/entity/entity.c @@ -97,22 +97,35 @@ void entityWalk(entity_t *entity, const entitydir_t direction) { ( // Can only walk UP the direction the ramp faces. (direction+TILE_SHAPE_RAMP_NORTH) == tileCurrent || - // If diagonal ramp, can go up one of two ways only. + // If diagonal ramp, can go up one of two ways only. Inner ramps + // share the same allowed directions as their outer counterparts. ( ( - tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST && + ( + tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST || + tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST_INNER + ) && (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST) ) || ( - tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST && + ( + tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST || + tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST_INNER + ) && (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST) ) || ( - tileCurrent == TILE_SHAPE_RAMP_NORTHEAST && + ( + tileCurrent == TILE_SHAPE_RAMP_NORTHEAST || + tileCurrent == TILE_SHAPE_RAMP_NORTHEAST_INNER + ) && (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST) ) || ( - tileCurrent == TILE_SHAPE_RAMP_NORTHWEST && + ( + tileCurrent == TILE_SHAPE_RAMP_NORTHWEST || + tileCurrent == TILE_SHAPE_RAMP_NORTHWEST_INNER + ) && (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST) ) ) @@ -141,22 +154,35 @@ void entityWalk(entity_t *entity, const entitydir_t direction) { ( // This handles regular cardinal ramps (entityDirGetOpposite(direction)+TILE_SHAPE_RAMP_NORTH) == tileBelow || - // This handles diagonal ramps + // This handles diagonal ramps. Inner ramps share the same + // allowed directions as their outer counterparts. ( ( - tileBelow == TILE_SHAPE_RAMP_SOUTHEAST && + ( + tileBelow == TILE_SHAPE_RAMP_SOUTHEAST || + tileBelow == TILE_SHAPE_RAMP_SOUTHEAST_INNER + ) && (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST) ) || ( - tileBelow == TILE_SHAPE_RAMP_SOUTHWEST && + ( + tileBelow == TILE_SHAPE_RAMP_SOUTHWEST || + tileBelow == TILE_SHAPE_RAMP_SOUTHWEST_INNER + ) && (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST) ) || ( - tileBelow == TILE_SHAPE_RAMP_NORTHEAST && + ( + tileBelow == TILE_SHAPE_RAMP_NORTHEAST || + tileBelow == TILE_SHAPE_RAMP_NORTHEAST_INNER + ) && (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST) ) || ( - tileBelow == TILE_SHAPE_RAMP_NORTHWEST && + ( + tileBelow == TILE_SHAPE_RAMP_NORTHWEST || + tileBelow == TILE_SHAPE_RAMP_NORTHWEST_INNER + ) && (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST) ) ) diff --git a/src/dusk/rpg/overworld/tile.c b/src/dusk/rpg/overworld/tile.c index cc25881b..a6f2914c 100644 --- a/src/dusk/rpg/overworld/tile.c +++ b/src/dusk/rpg/overworld/tile.c @@ -27,6 +27,10 @@ bool_t tileIsRamp(const tile_t tile) { case TILE_SHAPE_RAMP_NORTHWEST: case TILE_SHAPE_RAMP_SOUTHEAST: case TILE_SHAPE_RAMP_SOUTHWEST: + case TILE_SHAPE_RAMP_NORTHEAST_INNER: + case TILE_SHAPE_RAMP_NORTHWEST_INNER: + case TILE_SHAPE_RAMP_SOUTHEAST_INNER: + case TILE_SHAPE_RAMP_SOUTHWEST_INNER: return true; default: diff --git a/src/dusk/rpg/overworld/tile.h b/src/dusk/rpg/overworld/tile.h index 3bd2b23e..6abb1093 100644 --- a/src/dusk/rpg/overworld/tile.h +++ b/src/dusk/rpg/overworld/tile.h @@ -21,7 +21,11 @@ typedef enum { TILE_SHAPE_RAMP_NORTHWEST = 7, TILE_SHAPE_RAMP_SOUTHEAST = 8, TILE_SHAPE_RAMP_SOUTHWEST = 9, - + TILE_SHAPE_RAMP_NORTHEAST_INNER = 10, + TILE_SHAPE_RAMP_NORTHWEST_INNER = 11, + TILE_SHAPE_RAMP_SOUTHEAST_INNER = 12, + TILE_SHAPE_RAMP_SOUTHWEST_INNER = 13, + TILE_SHAPE_COUNT } tile_t; diff --git a/tools/asset/chunk/__main__.py b/tools/asset/chunk/__main__.py index a0d01b93..50e03110 100644 --- a/tools/asset/chunk/__main__.py +++ b/tools/asset/chunk/__main__.py @@ -74,6 +74,10 @@ TILE_SHAPE_RAMP_NORTHEAST = 6 TILE_SHAPE_RAMP_NORTHWEST = 7 TILE_SHAPE_RAMP_SOUTHEAST = 8 TILE_SHAPE_RAMP_SOUTHWEST = 9 +TILE_SHAPE_RAMP_NORTHEAST_INNER = 10 +TILE_SHAPE_RAMP_NORTHWEST_INNER = 11 +TILE_SHAPE_RAMP_SOUTHEAST_INNER = 12 +TILE_SHAPE_RAMP_SOUTHWEST_INNER = 13 FILE_MAGIC = b'DCF' DMF_MAGIC = b'DMF\x00' @@ -186,7 +190,9 @@ def _tile_quad(u0, u1, v0, v1, fx, fy, sw_z, se_z, ne_z, nw_z): # Per-shape corner heights as (sw, se, ne, nw) offsets from tile base Z. # NORTH=+Y, EAST=+X. Cardinal ramps: low face at 0, high face at 1. -# Diagonal ramps: opposite corner at 0/1, adjacent corners at 0.5. +# Diagonal outer-corner ramps: only the named corner raised, rest at 0. +# Diagonal inner-corner ramps: only the corner opposite the named one is +# lowered to 0, the rest (including the named corner) stay raised at 1. _RAMP_CORNERS = { TILE_SHAPE_GROUND: (0.0, 0.0, 0.0, 0.0), TILE_SHAPE_RAMP_NORTH: (0.0, 0.0, 1.0, 1.0), # south=low, north=high @@ -197,6 +203,10 @@ _RAMP_CORNERS = { TILE_SHAPE_RAMP_NORTHWEST: (0.0, 0.0, 0.0, 1.0), # only NW raised TILE_SHAPE_RAMP_SOUTHEAST: (0.0, 1.0, 0.0, 0.0), # only SE raised TILE_SHAPE_RAMP_SOUTHWEST: (1.0, 0.0, 0.0, 0.0), # only SW raised + TILE_SHAPE_RAMP_NORTHEAST_INNER: (0.0, 1.0, 1.0, 1.0), # only SW low + TILE_SHAPE_RAMP_NORTHWEST_INNER: (1.0, 0.0, 1.0, 1.0), # only SE low + TILE_SHAPE_RAMP_SOUTHEAST_INNER: (1.0, 1.0, 1.0, 0.0), # only NW low + TILE_SHAPE_RAMP_SOUTHWEST_INNER: (1.0, 1.0, 0.0, 1.0), # only NE low }