Erster Commit
This commit is contained in:
@@ -0,0 +1,654 @@
|
||||
|
||||
const canvas = document.getElementById("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
const mapNameInput = document.getElementById("mapName");
|
||||
const mapSelect = document.getElementById("mapSelect");
|
||||
const newMapBtn = document.getElementById("newMapBtn");
|
||||
const saveMapBtn = document.getElementById("saveMapBtn");
|
||||
const modeSelect = document.getElementById("modeSelect");
|
||||
|
||||
const doorTargetMap = document.getElementById("doorTargetMap");
|
||||
const doorTargetX = document.getElementById("doorTargetX");
|
||||
const doorTargetY = document.getElementById("doorTargetY");
|
||||
|
||||
const mapWidthInput = document.getElementById("mapWidth");
|
||||
const mapHeightInput = document.getElementById("mapHeight");
|
||||
const resizeMapBtn = document.getElementById("resizeMapBtn");
|
||||
|
||||
const tilePalette = document.getElementById("tilePalette");
|
||||
const objectPalette = document.getElementById("objectPalette");
|
||||
|
||||
let objectConfig = {};
|
||||
let selectedObjectType = null;
|
||||
let objects = [];
|
||||
|
||||
let tileConfig = {};
|
||||
let selectedTile = 1;
|
||||
|
||||
let tileSize = 32;
|
||||
let tiles = [];
|
||||
let doors = [];
|
||||
let spawn = { x: 32, y: 32 };
|
||||
|
||||
let cols = 20;
|
||||
let rows = 20;
|
||||
|
||||
let shops = []; // SHOP SYSTEM
|
||||
let atms = [];
|
||||
|
||||
let currentShop = null;
|
||||
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Tiles laden
|
||||
// -------------------------------------------------------------
|
||||
fetch("/tiles.json")
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
tileConfig = data;
|
||||
buildTilePalette();
|
||||
loadMapList();
|
||||
newMap();
|
||||
});
|
||||
|
||||
fetch("/objectConfig.json")
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
objectConfig = data;
|
||||
buildObjectPalette();
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Tile-Palette bauen
|
||||
// -------------------------------------------------------------
|
||||
function buildTilePalette() {
|
||||
tilePalette.innerHTML = "";
|
||||
|
||||
Object.entries(tileConfig).forEach(([id, tile]) => {
|
||||
const div = document.createElement("div");
|
||||
div.style.background = tile.color;
|
||||
div.title = `${id}: ${tile.name} (collision: ${tile.collision})`;
|
||||
div.dataset.id = id;
|
||||
|
||||
div.onclick = () => {
|
||||
selectedTile = parseInt(id);
|
||||
};
|
||||
|
||||
tilePalette.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Object-Palette bauen
|
||||
// -------------------------------------------------------------
|
||||
function buildObjectPalette() {
|
||||
objectPalette.innerHTML = "";
|
||||
|
||||
Object.entries(objectConfig).forEach(([type, obj]) => {
|
||||
const div = document.createElement("div");
|
||||
div.style.width = "32px";
|
||||
div.style.height = "32px";
|
||||
div.style.background = obj.color;
|
||||
div.style.border = "2px solid #000";
|
||||
div.style.display = "inline-block";
|
||||
div.style.margin = "4px";
|
||||
div.style.cursor = "pointer";
|
||||
div.title = `${type}: ${obj.name}`;
|
||||
|
||||
div.onclick = () => {
|
||||
selectedObjectType = type;
|
||||
};
|
||||
|
||||
objectPalette.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Neue Map
|
||||
// -------------------------------------------------------------
|
||||
function newMap() {
|
||||
tiles = [];
|
||||
for (let y = 0; y < rows; y++) {
|
||||
tiles[y] = [];
|
||||
for (let x = 0; x < cols; x++) {
|
||||
tiles[y][x] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
doors = [];
|
||||
objects = [];
|
||||
shops = [];
|
||||
spawn = { x: 32, y: 32 };
|
||||
|
||||
render();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Map-Liste laden
|
||||
// -------------------------------------------------------------
|
||||
function loadMapList() {
|
||||
fetch("/api/get_maps")
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
mapSelect.innerHTML = `<option value="">Map laden...</option>`;
|
||||
Object.keys(data.maps).forEach(name => {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = name;
|
||||
opt.textContent = name;
|
||||
mapSelect.appendChild(opt);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Map laden
|
||||
// -------------------------------------------------------------
|
||||
mapSelect.onchange = () => {
|
||||
const name = mapSelect.value;
|
||||
if (!name) return;
|
||||
|
||||
fetch("/api/get_maps")
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
const map = data.maps[name];
|
||||
|
||||
mapNameInput.value = map.name;
|
||||
tiles = map.tiles;
|
||||
doors = map.doors || [];
|
||||
objects = map.objects || [];
|
||||
shops = map.shops || [];
|
||||
atms = map.atms || [];
|
||||
atms = atms || [];
|
||||
|
||||
spawn = map.spawn;
|
||||
|
||||
rows = tiles.length;
|
||||
cols = tiles[0].length;
|
||||
|
||||
render();
|
||||
});
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Map speichern
|
||||
// -------------------------------------------------------------
|
||||
saveMapBtn.onclick = () => {
|
||||
const name = mapNameInput.value.trim();
|
||||
if (!name) return alert("Map-Name fehlt!");
|
||||
|
||||
const mapData = {
|
||||
name,
|
||||
spawn,
|
||||
tiles,
|
||||
doors,
|
||||
objects,
|
||||
shops,
|
||||
atms
|
||||
};
|
||||
|
||||
fetch("/api/save_map", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name, data: mapData })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.ok) {
|
||||
alert("Map gespeichert!");
|
||||
loadMapList();
|
||||
} else {
|
||||
alert("Fehler: " + data.error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
function saveMap() {
|
||||
const name = mapNameInput.value.trim();
|
||||
if (!name) return alert("Map-Name fehlt!");
|
||||
|
||||
const mapData = {
|
||||
name,
|
||||
spawn,
|
||||
tiles,
|
||||
doors,
|
||||
objects,
|
||||
shops,
|
||||
atms
|
||||
};
|
||||
|
||||
fetch("/api/save_map", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name, data: mapData })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.ok) {
|
||||
alert("Map gespeichert!");
|
||||
loadMapList();
|
||||
} else {
|
||||
alert("Fehler: " + data.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Map-Größe ändern
|
||||
// -------------------------------------------------------------
|
||||
resizeMapBtn.onclick = () => {
|
||||
const newW = parseInt(mapWidthInput.value);
|
||||
const newH = parseInt(mapHeightInput.value);
|
||||
|
||||
if (!newW || !newH) return;
|
||||
|
||||
const newTiles = [];
|
||||
|
||||
for (let y = 0; y < newH; y++) {
|
||||
newTiles[y] = [];
|
||||
for (let x = 0; x < newW; x++) {
|
||||
newTiles[y][x] = tiles[y]?.[x] ?? 1;
|
||||
}
|
||||
}
|
||||
|
||||
tiles = newTiles;
|
||||
cols = newW;
|
||||
rows = newH;
|
||||
|
||||
render();
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Canvas Click
|
||||
// -------------------------------------------------------------
|
||||
canvas.addEventListener("contextmenu", e => e.preventDefault());
|
||||
|
||||
/*
|
||||
canvas.addEventListener("mousedown", e => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const mx = e.clientX - rect.left;
|
||||
const my = e.clientY - rect.top;
|
||||
|
||||
const x = Math.floor(mx / tileSize);
|
||||
const y = Math.floor(my / tileSize);
|
||||
|
||||
if (x < 0 || y < 0 || x >= cols || y >= rows) return;
|
||||
|
||||
const mode = modeSelect.value;
|
||||
|
||||
// TILES
|
||||
if (mode === "tile") {
|
||||
if (e.button === 0) tiles[y][x] = selectedTile;
|
||||
if (e.button === 2) tiles[y][x] = 0;
|
||||
}
|
||||
if (mode === "shop") {
|
||||
map.objects.push({ type: "shop", x: tx, y: ty });
|
||||
saveMap();
|
||||
}
|
||||
|
||||
|
||||
// SPAWN
|
||||
if (mode === "spawn") {
|
||||
spawn.x = x * tileSize;
|
||||
spawn.y = y * tileSize;
|
||||
}
|
||||
|
||||
// DOORS
|
||||
if (mode === "door") {
|
||||
if (e.button === 0) {
|
||||
doors.push({
|
||||
x: x * tileSize,
|
||||
y: y * tileSize,
|
||||
targetMap: doorTargetMap.value,
|
||||
targetX: parseInt(doorTargetX.value),
|
||||
targetY: parseInt(doorTargetY.value)
|
||||
});
|
||||
}
|
||||
if (e.button === 2) {
|
||||
doors = doors.filter(d => !(d.x === x * tileSize && d.y === y * tileSize));
|
||||
}
|
||||
}
|
||||
|
||||
// OBJECTS
|
||||
if (mode === "object") {
|
||||
const ox = x * tileSize;
|
||||
const oy = y * tileSize;
|
||||
|
||||
if (e.button === 0) {
|
||||
if (!selectedObjectType) return;
|
||||
objects.push({
|
||||
type: selectedObjectType,
|
||||
x: ox,
|
||||
y: oy
|
||||
});
|
||||
}
|
||||
|
||||
if (e.button === 2) {
|
||||
objects = objects.filter(o => !(o.x === ox && o.y === oy));
|
||||
}
|
||||
}
|
||||
|
||||
// SHOPS
|
||||
if (mode === "shop") {
|
||||
const tileX = Math.floor(mx / tileSize);
|
||||
const tileY = Math.floor(my / tileSize);
|
||||
|
||||
// Wenn Linksklick auf existierenden Shop → Editor öffnen
|
||||
if (e.button === 0) {
|
||||
const existing = shops.find(s => s.x === tileX && s.y === tileY);
|
||||
if (existing) {
|
||||
openShopEditor(existing);
|
||||
return;
|
||||
}
|
||||
|
||||
// neuen Shop setzen
|
||||
shops.push({
|
||||
id: "shop_" + Date.now(),
|
||||
x: tileX,
|
||||
y: tileY,
|
||||
items: []
|
||||
});
|
||||
}
|
||||
|
||||
// Shop löschen
|
||||
if (e.button === 2) {
|
||||
shops = shops.filter(s => !(s.x === tileX && s.y === tileY));
|
||||
|
||||
if (currentShop && currentShop.x === tileX && currentShop.y === tileY) {
|
||||
currentShop = null;
|
||||
document.getElementById("shopEditor").style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (mode === "atm") {
|
||||
map.atms.push({
|
||||
id: "atm_" + Date.now(),
|
||||
x: tileX,
|
||||
y: tileY
|
||||
});
|
||||
saveMap();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
render();
|
||||
});
|
||||
*/
|
||||
|
||||
canvas.addEventListener("mousedown", e => {
|
||||
e.preventDefault();
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const mx = e.clientX - rect.left;
|
||||
const my = e.clientY - rect.top;
|
||||
|
||||
const tileX = Math.floor(mx / tileSize);
|
||||
const tileY = Math.floor(my / tileSize);
|
||||
|
||||
if (tileX < 0 || tileY < 0 || tileX >= cols || tileY >= rows) return;
|
||||
|
||||
const mode = modeSelect.value;
|
||||
|
||||
// TILES
|
||||
if (mode === "tile") {
|
||||
if (e.button === 0) tiles[tileY][tileX] = selectedTile;
|
||||
if (e.button === 2) tiles[tileY][tileX] = 0;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
// SPAWN
|
||||
if (mode === "spawn") {
|
||||
if (e.button === 0) {
|
||||
spawn.x = tileX * tileSize;
|
||||
spawn.y = tileY * tileSize;
|
||||
//saveMap();
|
||||
render();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// DOORS
|
||||
if (mode === "door") {
|
||||
if (e.button === 0) {
|
||||
doors.push({
|
||||
x: tileX * tileSize,
|
||||
y: tileY * tileSize,
|
||||
targetMap: doorTargetMap.value,
|
||||
targetX: parseInt(doorTargetX.value),
|
||||
targetY: parseInt(doorTargetY.value)
|
||||
});
|
||||
//saveMap();
|
||||
render();
|
||||
}
|
||||
if (e.button === 2) {
|
||||
doors = doors.filter(d => !(d.x === tileX * tileSize && d.y === tileY * tileSize));
|
||||
//saveMap();
|
||||
render();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// OBJECTS
|
||||
if (mode === "object") {
|
||||
const ox = tileX * tileSize;
|
||||
const oy = tileY * tileSize;
|
||||
|
||||
if (e.button === 0) {
|
||||
if (!selectedObjectType) return;
|
||||
objects.push({ type: selectedObjectType, x: ox, y: oy });
|
||||
//saveMap();
|
||||
render();
|
||||
}
|
||||
|
||||
if (e.button === 2) {
|
||||
objects = objects.filter(o => !(o.x === ox && o.y === oy));
|
||||
//saveMap();
|
||||
render();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// SHOPS
|
||||
if (mode === "shop") {
|
||||
if (e.button === 0) {
|
||||
const name = prompt("Shop-Name:");
|
||||
if (!name) return;
|
||||
|
||||
fetch("/api/admin/shops", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name, world: mapNameInput.value, x: tileX, y: tileY })
|
||||
}).then(() => alert("Shop angelegt – Items im Admin-Panel zuweisen"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
/*if (mode === "shop") {
|
||||
if (e.button === 0) {
|
||||
const existing = shops.find(s => s.x === tileX && s.y === tileY);
|
||||
if (existing) {
|
||||
openShopEditor(existing);
|
||||
return;
|
||||
}
|
||||
shops.push({
|
||||
id: "shop_" + Date.now(),
|
||||
x: tileX,
|
||||
y: tileY,
|
||||
items: []
|
||||
});
|
||||
//saveMap();
|
||||
render();
|
||||
}
|
||||
|
||||
if (e.button === 2) {
|
||||
shops = shops.filter(s => !(s.x === tileX && s.y === tileY));
|
||||
//saveMap();
|
||||
render();
|
||||
}
|
||||
return;
|
||||
}*/
|
||||
|
||||
// ATMS (wie Shops)
|
||||
if (mode === "atm") {
|
||||
if (e.button === 0) {
|
||||
const existing = atms.find(a => a.x === tileX && a.y === tileY);
|
||||
if (existing) return;
|
||||
|
||||
atms.push({
|
||||
id: "atm_" + Date.now(),
|
||||
x: tileX,
|
||||
y: tileY
|
||||
});
|
||||
|
||||
//saveMap();
|
||||
render();
|
||||
}
|
||||
|
||||
if (e.button === 2) {
|
||||
atms = atms.filter(a => !(a.x === tileX && a.y === tileY));
|
||||
//saveMap();
|
||||
render();
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Render Shops
|
||||
// -------------------------------------------------------------
|
||||
function renderShops() {
|
||||
shops.forEach(s => {
|
||||
ctx.fillStyle = "yellow";
|
||||
ctx.fillRect(s.x * tileSize, s.y * tileSize, tileSize, tileSize);
|
||||
ctx.strokeStyle = "black";
|
||||
ctx.strokeRect(s.x * tileSize, s.y * tileSize, tileSize, tileSize);
|
||||
});
|
||||
}
|
||||
|
||||
function renderATMs() {
|
||||
atms.forEach(a => {
|
||||
const px = a.x * tileSize;
|
||||
const py = a.y * tileSize;
|
||||
|
||||
ctx.fillStyle = "blue";
|
||||
ctx.fillRect(px, py, tileSize, tileSize);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// shop
|
||||
// -------------------------------------------------------------
|
||||
function openShopEditor(shop) {
|
||||
currentShop = shop;
|
||||
|
||||
const editor = document.getElementById("shopEditor");
|
||||
const info = document.getElementById("shopInfo");
|
||||
|
||||
editor.style.display = "block";
|
||||
info.innerHTML = `ID: ${shop.id}<br>Position: (${shop.x}, ${shop.y})`;
|
||||
|
||||
renderShopItemList();
|
||||
}
|
||||
|
||||
function renderShopItemList() {
|
||||
const list = document.getElementById("shopItemList");
|
||||
list.innerHTML = "";
|
||||
|
||||
if (!currentShop) return;
|
||||
|
||||
currentShop.items.forEach(item => {
|
||||
const div = document.createElement("div");
|
||||
div.innerHTML = `
|
||||
${item.name} (${item.price}$)
|
||||
<button onclick="removeShopItem('${item.id}')">X</button>
|
||||
`;
|
||||
list.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
function addShopItem() {
|
||||
if (!currentShop) return;
|
||||
|
||||
const id = prompt("Item ID:");
|
||||
if (!id) return;
|
||||
|
||||
const name = prompt("Item Name:");
|
||||
if (!name) return;
|
||||
|
||||
const priceStr = prompt("Preis:");
|
||||
const price = parseInt(priceStr, 10);
|
||||
if (isNaN(price)) return;
|
||||
|
||||
currentShop.items.push({ id, name, price });
|
||||
renderShopItemList();
|
||||
}
|
||||
|
||||
function removeShopItem(id) {
|
||||
if (!currentShop) return;
|
||||
|
||||
currentShop.items = currentShop.items.filter(i => i.id !== id);
|
||||
renderShopItemList();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Render
|
||||
// -------------------------------------------------------------
|
||||
function render() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Tiles
|
||||
for (let y = 0; y < rows; y++) {
|
||||
for (let x = 0; x < cols; x++) {
|
||||
const id = tiles[y][x];
|
||||
const tile = tileConfig[id];
|
||||
|
||||
ctx.fillStyle = tile ? tile.color : "#000";
|
||||
ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
|
||||
|
||||
ctx.strokeStyle = "#333";
|
||||
ctx.strokeRect(x * tileSize, y * tileSize, tileSize, tileSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn
|
||||
ctx.fillStyle = "yellow";
|
||||
ctx.fillRect(spawn.x, spawn.y, tileSize, tileSize);
|
||||
|
||||
// Doors
|
||||
ctx.fillStyle = "orange";
|
||||
doors.forEach(d => {
|
||||
ctx.fillRect(d.x, d.y, tileSize, tileSize);
|
||||
});
|
||||
|
||||
// Objects
|
||||
objects.forEach(o => {
|
||||
const cfg = objectConfig[o.type];
|
||||
if (!cfg) return;
|
||||
|
||||
ctx.fillStyle = cfg.color;
|
||||
ctx.fillRect(
|
||||
o.x,
|
||||
o.y - (cfg.height - tileSize),
|
||||
cfg.width,
|
||||
cfg.height
|
||||
);
|
||||
});
|
||||
|
||||
// Shops
|
||||
renderShops();
|
||||
renderATMs();
|
||||
}
|
||||
Reference in New Issue
Block a user