Spiel erweitert

This commit is contained in:
Torsten Schulz
2025-06-02 11:26:45 +02:00
parent a9e6c82275
commit 5029be81e9
56 changed files with 4549 additions and 436 deletions

View File

@@ -1,10 +1,10 @@
<template>
<div class="houseView">
<div class="house-view">
<StatusBar />
<h2>{{ $t('falukant.house.title') }}</h2>
<div class="existingHouse">
<div :style="houseStyle(picturePosition)" class="house"></div>
<div class="statusreport">
<div class="existing-house">
<div :style="houseType ? houseStyle(houseType.position, 341) : {}" class="house"></div>
<div class="status-panel">
<h3>{{ $t('falukant.house.statusreport') }}</h3>
<table>
<thead>
@@ -15,46 +15,54 @@
</tr>
</thead>
<tbody>
<tr v-for="status, index in status">
<td>{{ $t(`falukant.house.status.${index}`) }}</td>
<td>{{ status }} %</td>
<td><button v-if="status < 100">{{ $t('falukant.house.renovate') }} ({{
$t('falukant.house.cost') }}: {{ getRenovationCost(index, status) }}</button></td>
<tr v-for="(value, key) in status" :key="key">
<td>{{ $t(`falukant.house.status.${key}`) }}</td>
<td>{{ value }}%</td>
<td>
<button v-if="value < 100" @click="renovate(key)">
{{ $t('falukant.house.renovate') }} ({{ getRenovationCost(key, value) }})
</button>
</td>
</tr>
<tr>
<td>{{ $t('falukant.house.worth') }}</td>
<td>{{ getWorth(status) }}</td>
<td><button @click="sellHouse">{{ $t('falukant.house.sell') }}</button></td>
<td>{{ getWorth() }} {{ currency }}</td>
<td>
<button @click="renovateAll" :disabled="allRenovated">
{{ $t('falukant.house.renovateAll') }} ({{ getAllRenovationCost() }})
</button>
<button @click="sellHouse">
{{ $t('falukant.house.sell') }}
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="buyablehouses">
<div class="buyable-houses">
<h3>{{ $t('falukant.house.buyablehouses') }}</h3>
<div style="overflow:auto">
<div style="display: flex; flex-direction: row" v-for="house in buyableHouses">
<div style="width:100px; height:100px; display: hidden;">
<div :style="houseStyle(house.houseType.position)" class="housePreview buyableHouseInfo"></div>
</div>
<div class="buyableHouseInfo">
<h4 style="display: inline;">{{ $t('falukant.house.statusreport') }}</h4>
<div class="houses-list">
<div v-for="house in buyableHouses" :key="house.id" class="house-item">
<div :style="house.houseType ? houseStyle(house.houseType.position, 114) : {}" class="house-preview"></div>
<div class="house-info">
<h4>{{ $t(`falukant.house.type.${house.houseType.labelTr}`) }}</h4>
<table>
<tbody>
<template v-for="value, key in house">
<tr v-if="key != 'houseType' && key != 'id'">
<td>{{ $t(`falukant.house.status.${key}`) }}</td>
<td>{{ value }} %</td>
</tr>
</template>
<tr v-for="(val, prop) in house" :key="prop"
v-if="['roofCondition','wallCondition','floorCondition','windowCondition'].includes(prop)">
<td>{{ $t(`falukant.house.status.${prop}`) }}</td>
<td>{{ val }}%</td>
</tr>
</tbody>
</table>
</div>
<div>
{{ $t('falukant.house.price') }}: {{ buyCost(house) }}
</div>
<div>
<button @click="buyHouse(house.id)">{{ $t('falukant.house.buy') }}</button>
<div>
{{ $t('falukant.house.price') }}: {{ buyCost(house) }}
</div>
<button @click="buyHouse(house.id)">
{{ $t('falukant.house.buy') }}
</button>
</div>
</div>
</div>
@@ -65,195 +73,209 @@
<script>
import StatusBar from '@/components/falukant/StatusBar.vue';
import apiClient from '@/utils/axios.js';
import { mapState } from "vuex";
import { mapState } from 'vuex';
export default {
name: 'HouseView',
components: {
StatusBar
},
components: { StatusBar },
data() {
return {
houseTypes: [],
userHouse: {},
userHouse: null,
houseType: {},
status: {},
buyableHouses: [],
picturePosition: 0,
}
},
methods: {
async loadHouseTypes() {
try {
const houseTypesResult = await apiClient.get('/api/falukant/houses/types');
this.houseTypes = houseTypesResult.data;
} catch (error) {
}
},
async loadUserHouse() {
try {
const userHouseResult = await apiClient.get('/api/falukant/houses');
Object.assign(this.userHouse, userHouseResult.data);
const { houseType, ...houseStatus } = this.userHouse;
this.status = houseStatus;
this.picturePosition = parseInt(houseType.position);
this.houseType = houseType;
} catch (error) {
console.error('Fehler beim Laden des Hauses:', error);
this.userHouse = null;
this.status = null;
}
},
async loadBuyableHouses() {
try {
const buyableHousesResult = await apiClient.get('/api/falukant/houses/buyable');
this.buyableHouses = buyableHousesResult.data;
} catch (error) {
console.error('Fehler beim Laden der kaufbaren Häuser:', error);
}
},
houseStyle(housePosition) {
const columns = 3;
const spriteSize = 341; // Breite & Höhe eines einzelnen Hauses
let calculatePosition = Math.max(housePosition - 1, 0);
const x = (calculatePosition % columns) * spriteSize;
const y = Math.floor(calculatePosition / columns) * spriteSize;
return {
backgroundImage: 'url("/images/falukant/houses.png")',
backgroundPosition: `-${x}px -${y}px`,
backgroundSize: `${columns * spriteSize}px auto`, // z.B. 1023px auto
};
},
buyCost(house) {
const houseQuality = (house.roofCondition + house.windowCondition + house.floorCondition + house.wallCondition) / 4;
return (house.houseType.cost / 100 * houseQuality).toFixed(2);
},
getWorth() {
const house = {...this.userHouse, houseType: this.houseType};
const buyWorth = this.buyCost(house);
return (buyWorth * 0.8).toFixed(2);
},
async buyHouse(houseId) {
try {
const response = await apiClient.post('/api/falukant/houses',
{
houseId: houseId,
}
);
this.$router.push({ name: 'HouseView' });
} catch (error) {
console.error('Fehler beim Kaufen des Hauses:', error);
}
},
async getHouseData() {
await this.loadUserHouse();
await this.loadBuyableHouses();
}
currency: '€'
};
},
computed: {
...mapState(['socket', 'daemonSocket']),
getHouseStyle() {
if (!this.userHouse || this.userHouse.position === undefined || this.userHouse.position === null) {
return {};
}
return this.houseStyle(this.userHouse.position);
},
getHouseType(position) {
const houseType = this.houseTypes[position];
return houseType;
},
getHouseStatus(position) {
const houseStatus = this.houseStatuses[position];
return houseStatus;
},
getRenovationCost(index, status) {
const houseType = this.houseTypes[position];
const renovationCost = houseType.renovationCosts[status];
return renovationCost;
},
allRenovated() {
return Object.values(this.status).every(v => v >= 100);
}
},
created() {
methods: {
async loadData() {
try {
const userRes = await apiClient.get('/api/falukant/houses');
this.userHouse = userRes.data;
this.houseType = this.userHouse.houseType;
const { roofCondition, wallCondition, floorCondition, windowCondition } = this.userHouse;
this.status = { roofCondition, wallCondition, floorCondition, windowCondition };
const buyRes = await apiClient.get('/api/falukant/houses/buyable');
this.buyableHouses = buyRes.data;
} catch (err) {
console.error('Error loading house data', err);
}
},
houseStyle(position, picSize) {
const columns = 3;
const size = picSize;
const index = position - 1;
const x = (index % columns) * size;
const y = Math.floor(index / columns) * size;
return {
backgroundImage: 'url("/images/falukant/houses.png")',
backgroundPosition: `-${x}px -${y}px`,
backgroundSize: `${columns * size}px auto`
};
},
formatPrice(value) {
return new Intl.NumberFormat('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value);
},
getRenovationCost(key, value) {
const base = this.userHouse.houseType.cost || 0;
const weights = { roofCondition: 0.25, wallCondition: 0.25, floorCondition: 0.25, windowCondition: 0.25 };
const weight = weights[key] || 0;
const missing = 100 - value;
const cost = (missing / 100) * base * weight;
return this.formatPrice(cost);
},
getAllRenovationCost() {
const total = Object.keys(this.status).reduce((sum, k) => {
const raw = parseFloat(this.getRenovationCost(k, this.status[k]).replace(/\./g, '').replace(',', '.'));
return sum + (isNaN(raw) ? 0 : raw);
}, 0);
return this.formatPrice(total * 0.8);
},
getWorth() {
const vals = Object.values(this.status);
if (!vals.length) return this.formatPrice(0);
const avg = vals.reduce((s, v) => s + v, 0) / vals.length;
const price = this.houseType.cost || 0;
return this.formatPrice(price * avg / 100 * 0.8);
},
buyCost(house) {
const avg = (house.roofCondition + house.wallCondition + house.floorCondition + house.windowCondition) / 4;
return this.formatPrice(house.houseType.cost * avg / 100);
},
async renovate(key) {
try {
await apiClient.post('/api/falukant/houses/renovate', { element: key });
await this.loadData();
} catch (err) {
console.error('Error renovating', err);
}
},
async renovateAll() {
try {
await apiClient.post('/api/falukant/houses/renovate-all');
await this.loadData();
} catch (err) {
console.error('Error renovating all', err);
}
},
async sellHouse() {
try {
await apiClient.post('/api/falukant/houses/sell');
await this.loadData();
} catch (err) {
console.error('Error selling house', err);
}
},
async buyHouse(id) {
try {
await apiClient.post('/api/falukant/houses', { houseId: id });
await this.loadData();
} catch (err) {
console.error('Error buying house', err);
}
},
handleDaemonMessage(evt) {
try {
const msg = JSON.parse(evt.data);
if (msg.event === 'houseupdated') this.loadData();
} catch {}
}
},
async mounted() {
this.loadHouseTypes();
await this.getHouseData();
if (this.socket) {
this.socket.on("falukantHouseUpdate", this.getHouseData);
}
if (this.daemonSocket) {
this.daemonSocket.addEventListener("message", this.handleDaemonSocketMessage);
}
await this.loadData();
if (this.socket) this.socket.on('falukantHouseUpdate', this.loadData);
if (this.daemonSocket) this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
},
beforeUnmount() {
if (this.socket) {
this.socket.off("falukantHouseUpdate", this.fetchStatus);
}
if (this.daemonSocket) {
this.daemonSocket.removeEventListener("message", this.handleDaemonSocketMessage);
}
},
watch: {
if (this.socket) this.socket.off('falukantHouseUpdate', this.loadData);
if (this.daemonSocket) this.daemonSocket.removeEventListener('message', this.handleDaemonMessage);
}
}
};
</script>
<style lang="scss" scoped>
<style scoped>
.house-view {
display: flex;
flex-direction: column;
gap: 20px;
}
h2 {
padding-top: 20px;
margin: 0 0 10px;
}
.existingHouse {
display: block;
width: auto;
height: 255px;
}
Element {
background-position: 71px 54px;
.existing-house {
display: flex;
gap: 20px;
}
.house {
border: 1px solid #ccc;
border-radius: 4px;
background-repeat: no-repeat;
image-rendering: crisp-edges;
transform: scale(0.7);
transform-origin: top left;
display: inline-block;
overflow: hidden;
width: 341px;
height: 341px;
}
.statusreport {
display: inline-block;
vertical-align: top;
height: 250px;
}
.buyableHouseInfo {
vertical-align: top;
}
.housePreview {
transform: scale(0.2);
width: 341px;
height: 341px;
transform-origin: top left;
background-repeat: no-repeat;
image-rendering: crisp-edges;
border: 1px solid #ccc;
border-radius: 4px;
}
.houseView {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
.status-panel {
flex: 1;
}
.buyablehouses {
.buyable-houses {
display: flex;
flex-direction: column;
overflow: hidden;
gap: 10px;
}
.houses-list {
display: flex;
flex-direction: column; /* vertical list */
gap: 20px;
max-height: 400px;
overflow-y: auto; /* vertical scroll if needed */
}
.house-item {
display: flex;
flex-direction: column;
gap: 5px;
align-items: center;
}
.house-preview {
width: 100px;
height: 100px;
background-repeat: no-repeat;
image-rendering: crisp-edges;
border: 1px solid #ccc;
border-radius: 4px;
background-size: contain; /* scale image to container */
background-position: center; /* center sprite */
}
table {
width: 100%;
border-collapse: collapse;
}
table th,
table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
button {
padding: 6px 12px;
cursor: pointer;
}
</style>