feat(falukant): enhance marriage actions with production delay handling and UI updates
All checks were successful
Deploy to production / deploy (push) Successful in 2m58s

- Implemented logic in FalukantService to delay running productions when spending time with a spouse, updating the start timestamp accordingly.
- Enhanced the response from the spend time API to include details about delayed productions and household order changes.
- Updated FamilyView component to display the cost of marriage actions and added a help section detailing the effects of actions, including production delays.
- Added new translations for action effects in multiple languages to improve user understanding of the marriage actions.
This commit is contained in:
Torsten Schulz (local)
2026-04-13 12:09:17 +02:00
parent ba72d4fb74
commit 1bce855b3a
7 changed files with 136 additions and 10 deletions

View File

@@ -3886,17 +3886,64 @@ class FalukantService extends BaseService {
});
}
const userHouse = await UserHouse.findOne({
where: { userId: user.id },
attributes: ['householdOrder']
});
const productionDelayMinutes = 15;
const runningProductions = await Production.findAll({
where: { sleep: false },
include: [{
model: Branch,
as: 'branch',
required: true,
where: { falukantUserId: user.id },
attributes: ['id']
}],
attributes: ['id', 'startTimestamp']
});
const nextSatisfaction = this.clampScore((state.marriageSatisfaction ?? 55) + 2);
const nextPublicStability = this.clampScore((state.marriagePublicStability ?? 55) + 1);
await state.update({
marriageSatisfaction: nextSatisfaction,
marriagePublicStability: nextPublicStability
const nextHouseholdOrder = userHouse
? this.clampScore(Number(userHouse.householdOrder ?? 55) - 2)
: null;
const delayMs = productionDelayMinutes * 60 * 1000;
let delayedProductions = 0;
await sequelize.transaction(async (t) => {
await state.update({
marriageSatisfaction: nextSatisfaction,
marriagePublicStability: nextPublicStability
});
if (userHouse && nextHouseholdOrder != null) {
await userHouse.update({
householdOrder: nextHouseholdOrder
});
}
for (const production of runningProductions) {
const startAt = new Date(production.startTimestamp);
if (Number.isNaN(startAt.getTime())) {
continue;
}
await production.update(
{ startTimestamp: new Date(startAt.getTime() + delayMs) },
{ transaction: t }
);
delayedProductions += 1;
}
});
await this.refreshHouseholdTensionState(user, user.character);
await notifyUser(hashedUserId, 'falukantUpdateFamily', { reason: 'daily' });
await notifyUser(hashedUserId, 'falukantUpdateStatus', {});
return { success: true, marriageSatisfaction: nextSatisfaction };
return {
success: true,
marriageSatisfaction: nextSatisfaction,
marriagePublicStability: nextPublicStability,
householdOrder: nextHouseholdOrder,
delayedProductions,
productionDelayMinutes
};
}
async giftToSpouse(hashedUserId, giftLevel) {

View File

@@ -938,7 +938,14 @@
"giftDecent": "Decent regalo",
"giftLavish": "Lavish regalo",
"reconcile": "Reconcile dispute",
"costFree": "free",
"effectHintTitle": "Gasto ug epekto sa kini nga mga aksyon",
"effectSpendTime": "Conversation/time together: walay gasto sa kwarta, +2 marriage satisfaction, +1 public stability, -2 household order ug +{minutes} minutos sa running productions.",
"effectGifts": "Ang mga regalo naay gasto ug mas kusog nga epekto: small {small} (+2/+1), decent {decent} (+4/+2), lavish {lavish} (+7/+3).",
"effectReconcile": "Reconcile dispute: walay gasto sa kwarta, +1 marriage satisfaction, +1 public stability.",
"effectProduction": "Ang production effect daemon-based: ang running productions makadawat ug +{minutes} minutos pinaagi sa start timestamp ug mahuman nga mas ulahi.",
"spendTimeSuccess": "Ang time together naay stabilized ang kasal.",
"spendTimeSuccessWithDelay": "Ang time together naay stabilized ang kasal. {count} ka running production na-delay ug {minutes} minutos.",
"giftSuccess": "Ang regalo naay improved ang kasal.",
"reconcileSuccess": "Ang dispute naay eased para sa now.",
"actionError": "Ang action could dili be completed."

View File

@@ -695,7 +695,14 @@
"giftDecent": "Gutes Geschenk",
"giftLavish": "Großzügiges Geschenk",
"reconcile": "Streit schlichten",
"costFree": "kostenlos",
"effectHintTitle": "Kosten und Wirkung dieser Aktionen",
"effectSpendTime": "Gespräch/Zeit miteinander: keine Geldkosten, +2 Ehezufriedenheit, +1 öffentliche Stabilität, -2 Haushaltsordnung und laufende Produktionen +{minutes} Minuten.",
"effectGifts": "Geschenke kosten Geld und wirken stärker: klein {small} (+2/+1), gut {decent} (+4/+2), großzügig {lavish} (+7/+3).",
"effectReconcile": "Streit schlichten: keine Geldkosten, +1 Ehezufriedenheit, +1 öffentliche Stabilität.",
"effectProduction": "Die Produktionswirkung ist daemonbasiert: Laufende Produktionen erhalten +{minutes} Minuten über den Startzeitpunkt und werden entsprechend später abgeschlossen.",
"spendTimeSuccess": "Die gemeinsame Zeit hat die Ehe stabilisiert.",
"spendTimeSuccessWithDelay": "Die gemeinsame Zeit hat die Ehe stabilisiert. {count} laufende Produktion(en) wurden um {minutes} Minuten verzögert.",
"giftSuccess": "Das Geschenk hat die Ehe verbessert.",
"reconcileSuccess": "Der Streit wurde fürs Erste geschlichtet.",
"actionError": "Die Aktion konnte nicht ausgeführt werden."

View File

@@ -882,7 +882,14 @@
"giftDecent": "Decent gift",
"giftLavish": "Lavish gift",
"reconcile": "Reconcile dispute",
"costFree": "free",
"effectHintTitle": "Costs and effects of these actions",
"effectSpendTime": "Conversation/time together: no money cost, +2 marriage satisfaction, +1 public stability, -2 household order, and +{minutes} minutes on running productions.",
"effectGifts": "Gifts cost money and have stronger effects: small {small} (+2/+1), decent {decent} (+4/+2), lavish {lavish} (+7/+3).",
"effectReconcile": "Reconcile dispute: no money cost, +1 marriage satisfaction, +1 public stability.",
"effectProduction": "Production impact is daemon-based: running productions get +{minutes} minutes via their start timestamp and therefore finish later.",
"spendTimeSuccess": "The time together has stabilized the marriage.",
"spendTimeSuccessWithDelay": "The time together has stabilized the marriage. {count} running production(s) were delayed by {minutes} minutes.",
"giftSuccess": "The gift has improved the marriage.",
"reconcileSuccess": "The dispute has been eased for now.",
"actionError": "The action could not be completed."

View File

@@ -695,7 +695,14 @@
"giftDecent": "Buen regalo",
"giftLavish": "Regalo generoso",
"reconcile": "Resolver disputa",
"costFree": "gratis",
"effectHintTitle": "Costes y efectos de estas acciones",
"effectSpendTime": "Conversación/tiempo juntos: sin coste de dinero, +2 satisfacción matrimonial, +1 estabilidad pública, -2 de orden del hogar y +{minutes} minutos en producciones en curso.",
"effectGifts": "Los regalos cuestan dinero y tienen efectos más fuertes: pequeño {small} (+2/+1), bueno {decent} (+4/+2), generoso {lavish} (+7/+3).",
"effectReconcile": "Resolver disputa: sin coste de dinero, +1 satisfacción matrimonial, +1 estabilidad pública.",
"effectProduction": "El efecto de producción es basado en daemon: las producciones en curso reciben +{minutes} minutos mediante su marca de inicio y por eso terminan más tarde.",
"spendTimeSuccess": "El tiempo compartido ha estabilizado el matrimonio.",
"spendTimeSuccessWithDelay": "El tiempo compartido ha estabilizado el matrimonio. {count} producción(es) en curso se retrasaron {minutes} minutos.",
"giftSuccess": "El regalo ha mejorado el matrimonio.",
"reconcileSuccess": "La disputa se ha calmado por ahora.",
"actionError": "No se pudo realizar la acción."

View File

@@ -693,7 +693,14 @@
"giftDecent": "Bon cadeau",
"giftLavish": "Cadeau généreux",
"reconcile": "régler les différends",
"costFree": "gratuit",
"effectHintTitle": "Coûts et effets de ces actions",
"effectSpendTime": "Conversation/temps ensemble : pas de coût en argent, +2 satisfaction du mariage, +1 stabilité publique, -2 ordre du foyer et +{minutes} minutes sur les productions en cours.",
"effectGifts": "Les cadeaux coûtent de l'argent et ont des effets plus forts : petit {small} (+2/+1), bon {decent} (+4/+2), généreux {lavish} (+7/+3).",
"effectReconcile": "Régler les différends : pas de coût en argent, +1 satisfaction du mariage, +1 stabilité publique.",
"effectProduction": "L'effet de production est piloté par le daemon : les productions en cours reçoivent +{minutes} minutes via leur horodatage de départ et se terminent donc plus tard.",
"spendTimeSuccess": "Le temps passé ensemble a stabilisé le mariage.",
"spendTimeSuccessWithDelay": "Le temps passé ensemble a stabilisé le mariage. {count} production(s) en cours ont été retardées de {minutes} minutes.",
"giftSuccess": "Le cadeau a amélioré le mariage.",
"reconcileSuccess": "Le différend était pour le moment réglé.",
"actionError": "L'action n'a pas pu être réalisée."

View File

@@ -238,21 +238,30 @@
<h3>{{ $t('falukant.family.marriageActions.title') }}</h3>
<div class="marriage-actions__buttons">
<button class="button button--secondary" @click="spendTimeWithSpouse">
{{ $t('falukant.family.marriageActions.spendTime') }}
{{ $t('falukant.family.marriageActions.spendTime') }} ({{ $t('falukant.family.marriageActions.costFree') }})
</button>
<button class="button button--secondary" @click="giftToSpouse('small')">
{{ $t('falukant.family.marriageActions.giftSmall') }}
{{ $t('falukant.family.marriageActions.giftSmall') }} ({{ formatCost(marriageGiftCosts.small) }})
</button>
<button class="button button--secondary" @click="giftToSpouse('decent')">
{{ $t('falukant.family.marriageActions.giftDecent') }}
{{ $t('falukant.family.marriageActions.giftDecent') }} ({{ formatCost(marriageGiftCosts.decent) }})
</button>
<button class="button button--secondary" @click="giftToSpouse('lavish')">
{{ $t('falukant.family.marriageActions.giftLavish') }}
{{ $t('falukant.family.marriageActions.giftLavish') }} ({{ formatCost(marriageGiftCosts.lavish) }})
</button>
<button class="button button--secondary" @click="reconcileMarriage">
{{ $t('falukant.family.marriageActions.reconcile') }}
{{ $t('falukant.family.marriageActions.reconcile') }} ({{ $t('falukant.family.marriageActions.costFree') }})
</button>
</div>
<details class="marriage-actions__help">
<summary>{{ $t('falukant.family.marriageActions.effectHintTitle') }}</summary>
<ul>
<li>{{ $t('falukant.family.marriageActions.effectSpendTime', { minutes: 15 }) }}</li>
<li>{{ $t('falukant.family.marriageActions.effectGifts', { small: formatCost(marriageGiftCosts.small), decent: formatCost(marriageGiftCosts.decent), lavish: formatCost(marriageGiftCosts.lavish) }) }}</li>
<li>{{ $t('falukant.family.marriageActions.effectReconcile') }}</li>
<li>{{ $t('falukant.family.marriageActions.effectProduction', { minutes: 15 }) }}</li>
</ul>
</details>
<div v-if="householdTensionReasons.length > 0" class="marriage-actions__reasons">
<span class="marriage-actions__reasons-label">{{ $t('falukant.family.householdTension.reasonsLabel') }}</span>
<div class="marriage-actions__reason-list">
@@ -486,6 +495,11 @@ import { confirmAction, showError, showInfo, showSuccess } from '@/utils/feedbac
import { mapState } from 'vuex'
const WOOING_PROGRESS_TARGET = 70
const MARRIAGE_GIFT_COSTS = {
small: 25,
decent: 80,
lavish: 180
}
export default {
name: 'FamilyView',
@@ -527,6 +541,9 @@ export default {
},
computed: {
...mapState(['socket', 'daemonSocket', 'user']),
marriageGiftCosts() {
return MARRIAGE_GIFT_COSTS;
},
partnerSummaryLine() {
if (this.relationships?.length > 0) {
const r = this.relationships[0];
@@ -713,8 +730,20 @@ export default {
async spendTimeWithSpouse() {
try {
await apiClient.post('/api/falukant/family/marriage/spend-time');
const { data } = await apiClient.post('/api/falukant/family/marriage/spend-time');
await this.loadFamilyData();
const delayed = Number(data?.delayedProductions || 0);
const delayMinutes = Number(data?.productionDelayMinutes || 15);
if (delayed > 0) {
showInfo(
this,
this.$t('falukant.family.marriageActions.spendTimeSuccessWithDelay', {
count: delayed,
minutes: delayMinutes
})
);
return;
}
showSuccess(this, this.$t('falukant.family.marriageActions.spendTimeSuccess'));
} catch (error) {
console.error('Error spending time with spouse:', error);
@@ -1217,6 +1246,21 @@ export default {
gap: 10px;
}
.marriage-actions__help {
margin: -2px 0 0;
color: var(--color-text-secondary);
}
.marriage-actions__help summary {
cursor: pointer;
font-weight: 600;
}
.marriage-actions__help ul {
margin: 8px 0 0;
padding-left: 18px;
}
.marriage-actions__reasons {
display: grid;
gap: 8px;