diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js index acae733..7e97749 100644 --- a/backend/controllers/falukantController.js +++ b/backend/controllers/falukantController.js @@ -93,6 +93,8 @@ class FalukantController { return result; }); this.setHeir = this._wrapWithUser((userId, req) => this.service.setHeir(userId, req.body.childCharacterId)); + this.getPotentialHeirs = this._wrapWithUser((userId) => this.service.getPotentialHeirs(userId)); + this.selectHeir = this._wrapWithUser((userId, req) => this.service.selectHeir(userId, req.body.heirId)); this.acceptMarriageProposal = this._wrapWithUser((userId, req) => this.service.acceptMarriageProposal(userId, req.body.proposalId)); this.getGifts = this._wrapWithUser((userId) => { console.log('🔍 getGifts called with userId:', userId); diff --git a/backend/routers/falukantRouter.js b/backend/routers/falukantRouter.js index c1e1536..531e64b 100644 --- a/backend/routers/falukantRouter.js +++ b/backend/routers/falukantRouter.js @@ -39,6 +39,8 @@ router.get('/directors', falukantController.getAllDirectors); router.post('/directors', falukantController.updateDirector); router.post('/family/acceptmarriageproposal', falukantController.acceptMarriageProposal); router.post('/family/set-heir', falukantController.setHeir); +router.get('/heirs/potential', falukantController.getPotentialHeirs); +router.post('/heirs/select', falukantController.selectHeir); router.get('/family/gifts', falukantController.getGifts); router.get('/family/children', falukantController.getChildren); router.post('/family/gift', falukantController.sendGift); diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 1dac9d0..5d3c112 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -2925,6 +2925,107 @@ class FalukantService extends BaseService { return { success: true, childCharacterId }; } + async getPotentialHeirs(hashedUserId) { + const user = await getFalukantUserOrFail(hashedUserId); + + // Prüfe, ob der User bereits einen Charakter hat + const existingCharacter = await FalukantCharacter.findOne({ where: { userId: user.id } }); + if (existingCharacter) { + throw new Error('User already has a character'); + } + + if (!user.mainBranchRegionId) { + throw new Error('User has no main branch region'); + } + + // Hole den noncivil Titel + const noncivilTitle = await TitleOfNobility.findOne({ where: { labelTr: 'noncivil' } }); + if (!noncivilTitle) { + throw new Error('Noncivil title not found'); + } + + // Berechne das Datum für 10-14 Jahre alt (in Tagen) + const now = new Date(); + now.setHours(0, 0, 0, 0); + const minDate = new Date(now); + minDate.setDate(minDate.getDate() - 14); // 14 Tage = 14 Jahre + const maxDate = new Date(now); + maxDate.setDate(maxDate.getDate() - 10); // 10 Tage = 10 Jahre + + // Hole zufällige Charaktere aus der Hauptregion, die 10-14 Jahre alt sind + // und keinen userId haben (also noch keinem User zugeordnet sind) + // und den noncivil Titel haben + const potentialHeirs = await FalukantCharacter.findAll({ + where: { + regionId: user.mainBranchRegionId, + userId: null, + titleOfNobility: noncivilTitle.id, + birthdate: { + [Op.between]: [minDate, maxDate] + } + }, + include: [ + { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }, + { model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] } + ], + order: Sequelize.fn('RANDOM'), + limit: 5 + }); + + // Berechne das Alter für jeden Charakter + return potentialHeirs.map(heir => { + const age = calcAge(heir.birthdate); + return { + id: heir.id, + definedFirstName: heir.definedFirstName, + definedLastName: heir.definedLastName, + gender: heir.gender, + age: age + }; + }); + } + + async selectHeir(hashedUserId, heirId) { + const user = await getFalukantUserOrFail(hashedUserId); + + // Prüfe, ob der User bereits einen Charakter hat + const existingCharacter = await FalukantCharacter.findOne({ where: { userId: user.id } }); + if (existingCharacter) { + throw new Error('User already has a character'); + } + + // Hole den Erben-Charakter + const heir = await FalukantCharacter.findOne({ + where: { + id: heirId, + userId: null // Stelle sicher, dass er noch keinem User zugeordnet ist + } + }); + + if (!heir) { + throw new Error('Heir not found or already assigned'); + } + + // Prüfe, ob der Erbe in der Hauptregion ist + if (heir.regionId !== user.mainBranchRegionId) { + throw new Error('Heir is not in main branch region'); + } + + // Prüfe das Alter (10-14 Jahre) + const age = calcAge(heir.birthdate); + if (age < 10 || age > 14) { + throw new Error('Heir age is not between 10 and 14'); + } + + // Weise den Charakter dem User zu + await heir.update({ userId: user.id }); + + // Benachrichtige den User + notifyUser(hashedUserId, 'falukantUserUpdated', {}); + + return { success: true, characterId: heir.id }; + } + async getPossiblePartners(requestingCharacterId) { const proposals = await MarriageProposal.findAll({ where: { diff --git a/frontend/src/i18n/locales/de/falukant.json b/frontend/src/i18n/locales/de/falukant.json index 92be7b8..e601d74 100644 --- a/frontend/src/i18n/locales/de/falukant.json +++ b/frontend/src/i18n/locales/de/falukant.json @@ -135,6 +135,14 @@ "store": "Verkauf", "fullstack": "Produktion mit Verkauf" } + }, + "heirSelection": { + "title": "Charakter verloren - Erben auswählen", + "description": "Dein Charakter wurde durch einen Fehler verloren. Bitte wähle einen Erben aus deiner Hauptregion aus, um fortzufahren.", + "loading": "Lade mögliche Erben...", + "noHeirs": "Es wurden keine passenden Erben gefunden.", + "select": "Als Erben wählen", + "error": "Fehler beim Auswählen des Erben." } }, "titles": { diff --git a/frontend/src/i18n/locales/en/falukant.json b/frontend/src/i18n/locales/en/falukant.json index 81ff5c2..a168749 100644 --- a/frontend/src/i18n/locales/en/falukant.json +++ b/frontend/src/i18n/locales/en/falukant.json @@ -206,6 +206,39 @@ } } }, + "overview": { + "title": "Falukant - Overview", + "metadata": { + "title": "Personal", + "name": "Name", + "money": "Wealth", + "age": "Age", + "mainbranch": "Home City", + "nobleTitle": "Status" + }, + "productions": { + "title": "Productions" + }, + "stock": { + "title": "Stock" + }, + "branches": { + "title": "Branches", + "level": { + "production": "Production", + "store": "Store", + "fullstack": "Production with Store" + } + }, + "heirSelection": { + "title": "Character Lost - Select Heir", + "description": "Your character was lost due to an error. Please select an heir from your main region to continue.", + "loading": "Loading potential heirs...", + "noHeirs": "No suitable heirs were found.", + "select": "Select as Heir", + "error": "Error selecting heir." + } + }, "nobility": { "cooldown": "You can only advance again on {date}." }, diff --git a/frontend/src/views/falukant/OverviewView.vue b/frontend/src/views/falukant/OverviewView.vue index c7837c9..b968f9b 100644 --- a/frontend/src/views/falukant/OverviewView.vue +++ b/frontend/src/views/falukant/OverviewView.vue @@ -2,19 +2,45 @@

{{ $t('falukant.overview.title') }}

-
+ + +
+

{{ $t('falukant.overview.heirSelection.title') }}

+

{{ $t('falukant.overview.heirSelection.description') }}

+
{{ $t('falukant.overview.heirSelection.loading') }}
+
+ {{ $t('falukant.overview.heirSelection.noHeirs') }} +
+
+
+
+
+ {{ $t(`falukant.titles.${heir.gender}.noncivil`) }} + {{ heir.definedFirstName.name }} {{ heir.definedLastName.name }} +
+
{{ $t('falukant.overview.metadata.age') }}: {{ heir.age }}
+
+ +
+
+
+ + +

{{ $t('falukant.overview.metadata.title') }}

- + - + @@ -26,11 +52,11 @@ - + - +
{{ $t('falukant.overview.metadata.name') }}{{ falukantUser?.character.definedFirstName.name }} {{ - falukantUser?.character.definedLastName.name }}{{ falukantUser?.character?.definedFirstName?.name }} {{ + falukantUser?.character?.definedLastName?.name }}
{{ $t('falukant.overview.metadata.nobleTitle') }}{{ $t('falukant.titles.' + falukantUser?.character.gender + '.' + - falukantUser?.character.nobleTitle.labelTr) }}{{ $t('falukant.titles.' + falukantUser?.character?.gender + '.' + + falukantUser?.character?.nobleTitle?.labelTr) }}
{{ $t('falukant.overview.metadata.money') }}
{{ $t('falukant.overview.metadata.age') }}{{ falukantUser?.character.age }}{{ falukantUser?.character?.age }}
{{ $t('falukant.overview.metadata.mainbranch') }}{{ falukantUser?.mainBranchRegion.name }}{{ falukantUser?.mainBranchRegion?.name }}
@@ -90,7 +116,7 @@
-
+
@@ -149,12 +175,14 @@ export default { falukantUser: null, allStock: [], productions: [], + potentialHeirs: [], + loadingHeirs: false, }; }, computed: { ...mapState(['socket']), getAvatarStyle() { - if (!this.falukantUser) return {}; + if (!this.falukantUser || !this.falukantUser.character) return {}; const { gender, age } = this.falukantUser.character; const imageUrl = `/images/falukant/avatar/${gender}.png`; const ageGroup = this.getAgeGroup(age); @@ -212,8 +240,12 @@ export default { }, async mounted() { await this.fetchFalukantUser(); - await this.fetchAllStock(); - await this.fetchProductions(); + if (!this.falukantUser?.character) { + await this.fetchPotentialHeirs(); + } else { + await this.fetchAllStock(); + await this.fetchProductions(); + } // Daemon WebSocket deaktiviert - verwende Socket.io für alle Events this.setupSocketEvents(); }, @@ -306,6 +338,33 @@ export default { formatDate(timestamp) { return new Date(timestamp).toLocaleString(); }, + async fetchPotentialHeirs() { + if (!this.falukantUser?.mainBranchRegion?.id) return; + this.loadingHeirs = true; + try { + const response = await apiClient.get('/api/falukant/heirs/potential'); + this.potentialHeirs = response.data || []; + } catch (error) { + console.error('Error fetching potential heirs:', error); + this.potentialHeirs = []; + } finally { + this.loadingHeirs = false; + } + }, + async selectHeir(heirId) { + try { + await apiClient.post('/api/falukant/heirs/select', { heirId }); + // Lade User-Daten neu + await this.fetchFalukantUser(); + if (this.falukantUser?.character) { + await this.fetchAllStock(); + await this.fetchProductions(); + } + } catch (error) { + console.error('Error selecting heir:', error); + alert(this.$t('falukant.overview.heirSelection.error')); + } + }, }, }; @@ -348,4 +407,68 @@ export default { h2 { padding-top: 20px; } + +.heir-selection-container { + border: 2px solid #dc3545; + border-radius: 8px; + padding: 20px; + margin: 20px 0; + background-color: #fff3cd; +} + +.heir-selection-container h3 { + margin-top: 0; + color: #856404; +} + +.heirs-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 15px; + margin-top: 20px; +} + +.heir-card { + border: 1px solid #ccc; + border-radius: 4px; + padding: 15px; + background-color: white; + display: flex; + justify-content: space-between; + align-items: center; +} + +.heir-info { + flex: 1; +} + +.heir-name { + font-weight: bold; + margin-bottom: 5px; +} + +.heir-age { + color: #666; + font-size: 0.9em; +} + +.select-heir-button { + background-color: #28a745; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 0.9em; +} + +.select-heir-button:hover { + background-color: #218838; +} + +.loading, .no-heirs { + text-align: center; + padding: 20px; + color: #666; +}