Änderung: Erweiterung der Hausplatzierungslogik im Taxi-Spiel

Änderungen:
- Hinzufügung einer neuen Logik zur Auswahl und Platzierung von Häusern auf der Karte, einschließlich der Auswahl von Ecken und Tür-Richtungen.
- Implementierung einer Matrix zur Verwaltung erlaubter Haus-Tür-Richtungen basierend auf dem Tile-Typ.
- Verbesserung der Benutzeroberfläche in TaxiToolsView.vue zur Anzeige von verfügbaren Ecken und Tür-Richtungen.
- Anpassung der Datenstruktur zur Speicherung zusätzlicher Hausinformationen in den Tiles.

Diese Anpassungen verbessern die Funktionalität und Benutzererfahrung im Taxi-Minispiel erheblich, indem sie eine detailliertere Verwaltung von Häusern auf der Karte ermöglichen.
This commit is contained in:
Torsten Schulz (local)
2025-09-18 09:23:55 +02:00
parent 547c81b867
commit d530066868
2 changed files with 230 additions and 7 deletions

View File

@@ -88,6 +88,16 @@
:alt="getCellAtPosition(x, y).tileType"
class="tile-image"
/>
<div v-if="getCellAtPosition(x, y)?.extraHouses" class="house-overlay">
<div
v-for="(rotation, corner) in getCellAtPosition(x, y).extraHouses"
:key="corner"
class="house-square"
:class="cornerClass(corner)"
>
<div class="house-door" :class="doorClass(rotation)"></div>
</div>
</div>
</div>
</div>
</div>
@@ -139,7 +149,26 @@
<!-- Zusätzliche Elemente (viertes Panel) -->
<div v-if="showExtraElementsPanel" class="tool-panel extra-elements">
<h4>{{ $t('admin.taxiTools.mapEditor.extraElements') }}</h4>
<div class="panel-body placeholder"></div>
<div class="panel-body">
<div v-if="allowedHouseCorners.length" class="corner-chooser">
<button v-for="pos in allowedHouseCorners" :key="pos" class="corner-btn" @click="confirmHouseCorner(pos)">
{{ pos.toUpperCase() }}
</button>
</div>
<h5 v-if="pendingCorner" class="extra-subtitle">Haus</h5>
<div v-if="pendingCorner" class="extra-grid">
<div
v-for="dir in availableHouseDirections"
:key="dir"
class="extra-option"
@click="startPlaceHouse(directionToRotation(dir))"
:title="`Haus: Tür ${dir}`">
<div class="house-square">
<div class="house-door" :class="doorCssClass(dir)"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -210,6 +239,16 @@
:alt="getCellAtPosition(x, y).tileType"
class="tile-image"
/>
<div v-if="getCellAtPosition(x, y)?.extraHouses" class="house-overlay">
<div
v-for="(rotation, corner) in getCellAtPosition(x, y).extraHouses"
:key="corner"
class="house-square"
:class="cornerClass(corner)"
>
<div class="house-door" :class="doorClass(rotation)"></div>
</div>
</div>
</div>
</div>
</div>
@@ -261,7 +300,36 @@
<!-- Zusätzliche Elemente (viertes Panel) -->
<div v-if="showExtraElementsPanel" class="tool-panel extra-elements">
<h4>{{ $t('admin.taxiTools.mapEditor.extraElements') }}</h4>
<div class="panel-body placeholder"></div>
<div class="panel-body">
<div v-if="allowedHouseCorners.length" class="corner-chooser">
<button
v-for="pos in allowedHouseCorners"
:key="pos"
class="corner-btn"
:title="pos.toUpperCase()"
@click="confirmHouseCorner(pos)">
<div class="corner-preview">
<span class="q" :class="{ active: pos === 'lo' }"></span>
<span class="q" :class="{ active: pos === 'ro' }"></span>
<span class="q" :class="{ active: pos === 'lu' }"></span>
<span class="q" :class="{ active: pos === 'ru' }"></span>
</div>
</button>
</div>
<h5 v-if="pendingCorner" class="extra-subtitle">Haus</h5>
<div v-if="pendingCorner" class="extra-grid">
<div
v-for="dir in availableHouseDirections"
:key="dir"
class="extra-option"
@click="startPlaceHouse(directionToRotation(dir))"
:title="`Haus: Tür ${dir}`">
<div class="house-square">
<div class="house-door" :class="doorCssClass(dir)"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -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;