diff --git a/frontend/public/images/taxi/house_small.png b/frontend/public/images/taxi/house_small.png
new file mode 100644
index 0000000..25e9ae1
Binary files /dev/null and b/frontend/public/images/taxi/house_small.png differ
diff --git a/frontend/src/views/admin/TaxiToolsView.vue b/frontend/src/views/admin/TaxiToolsView.vue
index b372679..8fcb706 100644
--- a/frontend/src/views/admin/TaxiToolsView.vue
+++ b/frontend/src/views/admin/TaxiToolsView.vue
@@ -88,6 +88,16 @@
:alt="getCellAtPosition(x, y).tileType"
class="tile-image"
/>
+
@@ -139,7 +149,26 @@
@@ -210,6 +239,16 @@
:alt="getCellAtPosition(x, y).tileType"
class="tile-image"
/>
+
@@ -261,7 +300,36 @@
@@ -289,6 +357,26 @@ import SimpleTabs from '../../components/SimpleTabs.vue';
import MessageDialog from '../../dialogues/standard/MessageDialog.vue';
import apiClient from '../../utils/axios.js';
+// Matrix: erlaubte Haus-Tür-Richtungen je Tile-Typ und Ecke
+// Richtungen: bottom, right, top, left
+const HOUSE_TYPE_MATRIX = {
+ horizontal: { lo: ['bottom'], ro: ['bottom'], lu: ['top'], ru: ['top'] },
+ vertical: { lo: ['right'], ro: ['left'], lu: ['right'], ru: ['left'] },
+ cross: { lo: ['bottom', 'right'], ro: ['bottom', 'left'], lu: ['top', 'right'], ru: ['top', 'left'] },
+ cornertopleft: { lo: ['bottom', 'right'], ro: ['left'], lu: ['top'], ru: [] },
+ cornertopright: { lo: ['right'], ro: ['bottom', 'right'], lu: [], ru: ['top'] },
+ cornerbottomleft: { lo: ['bottom'], ro: [], lu: ['top', 'right'], ru: ['left'] },
+ cornerbottomright: { lo: [], ro: ['bottom'], lu: ['right'], ru: ['top', 'left'] },
+ tup: { lo: ['bottom', 'right'], ro: ['bottom', 'left'], lu: ['top'], ru: ['top'] },
+ tdown: { lo: ['bottom'], ro: ['bottom'], lu: ['top', 'right'], ru: ['top', 'left'] },
+ tleft: { lo: ['bottom', 'right'], ro: ['left'], lu: ['top', 'right'], ru: ['left'] },
+ tright: { lo: ['right'], ro: ['bottom', 'left'], lu: ['right'], ru: ['top', 'left'] },
+ fuelhorizontal: { lo: [], ro: [], lu: ['top'], ru: ['top'] },
+ fuelvertical: { lo: ['right'], ro: [], lu: ['right'], ru: [] }
+};
+
+const DIRECTION_TO_ROTATION = { bottom: 0, right: 90, top: 180, left: 270 };
+
export default {
name: 'AdminTaxiToolsView',
components: {
@@ -325,6 +413,9 @@ export default {
],
selectedCellKey: null,
selectedTileType: null
+ ,pendingHouse: null
+ ,allowedHouseCorners: []
+ ,pendingCorner: null
};
},
computed: {
@@ -353,8 +444,8 @@ export default {
return this.supportsHorizontal(type) || this.supportsVertical(type);
},
showExtraElementsPanel() {
- // Gleiche Logik wie Straßenpanel – nur sichtbar, wenn eine Zelle selektiert ist
- return !!this.selectedCell;
+ // Nur sichtbar, wenn eine Zelle selektiert ist UND ein Tile-Typ gesetzt ist
+ return !!(this.selectedCell && this.selectedCell.tileType);
},
supportsHorizontalName() {
if (!this.selectedCell) return false;
@@ -413,6 +504,15 @@ export default {
gridTemplateRows: `repeat(${this.boardHeight}, 50px)`,
gap: '2px'
};
+ },
+
+ // Verfügbare Haus-Tür-Richtungen anhand ausgewählter Ecke und Tile-Typ
+ availableHouseDirections() {
+ if (!this.pendingCorner || !this.selectedCell || !this.selectedCell.tileType) return [];
+ const entry = HOUSE_TYPE_MATRIX[this.selectedCell.tileType];
+ if (!entry) return [];
+ const dirs = entry[this.pendingCorner] || [];
+ return Array.isArray(dirs) ? dirs : [];
}
},
@@ -440,6 +540,18 @@ export default {
},
methods: {
+ directionToRotation(dir) {
+ return DIRECTION_TO_ROTATION[dir] ?? 0;
+ },
+ doorCssClass(dir) {
+ switch (dir) {
+ case 'bottom': return 'door-bottom';
+ case 'right': return 'door-right';
+ case 'top': return 'door-top';
+ case 'left': return 'door-left';
+ default: return 'door-bottom';
+ }
+ },
supportsHorizontal(tileType) {
// Horizontalen Straßennamen erlauben bei: horizontal, cross, tleft, tright, tup, tdown, corns wo horizontaler Ast existiert
const allowed = ['horizontal', 'cross', 'tleft', 'tright', 'tup', 'tdown', 'cornertopleft', 'cornerbottomleft', 'cornertopright', 'cornerbottomright', 'fuelhorizontal'];
@@ -661,6 +773,16 @@ export default {
for (const t of map.tiles) {
const key = `${t.x},${t.y}`;
this.boardCells[key] = { tileType: t.tileType, x: t.x, y: t.y };
+ if (t.meta && t.meta.house) {
+ // Legacy Einzel-Haus zu neuer Struktur migrieren
+ const corner = t.meta.house.corner || 'lo';
+ const rotation = t.meta.house.rotation || 0;
+ this.boardCells[key].extraHouses = { [corner]: rotation };
+ }
+ if (t.meta && t.meta.houses) {
+ // Neue Struktur: Mapping corner -> rotation
+ this.boardCells[key].extraHouses = { ...t.meta.houses };
+ }
}
// Straßennamen aus tileStreets übernehmen
if (Array.isArray(map.tileStreets)) {
@@ -688,10 +810,14 @@ export default {
},
selectCell(key) {
- // Wähle Position aus (Platzhalter)
+ // Wähle Position aus
this.selectedCellKey = this.selectedCellKey === key ? null : key;
- // Setze Tile-Auswahl zurück
+ // Reset temporärer Zustände
this.selectedTileType = null;
+ this.pendingHouse = null;
+ this.pendingCorner = null;
+ // Erlaubte Ecken vorberechnen
+ this.computeAllowedHouseCorners();
},
selectTileType(tileType) {
@@ -725,6 +851,46 @@ export default {
// Erweitere das Board um benachbarte leere Zellen falls nötig
this.expandBoardIfNeeded(x, y, tileType);
+ // Erlaubte Haus-Ecken aktualisieren
+ this.computeAllowedHouseCorners();
+ },
+
+ startPlaceHouse(rotationDeg) {
+ // Neuer Flow: Rotation wählen, nachdem Ecke gewählt wurde
+ if (!this.selectedCellKey || !this.pendingCorner) return;
+ const cell = this.boardCells[this.selectedCellKey];
+ if (!cell || !cell.tileType) return;
+ const existing = cell.extraHouses || {};
+ const next = { ...existing, [this.pendingCorner]: rotationDeg };
+ const updated = { ...cell, extraHouses: next };
+ this.$set ? this.$set(this.boardCells, this.selectedCellKey, updated) : (this.boardCells[this.selectedCellKey] = updated);
+ this.pendingCorner = null;
+ },
+ confirmHouseCorner(corner) {
+ if (!this.selectedCellKey) return;
+ const cell = this.boardCells[this.selectedCellKey];
+ if (!cell || !cell.tileType) return;
+ this.pendingCorner = corner;
+ },
+ computeAllowedHouseCorners() {
+ if (!this.selectedCell || !this.selectedCell.tileType) {
+ this.allowedHouseCorners = [];
+ return;
+ }
+ this.allowedHouseCorners = this.getAllowedHouseCorners(this.selectedCell.tileType);
+ },
+ getAllowedHouseCorners(tileType) {
+ // lo=links oben, ro=rechts oben, lu=links unten, ru=rechts unten
+ switch (tileType) {
+ case 'cornerbottomright': return ['ro','lu','ru'];
+ case 'cornerbottomleft': return ['lo','lu','ru'];
+ case 'cornertopright': return ['lo','ro','ru'];
+ case 'cornertopleft': return ['lo','ro','lu'];
+ case 'fuelhorizontal': return ['ru','lu'];
+ case 'fuelvertical': return ['lo','lu'];
+ default: // horizontal, vertical, cross, tup, tdown, tleft, tright
+ return ['lo','ro','lu','ru'];
+ }
},
expandBoardIfNeeded(x, y, tileType) {
@@ -864,6 +1030,25 @@ export default {
return { tileType: null, x, y };
},
+ doorClass(deg) {
+ switch (deg) {
+ case 0: return 'door-bottom';
+ case 90: return 'door-right';
+ case 180: return 'door-top';
+ case 270: return 'door-left';
+ default: return 'door-bottom';
+ }
+ },
+ cornerClass(corner) {
+ switch (corner) {
+ case 'lo': return 'corner-lo';
+ case 'ro': return 'corner-ro';
+ case 'lu': return 'corner-lu';
+ case 'ru': return 'corner-ru';
+ default: return 'corner-lo';
+ }
+ },
+
range(start, end) {
const result = [];
for (let i = start; i <= end; i++) {
@@ -954,7 +1139,12 @@ export default {
// Tiles in Array-Form bringen
const tiles = Object.values(this.boardCells)
.filter(c => !!c.tileType)
- .map(c => ({ x: c.x, y: c.y, tileType: c.tileType }));
+ .map(c => ({
+ x: c.x,
+ y: c.y,
+ tileType: c.tileType,
+ meta: c.extraHouses ? { houses: { ...c.extraHouses } } : null
+ }));
const tileStreetNames = this.collectTileStreetNames();
const mapData = {
...this.mapForm,
@@ -1238,6 +1428,39 @@ export default {
color: #F9A22C;
font-size: 18px;
}
+.extra-grid { display: flex; gap: 8px; align-items: center; }
+.extra-option {
+ width: 20px;
+ height: 20px;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #fff;
+ cursor: pointer;
+ transition: transform 0.15s ease;
+}
+.extra-option:hover { transform: scale(1.05); }
+.house-square { width: 10px; height: 10px; background: #d0d0d0; border: 1px solid #aaa; position: relative; }
+.house-door { position: absolute; background: #000; }
+.door-top { top: -1px; left: calc(50% - 2.5px); width: 5px; height: 2px; }
+.door-bottom { bottom: -1px; left: calc(50% - 2.5px); width: 5px; height: 2px; }
+.door-left { left: -1px; top: calc(50% - 2.5px); width: 2px; height: 5px; }
+.door-right { right: -1px; top: calc(50% - 2.5px); width: 2px; height: 5px; }
+.house-overlay { position: absolute; inset: 0; pointer-events: none; }
+.house-square.corner-lo { position: absolute; top: 1px; left: 1px; }
+.house-square.corner-ro { position: absolute; top: 1px; right: 1px; }
+.house-square.corner-lu { position: absolute; bottom: 1px; left: 1px; }
+.house-square.corner-ru { position: absolute; bottom: 1px; right: 1px; }
+.corner-chooser { margin-top: 8px; display: flex; gap: 6px; }
+.corner-btn { padding: 3px; border: 1px solid #ccc; border-radius: 4px; background: #f7f7f7; cursor: pointer; }
+.corner-btn:hover { background: #eee; }
+
+.corner-preview { width: 18px; height: 18px; display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
+.corner-preview .q { width: 100%; height: 100%; background: #000; display: block; }
+.corner-preview .q.active { background: #d60000; }
+
.panel-body.placeholder {
border: 2px dashed #ddd;
border-radius: 8px;