diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index e5bb5f4..db10ee8 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -70,6 +70,7 @@ import TownProductWorth from '../models/falukant/data/town_product_worth.js'; import ProductWeatherEffect from '../models/falukant/type/product_weather_effect.js'; import WeatherType from '../models/falukant/type/weather.js'; import ReputationActionType from '../models/falukant/type/reputation_action.js'; +import ReputationActionLog from '../models/falukant/log/reputation_action.js'; function calcAge(birthdate) { const b = new Date(birthdate); b.setHours(0, 0); @@ -465,7 +466,7 @@ class FalukantService extends BaseService { { model: FalukantCharacter, as: 'character', - attributes: ['id', 'birthdate', 'health'], + attributes: ['id', 'birthdate', 'health', 'reputation'], include: [ { model: Relationship, @@ -3007,10 +3008,96 @@ class FalukantService extends BaseService { return TitleOfNobility.findAll(); } - async getReputationActions() { - return ReputationActionType.findAll({ + async getReputationActions(hashedUserId) { + const user = await getFalukantUserOrFail(hashedUserId); + + // Lade alle Action-Typen + const actionTypes = await ReputationActionType.findAll({ order: [['cost', 'ASC']] }); + + // Berechne tägliche Nutzung (heute) + const todayStart = new Date(); + todayStart.setHours(0, 0, 0, 0); + const todayEnd = new Date(); + todayEnd.setHours(23, 59, 59, 999); + + const todayActions = await ReputationActionLog.count({ + where: { + falukantUserId: user.id, + actionTimestamp: { + [Op.between]: [todayStart, todayEnd] + } + } + }); + + // Standard-Limits (können später konfigurierbar gemacht werden) + const dailyCap = 10; // Maximal 10 Actions pro Tag + const dailyUsed = todayActions; + const dailyRemaining = Math.max(0, dailyCap - dailyUsed); + + // Cooldown: 60 Minuten zwischen Actions + const cooldownMinutes = 60; + const lastAction = await ReputationActionLog.findOne({ + where: { falukantUserId: user.id }, + order: [['actionTimestamp', 'DESC']], + attributes: ['actionTimestamp'] + }); + + let cooldownRemainingSec = 0; + if (lastAction && lastAction.actionTimestamp) { + const now = new Date(); + const lastActionTime = new Date(lastAction.actionTimestamp); + const secondsSinceLastAction = Math.floor((now - lastActionTime) / 1000); + const cooldownSec = cooldownMinutes * 60; + cooldownRemainingSec = Math.max(0, cooldownSec - secondsSinceLastAction); + } + + // Berechne timesUsed für jede Action (basierend auf decay_window_days) + const actionsWithUsage = await Promise.all(actionTypes.map(async (actionType) => { + const windowStart = new Date(); + windowStart.setDate(windowStart.getDate() - actionType.decayWindowDays); + + const usageCount = await ReputationActionLog.count({ + where: { + falukantUserId: user.id, + actionTypeId: actionType.id, + actionTimestamp: { + [Op.gte]: windowStart + } + } + }); + + // Berechne aktuellen Gewinn basierend auf decay + let currentGain = actionType.baseGain; + for (let i = 0; i < usageCount; i++) { + currentGain = Math.max( + actionType.minGain, + Math.floor(currentGain * actionType.decayFactor) + ); + } + + return { + id: actionType.id, + tr: actionType.tr, + cost: actionType.cost, + baseGain: actionType.baseGain, + currentGain: currentGain, + timesUsed: usageCount, + decayFactor: actionType.decayFactor, + minGain: actionType.minGain, + decayWindowDays: actionType.decayWindowDays + }; + })); + + return { + actions: actionsWithUsage, + dailyCap, + dailyUsed, + dailyRemaining, + cooldownMinutes, + cooldownRemainingSec + }; } async getHouseTypes() { diff --git a/frontend/src/i18n/locales/de/falukant.json b/frontend/src/i18n/locales/de/falukant.json index fbe28cd..ee61a6b 100644 --- a/frontend/src/i18n/locales/de/falukant.json +++ b/frontend/src/i18n/locales/de/falukant.json @@ -651,6 +651,7 @@ "happy": "Glücklich", "sad": "Traurig", "angry": "Wütend", + "calm": "Ruhig", "nervous": "Nervös", "excited": "Aufgeregt", "bored": "Gelangweilt", @@ -746,7 +747,34 @@ "reputation": { "title": "Reputation", "overview": { - "title": "Übersicht" + "title": "Übersicht", + "current": "Aktuelle Reputation" + }, + "actions": { + "title": "Reputations-Aktionen", + "description": "Du kannst verschiedene Aktionen durchführen, um deine Reputation zu verbessern.", + "none": "Keine Reputations-Aktionen verfügbar.", + "action": "Aktion", + "cost": "Kosten", + "gain": "Gewinn", + "timesUsed": "Verwendet", + "execute": "Ausführen", + "running": "Läuft...", + "dailyLimit": "Tägliches Limit: {remaining} von {cap} Aktionen übrig", + "cooldown": "Cooldown: Noch {minutes} Minuten", + "type": { + "soup_kitchen": "Suppenküche", + "library_donation": "Bibliotheksspende", + "scholarships": "Stipendien", + "church_hospice": "Kirchenhospiz", + "school_funding": "Schulfinanzierung", + "orphanage_build": "Waisenhaus bauen", + "bridge_build": "Brücke bauen", + "hospital_donation": "Krankenhausspende", + "patronage": "Mäzenatentum", + "statue_build": "Statue errichten", + "well_build": "Brunnen bauen" + } }, "party": { "title": "Feste", diff --git a/frontend/src/i18n/locales/en/falukant.json b/frontend/src/i18n/locales/en/falukant.json index 081acf1..88dcf8c 100644 --- a/frontend/src/i18n/locales/en/falukant.json +++ b/frontend/src/i18n/locales/en/falukant.json @@ -221,6 +221,60 @@ "nobility": { "cooldown": "You can only advance again on {date}." }, + "mood": { + "happy": "Happy", + "sad": "Sad", + "angry": "Angry", + "calm": "Calm", + "nervous": "Nervous", + "excited": "Excited", + "bored": "Bored", + "fearful": "Fearful", + "confident": "Confident", + "curious": "Curious", + "hopeful": "Hopeful", + "frustrated": "Frustrated", + "lonely": "Lonely", + "grateful": "Grateful", + "jealous": "Jealous", + "guilty": "Guilty", + "apathetic": "Apathetic", + "relieved": "Relieved", + "proud": "Proud", + "ashamed": "Ashamed" + }, + "character": { + "brave": "Brave", + "kind": "Kind", + "greedy": "Greedy", + "wise": "Wise", + "loyal": "Loyal", + "cunning": "Cunning", + "generous": "Generous", + "arrogant": "Arrogant", + "honest": "Honest", + "ambitious": "Ambitious", + "patient": "Patient", + "impatient": "Impatient", + "selfish": "Selfish", + "charismatic": "Charismatic", + "empathetic": "Empathetic", + "timid": "Timid", + "stubborn": "Stubborn", + "resourceful": "Resourceful", + "reckless": "Reckless", + "disciplined": "Disciplined", + "optimistic": "Optimistic", + "pessimistic": "Pessimistic", + "manipulative": "Manipulative", + "independent": "Independent", + "dependent": "Dependent", + "adventurous": "Adventurous", + "humble": "Humble", + "vengeful": "Vengeful", + "pragmatic": "Pragmatic", + "idealistic": "Idealistic" + }, "healthview": { "title": "Health", "age": "Age", @@ -436,6 +490,77 @@ "success": "The child has been baptized.", "error": "The child could not be baptized." } + }, + "reputation": { + "title": "Reputation", + "overview": { + "title": "Overview", + "current": "Current Reputation" + }, + "actions": { + "title": "Reputation Actions", + "description": "You can perform various actions to improve your reputation.", + "none": "No reputation actions available.", + "action": "Action", + "cost": "Cost", + "gain": "Gain", + "timesUsed": "Used", + "execute": "Execute", + "running": "Running...", + "dailyLimit": "Daily limit: {remaining} of {cap} actions remaining", + "cooldown": "Cooldown: {minutes} minutes remaining", + "type": { + "soup_kitchen": "Soup Kitchen", + "library_donation": "Library Donation", + "scholarships": "Scholarships", + "church_hospice": "Church Hospice", + "school_funding": "School Funding", + "orphanage_build": "Build Orphanage", + "bridge_build": "Build Bridge", + "hospital_donation": "Hospital Donation", + "patronage": "Patronage", + "statue_build": "Build Statue", + "well_build": "Build Well" + } + }, + "party": { + "title": "Parties", + "totalCost": "Total Cost", + "order": "Order Party", + "inProgress": "Parties in Preparation", + "completed": "Completed Parties", + "newpartyview": { + "open": "Create New Party", + "close": "Hide New Party", + "type": "Party Type" + }, + "music": { + "label": "Music", + "none": "No Music", + "bard": "A Bard", + "villageBand": "A Village Band", + "chamberOrchestra": "A Chamber Orchestra", + "symphonyOrchestra": "A Symphony Orchestra", + "symphonyOrchestraWithChorusAndSolists": "A Symphony Orchestra with Chorus and Soloists" + }, + "banquette": { + "label": "Food", + "bread": "Bread", + "roastWithBeer": "Roast with Beer", + "poultryWithVegetablesAndWine": "Poultry with Vegetables and Wine", + "extensiveBuffet": "Festive Meal" + }, + "servants": { + "label": "One servant per ", + "perPersons": " persons" + }, + "esteemedInvites": { + "label": "Invited Estates" + }, + "type": "Party Type", + "cost": "Cost", + "date": "Date" + } } } } \ No newline at end of file diff --git a/frontend/src/views/falukant/FamilyView.vue b/frontend/src/views/falukant/FamilyView.vue index c762129..31493ea 100644 --- a/frontend/src/views/falukant/FamilyView.vue +++ b/frontend/src/views/falukant/FamilyView.vue @@ -25,7 +25,7 @@ {{ $t('falukant.family.spouse.mood') }} - {{ $t(`falukant.mood.${relationships[0].character2.mood.tr}`) }} + {{ relationships[0].character2.mood?.tr ? $t(`falukant.mood.${relationships[0].character2.mood.tr}`) : '—' }} {{ $t('falukant.family.spouse.status') }} diff --git a/frontend/src/views/falukant/NobilityView.vue b/frontend/src/views/falukant/NobilityView.vue index 79ff74f..2808d51 100644 --- a/frontend/src/views/falukant/NobilityView.vue +++ b/frontend/src/views/falukant/NobilityView.vue @@ -20,7 +20,7 @@

- {{ $t('falukant.nobility.nextTitle') }}: + {{ $t('falukant.nobility.<{{ $t(`falukant.titles.${gender}.${next.labelTr}`) }}