feat(bisaya): implement child marriage proposal and wooing features, update age rules and relationships
All checks were successful
Deploy to production / deploy (push) Successful in 3m15s

This commit is contained in:
Torsten Schulz (local)
2026-09-08 14:46:26 +02:00
parent 0a9174c486
commit 3d235a621a
12 changed files with 281 additions and 90 deletions

View File

@@ -207,10 +207,17 @@ export default {
.app-section-bar__back {
flex: 0 0 auto;
background: rgba(255, 255, 255, 0.82);
color: var(--color-text-primary);
box-shadow: none;
border: 1px solid var(--color-border);
}
.app-section-bar__back:hover:not(:disabled) {
background: var(--color-secondary-soft);
color: var(--color-text-primary);
box-shadow: none;
}
@media (max-width: 760px) {
.app-section-bar {
flex-direction: column;

View File

@@ -775,6 +775,20 @@
"setAsHeir": "Als Erben festlegen",
"heirSetSuccess": "Das Kind wurde erfolgreich als Erbe festgelegt.",
"heirSetError": "Fehler beim Festlegen des Erben.",
"matchmaking": "Verkuppelung",
"selectPartner": "Partner auswählen",
"arrange": "Werbung arrangieren",
"arrangeSuccess": "Die Werbung des Kindes wurde arrangiert.",
"arrangeError": "Die Verkuppelung konnte nicht durchgeführt werden.",
"completeWooing": "Werbung abschließen",
"engagedSuccess": "Das Kind ist nun verlobt.",
"weddingHint": "Hochzeitsfeier ab 14 Jahren möglich.",
"noPartnerProposal": "Noch keine passenden Vorschläge.",
"relationshipStatus": {
"wooing": "Werbung läuft",
"engaged": "Verlobt",
"married": "Verheiratet"
},
"actions": "Aktionen",
"none": "Keine Kinder vorhanden.",
"detailButton": "Details anzeigen",

View File

@@ -314,6 +314,7 @@
<th>{{ $t('falukant.family.children.otherParent') }}</th>
<th>{{ $t('falukant.family.children.age') }}</th>
<th>{{ $t('falukant.family.children.heir') }}</th>
<th>{{ $t('falukant.family.children.matchmaking') }}</th>
<th>{{ $t('falukant.family.children.actions') }}</th>
</tr>
</thead>
@@ -365,6 +366,38 @@
{{ $t('falukant.family.children.setAsHeir') }}
</button>
</td>
<td class="child-matchmaking-cell">
<template v-if="child.relationship">
<strong>{{ child.relationship.partnerName }}</strong>
<span>{{ $t('falukant.family.children.relationshipStatus.' + child.relationship.status) }}</span>
<button
v-if="child.relationship.status === 'wooing'"
type="button"
@click="advanceChildWooing(child)"
>
{{ $t('falukant.family.children.completeWooing') }}
</button>
<span v-else-if="child.relationship.status === 'engaged'" class="child-matchmaking-hint">
{{ $t('falukant.family.children.weddingHint') }}
</span>
</template>
<template v-else-if="child.hasName && child.possiblePartners?.length">
<select v-model="selectedChildProposalIds[child.childCharacterId]">
<option :value="null">{{ $t('falukant.family.children.selectPartner') }}</option>
<option v-for="proposal in child.possiblePartners" :key="proposal.id" :value="proposal.proposedCharacterId">
{{ proposal.proposedCharacterName }} ({{ proposal.proposedCharacterAge }})
</option>
</select>
<button
type="button"
:disabled="!selectedChildProposalIds[child.childCharacterId]"
@click="acceptChildProposal(child)"
>
{{ $t('falukant.family.children.arrange') }}
</button>
</template>
<span v-else-if="child.hasName">{{ $t('falukant.family.children.noPartnerProposal') }}</span>
</td>
<td>
<button @click="showChildDetails(child)">
{{ $t('falukant.family.children.detailButton') }}
@@ -670,6 +703,7 @@ export default {
},
pregnancy: null,
selectedChild: null,
selectedChildProposalIds: {},
pendingFamilyRefresh: null,
familyTab: 'partner',
visualSettings: { ...FALUKANT_VISUAL_DEFAULTS },
@@ -854,6 +888,9 @@ export default {
const response = await apiClient.get('/api/falukant/family');
this.relationships = response.data.relationships;
this.children = response.data.children;
this.selectedChildProposalIds = Object.fromEntries(
this.children.map((child) => [child.childCharacterId, this.selectedChildProposalIds[child.childCharacterId] || null])
);
this.lovers = response.data.lovers;
this.politicalFreeLoverSlots = Number(response.data.politicalFreeLoverSlots) || 0;
this.possibleLovers = response.data.possibleLovers || [];
@@ -978,6 +1015,30 @@ export default {
}
},
async acceptChildProposal(child) {
const proposedCharacterId = this.selectedChildProposalIds[child.childCharacterId];
if (!proposedCharacterId) return;
try {
await apiClient.post(`/api/falukant/family/children/${child.childCharacterId}/accept-marriage-proposal`, { proposedCharacterId });
await this.loadFamilyData();
showSuccess(this, this.$t('falukant.family.children.arrangeSuccess'));
} catch (error) {
console.error('Error arranging child marriage:', error);
showError(this, error?.response?.data?.message || this.$t('falukant.family.children.arrangeError'));
}
},
async advanceChildWooing(child) {
try {
await apiClient.post(`/api/falukant/family/children/${child.childCharacterId}/advance-wooing`);
await this.loadFamilyData();
showSuccess(this, this.$t('falukant.family.children.engagedSuccess'));
} catch (error) {
console.error('Error advancing child wooing:', error);
showError(this, error?.response?.data?.message || this.$t('falukant.family.children.arrangeError'));
}
},
async setLoverMaintenance(lover, maintenanceLevel) {
try {
await apiClient.post(`/api/falukant/family/lover/${lover.relationshipId}/maintenance`, {

View File

@@ -76,6 +76,15 @@
</label>
<div v-if="newPartyTypeId" class="party-options">
<label v-if="selectedPartyType?.forMarriage">
Hochzeitspaar:
<select v-model.number="relationshipId">
<option :value="null">Bitte Verlobte auswählen</option>
<option v-for="candidate in weddingCandidates" :key="candidate.relationshipId" :value="candidate.relationshipId">
{{ candidate.label }}
</option>
</select>
</label>
<label>
{{ $t('falukant.reputation.party.music.label') }}:
<select v-model.number="musicId">
@@ -214,6 +223,8 @@ export default {
nobilityTitles: [],
selectedNobilityIds: [],
servantRatio: 50,
relationshipId: null,
weddingCandidates: [],
inProgressParties: [],
completedParties: [],
reputation: null,
@@ -235,6 +246,7 @@ export default {
this.partyTypes = data.partyTypes;
this.musicTypes = data.musicTypes;
this.banquetteTypes = data.banquetteTypes;
this.weddingCandidates = data.weddingCandidates || [];
this.musicId = this.musicTypes[0]?.id;
this.banquetteId = this.banquetteTypes[0]?.id;
},
@@ -310,7 +322,8 @@ export default {
musicId: this.musicId,
banquetteId: this.banquetteId,
nobilityIds: this.selectedNobilityIds.map(n => n.id ?? n),
servantRatio: this.servantRatio
servantRatio: this.servantRatio,
relationshipId: this.relationshipId
});
this.toggleNewPartyView();
},
@@ -343,6 +356,10 @@ export default {
maximumFractionDigits: 2
});
}
,
selectedPartyType() {
return this.partyTypes.find((type) => type.id === this.newPartyTypeId) || null;
}
},
async mounted() {
const tabFromQuery = this.$route?.query?.tab;