diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js index cd3ddd2..d9cebe3 100644 --- a/backend/controllers/falukantController.js +++ b/backend/controllers/falukantController.js @@ -20,6 +20,7 @@ class FalukantController { this.getDirectorProposals = this.getDirectorProposals.bind(this); this.convertProposalToDirector = this.convertProposalToDirector.bind(this); this.getDirectorForBranch = this.getDirectorForBranch.bind(this); + this.getAllDirectors = this.getAllDirectors.bind(this); this.setSetting = this.setSetting.bind(this); this.getFamily = this.getFamily.bind(this); this.acceptMarriageProposal = this.acceptMarriageProposal.bind(this); @@ -32,6 +33,21 @@ class FalukantController { this.getUserHouse = this.getUserHouse.bind(this); this.getBuyableHouses = this.getBuyableHouses.bind(this); this.buyUserHouse = this.buyUserHouse.bind(this); + this.getPartyTypes = this.getPartyTypes.bind(this); + this.createParty = this.createParty.bind(this); + this.getParties = this.getParties.bind(this); + this.getNotBaptisedChildren = this.getNotBaptisedChildren.bind(this); + this.baptise = this.baptise.bind(this); + this.getEducation = this.getEducation.bind(this); + this.getChildren = this.getChildren.bind(this); + this.sendToSchool = this.sendToSchool.bind(this); + this.getBankOverview = this.getBankOverview.bind(this); + this.getBankCredits = this.getBankCredits.bind(this); + this.takeBankCredits = this.takeBankCredits.bind(this); + this.getNobility = this.getNobility.bind(this); + this.advanceNobility = this.advanceNobility.bind(this); + this.getHealth = this.getHealth.bind(this); + this.healthActivity = this.healthActivity.bind(this); } async getUser(req, res) { @@ -324,6 +340,27 @@ class FalukantController { } } + async getAllDirectors(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getAllDirectors(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async updateDirector(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { directorId, income } = req.body; + const result = await FalukantService.updateDirector(hashedUserId, directorId, income); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + async setSetting(req, res) { try { const { userid: hashedUserId } = req.headers; @@ -373,6 +410,17 @@ class FalukantController { } } + async getChildren(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getChildren(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + async sendGift(req, res) { try { const { userid: hashedUserId } = req.headers; @@ -464,6 +512,165 @@ class FalukantController { console.log(error); } } + + async getPartyTypes(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getPartyTypes(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async createParty(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { partyTypeId, musicId, banquetteId, nobilityIds, servantRatio } = req.body; + const result = await FalukantService.createParty(hashedUserId, partyTypeId, musicId, banquetteId, nobilityIds, servantRatio); + res.status(201).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async getParties(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getParties(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async getNotBaptisedChildren(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getNotBaptisedChildren(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async baptise(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { characterId: childId, firstName } = req.body; + const result = await FalukantService.baptise(hashedUserId, childId, firstName); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async getEducation(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getEducation(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async sendToSchool(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { item, student, studentId } = req.body; + const result = await FalukantService.sendToSchool(hashedUserId, item, student, studentId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async getBankOverview(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getBankOverview(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async getBankCredits(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getBankCredits(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async takeBankCredits(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { height } = req.body; + const result = await FalukantService.takeBankCredits(hashedUserId, height); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async getNobility(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getNobility(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async advanceNobility(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.advanceNobility(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async getHealth(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getHealth(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async healthActivity(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { measureTr: activity } = req.body; + const result = await FalukantService.healthActivity(hashedUserId, activity); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } } export default FalukantController; diff --git a/backend/controllers/navigationController.js b/backend/controllers/navigationController.js index 7ea5daf..6b3a308 100644 --- a/backend/controllers/navigationController.js +++ b/backend/controllers/navigationController.js @@ -105,6 +105,10 @@ const menuStructure = { visible: ["hasfalukantaccount"], path: "/falukant/nobility" }, + church: { + visible: ["hasfalukantaccount"], + path: "/falukant/church" + }, politics: { visible: ["hasfalukantaccount"], path: "/falukant/politics" diff --git a/backend/models/associations.js b/backend/models/associations.js index 8dd4b42..18275c7 100644 --- a/backend/models/associations.js +++ b/backend/models/associations.js @@ -65,6 +65,26 @@ import PromotionalGiftLog from './falukant/log/promotional_gift.js'; import HouseType from './falukant/type/house.js'; import BuyableHouse from './falukant/data/buyable_house.js'; import UserHouse from './falukant/data/user_house.js'; +import PartyType from './falukant/type/party.js'; +import Party from './falukant/data/party.js'; +import MusicType from './falukant/type/music.js'; +import BanquetteType from './falukant/type/banquette.js'; +import PartyInvitedNobility from './falukant/data/partyInvitedNobility.js'; +import ChildRelation from './falukant/data/child_relation.js'; +import Learning from './falukant/data/learning.js'; +import LearnRecipient from './falukant/type/learn_recipient.js'; +import Credit from './falukant/data/credit.js'; +import DebtorsPrism from './falukant/data/debtors_prism.js'; +import HealthActivity from './falukant/log/health_activity.js'; +import Election from './falukant/data/election.js'; +import ElectionResult from './falukant/data/election_result.js'; +import Candidate from './falukant/data/candidate.js'; +import Vote from './falukant/data/vote.js'; +import PoliticalOfficeType from './falukant/type/political_office_type.js'; +import PoliticalOffice from './falukant/data/political_office.js'; +import PoliticalOfficeBenefit from './falukant/predefine/political_office_benefit.js'; +import PoliticalOfficeBenefitType from './falukant/type/political_office_benefit_type.js'; +import PoliticalOfficeRequirement from './falukant/predefine/political_office_prerequisite.js'; export default function setupAssociations() { // UserParam related associations @@ -275,7 +295,7 @@ export default function setupAssociations() { BuyableStock.belongsTo(FalukantStockType, { foreignKey: 'stockTypeId', as: 'stockType' }); FalukantStockType.hasMany(BuyableStock, { foreignKey: 'stockTypeId', as: 'buyableStocks' }); - + Director.belongsTo(FalukantUser, { foreignKey: 'employerUserId', as: 'user' }); FalukantUser.hasMany(Director, { foreignKey: 'employerUserId', as: 'directors' }); @@ -315,31 +335,31 @@ export default function setupAssociations() { Notification.belongsTo(FalukantUser, { foreignKey: 'userId', as: 'user' }); FalukantUser.hasMany(Notification, { foreignKey: 'userId', as: 'notifications' }); - MarriageProposal.belongsTo(FalukantCharacter, {foreignKey: 'requesterCharacterId', as: 'requesterCharacter', }); + MarriageProposal.belongsTo(FalukantCharacter, { foreignKey: 'requesterCharacterId', as: 'requesterCharacter', }); FalukantCharacter.hasMany(MarriageProposal, { foreignKey: 'requesterCharacterId', as: 'initiatedProposals' }); - MarriageProposal.belongsTo(FalukantCharacter, {foreignKey: 'proposedCharacterId', as: 'proposedCharacter', }); - FalukantCharacter.hasMany(MarriageProposal, {foreignKey: 'proposedCharacterId', as: 'receivedProposals' }); - - FalukantCharacter.belongsToMany(CharacterTrait, {through: FalukantCharacterTrait, foreignKey: 'character_id', as: 'traits', }); - CharacterTrait.belongsToMany(FalukantCharacter, {through: FalukantCharacterTrait, foreignKey: 'trait_id', as: 'characters', }); - - Mood.hasMany(FalukantCharacter, {foreignKey: 'mood_id', as: 'moods'}); - FalukantCharacter.belongsTo(Mood, {foreignKey: 'mood_id', as: 'mood'}); - - PromotionalGift.belongsToMany(CharacterTrait, {through: PromotionalGiftCharacterTrait, foreignKey: 'gift_id', as: 'traits',}); - CharacterTrait.belongsToMany(PromotionalGift, {through: PromotionalGiftCharacterTrait, foreignKey: 'trait_id', as: 'gifts',}); + MarriageProposal.belongsTo(FalukantCharacter, { foreignKey: 'proposedCharacterId', as: 'proposedCharacter', }); + FalukantCharacter.hasMany(MarriageProposal, { foreignKey: 'proposedCharacterId', as: 'receivedProposals' }); - PromotionalGift.belongsToMany(Mood, {through: PromotionalGiftMood, foreignKey: 'gift_id', as: 'moods',}); - Mood.belongsToMany(PromotionalGift, {through: PromotionalGiftMood, foreignKey: 'mood_id', as: 'gifts',}); + FalukantCharacter.belongsToMany(CharacterTrait, { through: FalukantCharacterTrait, foreignKey: 'character_id', as: 'traits', }); + CharacterTrait.belongsToMany(FalukantCharacter, { through: FalukantCharacterTrait, foreignKey: 'trait_id', as: 'characters', }); + + Mood.hasMany(FalukantCharacter, { foreignKey: 'mood_id', as: 'moods' }); + FalukantCharacter.belongsTo(Mood, { foreignKey: 'mood_id', as: 'mood' }); + + PromotionalGift.belongsToMany(CharacterTrait, { through: PromotionalGiftCharacterTrait, foreignKey: 'gift_id', as: 'traits', }); + CharacterTrait.belongsToMany(PromotionalGift, { through: PromotionalGiftCharacterTrait, foreignKey: 'trait_id', as: 'gifts', }); + + PromotionalGift.belongsToMany(Mood, { through: PromotionalGiftMood, foreignKey: 'gift_id', as: 'moods', }); + Mood.belongsToMany(PromotionalGift, { through: PromotionalGiftMood, foreignKey: 'mood_id', as: 'gifts', }); Relationship.belongsTo(RelationshipType, { foreignKey: 'relationshipTypeId', as: 'relationshipType' }); RelationshipType.hasMany(Relationship, { foreignKey: 'relationshipTypeId', as: 'relationships' }); - - Relationship.belongsTo(FalukantCharacter, {foreignKey: 'character1Id', as: 'character1', }); - Relationship.belongsTo(FalukantCharacter, {foreignKey: 'character2Id', as: 'character2', }); - FalukantCharacter.hasMany(Relationship, {foreignKey: 'character1Id', as: 'relationshipsAsCharacter1', }); - FalukantCharacter.hasMany(Relationship, {foreignKey: 'character2Id', as: 'relationshipsAsCharacter2', }); + + Relationship.belongsTo(FalukantCharacter, { foreignKey: 'character1Id', as: 'character1', }); + Relationship.belongsTo(FalukantCharacter, { foreignKey: 'character2Id', as: 'character2', }); + FalukantCharacter.hasMany(Relationship, { foreignKey: 'character1Id', as: 'relationshipsAsCharacter1', }); + FalukantCharacter.hasMany(Relationship, { foreignKey: 'character2Id', as: 'relationshipsAsCharacter2', }); PromotionalGiftLog.belongsTo(PromotionalGift, { foreignKey: 'giftId', as: 'gift' }); PromotionalGift.hasMany(PromotionalGiftLog, { foreignKey: 'giftId', as: 'logs' }); @@ -367,4 +387,262 @@ export default function setupAssociations() { TitleOfNobility.hasMany(HouseType, { foreignKey: 'minimumNobleTitle', as: 'houseTypes' }); HouseType.belongsTo(TitleOfNobility, { foreignKey: 'minimumNobleTitle', as: 'titleOfNobility' }); + + PartyType.hasMany(Party, { foreignKey: 'partyTypeId', as: 'parties' }); + Party.belongsTo(PartyType, { foreignKey: 'partyTypeId', as: 'partyType' }); + + MusicType.hasMany(Party, { foreignKey: 'musicTypeId', as: 'parties' }); + Party.belongsTo(MusicType, { foreignKey: 'musicTypeId', as: 'musicType' }); + + BanquetteType.hasMany(Party, { foreignKey: 'banquetteTypeId', as: 'parties' }); + Party.belongsTo(BanquetteType, { foreignKey: 'banquetteTypeId', as: 'banquetteType' }); + + FalukantUser.hasMany(Party, { foreignKey: 'falukantUserId', as: 'parties' }); + Party.belongsTo(FalukantUser, { foreignKey: 'falukantUserId', as: 'partyUser' }); + + Party.belongsToMany(TitleOfNobility, { + through: PartyInvitedNobility, + foreignKey: 'party_id', + otherKey: 'title_of_nobility_id', + as: 'invitedNobilities', + }); + TitleOfNobility.belongsToMany(Party, { + through: PartyInvitedNobility, + foreignKey: 'title_of_nobility_id', + otherKey: 'party_id', + as: 'partiesInvitedTo', + }); + + ChildRelation.belongsTo(FalukantCharacter, { + foreignKey: 'fatherCharacterId', + as: 'father' + }); + FalukantCharacter.hasMany(ChildRelation, { + foreignKey: 'fatherCharacterId', + as: 'childrenFather' + }); + + ChildRelation.belongsTo(FalukantCharacter, { + foreignKey: 'motherCharacterId', + as: 'mother' + }); + FalukantCharacter.hasMany(ChildRelation, { + foreignKey: 'motherCharacterId', + as: 'childrenMother' + }); + + ChildRelation.belongsTo(FalukantCharacter, { + foreignKey: 'childCharacterId', + as: 'child' + }); + FalukantCharacter.hasMany(ChildRelation, { + foreignKey: 'childCharacterId', + as: 'parentRelations' + }); + + Learning.belongsTo(LearnRecipient, { + foreignKey: 'learningRecipientId', + as: 'recipient' + } + ); + + LearnRecipient.hasMany(Learning, { + foreignKey: 'learningRecipientId', + as: 'learnings' + }); + + Learning.belongsTo(FalukantUser, { + foreignKey: 'associatedFalukantUserId', + as: 'learner' + } + ); + + FalukantUser.hasMany(Learning, { + foreignKey: 'associatedFalukantUserId', + as: 'learnings' + }); + + Learning.belongsTo(ProductType, { + foreignKey: 'productId', + as: 'productType' + }); + + ProductType.hasMany(Learning, { + foreignKey: 'productId', + as: 'learnings' + }); + + Learning.belongsTo(FalukantCharacter, { + foreignKey: 'associatedLearningCharacterId', + as: 'learningCharacter' + }); + + FalukantCharacter.hasMany(Learning, { + foreignKey: 'associatedLearningCharacterId', + as: 'learningsCharacter' + }); + + FalukantUser.hasMany(Credit, { + foreignKey: 'falukantUserId', + as: 'credits' + }); + Credit.belongsTo(FalukantUser, { + foreignKey: 'falukantUserId', + as: 'user' + }); + + FalukantCharacter.hasMany(DebtorsPrism, { + foreignKey: 'character_id', + as: 'debtorsPrisms' + }); + DebtorsPrism.belongsTo(FalukantCharacter, { + foreignKey: 'character_id', + as: 'character' + }); + + HealthActivity.belongsTo(FalukantCharacter, { + foreignKey: 'character_id', + as: 'character' + }); + FalukantCharacter.hasMany(HealthActivity, { + foreignKey: 'character_id', + as: 'healthActivities' + }); + + // — Political Offices — + +// predefine requirements for office +PoliticalOfficeRequirement.belongsTo(PoliticalOfficeType, { + foreignKey: 'officeTypeId', + as: 'officeType' + }); + PoliticalOfficeType.hasMany(PoliticalOfficeRequirement, { + foreignKey: 'officeTypeId', + as: 'requirements' + }); + + // predefine benefits for office + PoliticalOfficeBenefit.belongsTo( + PoliticalOfficeBenefitType, + { foreignKey: 'benefitTypeId', as: 'benefitDefinition' } + ); + PoliticalOfficeBenefitType.hasMany( + PoliticalOfficeBenefit, + { foreignKey: 'benefitTypeId', as: 'benefitDefinitions' } + ); + + // tie benefits back to office type + PoliticalOfficeBenefit.belongsTo(PoliticalOfficeType, { + foreignKey: 'officeTypeId', + as: 'officeType' + }); + PoliticalOfficeType.hasMany(PoliticalOfficeBenefit, { + foreignKey: 'officeTypeId', + as: 'benefits' + }); + + // actual office holdings + PoliticalOffice.belongsTo(PoliticalOfficeType, { + foreignKey: 'officeTypeId', + as: 'type' + }); + PoliticalOfficeType.hasMany(PoliticalOffice, { + foreignKey: 'officeTypeId', + as: 'offices' + }); + + PoliticalOffice.belongsTo(FalukantCharacter, { + foreignKey: 'characterId', + as: 'holder' + }); + FalukantCharacter.hasMany(PoliticalOffice, { + foreignKey: 'characterId', + as: 'heldOffices' + }); + + // elections + Election.belongsTo(PoliticalOfficeType, { + foreignKey: 'officeTypeId', + as: 'officeType' + }); + PoliticalOfficeType.hasMany(Election, { + foreignKey: 'officeTypeId', + as: 'elections' + }); + + // candidates in an election + Candidate.belongsTo(Election, { + foreignKey: 'electionId', + as: 'election' + }); + Election.hasMany(Candidate, { + foreignKey: 'electionId', + as: 'candidates' + }); + + Candidate.belongsTo(FalukantCharacter, { + foreignKey: 'characterId', + as: 'character' + }); + FalukantCharacter.hasMany(Candidate, { + foreignKey: 'characterId', + as: 'candidacies' + }); + + // votes cast + Vote.belongsTo(Election, { + foreignKey: 'electionId', + as: 'election' + }); + Election.hasMany(Vote, { + foreignKey: 'electionId', + as: 'votes' + }); + + Vote.belongsTo(Candidate, { + foreignKey: 'candidateId', + as: 'candidate' + }); + Candidate.hasMany(Vote, { + foreignKey: 'candidateId', + as: 'votes' + }); + + Vote.belongsTo(FalukantCharacter, { + foreignKey: 'voterCharacterId', + as: 'voter' + }); + FalukantCharacter.hasMany(Vote, { + foreignKey: 'voterCharacterId', + as: 'votesCast' + }); + + // election results + ElectionResult.belongsTo(Election, { + foreignKey: 'electionId', + as: 'election' + }); + Election.hasMany(ElectionResult, { + foreignKey: 'electionId', + as: 'results' + }); + + ElectionResult.belongsTo(Candidate, { + foreignKey: 'candidateId', + as: 'candidate' + }); + Candidate.hasMany(ElectionResult, { + foreignKey: 'candidateId', + as: 'results' + }); + + PoliticalOffice.belongsTo(RegionData, { + foreignKey: 'regionId', + as: 'region' + }); + RegionData.hasMany(PoliticalOffice, { + foreignKey: 'regionId', + as: 'offices' + }); + } diff --git a/backend/models/falukant/data/candidate.js b/backend/models/falukant/data/candidate.js new file mode 100644 index 0000000..6a564e7 --- /dev/null +++ b/backend/models/falukant/data/candidate.js @@ -0,0 +1,30 @@ +// falukant/data/candidate.js +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class Candidate extends Model {} + +Candidate.init({ + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + election_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + character_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}, { + sequelize, + modelName: 'Candidate', + tableName: 'candidate', + schema: 'falukant_data', + timestamps: true, + underscored: true, +}); + +export default Candidate; diff --git a/backend/models/falukant/data/child_relation.js b/backend/models/falukant/data/child_relation.js new file mode 100644 index 0000000..dc5eec2 --- /dev/null +++ b/backend/models/falukant/data/child_relation.js @@ -0,0 +1,45 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class ChildRelation extends Model {} + +ChildRelation.init( + { + fatherCharacterId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + motherCharacterId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + childCharacterId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + fatherName: { + type: DataTypes.STRING, + allowNull: false, + }, + motherName: { + type: DataTypes.STRING, + allowNull: false, + }, + nameSet: { + type: DataTypes.BOOLEAN, + allowNull: false, + default: false, + }, + }, + { + sequelize, + modelName: 'ChildRelation', + tableName: 'child_relation', // exakter Tabellenname + schema: 'falukant_data', // exaktes Schema + freezeTableName: true, // keine Pluralisierung + timestamps: true, + underscored: true, + } +); + +export default ChildRelation; diff --git a/backend/models/falukant/data/credit.js b/backend/models/falukant/data/credit.js new file mode 100644 index 0000000..eef372f --- /dev/null +++ b/backend/models/falukant/data/credit.js @@ -0,0 +1,37 @@ +// models/falukant/data/credit.js +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class Credit extends Model {} + +Credit.init({ + // aufgenommener Kredit-Betrag + amount: { + type: DataTypes.DECIMAL(14,2), + allowNull: false, + }, + // noch offener Kreditbetrag + remainingAmount: { + type: DataTypes.DECIMAL(14,2), + allowNull: false, + }, + // Zinssatz als Prozentsatz (z.B. 3.5 für 3.5%) + interestRate: { + type: DataTypes.DECIMAL(5,2), + allowNull: false, + }, + // Verknüpfung auf FalukantUser + falukantUserId: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}, { + sequelize, + modelName: 'Credit', + tableName: 'credit', + schema: 'falukant_data', + timestamps: true, + underscored: true, +}); + +export default Credit; diff --git a/backend/models/falukant/data/debtors_prism.js b/backend/models/falukant/data/debtors_prism.js new file mode 100644 index 0000000..0e08c57 --- /dev/null +++ b/backend/models/falukant/data/debtors_prism.js @@ -0,0 +1,21 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class DebtorsPrism extends Model {} + +DebtorsPrism.init({ + // Verknüpfung auf FalukantCharacter + characterId: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}, { + sequelize, + modelName: 'DebtorsPrism', + tableName: 'debtors_prism', + schema: 'falukant_data', + timestamps: true, + underscored: true, +}); + +export default DebtorsPrism; diff --git a/backend/models/falukant/data/election.js b/backend/models/falukant/data/election.js new file mode 100644 index 0000000..18086a1 --- /dev/null +++ b/backend/models/falukant/data/election.js @@ -0,0 +1,34 @@ +// falukant/data/election.js +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class Election extends Model {} + +Election.init({ + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + political_office_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + date: { + type: DataTypes.DATE, + allowNull: false, + }, + posts_to_fill: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}, { + sequelize, + modelName: 'Election', + tableName: 'election', + schema: 'falukant_data', + timestamps: true, + underscored: true, +}); + +export default Election; diff --git a/backend/models/falukant/data/election_result.js b/backend/models/falukant/data/election_result.js new file mode 100644 index 0000000..5d1e95f --- /dev/null +++ b/backend/models/falukant/data/election_result.js @@ -0,0 +1,34 @@ +// falukant/data/election_result.js +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class ElectionResult extends Model {} + +ElectionResult.init({ + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + election_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + candidate_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + votes_received: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}, { + sequelize, + modelName: 'ElectionResult', + tableName: 'election_result', + schema: 'falukant_data', + timestamps: true, + underscored: true, +}); + +export default ElectionResult; diff --git a/backend/models/falukant/data/learning.js b/backend/models/falukant/data/learning.js new file mode 100644 index 0000000..3c73dcb --- /dev/null +++ b/backend/models/falukant/data/learning.js @@ -0,0 +1,48 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class Learning extends Model {} + +Learning.init( + { + learningRecipientId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + productId: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + }, + learnAllProducts: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + associatedFalukantUserId: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + }, + associatedLearningCharacterId: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + }, + learningIsExecuted: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + } + }, + { + sequelize, + modelName: 'Learning', + tableName: 'learning', + schema: 'falukant_data', + timestamps: true, + underscored: true, + } +); + +export default Learning; diff --git a/backend/models/falukant/data/occupied_political_office.js b/backend/models/falukant/data/occupied_political_office.js new file mode 100644 index 0000000..eed3586 --- /dev/null +++ b/backend/models/falukant/data/occupied_political_office.js @@ -0,0 +1,30 @@ +// falukant/data/occupied_political_office.js +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class OccupiedPoliticalOffice extends Model {} + +OccupiedPoliticalOffice.init({ + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + political_office_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + character_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}, { + sequelize, + modelName: 'OccupiedPoliticalOffice', + tableName: 'occupied_political_office', + schema: 'falukant_data', + timestamps: true, + underscored: true, +}); + +export default OccupiedPoliticalOffice; diff --git a/backend/models/falukant/data/party.js b/backend/models/falukant/data/party.js new file mode 100644 index 0000000..ef202f8 --- /dev/null +++ b/backend/models/falukant/data/party.js @@ -0,0 +1,46 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class Party extends Model {} + +Party.init({ + partyTypeId: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'party_type_id' + }, + falukantUserId: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'falukant_user_id' + }, + musicTypeId: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'music_type' + }, + banquetteTypeId: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'banquette_type' + }, + servantRatio: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'servant_ratio' + }, + cost: { + type: DataTypes.FLOAT, + allowNull: false, + defaultValue: 0 + }, + }, { + sequelize, + modelName: 'Party', + tableName: 'party', + schema: 'falukant_data', + timestamps: true, + underscored: true, + }); + +export default Party; \ No newline at end of file diff --git a/backend/models/falukant/data/partyInvitedNobility.js b/backend/models/falukant/data/partyInvitedNobility.js new file mode 100644 index 0000000..ebc4124 --- /dev/null +++ b/backend/models/falukant/data/partyInvitedNobility.js @@ -0,0 +1,26 @@ +import { Model, DataTypes } from 'sequelize' +import { sequelize } from '../../../utils/sequelize.js' + +class PartyInvitedNobility extends Model {} + +PartyInvitedNobility.init({ + partyId: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'party_id' + }, + titleOfNobilityId: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'title_of_nobility_id' + } +}, { + sequelize, + modelName: 'PartyInvitedNobility', + tableName: 'party_invited_nobility', + schema: 'falukant_data', + timestamps: false, + underscored: true +}) + +export default PartyInvitedNobility diff --git a/backend/models/falukant/data/political_office.js b/backend/models/falukant/data/political_office.js new file mode 100644 index 0000000..f916755 --- /dev/null +++ b/backend/models/falukant/data/political_office.js @@ -0,0 +1,34 @@ +// backend/models/falukant/data/political_office.js +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class PoliticalOffice extends Model {} + +PoliticalOffice.init({ + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + officeTypeId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + characterId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + regionId: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}, { + sequelize, + modelName: 'PoliticalOffice', + tableName: 'political_office', + schema: 'falukant_data', + timestamps: true, + underscored: true, +}); + +export default PoliticalOffice; diff --git a/backend/models/falukant/data/vote.js b/backend/models/falukant/data/vote.js new file mode 100644 index 0000000..2b0e528 --- /dev/null +++ b/backend/models/falukant/data/vote.js @@ -0,0 +1,39 @@ +// falukant/data/vote.js +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class Vote extends Model {} + +Vote.init({ + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + election_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + voter_character_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + candidate_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + timestamp: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW, + }, +}, { + sequelize, + modelName: 'Vote', + tableName: 'vote', + schema: 'falukant_data', + timestamps: false, + underscored: true, +}); + +export default Vote; diff --git a/backend/models/falukant/log/health_activity.js b/backend/models/falukant/log/health_activity.js new file mode 100644 index 0000000..bd70124 --- /dev/null +++ b/backend/models/falukant/log/health_activity.js @@ -0,0 +1,37 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class HealthActivity extends Model { } + +HealthActivity.init({ + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + characterId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + activityTr: { + type: DataTypes.STRING, + allowNull: false, + }, + cost: { + type: DataTypes.FLOAT, + allowNull: false, + }, + successPercentage: { + type: DataTypes.INTEGER, + allowNull: false + } +}, { + sequelize, + modelName: 'health_activity', + tableName: 'health_activity', + schema: 'falukant_log', + timestamps: true, + underscored: true, +}); + +export default HealthActivity; diff --git a/backend/models/falukant/predefine/political_office_benefit.js b/backend/models/falukant/predefine/political_office_benefit.js new file mode 100644 index 0000000..2ffb2f1 --- /dev/null +++ b/backend/models/falukant/predefine/political_office_benefit.js @@ -0,0 +1,45 @@ +// falukant/predefine/political_office_benefit.js +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; +import PoliticalOfficeBenefitType from '../type/political_office_benefit_type.js'; + +class PoliticalOfficeBenefit extends Model {} + +PoliticalOfficeBenefit.init({ + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + political_office_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + benefit_type_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + value: { + type: DataTypes.JSONB, + allowNull: false, + }, +}, { + sequelize, + modelName: 'PoliticalOfficeBenefit', + tableName: 'political_office_benefit', + schema: 'falukant_predefine', + timestamps: false, + underscored: true, +}); + +// Association +PoliticalOfficeBenefit.belongsTo(PoliticalOfficeBenefitType, { + foreignKey: 'benefit_type_id', + as: 'benefitType' +}); +PoliticalOfficeBenefitType.hasMany(PoliticalOfficeBenefit, { + foreignKey: 'benefit_type_id', + as: 'benefits' +}); + +export default PoliticalOfficeBenefit; diff --git a/backend/models/falukant/predefine/political_office_prerequisite.js b/backend/models/falukant/predefine/political_office_prerequisite.js new file mode 100644 index 0000000..d04ea55 --- /dev/null +++ b/backend/models/falukant/predefine/political_office_prerequisite.js @@ -0,0 +1,30 @@ +// falukant/predefine/political_office_prerequisite.js +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class PoliticalOfficePrerequisite extends Model {} + +PoliticalOfficePrerequisite.init({ + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + political_office_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + prerequisite: { + type: DataTypes.JSONB, + allowNull: false, + }, +}, { + sequelize, + modelName: 'PoliticalOfficePrerequisite', + tableName: 'political_office_prerequisite', + schema: 'falukant_predefine', + timestamps: false, + underscored: true, +}); + +export default PoliticalOfficePrerequisite; diff --git a/backend/models/falukant/type/banquette.js b/backend/models/falukant/type/banquette.js new file mode 100644 index 0000000..7cba1f0 --- /dev/null +++ b/backend/models/falukant/type/banquette.js @@ -0,0 +1,31 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class BanquetteType extends Model {} + +BanquetteType.init( + { + tr: { + type: DataTypes.STRING, + allowNull: false, + }, + cost: { + type: DataTypes.INTEGER, + allowNull: false, + }, + reputationGrowth: { + type: DataTypes.INTEGER, + allowNull: false, + }, + }, + { + sequelize, + modelName: 'BanquetteType', + tableName: 'banquette', + schema: 'falukant_type', + timestamps: false, + underscored: true, + } +); + +export default BanquetteType; diff --git a/backend/models/falukant/type/learn_recipient.js b/backend/models/falukant/type/learn_recipient.js new file mode 100644 index 0000000..1b71cea --- /dev/null +++ b/backend/models/falukant/type/learn_recipient.js @@ -0,0 +1,23 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class LearnRecipient extends Model {} + +LearnRecipient.init( + { + tr: { + type: DataTypes.STRING, + allowNull: false, + }, + }, + { + sequelize, + modelName: 'LearnRecipient', + tableName: 'learn_recipient', + schema: 'falukant_type', + timestamps: false, + underscored: true, + } +); + +export default LearnRecipient; diff --git a/backend/models/falukant/type/music.js b/backend/models/falukant/type/music.js new file mode 100644 index 0000000..b7d424f --- /dev/null +++ b/backend/models/falukant/type/music.js @@ -0,0 +1,31 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class MusicType extends Model {} + +MusicType.init( + { + tr: { + type: DataTypes.STRING, + allowNull: false, + }, + cost: { + type: DataTypes.INTEGER, + allowNull: false, + }, + reputationGrowth: { + type: DataTypes.INTEGER, + allowNull: false, + }, + }, + { + sequelize, + modelName: 'MusicType', + tableName: 'music', + schema: 'falukant_type', + timestamps: false, + underscored: true, + } +); + +export default MusicType; diff --git a/backend/models/falukant/type/party.js b/backend/models/falukant/type/party.js new file mode 100644 index 0000000..9621986 --- /dev/null +++ b/backend/models/falukant/type/party.js @@ -0,0 +1,36 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class PartyType extends Model {} + +PartyType.init( + { + tr: { + type: DataTypes.STRING, + allowNull: false, + }, + cost: { + type: DataTypes.INTEGER, + allowNull: false, + }, + forMarriage: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + reputationGrowth: { + type: DataTypes.INTEGER, + allowNull: false, + } + }, + { + sequelize, + modelName: 'PartyType', + tableName: 'party', + schema: 'falukant_type', + timestamps: false, + underscored: true, + } +); + +export default PartyType; diff --git a/backend/models/falukant/type/political_office_benefit_type.js b/backend/models/falukant/type/political_office_benefit_type.js new file mode 100644 index 0000000..cbf971a --- /dev/null +++ b/backend/models/falukant/type/political_office_benefit_type.js @@ -0,0 +1,26 @@ +// falukant/type/political_office_benefit_type.js +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class PoliticalOfficeBenefitType extends Model {} + +PoliticalOfficeBenefitType.init({ + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + tr: { + type: DataTypes.STRING, + allowNull: false, + }, +}, { + sequelize, + modelName: 'PoliticalOfficeBenefitType', + tableName: 'political_office_benefit_type', + schema: 'falukant_type', + timestamps: false, + underscored: true, +}); + +export default PoliticalOfficeBenefitType; diff --git a/backend/models/falukant/type/political_office_type.js b/backend/models/falukant/type/political_office_type.js new file mode 100644 index 0000000..b89ffc3 --- /dev/null +++ b/backend/models/falukant/type/political_office_type.js @@ -0,0 +1,38 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class PoliticalOfficeType extends Model {} + +PoliticalOfficeType.init({ + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + }, + seatsPerRegion: { + type: DataTypes.INTEGER, + allowNull: false, + }, + regionType: { + type: DataTypes.STRING, + allowNull: false, + }, + termLength: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0, + }, +}, { + sequelize, + modelName: 'PoliticalOfficeType', + tableName: 'political_office_type', + schema: 'falukant_type', + timestamps: false, + underscored: true, +}); + +export default PoliticalOfficeType; diff --git a/backend/models/falukant/type/title_of_nobility.js b/backend/models/falukant/type/title_of_nobility.js index 5b70de7..41f8ecf 100644 --- a/backend/models/falukant/type/title_of_nobility.js +++ b/backend/models/falukant/type/title_of_nobility.js @@ -8,6 +8,11 @@ TitleOfNobility.init({ type: DataTypes.STRING, allowNull: false, }, + level: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0, + }, }, { sequelize, modelName: 'Title', diff --git a/backend/models/falukant/type/title_requirement.js b/backend/models/falukant/type/title_requirement.js index 2996252..78f990e 100644 --- a/backend/models/falukant/type/title_requirement.js +++ b/backend/models/falukant/type/title_requirement.js @@ -4,12 +4,16 @@ import { sequelize } from '../../../utils/sequelize.js'; class TitleRequirement extends Model { } TitleRequirement.init({ - titleId: { + id : { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true, }, + titleId: { + type: DataTypes.INTEGER, + allowNull: false, + }, requirementType: { type: DataTypes.STRING, allowNull: false, @@ -19,12 +23,19 @@ TitleRequirement.init({ allowNull: false, }, }, { - sequelize, - modelName: 'TitleRequirement', + sequelize, + modelName: 'TitleRequirement', tableName: 'title_requirement', schema: 'falukant_type', timestamps: false, underscored: true, + indexes: [ + { + unique: true, + fields: ['title_id', 'requirement_type'], + name: 'title_requirement_titleid_reqtype_unique' + } + ] }); export default TitleRequirement; diff --git a/backend/models/index.js b/backend/models/index.js index 9c2f268..cdbb100 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -1,3 +1,5 @@ +// models/index.js + import SettingsType from './type/settings.js'; import UserParamValue from './type/user_param_value.js'; import UserParamType from './type/user_param.js'; @@ -32,6 +34,7 @@ import MessageHistory from './forum/message_history.js'; import MessageImage from './forum/message_image.js'; import ForumForumPermission from './forum/forum_forum_permission.js'; import Friendship from './community/friendship.js'; + import FalukantUser from './falukant/data/user.js'; import RegionType from './falukant/type/region.js'; import RegionData from './falukant/data/region.js'; @@ -58,90 +61,136 @@ import DaySell from './falukant/log/daysell.js'; import Notification from './falukant/log/notification.js'; import MarriageProposal from './falukant/data/marriage_proposal.js'; import RelationshipType from './falukant/type/relationship.js'; +import Relationship from './falukant/data/relationship.js'; import CharacterTrait from './falukant/type/character_trait.js'; import FalukantCharacterTrait from './falukant/data/falukant_character_trait.js'; import Mood from './falukant/type/mood.js'; import PromotionalGift from './falukant/type/promotional_gift.js'; import PromotionalGiftCharacterTrait from './falukant/predefine/promotional_gift_character_trait.js'; import PromotionalGiftMood from './falukant/predefine/promotional_gift_mood.js'; -import Relationship from './falukant/data/relationship.js'; import PromotionalGiftLog from './falukant/log/promotional_gift.js'; import HouseType from './falukant/type/house.js'; import BuyableHouse from './falukant/data/buyable_house.js'; import UserHouse from './falukant/data/user_house.js'; +import PartyType from './falukant/type/party.js'; +import Party from './falukant/data/party.js'; +import MusicType from './falukant/type/music.js'; +import BanquetteType from './falukant/type/banquette.js'; +import PartyInvitedNobility from './falukant/data/partyInvitedNobility.js'; +import ChildRelation from './falukant/data/child_relation.js'; +import LearnRecipient from './falukant/type/learn_recipient.js'; +import Learning from './falukant/data/learning.js'; +import Credit from './falukant/data/credit.js'; +import DebtorsPrism from './falukant/data/debtors_prism.js'; +import HealthActivity from './falukant/log/health_activity.js'; + +// — Politische Ämter (Politics) — +import PoliticalOfficeType from './falukant/type/political_office_type.js'; +import PoliticalOfficeRequirement from './falukant/predefine/political_office_prerequisite.js'; +import PoliticalOfficeBenefitType from './falukant/type/political_office_benefit_type.js'; +import PoliticalOfficeBenefit from './falukant/predefine/political_office_benefit.js'; +import PoliticalOffice from './falukant/data/political_office.js'; +import Election from './falukant/data/election.js'; +import Candidate from './falukant/data/candidate.js'; +import Vote from './falukant/data/vote.js'; +import ElectionResult from './falukant/data/election_result.js'; const models = { - SettingsType, - UserParamValue, - UserParamType, - UserRightType, - User, - UserParam, - Login, - UserRight, - InterestType, - InterestTranslationType, - Interest, - ContactMessage, - UserParamVisibilityType, - UserParamVisibility, - Folder, - Image, - ImageVisibilityType, - ImageVisibilityUser, - FolderImageVisibility, - ImageImageVisibility, - FolderVisibilityUser, - GuestbookEntry, - DiaryHistory, - Diary, - Forum, - ForumPermission, - ForumForumPermission, - ForumUserPermission, - Title, - TitleHistory, - Message, - MessageHistory, - MessageImage, - Friendship, - RegionType, - RegionData, - FalukantUser, - FalukantPredefineFirstname, - FalukantPredefineLastname, - FalukantCharacter, - FalukantStock, - FalukantStockType, - ProductType, - Knowledge, - TitleOfNobility, - TitleRequirement, - BranchType, - Branch, - Production, - Inventory, - BuyableStock, - MoneyFlow, - Director, - DirectorProposal, - TownProductWorth, - DayProduction, - DaySell, - Notification, - MarriageProposal, - RelationshipType, - Relationship, - CharacterTrait, - FalukantCharacterTrait, - Mood, - PromotionalGift, - PromotionalGiftCharacterTrait, - PromotionalGiftMood, - PromotionalGiftLog, - HouseType, - BuyableHouse, - UserHouse, + SettingsType, + UserParamValue, + UserParamType, + UserRightType, + User, + UserParam, + Login, + UserRight, + InterestType, + InterestTranslationType, + Interest, + ContactMessage, + UserParamVisibilityType, + UserParamVisibility, + Folder, + Image, + ImageVisibilityType, + ImageVisibilityUser, + FolderImageVisibility, + ImageImageVisibility, + FolderVisibilityUser, + GuestbookEntry, + DiaryHistory, + Diary, + Forum, + ForumPermission, + ForumForumPermission, + ForumUserPermission, + Title, + TitleHistory, + Message, + MessageHistory, + MessageImage, + Friendship, + + // Falukant core + RegionType, + RegionData, + FalukantUser, + FalukantPredefineFirstname, + FalukantPredefineLastname, + FalukantCharacter, + FalukantStock, + FalukantStockType, + ProductType, + Knowledge, + TitleOfNobility, + TitleRequirement, + BranchType, + Branch, + Production, + Inventory, + BuyableStock, + MoneyFlow, + Director, + DirectorProposal, + TownProductWorth, + DayProduction, + DaySell, + Notification, + MarriageProposal, + RelationshipType, + Relationship, + CharacterTrait, + FalukantCharacterTrait, + Mood, + PromotionalGift, + PromotionalGiftCharacterTrait, + PromotionalGiftMood, + PromotionalGiftLog, + HouseType, + BuyableHouse, + UserHouse, + PartyType, + MusicType, + BanquetteType, + Party, + PartyInvitedNobility, + ChildRelation, + LearnRecipient, + Learning, + Credit, + DebtorsPrism, + HealthActivity, + + // Politics + PoliticalOfficeType, + PoliticalOfficeRequirement, + PoliticalOfficeBenefitType, + PoliticalOfficeBenefit, + PoliticalOffice, + Election, + Candidate, + Vote, + ElectionResult, }; export default models; diff --git a/backend/models/trigger.js b/backend/models/trigger.js index 9adc1e1..137594d 100644 --- a/backend/models/trigger.js +++ b/backend/models/trigger.js @@ -180,6 +180,67 @@ export async function createTriggers() { $function$; `; + const createChildRelationNameFunction = ` + CREATE OR REPLACE FUNCTION falukant_data.populate_child_relation_names() + RETURNS TRIGGER AS $$ + DECLARE + v_first_name TEXT; + v_last_name TEXT; + v_full_father TEXT; + v_full_mother TEXT; + BEGIN + -- Vaternamen holen + SELECT pf.name, pl.name + INTO v_first_name, v_last_name + FROM falukant_data.character c + JOIN falukant_predefine.firstname pf ON pf.id = c.first_name + JOIN falukant_predefine.lastname pl ON pl.id = c.last_name + WHERE c.id = NEW.father_character_id; + + v_full_father := v_first_name || ' ' || v_last_name; + + -- Mutternamen holen + SELECT pf.name, pl.name + INTO v_first_name, v_last_name + FROM falukant_data.character c + JOIN falukant_predefine.firstname pf ON pf.id = c.first_name + JOIN falukant_predefine.lastname pl ON pl.id = c.last_name + WHERE c.id = NEW.mother_character_id; + + v_full_mother := v_first_name || ' ' || v_last_name; + + -- Felder füllen + NEW.father_name := v_full_father; + NEW.mother_name := v_full_mother; + + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + `; + + const createChildRelationNameTrigger = ` + DROP TRIGGER IF EXISTS trg_child_relation_populate_names + ON falukant_data.child_relation; + CREATE TRIGGER trg_child_relation_populate_names + BEFORE INSERT ON falukant_data.child_relation + FOR EACH ROW + EXECUTE FUNCTION falukant_data.populate_child_relation_names(); + `; + + const createRandomMoodUpdateMethod = ` + CREATE OR REPLACE FUNCTION falukant_data.get_random_mood_id() + RETURNS INTEGER AS $$ + BEGIN + RETURN ( + SELECT id + FROM falukant_type.mood + ORDER BY random() + LIMIT 1 + ); + END; + $$ LANGUAGE plpgsql VOLATILE; + `; + try { await sequelize.query(createTriggerFunction); await sequelize.query(createInsertTrigger); @@ -193,7 +254,10 @@ export async function createTriggers() { await sequelize.query(createKnowledgeTriggerMethod); await sequelize.query(createKnowledgeTrigger); await sequelize.query(updateMoney); - await initializeCharacterTraitTrigger(); + await sequelize.query(createChildRelationNameFunction); + await sequelize.query(createChildRelationNameTrigger); + await sequelize.query(createRandomMoodUpdateMethod); + await initializeCharacterTraitTrigger(); console.log('Triggers created successfully'); } catch (error) { @@ -250,4 +314,3 @@ export const initializeCharacterTraitTrigger = async () => { console.error('❌ Fehler beim Erstellen des Triggers:', error); } }; - \ No newline at end of file diff --git a/backend/routers/falukantRouter.js b/backend/routers/falukantRouter.js index 0472b81..119d230 100644 --- a/backend/routers/falukantRouter.js +++ b/backend/routers/falukantRouter.js @@ -6,6 +6,8 @@ const falukantController = new FalukantController(); router.get('/user', falukantController.getUser); router.post('/user', falukantController.createUser); +router.get('/mood/affect', falukantController.getMoodAffect); +router.get('/character/affect', falukantController.getCharacterAffect); router.get('/name/randomfirstname/:gender', falukantController.randomFirstName); router.get('/name/randomlastname', falukantController.randomLastName); router.get('/info', falukantController.getInfo); @@ -30,15 +32,30 @@ router.post('/director/proposal', falukantController.getDirectorProposals); router.post('/director/convertproposal', falukantController.convertProposalToDirector); router.post('/director/settings', falukantController.setSetting); router.get('/director/:branchId', falukantController.getDirectorForBranch); +router.get('/directors', falukantController.getAllDirectors); +router.post('/directors', falukantController.updateDirector); router.post('/family/acceptmarriageproposal', falukantController.acceptMarriageProposal); router.get('/family/gifts', falukantController.getGifts); +router.get('/family/children', falukantController.getChildren); router.post('/family/gift', falukantController.sendGift); router.get('/family', falukantController.getFamily); router.get('/nobility/titels', falukantController.getTitelsOfNobility); router.get('/houses/types', falukantController.getHouseTypes); router.get('/houses/buyable', falukantController.getBuyableHouses); -router.get('/mood/affect', falukantController.getMoodAffect); -router.get('/character/affect', falukantController.getCharacterAffect); router.get('/houses', falukantController.getUserHouse); router.post('/houses', falukantController.buyUserHouse); +router.get('/party/types', falukantController.getPartyTypes); +router.post('/party', falukantController.createParty); +router.get('/party', falukantController.getParties); +router.get('/family/notbaptised', falukantController.getNotBaptisedChildren); +router.post('/church/baptise', falukantController.baptise); +router.get('/education', falukantController.getEducation); +router.post('/education', falukantController.sendToSchool); +router.get('/bank/overview', falukantController.getBankOverview); +router.get('/bank/credits', falukantController.getBankCredits); +router.post('/bank/credits', falukantController.takeBankCredits); +router.get('/nobility', falukantController.getNobility); +router.post('/nobility', falukantController.advanceNobility); +router.get('/health', falukantController.getHealth); +router.post('/health', falukantController.healthActivity) export default router; diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 892ee61..867325e 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -37,6 +37,16 @@ import Mood from '../models/falukant/type/mood.js'; import UserHouse from '../models/falukant/data/user_house.js'; import HouseType from '../models/falukant/type/house.js'; import BuyableHouse from '../models/falukant/data/buyable_house.js'; +import PartyType from '../models/falukant/type/party.js'; +import MusicType from '../models/falukant/type/music.js'; +import BanquetteType from '../models/falukant/type/banquette.js'; +import Party from '../models/falukant/data/party.js'; +import ChildRelation from '../models/falukant/data/child_relation.js'; +import Learning from '../models/falukant/data/learning.js'; +import LearnRecipient from '../models/falukant/type/learn_recipient.js'; +import Credit from '../models/falukant/data/credit.js'; +import TitleRequirement from '../models/falukant/type/title_requirement.js'; +import HealthActivity from '../models/falukant/log/health_activity.js'; function calcAge(birthdate) { const b = new Date(birthdate); b.setHours(0, 0); @@ -80,6 +90,19 @@ class PreconditionError extends Error { } class FalukantService extends BaseService { + static KNOWLEDGE_MAX = 99; + static COST_CONFIG = { + one: { min: 50, max: 5000 }, + all: { min: 400, max: 40000 } + }; + static HEALTH_ACTIVITIES = [ + { tr: "barber", method: "healthBarber", cost: 10 }, + { tr: "doctor", method: "healthDoctor", cost: 50 }, + { tr: "witch", method: "healthWitch", cost: 500 }, + { tr: "pill", method: "healthPill", cost: 5000 }, + { tr: "drunkOfLife", method: "healthDruckOfLife", cost:5000000 } + ]; + async getFalukantUserByHashedId(hashedId) { const user = await FalukantUser.findOne({ include: [ @@ -93,7 +116,7 @@ class FalukantService extends BaseService { { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr', 'id'] }, { model: CharacterTrait, as: 'traits', attributes: ['id', 'tr'] } ], - attributes: ['id', 'birthdate', 'gender', 'moodId'] + attributes: ['id', 'birthdate', 'gender', 'moodId', 'health'] }, { model: UserHouse, @@ -201,7 +224,37 @@ class FalukantService extends BaseService { const falukantUser = await FalukantUser.findOne({ include: [ { model: User, as: 'user', attributes: ['hashedId'], where: { hashedId: hashedUserId } }, - { model: FalukantCharacter, as: 'character', attributes: ['birthdate', 'health'] } + { + model: FalukantCharacter, + as: 'character', + attributes: ['birthdate', 'health'], + include: [ + { + model: Relationship, + as: 'relationshipsAsCharacter1', + required: false, + attributes: ['id', 'character2Id', 'relationshipTypeId'], + include: [{ + model: RelationshipType, + as: 'relationshipType', + attributes: ['tr'], + where: { tr: { [Op.not]: 'lover' } } + }] + }, + { + model: Relationship, + as: 'relationshipsAsCharacter2', + required: false, + attributes: ['id', 'character1Id', 'relationshipTypeId'], + include: [{ + model: RelationshipType, + as: 'relationshipType', + attributes: ['tr'], + where: { tr: { [Op.not]: 'lover' } } + }] + } + ] + }, ], attributes: ['money'] }); @@ -822,6 +875,13 @@ class FalukantService extends BaseService { createdAt: { [Op.lt]: new Date(Date.now() - 21 * 24 * 60 * 60 * 1000), }, + include: [ + { + model: TitleOfNobility, + as: 'nobleTitle', + attributes: ['level'], + }, + ] }, order: Sequelize.fn('RANDOM'), }); @@ -830,7 +890,7 @@ class FalukantService extends BaseService { } const avgKnowledge = await this.calculateAverageKnowledge(directorCharacter.id); const proposedIncome = Math.round( - directorCharacter.titleOfNobility * Math.pow(1.231, avgKnowledge / 1.5) + directorCharacter.nobleTitle.level * Math.pow(1.231, avgKnowledge / 1.5) ); await DirectorProposal.create({ directorCharacterId: directorCharacter.id, @@ -990,6 +1050,95 @@ class FalukantService extends BaseService { }; } + async getAllDirectors(hashedUserId) { + const user = await getFalukantUserOrFail(hashedUserId); + if (!user) { + throw new Error('User not found'); + } + const directors = await Director.findAll({ + where: { employerUserId: user.id }, + include: [ + { + model: FalukantCharacter, + as: 'character', + attributes: ['id', 'birthdate', 'gender'], + include: [ + { + model: TitleOfNobility, + as: 'nobleTitle', + attributes: ['labelTr', 'level'], + }, + { + model: FalukantPredefineFirstname, + as: 'definedFirstName', + attributes: ['name'], + }, + { + model: FalukantPredefineLastname, + as: 'definedLastName', + attributes: ['name'], + }, + { + model: Knowledge, + as: 'knowledges', + attributes: ['productId', 'knowledge'], + include: [ + { + model: ProductType, + as: 'productType', + attributes: ['labelTr'], + }, + ], + }, + { + model: RegionData, + as: 'region', + attributes: ['name'] + } + ] + }, + ], + attributes: ['id', 'satisfaction', 'income'], + }); + return directors.map(director => { + // 1) avgKnowledge berechnen + const knowledges = director.character.knowledges || []; + const avgKnowledge = knowledges.length + ? knowledges.reduce((sum, k) => sum + k.knowledge, 0) / knowledges.length + : 0; + + // 2) wishedIncome anhand der JS-Formel + const wishedIncome = Math.round( + director.character.nobleTitle.level * Math.pow(1.231, avgKnowledge / 1.5) + ); + + return { + id: director.id, + satisfaction: director.satisfaction, + character: director.character, + age: calcAge(director.character.birthdate), + income: director.income, + region: director.character.region.name, + wishedIncome, + }; + }); + } + + async updateDirector(hashedUserId, directorId, income) { + const user = await this.getFalukantUserByHashedId(hashedUserId); + const director = await Director.findOne({ + where: { + id: directorId, + employerUserId: user.id + } + }); + if (!director) { + throw new Error('Director not found'); + } + director.update({ income: income }); + return { success: true }; + } + async setSetting(hashedUserId, branchId, directorId, settingKey, value) { const user = await this.getFalukantUserByHashedId(hashedUserId); const branch = await Branch.findOne({ @@ -1030,71 +1179,99 @@ class FalukantService extends BaseService { async getFamily(hashedUserId) { const user = await this.getFalukantUserByHashedId(hashedUserId); - if (!user) { - throw new Error('User not found'); - } + if (!user) throw new Error('User not found'); const character = await FalukantCharacter.findOne({ where: { userId: user.id } }); - if (!character) { - throw new Error('Character not found for this user'); - } - const family = { - relationships: [], - deathPartners: [], - children: [], - lovers: [], - possiblePartners: [], - }; - let relationships = await Relationship.findAll( - { - where: { - character1Id: character.id, + if (!character) throw new Error('Character not found for this user'); + let relationships = await Relationship.findAll({ + where: { character1Id: character.id }, + attributes: ['createdAt', 'widowFirstName2', 'nextStepProgress'], + include: [ + { + model: FalukantCharacter, as: 'character2', + attributes: ['id', 'birthdate', 'gender', 'moodId'], + include: [ + { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }, + { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] }, + { model: CharacterTrait, as: 'traits' }, + { model: Mood, as: 'mood' }, + ] }, - attributes: ['createdAt', 'widowFirstName2', 'nextStepProgress'], - include: [ - { - model: FalukantCharacter, - as: 'character2', - attributes: ['id', 'birthdate', 'gender', 'moodId'], - include: [ - { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }, - { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] }, - { model: CharacterTrait, as: 'traits' }, - { model: Mood, as: 'mood' }, - ], - }, - { - model: RelationshipType, - as: 'relationshipType', - attributes: ['tr'], - }, - ], - } - ); - relationships = relationships.map((relationship) => ({ - createdAt: relationship.createdAt, - widowFirstName2: relationship.widowFirstName2, - progress: relationship.nextStepProgress, + { model: RelationshipType, as: 'relationshipType', attributes: ['tr'] } + ] + }); + relationships = relationships.map(r => ({ + createdAt: r.createdAt, + widowFirstName2: r.widowFirstName2, + progress: r.nextStepProgress, character2: { - id: relationship.character2.id, - age: calcAge(relationship.character2.birthdate), - gender: relationship.character2.gender, - firstName: relationship.character2.definedFirstName?.name || 'Unknown', - nobleTitle: relationship.character2.nobleTitle?.labelTr || '', - mood: relationship.character2.mood, - characterTrait: relationship.character2.traits, + id: r.character2.id, + age: calcAge(r.character2.birthdate), + gender: r.character2.gender, + firstName: r.character2.definedFirstName?.name || 'Unknown', + nobleTitle: r.character2.nobleTitle?.labelTr || '', + mood: r.character2.mood, + traits: r.character2.traits }, - relationshipType: relationship.relationshipType.tr, + relationshipType: r.relationshipType.tr })); - family.relationships = relationships.filter((relationship) => ['wooing', 'engaged', 'married'].includes(relationship.relationshipType)); - family.lovers = relationships.filter((relationship) => ['lover'].includes(relationship.relationshipType.tr)); - family.deathPartners = relationships.filter((relationship) => ['widowed'].includes(relationship.relationshipType.tr)); + const charsWithChildren = await FalukantCharacter.findAll({ + where: { userId: user.id }, + include: [ + { + model: ChildRelation, + as: 'childrenFather', + include: [{ + model: FalukantCharacter, + as: 'child', + include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }] + }] + }, + { + model: ChildRelation, + as: 'childrenMother', + include: [{ + model: FalukantCharacter, + as: 'child', + include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }] + }] + } + ] + }); + const children = []; + for (const parentChar of charsWithChildren) { + const allRels = [ + ...(parentChar.childrenFather || []), + ...(parentChar.childrenMother || []) + ]; + for (const rel of allRels) { + const kid = rel.child; + children.push({ + name: kid.definedFirstName?.name || 'Unknown', + gender: kid.gender, + age: calcAge(kid.birthdate), + hasName: rel.nameSet, + }); + } + } + const inProgress = ['wooing', 'engaged', 'married']; + const family = { + relationships: relationships.filter(r => inProgress.includes(r.relationshipType)), + lovers: relationships.filter(r => r.relationshipType === 'lover'), + deathPartners: relationships.filter(r => r.relationshipType === 'widowed'), + children, + possiblePartners: [] + }; const ownAge = calcAge(character.birthdate); - if (ownAge < 12) { - family.possiblePartners = []; - } else if (family.relationships.length === 0) { + if (ownAge >= 12 && family.relationships.length === 0) { family.possiblePartners = await this.getPossiblePartners(character.id); if (family.possiblePartners.length === 0) { - await this.createPossiblePartners(character.id, character.gender, character.regionId, character.titleOfNobility, ownAge); + await this.createPossiblePartners( + character.id, + character.gender, + character.regionId, + character.titleOfNobility, + ownAge + ); family.possiblePartners = await this.getPossiblePartners(character.id); } } @@ -1252,6 +1429,55 @@ class FalukantService extends BaseService { })); } + async getChildren(hashedUserId) { + const user = await this.getFalukantUserByHashedId(hashedUserId); + console.log(user); + const children = await ChildRelation.findAll({ + where: { + [Op.or]: [ + { fatherCharacterId: user.character.id }, + { motherCharacterId: user.character.id } + ] + }, + include: [ + { + model: FalukantCharacter, + as: 'child', + attributes: ['id', 'birthdate'], + include: [ + { + model: FalukantPredefineFirstname, + as: 'definedFirstName', + attributes: ['name'] + }, + { + model: Knowledge, + as: 'knowledges', + attributes: ['knowledge'], + include: [ + { + model: ProductType, + as: 'productType', + attributes: ['id', 'labelTr'] + } + ] + } + ] + }, + ] + }); + return children.map(rel => ({ + id: rel.child.id, + name: rel.child.definedFirstName.name, + age: calcAge(rel.child.birthdate), + knowledge: rel.child.knowledges.map(k => ({ + id: k.productType.id, + tr: k.productType.labelTr, + knowledge: k.knowledge + })) + })); + } + async sendGift(hashedUserId, giftId) { const user = await this.getFalukantUserByHashedId(hashedUserId); const lowestTitle = await TitleOfNobility.findOne({ order: [['id', 'ASC']] }); @@ -1390,7 +1616,6 @@ class FalukantService extends BaseService { } ] }); - console.log(user.falukantData[0].userHouse); return user.falukantData[0].userHouse ?? { position: 0, roofCondition: 100, wallCondition: 100, floorCondition: 100, windowCondition: 100 }; } catch (error) { console.log(error); @@ -1405,7 +1630,7 @@ class FalukantService extends BaseService { include: [{ model: HouseType, as: 'houseType', - attributes: ['position', 'cost'], + attributes: ['position', 'cost', 'labelTr'], where: { minimumNobleTitle: { [Op.lte]: user.character.nobleTitle.id @@ -1465,8 +1690,598 @@ class FalukantService extends BaseService { housePrice(house) { const houseQuality = (house.roofCondition + house.windowCondition + house.floorCondition + house.wallCondition) / 4; - return (house.houseType.cost / 100 * houseQuality ).toFixed(2); -} -} + return (house.houseType.cost / 100 * houseQuality).toFixed(2); + } + + async getPartyTypes(hashedUserId) { + const falukantUser = await getFalukantUserOrFail(hashedUserId); + const engagedCount = await Relationship.count({ + include: [ + { + model: RelationshipType, + as: 'relationshipType', + where: { tr: 'engaged' }, + required: true + }, + { + model: FalukantCharacter, + as: 'character1', + where: { userId: falukantUser.id }, + required: false + }, + { + model: FalukantCharacter, + as: 'character2', + where: { userId: falukantUser.id }, + required: false + } + ], + where: { + [Op.or]: [ + { '$character1.user_id$': falukantUser.id }, + { '$character2.user_id$': falukantUser.id } + ] + } + }); + const orConditions = [{ forMarriage: false }]; + if (engagedCount > 0) { + orConditions.push({ forMarriage: true }); + } + const partyTypes = await PartyType.findAll({ + where: { + [Op.or]: orConditions + }, + order: [['cost', 'ASC']] + }); + const musicTypes = await MusicType.findAll(); + const banquetteTypes = await BanquetteType.findAll(); + return { partyTypes, musicTypes, banquetteTypes }; + } + + async createParty(hashedUserId, partyTypeId, musicId, banquetteId, nobilityIds = [], servantRatio) { + const falukantUser = await getFalukantUserOrFail(hashedUserId); + const since = new Date(Date.now() - 24 * 3600 * 1000); + const already = await Party.findOne({ + where: { + falukantUserId: falukantUser.id, + partyTypeId, + createdAt: { [Op.gte]: since }, + }, + attributes: ['id'] + }); + if (already) { + throw new Error('Diese Party wurde bereits innerhalb der letzten 24 Stunden bestellt'); + } + const [ptype, music, banquette] = await Promise.all([ + PartyType.findByPk(partyTypeId), + MusicType.findByPk(musicId), + BanquetteType.findByPk(banquetteId), + ]); + if (!ptype || !music || !banquette) { + throw new Error('Ungültige Party-, Musik- oder Bankett-Auswahl'); + } + const nobilities = nobilityIds.length + ? await TitleOfNobility.findAll({ where: { id: { [Op.in]: nobilityIds } } }) + : []; + let cost = (ptype.cost || 0) + (music.cost || 0) + (banquette.cost || 0); + cost += (50 / servantRatio - 1) * 1000; + const nobilityCost = nobilities.reduce((sum, n) => sum + ((n.id ^ 5) * 1000), 0); + cost += nobilityCost; + if (Number(falukantUser.money) < cost) { + throw new Error('Nicht genügend Guthaben für diese Party'); + } + const moneyResult = await updateFalukantUserMoney( + falukantUser.id, + -cost, + 'partyOrder', + falukantUser.id + ); + if (!moneyResult.success) { + throw new Error('Geld konnte nicht abgezogen werden'); + } + const party = await Party.create({ + partyTypeId, + falukantUserId: falukantUser.id, + musicTypeId: musicId, + banquetteTypeId: banquetteId, + servantRatio, + cost: cost + }); + if (nobilityIds.length) { + await party.addInvitedNobilities(nobilityIds); + } + const user = await User.findByPk(falukantUser.userId); + notifyUser(user.hashedId, 'falukantPartyUpdate', { + partyId: party.id, + cost, + }); + return { 'success': true }; + } + + async getParties(hashedUserId) { + const falukantUser = await getFalukantUserOrFail(hashedUserId); + const parties = await Party.findAll({ + where: { falukantUserId: falukantUser.id }, + include: [ + { + model: PartyType, + as: 'partyType', + attributes: ['tr'], + }, + { + model: MusicType, + as: 'musicType', + attributes: ['tr'], + }, + { + model: BanquetteType, + as: 'banquetteType', + attributes: ['tr'], + }, + ], + order: [['createdAt', 'DESC']], + attributes: ['id', 'createdAt', 'servantRatio', 'cost'], + }); + return parties; + } + + async getNotBaptisedChildren(hashedUserId) { + const falukantUser = await getFalukantUserOrFail(hashedUserId); + const children = await ChildRelation.findAll({ + include: [ + { + model: FalukantCharacter, + as: 'father', + where: { + userId: falukantUser.id, + }, + required: false, + }, + { + model: FalukantCharacter, + as: 'mother', + where: { + userId: falukantUser.id, + }, + required: false, + }, + { + model: FalukantCharacter, + as: 'child', + required: true, + include: [ + { + model: FalukantPredefineFirstname, + as: 'definedFirstName', + required: true, + }, + ] + }, + ], + where: { + nameSet: false, + }, + order: [['createdAt', 'DESC']], + }); + return children.map(child => { + return { + id: child.child.id, + gender: child.child.gender, + age: calcAge(child.child.birthdate), + proposedFirstName: child.child.definedFirstName.name, + }; + }); + } + + async baptise(hashedUserId, childId, firstName) { + const falukantUser = await getFalukantUserOrFail(hashedUserId); + const parentCharacter = await FalukantCharacter.findOne({ + where: { + userId: falukantUser.id, + }, + }); + if (!parentCharacter) { + throw new Error('Parent character not found'); + } + const child = await FalukantCharacter.findOne({ + where: { + id: childId, + }, + }); + if (!child) { + throw new Error('Child not found'); + } + const childRelation = await ChildRelation.findOne({ + where: { + [Op.or]: [ + { + fatherCharacterId: parentCharacter.id, + childCharacterId: child.id, + }, + { + motherCharacterId: parentCharacter.id, + childCharacterId: child.id, + } + ] + } + }); + if (!childRelation) { + throw new Error('Child relation not found'); + } + await childRelation.update({ + nameSet: true, + }); + let firstNameObject = FalukantPredefineFirstname.findOne({ + where: { + name: firstName, + gender: child.gender, + }, + }); + if (!firstNameObject) { + firstNameObject = await FalukantPredefineFirstname.create({ + name: firstName, + gender: child.gender, + }); + } + await child.update({ + firstName: firstNameObject.id, + }); + updateFalukantUserMoney(falukantUser.id, -50, 'Baptism', falukantUser.id); + return { success: true }; + } catch(error) { + throw new Error(error.message); + } + + async getEducation(hashedUserId) { + try { + const falukantUser = await getFalukantUserOrFail(hashedUserId); + const education = await Learning.findAll({ + where: { + createdAt: { [Op.gt]: new Date().getTime() - 1000 * 60 * 60 * 24 }, + }, + include: [ + { + model: LearnRecipient, + as: 'recipient', + attributes: ['tr'] + }, + { + model: ProductType, + as: 'productType', + attributes: ['labelTr'] + }, + { + model: FalukantUser, + as: 'learner', + where: { + id: falukantUser.id + }, + attributes: [] + }, + { + model: FalukantCharacter, + as: 'learningCharacter', + attributes: ['id'] + } + ], + attributes: ['createdAt'], + }); + return education; + } catch (error) { + console.log(error); + throw new Error(error.message); + } + } + + computeCost(percent, mode) { + const cfg = FalukantService.COST_CONFIG[mode]; + // clamp percent auf [0, KNOWLEDGE_MAX] + const p = Math.min(Math.max(percent, 0), FalukantService.KNOWLEDGE_MAX) + / FalukantService.KNOWLEDGE_MAX; + return Math.round(cfg.min + (cfg.max - cfg.min) * p); + } + + async sendToSchool(hashedUserId, item, student, studentId) { + const falukantUser = await getFalukantUserOrFail(hashedUserId); + + // 1) prüfen, ob schon in Arbeit + const education = await this.getEducation(hashedUserId); + const already = education.some(e => + e.recipient.tr === student && + (studentId == null || e.learningCharacter?.id === studentId) + ); + if (already) throw new Error('Already learning this character'); + + // 2) Empfänger holen + const rec = await LearnRecipient.findOne({ where: { tr: student } }); + if (!rec) throw new Error('Character not found'); + + // 3) Wissens-Prozentsatz ermitteln + let percent; + if (item === 'all') { + const all = await this.getKnowledgeForAll(hashedUserId, student, studentId); + const sum = all.reduce((s, k) => s + k.knowledge, 0); + percent = sum / all.length; + } else { + const single = await this.getKnowledgeSingle(hashedUserId, student, studentId, item); + percent = single.knowledge; + } + + // 4) Kosten berechnen + const mode = item === 'all' ? 'all' : 'one'; + const cost = this.computeCost(percent, mode); + + // 5) Kontostand prüfen + if (parseFloat(falukantUser.money) < cost) { + throw new Error('Not enough money'); + } + + // 6) Learning anlegen + await Learning.create({ + learningRecipientId: rec.id, + associatedLearningCharacterId: studentId, + associatedFalukantUserId: falukantUser.id, + productId: item === 'all' ? null : item, + learnAllProducts: (item === 'all') + }); + + // 7) Geld abziehen + const upd = await updateFalukantUserMoney( + falukantUser.id, + -cost, + item === 'all' ? 'learnAll' : `learnItem:${item}`, + falukantUser.id + ); + if (!upd.success) throw new Error(upd.message); + + return true; + } + + async getBankOverview(hashedUserId) { + const falukantUser = await getFalukantUserOrFail(hashedUserId); + if (!falukantUser) throw new Error('User not found'); + + // 1) offene Schulden + const totalDebt = await Credit.sum('remaining_amount', { + where: { falukant_user_id: falukantUser.id } + }) || 0; + + // 2) Häuser ermitteln + const userHouses = await UserHouse.findAll({ + where: { userId: falukantUser.id }, + include: [{ model: HouseType, as: 'houseType', attributes: ['cost'] }] + }); + + // 3) Hauswert berechnen: buyCost * 0.8 + let houseValue = 0; + for (const uh of userHouses) { + const { roofCondition, wallCondition, floorCondition, windowCondition } = uh; + const qualityAvg = (roofCondition + wallCondition + floorCondition + windowCondition) / 4; + const buyWorth = (uh.houseType.cost / 100) * qualityAvg; + houseValue += buyWorth * 0.8; + } + + // 4) Filialwert (1000 pro Branch) + const branchCount = await Branch.count({ where: { falukantUserId: falukantUser.id } }); + const branchValue = branchCount * 1000; + + // 5) Maximaler Kredit und verfügbare Linie + const maxCredit = Math.floor(houseValue + branchValue); + const availableCredit = maxCredit - totalDebt; + + // 6) aktive Kredite laden + const activeCredits = await Credit.findAll({ + where: { falukantUserId: falukantUser.id }, + attributes: ['id', 'amount', 'remainingAmount', 'interestRate'] + }); + + return { + money: falukantUser.money, + totalDebt, + maxCredit, + availableCredit, + activeCredits, + fee: 7 + }; + } + + async getBankCredits(hashedUserId) { + const falukantUser = await getFalukantUserOrFail(hashedUserId); + if (!falukantUser) throw new Error('User not found'); + const credits = await Credit.findAll({ + where: { falukantUserId: falukantUser.id }, + }); + return credits; + } + + async takeBankCredits(hashedUserId, height) { + const falukantUser = await getFalukantUserOrFail(hashedUserId); + if (!falukantUser) throw new Error('User not found'); + const financialData = await this.getBankOverview(hashedUserId); + if (financialData.availableCredit < height) { + throw new Error('Not enough credit'); + } + const newCredit = await Credit.create({ + falukantUserId: falukantUser.id, + amount: height, + remainingAmount: height, + interestRate: financialData.fee, + }); + updateFalukantUserMoney(falukantUser.id, height, 'credit taken', falukantUser.id); + return { height: newCredit.amount, fee: newCredit.interestRate }; + } + + async getNobility(hashedUserId) { + const falukantUser = await getFalukantUserOrFail(hashedUserId); + const nobility = await TitleOfNobility.findOne({ + include: [ + { + model: FalukantCharacter, + as: 'charactersWithNobleTitle', + attributes: ['gender'], + where: { + userId: falukantUser.id + } + }, + { + model: TitleRequirement, + as: 'requirements', + attributes: ['requirementType', 'requirementValue'] + } + ], + attributes: ['labelTr', 'level'] + }); + const currentTitleLevel = nobility.level; + const nextTitle = await TitleOfNobility.findOne({ + where: { + level: currentTitleLevel + 1 + }, + include: [ + { + model: TitleRequirement, + as: 'requirements', + } + ], + attributes: ['labelTr'] + }); + return { + current: nobility, + next: nextTitle + }; + } + + async advanceNobility(hashedUserId) { + const nobility = await this.getNobility(hashedUserId); + if (!nobility || !nobility.next) { + throw new Error('User does not have a nobility'); + } + const nextTitle = nobility.next.toJSON(); + const user = await this.getFalukantUserByHashedId(hashedUserId); + let fulfilled = true; + let cost = 0; + for (const requirement of nextTitle.requirements) { + switch (requirement.requirementType) { + case 'money': + fulfilled = fulfilled && await this.checkMoneyRequirement(user, requirement); + break; + case 'cost': + fulfilled = fulfilled && await this.checkMoneyRequirement(user, requirement); + cost = requirement.requirementValue; + break; + case 'branches': + fulfilled = fulfilled && await this.checkBranchesRequirement(hashedUserId, requirement); + break; + default: + fulfilled = false; + }; + } + if (!fulfilled) { + throw new Error('Requirements not fulfilled'); + } + const newTitle = await TitleOfNobility.findOne({ + where: { level: nobility.current.level + 1 } + }); + const character = await FalukantCharacter.findOne({ where: { userId: user.id } }); + await character.update({ titleOfNobility: newTitle.id }); + if (cost > 0) { + updateFalukantUserMoney(user.id, -cost, 'new nobility title', user.id); + } + return {}; + } + + async checkMoneyRequirement(user, requirement) { + return user.money >= requirement.requirementValue; + } + + async checkBranchesRequirement(hashedUserId, requirement) { + const user = await this.getFalukantUserByHashedId(hashedUserId); + } + + async getHealth(hashedUserId) { + const user = await this.getFalukantUserByHashedId(hashedUserId); + const healthActivities = FalukantService.HEALTH_ACTIVITIES.map((activity) => {return { tr: activity.tr, cost: activity.cost }}); + const healthHistory = await HealthActivity.findAll({ + where: { characterId: user.character.id }, + order: [['createdAt', 'DESC']], + }); + return { + age: calcAge(user.character.birthdate), + health: user.character.health, + healthActivities: healthActivities, + history: healthHistory.map((activity) => {return { tr: activity.activityTr, cost: activity.cost, createdAt: activity.createdAt, success: activity.successPercentage }}), + }; + } + + async healthActivity(hashedUserId, activity) { + const user = await this.getFalukantUserByHashedId(hashedUserId); + const lastHealthActivity = await HealthActivity.findOne({ + where: { + characterId: user.character.id, + activityTr: activity, + createdAt: { + [Op.gte]: new Date(new Date().setDate(new Date().getDate() - 1)) + } + }, + order: [['createdAt', 'DESC']], + limit: 1 + }); + if (lastHealthActivity) { + throw new Error('too close'); + } + const activityObject = FalukantService.HEALTH_ACTIVITIES.find((a) => a.tr === activity); + if (!activityObject) { + throw new Error('invalid'); + } + if (user.money - activityObject.cost < 0) { + throw new Error('no money'); + } + user.character.health -= activityObject.cost; + await HealthActivity.create({ + characterId: user.character.id, + activityTr: activity, + successPercentage: await this[activityObject.method](user), + cost: activityObject.cost + }); + updateFalukantUserMoney(user.id, -activityObject.cost, 'health.' + activity); + return { success: true }; + } + + async healthChange(user, delta) { + const char = await FalukantCharacter.findOne({ + where: { + id: user.character.id + } + }); + await char.update({ + health: Math.min(FalukantService.HEALTH_MAX || 100, Math.max(0, char.health + delta)) + }); + return delta; + } + + async healthBarber(user) { + const raw = Math.floor(Math.random() * 11) - 5; + return this.healthChange(user, raw); + } + + async healthDoctor(user) { + const raw = Math.floor(Math.random() * 8) - 2; + return this.healthChange(user, raw); + } + + async healthWitch(user) { + const raw = Math.floor(Math.random() * 7) - 1; + return this.healthChange(user, raw); + } + + async healthPill(user) { + const raw = Math.floor(Math.random() * 8); + return this.healthChange(user, raw); + } + + async healthDrunkOfLife(user) { + const raw = Math.floor(Math.random() * 26); + return this.healthChange(user, raw); + } + } export default new FalukantService(); diff --git a/backend/utils/falukant/initializeFalukantPredefines.js b/backend/utils/falukant/initializeFalukantPredefines.js index 482b1e3..b466502 100644 --- a/backend/utils/falukant/initializeFalukantPredefines.js +++ b/backend/utils/falukant/initializeFalukantPredefines.js @@ -267,73 +267,81 @@ async function initializeFalukantProducts() { } async function initializeFalukantTitles() { - await TitleOfNobility.bulkCreate([ - { labelTr: "noncivil" }, - { labelTr: "civil" }, - { labelTr: "sir" }, - { labelTr: "townlord" }, - { labelTr: "by" }, - { labelTr: "landlord" }, - { labelTr: "knight" }, - { labelTr: "baron" }, - { labelTr: "count" }, - { labelTr: "palsgrave" }, - { labelTr: "margrave" }, - { labelTr: "landgrave" }, - { labelTr: "ruler" }, - { labelTr: "elector" }, - { labelTr: "imperial-prince" }, - { labelTr: "duke" }, - { labelTr: "grand-duke" }, - { labelTr: "prince-regent" }, - { labelTr: "king" }, - ], { - updateOnDuplicate: ['labelTr'], - }); + try { + await TitleOfNobility.bulkCreate([ + { labelTr: "noncivil", level: 1 }, + { labelTr: "civil", level: 2 }, + { labelTr: "sir", level: 3 }, + { labelTr: "townlord", level: 4 }, + { labelTr: "by", level: 5 }, + { labelTr: "landlord", level: 6 }, + { labelTr: "knight", level: 7 }, + { labelTr: "baron", level: 8 }, + { labelTr: "count", level: 9 }, + { labelTr: "palsgrave", level: 10 }, + { labelTr: "margrave", level: 11 }, + { labelTr: "landgrave", level: 12 }, + { labelTr: "ruler", level: 13 }, + { labelTr: "elector", level: 14 }, + { labelTr: "imperial-prince", level: 15 }, + { labelTr: "duke", level: 16 }, + { labelTr: "grand-duke", level: 17 }, + { labelTr: "prince-regent", level: 18 }, + { labelTr: "king", level: 19 }, + ], { + updateOnDuplicate: ['labelTr'], + }); + } catch (error) { + console.error('Error initializing Falukant titles:', error); + } } async function initializeFalukantTitleRequirements() { const titleRequirements = [ - { labelTr: "civil", requirements: [{ type: "money", value: 500 }] }, - { labelTr: "sir", requirements: [{ type: "branches", value: 2 }] }, - { labelTr: "townlord", requirements: [] }, - { labelTr: "by", requirements: [] }, - { labelTr: "landlord", requirements: [] }, - { labelTr: "knight", requirements: [] }, - { labelTr: "baron", requirements: [{ type: "branches", value: 4 }] }, - { labelTr: "count", requirements: [] }, - { labelTr: "palsgrave", requirements: [] }, - { labelTr: "margrave", requirements: [] }, - { labelTr: "landgrave", requirements: [] }, - { labelTr: "ruler", requirements: [] }, - { labelTr: "elector", requirements: [] }, - { labelTr: "imperial-prince", requirements: [] }, - { labelTr: "duke", requirements: [] }, - { labelTr: "grand-duke", requirements: [] }, - { labelTr: "prince-regent", requirements: [] }, - { labelTr: "king", requirements: [] }, + { labelTr: "civil", requirements: [{ type: "money", value: 5000 }, { type: "cost", value: 500 }] }, + { labelTr: "sir", requirements: [{ type: "branches", value: 2 }, { type: "cost", value: 1000 }] }, + { labelTr: "townlord", requirements: [{ type: "cost", value: 3000 }] }, + { labelTr: "by", requirements: [{ type: "cost", value: 6000 }] }, + { labelTr: "landlord", requirements: [{ type: "cost", value: 9000 }] }, + { labelTr: "knight", requirements: [{ type: "cost", value: 11000 }] }, + { labelTr: "baron", requirements: [{ type: "branches", value: 4 }, { type: "cost", value: 15000 }] }, + { labelTr: "count", requirements: [{ type: "cost", value: 19000 }] }, + { labelTr: "palsgrave", requirements: [{ type: "cost", value: 25000 }] }, + { labelTr: "margrave", requirements: [{ type: "cost", value: 33000 }] }, + { labelTr: "landgrave", requirements: [{ type: "cost", value: 47000 }] }, + { labelTr: "ruler", requirements: [{ type: "cost", value: 66000 }] }, + { labelTr: "elector", requirements: [{ type: "cost", value: 79000 }] }, + { labelTr: "imperial-prince", requirements: [{ type: "cost", value: 99999 }] }, + { labelTr: "duke", requirements: [{ type: "cost", value: 130000 }] }, + { labelTr: "grand-duke",requirements: [{ type: "cost", value: 170000 }] }, + { labelTr: "prince-regent", requirements: [{ type: "cost", value: 270000 }] }, + { labelTr: "king", requirements: [{ type: "cost", value: 500000 }] }, ]; + const titles = await TitleOfNobility.findAll(); const requirementsToInsert = []; + for (let i = 0; i < titleRequirements.length; i++) { - const titleRequirement = titleRequirements[i]; - const title = titles.find(t => t.labelTr === titleRequirement.labelTr); + const titleReq = titleRequirements[i]; + const title = titles.find(t => t.labelTr === titleReq.labelTr); if (!title) continue; + if (i > 1) { - const moneyRequirement = { - type: "money", + titleReq.requirements.push({ + type: "money", value: 5000 * Math.pow(3, i - 1), - }; - titleRequirement.requirements.push(moneyRequirement); + }); } - for (const requirement of titleRequirement.requirements) { + + for (const req of titleReq.requirements) { requirementsToInsert.push({ - titleId: title.id, - requirementType: requirement.type, - requirementValue: requirement.value, + titleId: title.id, + requirementType: req.type, + requirementValue: req.value, }); } } + await TitleRequirement.bulkCreate(requirementsToInsert, { ignoreDuplicates: true }); } diff --git a/backend/utils/falukant/initializeFalukantTypes.js b/backend/utils/falukant/initializeFalukantTypes.js index 8621c03..d55c1c6 100644 --- a/backend/utils/falukant/initializeFalukantTypes.js +++ b/backend/utils/falukant/initializeFalukantTypes.js @@ -8,6 +8,10 @@ import PromotionalGiftCharacterTrait from "../../models/falukant/predefine/promo import PromotionalGiftMood from "../../models/falukant/predefine/promotional_gift_mood.js"; import HouseType from '../../models/falukant/type/house.js'; import TitleOfNobility from "../../models/falukant/type/title_of_nobility.js"; +import PartyType from "../../models/falukant/type/party.js"; +import MusicType from "../../models/falukant/type/music.js"; +import BanquetteType from "../../models/falukant/type/banquette.js"; +import LearnRecipient from "../../models/falukant/type/learn_recipient.js"; export const initializeFalukantTypes = async () => { await initializeFalukantTypeRegions(); @@ -17,6 +21,10 @@ export const initializeFalukantTypes = async () => { await initializeFalukantPromotionalGifts(); await initializePromotionalGiftMoodLinks(); await initializeFalukantHouseTypes(); + await initializeFalukantPartyTypes(); + await initializeFalukantMusicTypes(); + await initializeFalukantBanquetteTypes(); + await initializeLearnerTypes(); }; const regionTypes = []; @@ -208,15 +216,44 @@ const promotionalGiftMoodLinks = [ ]; const houseTypes = [ - { labelTr: 'Unter der Brücke', abbr: 'under_bridge', cost: 10, position: 1, minimumTitle: 'noncivil' }, - { labelTr: 'Strohhütte', abbr: 'straw_hut', cost: 20, position: 2, minimumTitle: 'noncivil' }, - { labelTr: 'Holzhaus', abbr: 'wooden_house', cost: 50, position: 3, minimumTitle: 'civil' }, - { labelTr: 'Hinterhofzimmer', abbr: 'backyard_room', cost: 5, position: 4, minimumTitle: 'civil' }, - { labelTr: 'Kleines Familienhaus', abbr: 'family_house', cost: 100, position: 5, minimumTitle: 'sir' }, - { labelTr: 'Stadthaus', abbr: 'townhouse', cost: 200, position: 6, minimumTitle: 'townlord' }, - { labelTr: 'Villa', abbr: 'villa', cost: 500, position: 7, minimumTitle: 'knight' }, - { labelTr: 'Herrenhaus', abbr: 'mansion', cost: 1000, position: 8, minimumTitle: 'ruler' }, - { labelTr: 'Schloss', abbr: 'castle', cost: 5000, position: 9, minimumTitle: 'prince-regent' }, + { labelTr: 'Unter der Brücke', abbr: 'under_bridge', cost: 0, position: 1, minimumTitle: 'noncivil' }, + { labelTr: 'Strohhütte', abbr: 'straw_hut', cost: 100, position: 2, minimumTitle: 'noncivil' }, + { labelTr: 'Holzhaus', abbr: 'wooden_house', cost: 5000, position: 3, minimumTitle: 'civil' }, + { labelTr: 'Hinterhofzimmer', abbr: 'backyard_room', cost: 75000, position: 4, minimumTitle: 'civil' }, + { labelTr: 'Kleines Familienhaus', abbr: 'family_house', cost: 273000, position: 5, minimumTitle: 'sir' }, + { labelTr: 'Stadthaus', abbr: 'townhouse', cost: 719432, position: 6, minimumTitle: 'townlord' }, + { labelTr: 'Villa', abbr: 'villa', cost: 3500000, position: 7, minimumTitle: 'knight' }, + { labelTr: 'Herrenhaus', abbr: 'mansion', cost: 18000000, position: 8, minimumTitle: 'ruler' }, + { labelTr: 'Schloss', abbr: 'castle', cost: 500000000, position: 9, minimumTitle: 'prince-regent' }, +]; + +const partyTypes = [ + { labelTr: 'wedding', cost: 50, forMarriage: true, reputationGrowth: 5 }, + { labelTr: 'ball', cost: 250, forMarriage: false, reputationGrowth: 7 }, + { labelTr: 'town fair', cost: 1000, forMarriage: false, reputationGrowth: 10 }, + { labelTr: 'royal feast', cost: 50000, forMarriage: false, reputationGrowth: 25 }, +]; + +const musicTypes = [ + { type: 'none', cost: 0, reputationGrowth: 0 }, + { type: 'bard', cost: 100, reputationGrowth: 2 }, + { type: 'villageBand', cost: 2500, reputationGrowth: 5 }, + { type: 'chamberOrchestra', cost: 12000, reputationGrowth: 10 }, + { type: 'symphonyOrchestra', cost: 37000, reputationGrowth: 15 }, + { type: 'symphonyOrchestraWithChorusAndSolists', cost: 500000, reputationGrowth: 25 }, +]; + +const banquetteTypes = [ + { type: 'bread', cost: 5, reputationGrowth: 0 }, + { type: 'roastWithBeer', cost: 200, reputationGrowth: 5 }, + { type: 'poultryWithVegetablesAndWine', cost: 5000, reputationGrowth: 10 }, + { type: 'extensiveBuffet', cost: 100000, reputationGrowth: 20 } +]; + +const learnerTypes = [ + { tr: 'self', }, + { tr: 'children', }, + { tr: 'director', }, ]; { @@ -379,7 +416,7 @@ export const initializePromotionalGiftMoodLinks = async () => { }; export const initializeFalukantHouseTypes = async () => { - for (const ht of houseTypes) { + for (const ht of houseTypes) { const [record, created] = await HouseType.findOrCreate({ where: { labelTr: ht.abbr }, defaults: { @@ -391,3 +428,54 @@ export const initializeFalukantHouseTypes = async () => { }); } }; + +export const initializeFalukantPartyTypes = async () => { + for (const pt of partyTypes) { + const [record, created] = await PartyType.findOrCreate({ + where: { tr: pt.labelTr }, + defaults: { + cost: pt.cost, + tr: pt.labelTr, + forMarriage: pt.forMarriage, + reputationGrowth: pt.reputationGrowth, + } + }); + } +} + +export const initializeFalukantMusicTypes = async () => { + for (const mt of musicTypes) { + const [record, created] = await MusicType.findOrCreate({ + where: { tr: mt.type }, + defaults: { + cost: mt.cost, + tr: mt.type, + reputationGrowth: mt.reputationGrowth, + } + }); + } +} + +export const initializeFalukantBanquetteTypes = async () => { + for (const bt of banquetteTypes) { + const [record, created] = await BanquetteType.findOrCreate({ + where: { tr: bt.type }, + defaults: { + tr: bt.type, + cost: bt.cost, + reputationGrowth: bt.reputationGrowth, + } + }); + } +}; + +export const initializeLearnerTypes = async () => { + for (const lt of learnerTypes) { + const [record, created] = await LearnRecipient.findOrCreate({ + where: { tr: lt.tr }, + defaults: { + tr: lt.tr, + } + }); + } +} \ No newline at end of file diff --git a/dump.rdb b/dump.rdb index 7c978bd..45e19e6 100644 Binary files a/dump.rdb and b/dump.rdb differ diff --git a/frontend/public/images/icons/falukant/church.jpg b/frontend/public/images/icons/falukant/church.jpg new file mode 100644 index 0000000..cfbb602 Binary files /dev/null and b/frontend/public/images/icons/falukant/church.jpg differ diff --git a/frontend/public/images/icons/falukant/german.zip b/frontend/public/images/icons/falukant/german.zip new file mode 100644 index 0000000..a5217c3 Binary files /dev/null and b/frontend/public/images/icons/falukant/german.zip differ diff --git a/frontend/public/images/icons/falukant/relationship-.png b/frontend/public/images/icons/falukant/relationship-.png new file mode 100644 index 0000000..bf1207a Binary files /dev/null and b/frontend/public/images/icons/falukant/relationship-.png differ diff --git a/frontend/public/images/icons/falukant/relationship-engaged.png b/frontend/public/images/icons/falukant/relationship-engaged.png new file mode 100644 index 0000000..0d16683 Binary files /dev/null and b/frontend/public/images/icons/falukant/relationship-engaged.png differ diff --git a/frontend/public/images/icons/falukant/relationship-married.png b/frontend/public/images/icons/falukant/relationship-married.png new file mode 100644 index 0000000..10ea6ed Binary files /dev/null and b/frontend/public/images/icons/falukant/relationship-married.png differ diff --git a/frontend/public/images/icons/falukant/relationship-widow.png b/frontend/public/images/icons/falukant/relationship-widow.png new file mode 100644 index 0000000..0652b9e Binary files /dev/null and b/frontend/public/images/icons/falukant/relationship-widow.png differ diff --git a/frontend/public/images/icons/falukant/relationship-wooing.png b/frontend/public/images/icons/falukant/relationship-wooing.png new file mode 100644 index 0000000..9cc7917 Binary files /dev/null and b/frontend/public/images/icons/falukant/relationship-wooing.png differ diff --git a/frontend/public/images/icons/falukant/relationships.png b/frontend/public/images/icons/falukant/relationships.png new file mode 100644 index 0000000..60867d9 Binary files /dev/null and b/frontend/public/images/icons/falukant/relationships.png differ diff --git a/frontend/src/components/SimpleTabs.vue b/frontend/src/components/SimpleTabs.vue new file mode 100644 index 0000000..df24dce --- /dev/null +++ b/frontend/src/components/SimpleTabs.vue @@ -0,0 +1,56 @@ + + + + + {{ $t(tab.label) }} + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/components/falukant/StatusBar.vue b/frontend/src/components/falukant/StatusBar.vue index e7de2f6..c152193 100644 --- a/frontend/src/components/falukant/StatusBar.vue +++ b/frontend/src/components/falukant/StatusBar.vue @@ -1,9 +1,12 @@ - + {{ item.icon }}: {{ item.value }} + + {{ item.icon }}: + @@ -23,9 +26,10 @@ export default { return { statusItems: [ { key: "age", icon: "👶", value: 0 }, + { key: "relationship", icon: "💑", image: null }, { key: "wealth", icon: "💰", value: 0 }, { key: "health", icon: "❤️", value: "Good" }, - { key: "events", icon: "📰", value: null }, + { key: "events", icon: "📰", value: null, image: null }, ], }; }, @@ -56,6 +60,9 @@ export default { const response = await apiClient.get("/api/falukant/info"); const { money, character, events } = response.data; const { age, health } = character; + const relationship = response.data.character.relationshipsAsCharacter1[0]?.relationshipType?.tr + || response.data.character.relationshipsAsCharacter2[0]?.relationshipType?.tr + || null; let healthStatus = ''; if (health > 90) { healthStatus = this.$t("falukant.health.amazing"); @@ -70,9 +77,10 @@ export default { } this.statusItems = [ { key: "age", icon: "👶", value: age }, + { key: "relationship", icon: "💑", image: relationship }, { key: "wealth", icon: "💰", value: Intl.NumberFormat(navigator.language, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(money) }, { key: "health", icon: "❤️", value: healthStatus }, - { key: "events", icon: "📰", value: events || null }, + { key: "events", icon: "📰", value: events || null, image: null }, ]; } catch (error) { console.error("Error fetching status:", error); @@ -112,7 +120,7 @@ export default { border: 1px solid #ccc; border-radius: 4px; width: calc(100% + 40px); - gap: 2em; + gap: 1.2em; margin: -21px -20px 1.5em -20px; position: fixed; } @@ -120,6 +128,8 @@ export default { .status-item { text-align: center; cursor: pointer; + display: inline-flex; + align-items: center; } .status-icon { @@ -132,4 +142,9 @@ export default { cursor: pointer; padding: 4px 2px 0 0; } + +.relationship-icon { + max-width: 24px; + max-height: 24px; +} diff --git a/frontend/src/i18n/locales/de/falukant.json b/frontend/src/i18n/locales/de/falukant.json index c98ba44..76f04df 100644 --- a/frontend/src/i18n/locales/de/falukant.json +++ b/frontend/src/i18n/locales/de/falukant.json @@ -226,7 +226,8 @@ }, "mood": "Stimmung", - "progress": "Zuneigung" + "progress": "Zuneigung", + "jumpToPartyForm": "Hochzeitsfeier veranstalten (Nötig für Hochzeit und Kinder)" }, "relationships": { "name": "Name" @@ -238,7 +239,8 @@ "actions": "Aktionen", "none": "Keine Kinder vorhanden.", "detailButton": "Details anzeigen", - "addChild": "Kind hinzufügen" + "addChild": "Kind hinzufügen", + "baptism": "Taufen" }, "lovers": { "title": "Liebhaber", @@ -407,12 +409,192 @@ "price": "Kaufpreis", "worth": "Restwert", "sell": "Verkaufen", + "renovate": "Renovieren", + "renovateAll": "Komplett renovieren", "status": { "roofCondition": "Dach", "wallCondition": "Wände", "floorCondition": "Böden", "windowCondition": "Fenster" + }, + "type": { + "backyard_room": "Hinterhofzimmer", + "wooden_house": "Holzhütte", + "straw_hut": "Strohhütte" } + }, + "nobility": { + "title": "Sozialstatus", + "tabs": { + "overview": "Übersicht", + "advance": "Erweitern" + }, + "nextTitle": "Nächster möglicher Titel", + "requirement": { + "money": "Vermögen mindestens {amount}", + "cost": "Kosten: {amount}", + "branches": "Mindestens {amount} Niederlassungen" + }, + "advance": { + "confirm": "Aufsteigen beantragen" + } + }, + "reputation": { + "title": "Reputation", + "overview": { + "title": "Übersicht" + }, + "party": { + "title": "Feste", + "totalCost": "Gesamtkosten", + "order": "Fest veranstalten", + "inProgress": "Feste in Vorbereitung", + "completed": "Abgeschlossene Feste", + "newpartyview": { + "open": "Neues Fest erstellen", + "close": "Neues Fest verbergen", + "type": "Art des Festes" + }, + "music": { + "label": "Musik", + "none": "Ohne Musik", + "bard": "Ein Barde", + "villageBand": "Eine Dorfkapelle", + "chamberOrchestra": "Ein Kammerorchester", + "symphonyOrchestra": "Ein Sinfonieorchester", + "symphonyOrchestraWithChorusAndSolists": "Ein Sinfonieorchester mit Chor und Solisten" + }, + "banquette": { + "label": "Essen", + "bread": "Brot", + "roastWithBeer": "Rostbraten mit Bier", + "poultryWithVegetablesAndWine": "Geflügel mit Gemüse und Wein", + "extensiveBuffet": "Festliches Essen" + }, + "servants": { + "label": "Ein Bediensteter pro ", + "perPersons": " Personen" + }, + "esteemedInvites": { + "label": "Eingeladene Stände" + }, + "type": "Festart", + "cost": "Kosten", + "date": "Datum" + } + }, + "party": { + "type": { + "ball": "Ball", + "wedding": "Hochzeit", + "royal feast": "Königliches Bankett", + "town fair": "Stadtmarkt" + } + }, + "church": { + "title": "Kirche", + "baptism": { + "title": "Taufen", + "table": { + "name": "Vorname", + "gender": "Geschlecht", + "age": "Alter", + "baptise": "Taufen (50)", + "newName": "Namen vorschlagen" + }, + "gender": { + "male": "Junge", + "female": "Mädchen" + }, + "success": "Das Kind wurde getauft.", + "error": "Das Kind konnte nicht getauft werden." + } + }, + "education": { + "title": "Bildung", + "self": { + "title": "Eigene Bildung" + }, + "children": { + "title": "Kinderbildung" + }, + "director": { + "title": "Direktoren-Ausbildung" + }, + "table": { + "article": "Produkt", + "knowledge": "Wissen", + "activity": "Aktivität" + }, + "learn": "Weiterbilden", + "learnAll": "In allem weiterbilden" + }, + "bank": { + "title": "Bank", + "account": { + "title": "Kontostand", + "balance": "Kontostand", + "totalDebt": "Ausstände", + "maxCredit": "Maximaler Kredit", + "availableCredit": "Verfügbarer Kredit" + }, + "credits": { + "title": "Kredite", + "none": "Derzeit hast Du keinen Kredit aufgenommen.", + "amount": "Betrag", + "remaining": "Verbleibend", + "interestRate": "Zinssatz", + "table": { + "name": "Name", + "amount": "Betrag", + "reason": "Grund", + "date": "Datum" + }, + "payoff": { + "title": "Neuen Kredit aufnehmen", + "height": "Kredithöhe", + "remaining": "Verbleibende mögliche Kredithöhe", + "fee": "Kreditzins", + "feeHeight": "Rate (a 10 Raten)", + "total": "Gesamtsumme", + "confirm": "Kredit aufnehmen" + } + } + }, + "director": { + "title": "Direktoren", + "branch": "Niederlassung", + "income": "Einkommen", + "satisfaction": "Zufriedenheit", + "name": "Name", + "age": "Alter", + "knowledge": { + "title": "Wissen", + "knowledge": "Wissen" + }, + "product": "Produkt", + "updateButton": "Gehalt aktualisieren", + "wishedIncome": "Gewünschtes Einkommen" + }, + "healthview": { + "title": "Gesundheit", + "age": "Alter", + "status": "Gesundheitszustand", + "measuresTaken": "Ergriffene Maßnahmen", + "measure": "Maßnahme", + "date": "Datum", + "cost": "Kosten", + "success": "Erfolg", + "selectMeasure": "Maßnahme", + "perform": "Durchführen", + "measures": { + "pill": "Tablette", + "doctor": "Arztbesuch", + "witch": "Hexe", + "drunkOfLife": "Trunk des Lebens", + "barber": "Barbier" + }, + "choose": "Bitte auswählen" } } } \ No newline at end of file diff --git a/frontend/src/i18n/locales/de/navigation.json b/frontend/src/i18n/locales/de/navigation.json index d5a7a77..62e60c6 100644 --- a/frontend/src/i18n/locales/de/navigation.json +++ b/frontend/src/i18n/locales/de/navigation.json @@ -65,7 +65,8 @@ "politics": "Politik", "education": "Bildung", "health": "Gesundheit", - "bank": "Bank" + "bank": "Bank", + "church": "Kirche" } } } diff --git a/frontend/src/router/falukantRoutes.js b/frontend/src/router/falukantRoutes.js index 29e639c..c259c75 100644 --- a/frontend/src/router/falukantRoutes.js +++ b/frontend/src/router/falukantRoutes.js @@ -4,6 +4,13 @@ import FalukantOverviewView from '../views/falukant/OverviewView.vue'; import MoneyHistoryView from '../views/falukant/MoneyHistoryView.vue'; import FamilyView from '../views/falukant/FamilyView.vue'; import HouseView from '../views/falukant/HouseView.vue'; +import NobilityView from '../views/falukant/NobilityView.vue'; +import ReputationView from '../views/falukant/ReputationView.vue'; +import ChurchView from '../views/falukant/ChurchView.vue'; +import EducationView from '../views/falukant/EducationView.vue'; +import BankView from '../views/falukant/BankView.vue'; +import DirectorView from '../views/falukant/DirectorView.vue'; +import HealthView from '../views/falukant/HealthView.vue'; const falukantRoutes = [ { @@ -42,6 +49,48 @@ const falukantRoutes = [ component: HouseView, meta: { requiresAuth: true }, }, + { + path: '/falukant/nobility', + name: 'NobilityView', + component: NobilityView, + meta: { requiresAuth: true } + }, + { + path: '/falukant/reputation', + name: 'ReputationView', + component: ReputationView, + meta: { requiresAuth: true } + }, + { + path: '/falukant/church', + name: 'ChurchView', + component: ChurchView, + meta: { requiresAuth: true } + }, + { + path: '/falukant/education', + name: 'EducationView', + component: EducationView, + meta: { requiresAuth: true } + }, + { + path: '/falukant/bank', + name: 'BankView', + component: BankView, + meta: { requiresAuth: true } + }, + { + path: '/falukant/directors', + name: 'DirectorView', + component: DirectorView, + meta: { requiresAuth: true } + }, + { + path: '/falukant/health', + name: 'HealthView', + component: HealthView, + meta: { requiresAuth: true } + }, ]; export default falukantRoutes; diff --git a/frontend/src/views/falukant/BankView.vue b/frontend/src/views/falukant/BankView.vue new file mode 100644 index 0000000..ea82907 --- /dev/null +++ b/frontend/src/views/falukant/BankView.vue @@ -0,0 +1,175 @@ + + + + + + {{ $t('falukant.bank.title') }} + + + + + + + + {{ $t('falukant.bank.account.balance') }} + {{ formatCost(bankOverview.money) }} + + + {{ $t('falukant.bank.account.totalDebt') }} + {{ formatCost(bankOverview.totalDebt) }} + + + {{ $t('falukant.bank.account.maxCredit') }} + {{ formatCost(bankOverview.maxCredit) }} + + + {{ $t('falukant.bank.account.availableCredit') }} + {{ formatCost(bankOverview.availableCredit) }} + + + + + + + + + + + + + {{ $t('falukant.bank.credits.amount') }} + {{ $t('falukant.bank.credits.remaining') }} + {{ $t('falukant.bank.credits.interestRate') }} + + + + + {{ formatCost(credit.amount) }} + {{ formatCost(credit.remainingAmount) }} + {{ credit.interestRate }}% + + + + + + {{ $t('falukant.bank.credits.none') }} + + + + + + + + + {{ $t('falukant.bank.credits.payoff.height') }}: + + + + {{ $t('falukant.bank.credits.payoff.remaining') }}: {{ formatCost(bankOverview.availableCredit - selectedCredit) }} + {{ $t('falukant.bank.credits.payoff.fee') }}: {{ formatCost(bankOverview.fee) }} + {{ $t('falukant.bank.credits.payoff.feeHeight') }}: {{ formatCost(feeRate()) }} + + {{ $t('falukant.bank.credits.payoff.total') }}: {{ formatCost(creditCost()) }} + + + {{ $t('falukant.bank.credits.payoff.confirm') }} + + + + + + + + + + + + diff --git a/frontend/src/views/falukant/BranchView.vue b/frontend/src/views/falukant/BranchView.vue index 0c8c288..eef5439 100644 --- a/frontend/src/views/falukant/BranchView.vue +++ b/frontend/src/views/falukant/BranchView.vue @@ -69,15 +69,15 @@ export default { "falukantUpdateStatus", "falukantBranchUpdate", ]; + if (this.daemonSocket) { + this.daemonSocket.addEventListener('message', this.handleDaemonMessage); + } events.forEach(eventName => { if (this.socket) { this.socket.on(eventName, (data) => { this.handleEvent({ event: eventName, ...data }); }); } - if (this.daemonSocket) { - this.daemonSocket.addEventListener('message', this.handleDaemonMessage); - } }); }, beforeUnmount() { diff --git a/frontend/src/views/falukant/ChurchView.vue b/frontend/src/views/falukant/ChurchView.vue new file mode 100644 index 0000000..b1651d5 --- /dev/null +++ b/frontend/src/views/falukant/ChurchView.vue @@ -0,0 +1,143 @@ + + + + + {{ $t('falukant.church.title') }} + + + + + {{ $t('falukant.church.baptism.title') }} + + + + {{ $t('falukant.church.baptism.table.gender') }} + {{ $t('falukant.church.baptism.table.name') }} + {{ $t('falukant.church.baptism.table.age') }} + + + + + + {{ $t(`falukant.church.baptism.gender.${person.gender}`) }} + + + + {{ $t('falukant.church.baptism.table.newName') }} + + + {{ person.age }} + + + {{ $t('falukant.church.baptism.table.baptise') }} + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/views/falukant/DirectorView.vue b/frontend/src/views/falukant/DirectorView.vue new file mode 100644 index 0000000..492152c --- /dev/null +++ b/frontend/src/views/falukant/DirectorView.vue @@ -0,0 +1,258 @@ + + + + + + + {{ $t('falukant.director.title') }} + + + + {{ $t('falukant.director.name') }} + {{ $t('falukant.director.branch') }} + {{ $t('falukant.director.age') }} + {{ $t('falukant.director.satisfaction') }} + + + + + + {{ $t(`falukant.titles.${dir.character.gender}.${dir.character.nobleTitle.labelTr}`) }} + {{ dir.character.definedFirstName.name }} {{ dir.character.definedLastName.name }} + + {{ dir.region || '-' }} + {{ dir.age }} + {{ dir.satisfaction }} % + + + + + + + + {{ $t(`falukant.titles.${selected.character.gender}.${selected.character.nobleTitle.labelTr}`) }} + {{ selected.character.definedFirstName.name }} {{ selected.character.definedLastName.name }} + + {{ $t('falukant.director.age') }}: {{ selected.age }} + {{ $t('falukant.director.knowledge.title') }} + + + + + {{ $t('falukant.director.product') }} + {{ $t('falukant.director.knowledge.knowledge') }} + + + + + {{ $t(`falukant.product.${item.productType.labelTr}`) }} + {{ item.knowledge }} % + + + + + + + + {{ $t('falukant.director.satisfaction') }}: + {{ selected.satisfaction }} % + + + + + {{ $t('falukant.director.income') }}: + + + ({{ $t('falukant.director.wishedIncome') }}: {{ selected.wishedIncome }}) + + + {{ $t('falukant.director.updateButton') }} + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/views/falukant/EducationView.vue b/frontend/src/views/falukant/EducationView.vue new file mode 100644 index 0000000..03814b3 --- /dev/null +++ b/frontend/src/views/falukant/EducationView.vue @@ -0,0 +1,300 @@ + + + + + {{ $t('falukant.education.title') }} + + + + + + + + {{ $t('falukant.education.table.article') }} + {{ $t('falukant.education.table.knowledge') }} + {{ $t('falukant.education.table.activity') }} + + + + + {{ $t(`falukant.product.${product.labelTr}`) }} + {{ product.knowledges[0].knowledge }} % + + + {{ $t('falukant.education.learn') }} + ({{ formatCost(getSelfCost(product.knowledges[0].knowledge)) }}) + + + + + + + + {{ $t('falukant.education.learnAll') }} + ({{ formatCost(getSelfAllCost()) }}) + + + + + + + + + + {{ child.name }} ({{ child.age }}) + + + + + + + {{ $t('falukant.education.table.article') }} + {{ $t('falukant.education.table.knowledge') }} + {{ $t('falukant.education.table.activity') }} + + + + + {{ $t(`falukant.product.${product.labelTr}`) }} + {{ getChildKnowledge(product.id) }} % + + + {{ $t('falukant.education.learn') }} + ({{ formatCost(getChildCost(product.id)) }}) + + + + + + + + {{ $t('falukant.education.learnAll') }} + ({{ formatCost(getChildrenAllCost(activeChild)) }}) + + + + + + + + + + {{ director.character.nobleTitle.tr }} + {{ director.character.definedFirstName.name }} + {{ director.character.definedLastName.name }} + + + + + + + {{ $t('falukant.education.table.article') }} + {{ $t('falukant.education.table.knowledge') }} + {{ $t('falukant.education.table.activity') }} + + + + + {{ $t(`falukant.product.${product.labelTr}`) }} + {{ getDirectorKnowledge(product.id) }} % + + + {{ $t('falukant.education.learn') }} + ({{ formatCost(getDirectorCost(product.id)) }}) + + + + + + + + {{ $t('falukant.education.learnAll') }} + ({{ formatCost(getDirectorAllCost(getDirectorCharacterId())) }}) + + + + + + + + + + + diff --git a/frontend/src/views/falukant/FamilyView.vue b/frontend/src/views/falukant/FamilyView.vue index b59603f..a0d8102 100644 --- a/frontend/src/views/falukant/FamilyView.vue +++ b/frontend/src/views/falukant/FamilyView.vue @@ -41,6 +41,10 @@ + + {{ $t('falukant.family.spouse.jumpToPartyForm') + }} + - - {{ $t('falukant.titles.' + child.gender + '.' + child.title) }} + {{ child.name }} + + {{ $t('falukant.family.children.baptism') }} + {{ child.age }} @@ -191,6 +197,9 @@ export default { await this.loadGifts(); await this.loadMoodAffects(); await this.loadCharacterAffects(); + if (this.daemonSocket) { + this.daemonSocket.addEventListener('message', this.handleDaemonMessage); + } }, methods: { async loadFamilyData() { @@ -247,10 +256,10 @@ export default { return; } try { - await apiClient.post('/api/falukant/family/gift' - , { giftId: this.selectedGiftId }); - this.loadFamilyData(); - this.$root.$refs.messageDialog.open('tr:falukant.family.sendgift.success'); + await apiClient.post('/api/falukant/family/gift' + , { giftId: this.selectedGiftId }); + this.loadFamilyData(); + this.$root.$refs.messageDialog.open('tr:falukant.family.sendgift.success'); } catch (error) { console.log(error.response); if (error.response.status === 412) { @@ -285,6 +294,26 @@ export default { const green = Math.round(255 * pct); return `rgb(${red}, ${green}, 0)`; }, + + jumpToPartyForm() { + this.$router.push({ + name: 'ReputationView', + query: { tab: 'party' } + }); + }, + + jumpToChurchForm() { + this.$router.push({ + name: 'ChurchView', + }); + }, + + handleDaemonMessage() { + const message = JSON.parse(event.data); + if (message.event === 'children_update') { + this.loadFamilyData(); + } + } } } @@ -369,15 +398,15 @@ h2 { } .progress { - width: 100%; - background-color: #e5e7eb; - border-radius: 0.25rem; - overflow: hidden; - height: 1rem; + width: 100%; + background-color: #e5e7eb; + border-radius: 0.25rem; + overflow: hidden; + height: 1rem; } .progress-inner { - height: 100%; - transition: width 0.3s ease, background-color 0.3s ease; + height: 100%; + transition: width 0.3s ease, background-color 0.3s ease; } \ No newline at end of file diff --git a/frontend/src/views/falukant/HealthView.vue b/frontend/src/views/falukant/HealthView.vue new file mode 100644 index 0000000..d14521f --- /dev/null +++ b/frontend/src/views/falukant/HealthView.vue @@ -0,0 +1,186 @@ + + + + {{ $t('falukant.healthview.title') }} + + + {{ $t('falukant.healthview.age') }}: {{ age }} + {{ $t('falukant.healthview.status') }}: {{ healthState }} + + + + {{ $t('falukant.healthview.measuresTaken') }} + + + + {{ $t('falukant.healthview.measure') }} + {{ $t('falukant.healthview.date') }} + {{ $t('falukant.healthview.success') }} + {{ $t('falukant.healthview.cost') }} + + + + + {{ $t(`falukant.healthview.measures.${entry.tr}`) }} + {{ formatDate(entry.createdAt) }} + {{ entry.success }} + {{ formatPrice(entry.cost) }} + + + + + + + {{ $t('falukant.healthview.selectMeasure') }}: + + {{ $t('falukant.healthview.choose') }} + + {{ $t(`falukant.healthview.measures.${m.tr}`) }} ({{ formatPrice(m.cost) }}) + + + + + {{ $t('falukant.healthview.perform') }} + ({{ formatPrice(selectedMeasure.cost) }}) + + + + + + + + + + diff --git a/frontend/src/views/falukant/HouseView.vue b/frontend/src/views/falukant/HouseView.vue index 4165723..3b7ba03 100644 --- a/frontend/src/views/falukant/HouseView.vue +++ b/frontend/src/views/falukant/HouseView.vue @@ -1,10 +1,10 @@ - + {{ $t('falukant.house.title') }} - - - + + + {{ $t('falukant.house.statusreport') }} @@ -15,46 +15,54 @@ - - {{ $t(`falukant.house.status.${index}`) }} - {{ status }} % - {{ $t('falukant.house.renovate') }} ({{ - $t('falukant.house.cost') }}: {{ getRenovationCost(index, status) }} + + {{ $t(`falukant.house.status.${key}`) }} + {{ value }}% + + + {{ $t('falukant.house.renovate') }} ({{ getRenovationCost(key, value) }}) + + {{ $t('falukant.house.worth') }} - {{ getWorth(status) }} - {{ $t('falukant.house.sell') }} + {{ getWorth() }} {{ currency }} + + + {{ $t('falukant.house.renovateAll') }} ({{ getAllRenovationCost() }}) + + + {{ $t('falukant.house.sell') }} + + - + + {{ $t('falukant.house.buyablehouses') }} - - - - - - - {{ $t('falukant.house.statusreport') }} + + + + + {{ $t(`falukant.house.type.${house.houseType.labelTr}`) }} - - - {{ $t(`falukant.house.status.${key}`) }} - {{ value }} % - - + + {{ $t(`falukant.house.status.${prop}`) }} + {{ val }}% + - - - {{ $t('falukant.house.price') }}: {{ buyCost(house) }} - - - {{ $t('falukant.house.buy') }} + + {{ $t('falukant.house.price') }}: {{ buyCost(house) }} + + + {{ $t('falukant.house.buy') }} + @@ -65,195 +73,209 @@ - diff --git a/frontend/src/views/falukant/NobilityView.vue b/frontend/src/views/falukant/NobilityView.vue new file mode 100644 index 0000000..393d4c2 --- /dev/null +++ b/frontend/src/views/falukant/NobilityView.vue @@ -0,0 +1,130 @@ + + + + + {{ $t('falukant.nobility.title') }} + + + + + + + + {{ $t(`falukant.titles.${gender}.${current.labelTr}`) }} + + + + + + + + + + {{ $t('falukant.nobility.nextTitle') }}: + {{ $t(`falukant.titles.${gender}.${next.labelTr}`) }} + + + + {{ $t(`falukant.nobility.requirement.${req.requirementType}`, { amount: formatCost(req.requirementValue) }) }} + + + + {{ $t('falukant.nobility.advance.confirm') }} + {{ $t('falukant.nobility.advance.processing') }} + + ->{{ canAdvance }}, {{ isAdvancing }}<- + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/views/falukant/ReputationView.vue b/frontend/src/views/falukant/ReputationView.vue new file mode 100644 index 0000000..8a31d26 --- /dev/null +++ b/frontend/src/views/falukant/ReputationView.vue @@ -0,0 +1,301 @@ + + + + {{ $t('falukant.reputation.title') }} + + + + {{ $t(tab.label) }} + + + + + + Deine aktuelle Reputation: … + + + + + {{ $t('falukant.reputation.party.newpartyview.' + (newPartyView ? 'close' : 'open')) }} + + + + + {{ $t('falukant.reputation.party.newpartyview.type') }}: + + + {{ $t('falukant.party.type.' + type.tr) }} + + + + + + + {{ $t('falukant.reputation.party.music.label') }}: + + + {{ $t(`falukant.reputation.party.music.${m.tr}`) }} + + + + + + {{ $t('falukant.reputation.party.banquette.label') }}: + + + {{ $t(`falukant.reputation.party.banquette.${b.tr}`) }} + + + + + + {{ $t('falukant.reputation.party.servants.label') }}: + + {{ $t('falukant.reputation.party.servants.perPersons') }} + + + + {{ $t('falukant.reputation.party.esteemedInvites.label') }}: + + + {{ $t('falukant.titles.male.' + option.labelTr) }} + + + + {{ $t('falukant.titles.male.' + option.labelTr) }} + + + + + + + + {{ $t('falukant.reputation.party.totalCost') }}: + {{ formattedCost }} + + + + + + {{ $t('falukant.reputation.party.order') }} + + + + + + + {{ $t('falukant.reputation.party.inProgress') }} + + + + {{ $t('falukant.reputation.party.type') }} + {{ $t('falukant.reputation.party.music.label') }} + {{ $t('falukant.reputation.party.banquette.label') }} + {{ $t('falukant.reputation.party.servants.label') }} + {{ $t('falukant.reputation.party.cost') }} + {{ $t('falukant.reputation.party.date') }} + + + + + {{ $t('falukant.party.type.' + party.partyType.tr) }} + {{ $t('falukant.reputation.party.music.' + party.musicType.tr) }} + {{ $t('falukant.reputation.party.banquette.' + party.banquetteType.tr) }} + {{ party.servantRatio }} + {{ party.cost.toLocaleString($i18n.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }} + {{ new Date(party.createdAt).toLocaleString() }} + + + + + + + + {{ $t('falukant.reputation.party.completed') }} + + + + {{ $t('falukant.reputation.party.type') }} + {{ $t('falukant.reputation.party.music.label') }} + {{ $t('falukant.reputation.party.banquette.label') }} + {{ $t('falukant.reputation.party.servants.label') }} + {{ $t('falukant.reputation.party.cost') }} + {{ $t('falukant.reputation.party.date') }} + + + + + {{ $t('falukant.party.type.' + party.partyType.tr) }} + {{ $t('falukant.reputation.party.music.' + party.musicType.tr) }} + {{ $t('falukant.reputation.party.banquette.' + party.banquetteType.tr) }} + {{ party.servantRatio }} + {{ party.cost.toLocaleString($i18n.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }} + {{ new Date(party.createdAt).toLocaleString() }} + + + + + + + + + + + + \ No newline at end of file
{{ $t('falukant.bank.credits.none') }}
{{ $t('falukant.bank.credits.payoff.remaining') }}: {{ formatCost(bankOverview.availableCredit - selectedCredit) }}
{{ $t('falukant.bank.credits.payoff.fee') }}: {{ formatCost(bankOverview.fee) }}
{{ $t('falukant.bank.credits.payoff.feeHeight') }}: {{ formatCost(feeRate()) }}
+ {{ $t('falukant.bank.credits.payoff.total') }}: {{ formatCost(creditCost()) }} +
{{ $t('falukant.director.age') }}: {{ selected.age }}
{{ $t('falukant.healthview.age') }}: {{ age }}
{{ $t('falukant.healthview.status') }}: {{ healthState }}
+ + {{ $t(`falukant.titles.${gender}.${current.labelTr}`) }} + +
+ {{ $t('falukant.nobility.nextTitle') }}: + {{ $t(`falukant.titles.${gender}.${next.labelTr}`) }} +
Deine aktuelle Reputation: …
+ {{ $t('falukant.reputation.party.totalCost') }}: + {{ formattedCost }} +