diff --git a/assets/chunks/0_0_0.dcf b/assets/chunks/0_0_0.dcf
index 8c91ef16..74c1f785 100644
Binary files a/assets/chunks/0_0_0.dcf and b/assets/chunks/0_0_0.dcf differ
diff --git a/assets/meshes/chunks/chunk_0_0_0_0.dmf b/assets/meshes/chunks/chunk_0_0_0_0.dmf
index 0454d8ab..d443df1b 100644
Binary files a/assets/meshes/chunks/chunk_0_0_0_0.dmf and b/assets/meshes/chunks/chunk_0_0_0_0.dmf differ
diff --git a/assetsraw/chunks/chunk_0_0_0.json b/assetsraw/chunks/chunk_0_0_0.json
index c84b7cdd..06fce022 100644
--- a/assetsraw/chunks/chunk_0_0_0.json
+++ b/assetsraw/chunks/chunk_0_0_0.json
@@ -1086,7 +1086,7 @@
9,
0
],
- "type": 8,
+ "type": 12,
"tile": 0
},
{
diff --git a/editor/client/public/common/chunkterrain.js b/editor/client/public/common/chunkterrain.js
index 663ab029..b01af727 100644
--- a/editor/client/public/common/chunkterrain.js
+++ b/editor/client/public/common/chunkterrain.js
@@ -27,10 +27,16 @@ const ChunkTerrain = (() => {
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
}