diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js
index 7169d3c..cd3ddd2 100644
--- a/backend/controllers/falukantController.js
+++ b/backend/controllers/falukantController.js
@@ -25,6 +25,13 @@ class FalukantController {
this.acceptMarriageProposal = this.acceptMarriageProposal.bind(this);
this.getGifts = this.getGifts.bind(this);
this.sendGift = this.sendGift.bind(this);
+ this.getHouseTypes = this.getHouseTypes.bind(this);
+ this.getTitelsOfNobility = this.getTitelsOfNobility.bind(this);
+ this.getMoodAffect = this.getMoodAffect.bind(this);
+ this.getCharacterAffect = this.getCharacterAffect.bind(this);
+ this.getUserHouse = this.getUserHouse.bind(this);
+ this.getBuyableHouses = this.getBuyableHouses.bind(this);
+ this.buyUserHouse = this.buyUserHouse.bind(this);
}
async getUser(req, res) {
@@ -368,16 +375,17 @@ class FalukantController {
async sendGift(req, res) {
try {
- const { userid: hashedUserId } = req.headers;
- const { giftId} = req.body;
- const result = await FalukantService.sendGift(hashedUserId, giftId);
- res.status(200).json(result);
+ const { userid: hashedUserId } = req.headers;
+ const { giftId } = req.body;
+ const result = await FalukantService.sendGift(hashedUserId, giftId);
+ res.status(200).json(result);
} catch (error) {
- res.status(500).json({ error: error.message });
- console.log(error);
+ const status = error.status === 412 ? 412 : 500;
+ res.status(status).json({ error: error.message });
+ console.error(error);
}
- }
-
+ }
+
async getTitelsOfNobility(req, res) {
try {
const { userid: hashedUserId } = req.headers;
@@ -399,6 +407,63 @@ class FalukantController {
console.log(error);
}
}
+
+ async getMoodAffect(req, res) {
+ try {
+ const { userid: hashedUserId } = req.headers;
+ const result = await FalukantService.getMoodAffect(hashedUserId);
+ res.status(200).json(result);
+ } catch (error) {
+ res.status(500).json({ error: error.message });
+ console.log(error);
+ }
+ }
+
+ async getCharacterAffect(req, res) {
+ try {
+ const { userid: hashedUserId } = req.headers;
+ const result = await FalukantService.getCharacterAffect(hashedUserId);
+ res.status(200).json(result);
+ } catch (error) {
+ res.status(500).json({ error: error.message });
+ console.log(error);
+ }
+ }
+
+ async getUserHouse(req, res) {
+ try {
+ const { userid: hashedUserId } = req.headers;
+ const result = await FalukantService.getUserHouse(hashedUserId);
+ console.log(result);
+ res.status(200).json(result);
+ } catch (error) {
+ res.status(500).json({ error: error.message });
+ console.log(error);
+ }
+ }
+
+ async getBuyableHouses(req, res) {
+ try {
+ const { userid: hashedUserId } = req.headers;
+ const result = await FalukantService.getBuyableHouses(hashedUserId);
+ res.status(200).json(result);
+ } catch (error) {
+ res.status(500).json({ error: error.message });
+ console.log(error);
+ }
+ }
+
+ async buyUserHouse(req, res) {
+ try {
+ const { userid: hashedUserId } = req.headers;
+ const { houseId } = req.body;
+ const result = await FalukantService.buyUserHouse(hashedUserId, houseId);
+ res.status(201).json(result);
+ } catch (error) {
+ res.status(500).json({ error: error.message });
+ console.log(error);
+ }
+ }
}
export default FalukantController;
diff --git a/backend/models/associations.js b/backend/models/associations.js
index 75877ca..8dd4b42 100644
--- a/backend/models/associations.js
+++ b/backend/models/associations.js
@@ -62,6 +62,9 @@ import PromotionalGiftMood from './falukant/predefine/promotional_gift_mood.js';
import RelationshipType from './falukant/type/relationship.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';
export default function setupAssociations() {
// UserParam related associations
@@ -352,4 +355,16 @@ export default function setupAssociations() {
PromotionalGiftCharacterTrait.belongsTo(PromotionalGift, { foreignKey: 'gift_id', as: 'promotionalgiftcharactertrait' });
PromotionalGiftMood.belongsTo(PromotionalGift, { foreignKey: 'gift_id', as: 'promotionalgiftcharactermood' });
+
+ HouseType.hasMany(BuyableHouse, { foreignKey: 'houseTypeId', as: 'buyableHouses' });
+ BuyableHouse.belongsTo(HouseType, { foreignKey: 'houseTypeId', as: 'houseType' });
+
+ HouseType.hasMany(UserHouse, { foreignKey: 'houseTypeId', as: 'userHouses' });
+ UserHouse.belongsTo(HouseType, { foreignKey: 'houseTypeId', as: 'houseType' });
+
+ FalukantUser.hasOne(UserHouse, { foreignKey: 'userId', as: 'userHouse' });
+ UserHouse.belongsTo(FalukantUser, { foreignKey: 'userId', as: 'houseUser' });
+
+ TitleOfNobility.hasMany(HouseType, { foreignKey: 'minimumNobleTitle', as: 'houseTypes' });
+ HouseType.belongsTo(TitleOfNobility, { foreignKey: 'minimumNobleTitle', as: 'titleOfNobility' });
}
diff --git a/backend/models/falukant/data/buyable_house.js b/backend/models/falukant/data/buyable_house.js
new file mode 100644
index 0000000..2797080
--- /dev/null
+++ b/backend/models/falukant/data/buyable_house.js
@@ -0,0 +1,40 @@
+import { Model, DataTypes } from 'sequelize';
+import { sequelize } from '../../../utils/sequelize.js';
+
+class BuyableHouse extends Model { }
+
+BuyableHouse.init({
+ roofCondition: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 100
+ },
+ floorCondition: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 100
+ },
+ wallCondition: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 100
+ },
+ windowCondition: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 100
+ },
+ houseTypeId: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ }
+}, {
+ sequelize,
+ modelName: 'BuyableHouse',
+ tableName: 'buyable_house',
+ schema: 'falukant_data',
+ timestamps: false,
+ underscored: true,
+});
+
+export default BuyableHouse;
diff --git a/backend/models/falukant/data/user_house.js b/backend/models/falukant/data/user_house.js
new file mode 100644
index 0000000..2004b85
--- /dev/null
+++ b/backend/models/falukant/data/user_house.js
@@ -0,0 +1,46 @@
+import { Model, DataTypes } from 'sequelize';
+import { sequelize } from '../../../utils/sequelize.js';
+
+class UserHouse extends Model { }
+
+UserHouse.init({
+ roofCondition: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 100
+ },
+ floorCondition: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 100
+ },
+ wallCondition: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 100
+ },
+ windowCondition: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 100
+ },
+ houseTypeId: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 1
+ },
+ userId: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 1
+ }
+}, {
+ sequelize,
+ modelName: 'UserHouse',
+ tableName: 'user_house',
+ schema: 'falukant_data',
+ timestamps: false,
+ underscored: true,
+});
+
+export default UserHouse;
diff --git a/backend/models/falukant/type/house.js b/backend/models/falukant/type/house.js
new file mode 100644
index 0000000..6c1ec75
--- /dev/null
+++ b/backend/models/falukant/type/house.js
@@ -0,0 +1,38 @@
+import { Model, DataTypes } from 'sequelize';
+import { sequelize } from '../../../utils/sequelize.js';
+
+class HouseType extends Model { }
+
+HouseType.init({
+ labelTr: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ },
+ cost: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ },
+ position: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ },
+ minimumNobleTitle: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ },
+}, {
+ sequelize,
+ modelName: 'HouseType',
+ tableName: 'house',
+ schema: 'falukant_type',
+ timestamps: false,
+ underscored: true,
+ indexes: [
+ {
+ unique: true,
+ fields: ['label_tr']
+ }
+ ],
+});
+
+export default HouseType;
diff --git a/backend/models/index.js b/backend/models/index.js
index 8928ab6..9c2f268 100644
--- a/backend/models/index.js
+++ b/backend/models/index.js
@@ -66,6 +66,9 @@ import PromotionalGiftCharacterTrait from './falukant/predefine/promotional_gift
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';
const models = {
SettingsType,
@@ -136,6 +139,9 @@ const models = {
PromotionalGiftCharacterTrait,
PromotionalGiftMood,
PromotionalGiftLog,
+ HouseType,
+ BuyableHouse,
+ UserHouse,
};
export default models;
diff --git a/backend/routers/falukantRouter.js b/backend/routers/falukantRouter.js
index 83d15ca..0472b81 100644
--- a/backend/routers/falukantRouter.js
+++ b/backend/routers/falukantRouter.js
@@ -36,5 +36,9 @@ 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);
export default router;
diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js
index d5418e7..892ee61 100644
--- a/backend/services/falukantService.js
+++ b/backend/services/falukantService.js
@@ -33,6 +33,10 @@ import PromotionalGiftCharacterTrait from '../models/falukant/predefine/promotio
import PromotionalGiftMood from '../models/falukant/predefine/promotional_gift_mood.js';
import PromotionalGiftLog from '../models/falukant/log/promotional_gift.js';
import CharacterTrait from '../models/falukant/type/character_trait.js';
+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';
function calcAge(birthdate) {
const b = new Date(birthdate); b.setHours(0, 0);
@@ -67,6 +71,14 @@ function calculateMarriageCost(titleOfNobility, age) {
return baseCost * Math.pow(adjustedTitle, 1.3) - (age - 12) * 20;
}
+class PreconditionError extends Error {
+ constructor(label) {
+ super(label);
+ this.name = 'PreconditionError';
+ this.status = 412;
+ }
+}
+
class FalukantService extends BaseService {
async getFalukantUserByHashedId(hashedId) {
const user = await FalukantUser.findOne({
@@ -78,10 +90,22 @@ class FalukantService extends BaseService {
include: [
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
- { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] },
+ { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr', 'id'] },
{ model: CharacterTrait, as: 'traits', attributes: ['id', 'tr'] }
],
- attributes: ['id', 'birthdate', 'gender']
+ attributes: ['id', 'birthdate', 'gender', 'moodId']
+ },
+ {
+ model: UserHouse,
+ as: 'userHouse',
+ attributes: ['roofCondition', 'wallCondition', 'floorCondition', 'windowCondition'],
+ include: [
+ {
+ model: HouseType,
+ as: 'houseType',
+ attributes: ['labelTr', 'position']
+ }
+ ]
},
]
});
@@ -262,9 +286,7 @@ class FalukantService extends BaseService {
async getProducts(hashedUserId) {
const u = await getFalukantUserOrFail(hashedUserId);
- console.log(u);
const c = await FalukantCharacter.findOne({ where: { userId: u.id } });
- console.log(c);
if (!c) {
throw new Error(`No FalukantCharacter found for user with id ${u.id}`);
}
@@ -348,9 +370,7 @@ class FalukantService extends BaseService {
if (!inventory.length) {
throw new Error('No inventory found');
}
- console.log(inventory);
const available = inventory.reduce((sum, i) => sum + i.quantity, 0);
- console.log(available);
if (available < quantity) throw new Error('Not enough inventory available');
const item = inventory[0].productType;
const knowledgeVal = item.knowledges?.[0]?.knowledge || 0;
@@ -443,7 +463,7 @@ class FalukantService extends BaseService {
const branch = await Branch.findOne({
where: { id: branchId },
})
- ;
+ ;
const daySell = await DaySell.findOne({
where: {
regionId: branch.regionId,
@@ -549,23 +569,47 @@ class FalukantService extends BaseService {
async buyStorage(hashedUserId, branchId, amount, stockTypeId) {
const user = await getFalukantUserOrFail(hashedUserId);
const branch = await getBranchOrFail(user.id, branchId);
- const buyable = await BuyableStock.findOne({
+ const buyableStocks = await BuyableStock.findAll({
where: { regionId: branch.regionId, stockTypeId },
include: [{ model: FalukantStockType, as: 'stockType' }]
});
- if (!buyable || buyable.quantity < amount) throw new Error('Not enough buyable stock');
- const costPerUnit = buyable.stockType.cost;
+ if (!buyableStocks || buyableStocks.length === 0) {
+ throw new Error('Not enough buyable stock');
+ }
+ const totalAvailable = buyableStocks.reduce((sum, entry) => sum + entry.quantity, 0);
+ if (totalAvailable < amount) {
+ throw new Error('Not enough buyable stock');
+ }
+ const costPerUnit = buyableStocks[0].stockType.cost;
const totalCost = costPerUnit * amount;
- if (user.money < totalCost) throw new Error('notenoughmoney');
+ if (user.money < totalCost) {
+ throw new Error('notenoughmoney');
+ }
const moneyResult = await updateFalukantUserMoney(
user.id,
-totalCost,
- `Buy storage (type: ${buyable.stockType.labelTr})`,
+ `Buy storage (type: ${buyableStocks[0].stockType.labelTr})`,
user.id
);
- if (!moneyResult.success) throw new Error('Failed to update money');
- buyable.quantity -= amount;
- await buyable.save();
+ if (!moneyResult.success) {
+ throw new Error('Failed to update money');
+ }
+ let remainingToDeduct = amount;
+ for (const entry of buyableStocks) {
+ if (entry.quantity > remainingToDeduct) {
+ entry.quantity -= remainingToDeduct;
+ await entry.save();
+ remainingToDeduct = 0;
+ break;
+ } else if (entry.quantity === remainingToDeduct) {
+ await entry.destroy();
+ remainingToDeduct = 0;
+ break;
+ } else {
+ remainingToDeduct -= entry.quantity;
+ await entry.destroy();
+ }
+ }
let stock = await FalukantStock.findOne({
where: { branchId: branch.id, stockTypeId },
include: [{ model: FalukantStockType, as: 'stockType' }]
@@ -576,13 +620,19 @@ class FalukantService extends BaseService {
stockTypeId,
quantity: amount,
});
- return { success: true, bought: amount, totalCost, stockType: buyable.stockType.labelTr };
+ } else {
+ stock.quantity += amount;
+ await stock.save();
}
- stock.quantity += amount;
- await stock.save();
notifyUser(user.user.hashedId, 'falukantUpdateStatus', {});
notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId });
- return { success: true, bought: amount, totalCost, stockType: buyable.stockType.labelTr };
+
+ return {
+ success: true,
+ bought: amount,
+ totalCost,
+ stockType: buyableStocks[0].stockType.labelTr
+ };
}
async sellStorage(hashedUserId, branchId, amount, stockTypeId) {
@@ -725,7 +775,7 @@ class FalukantService extends BaseService {
const newProposals = await this.fetchProposals(falukantUserId, regionId);
return this.formatProposals(newProposals);
}
-
+
async deleteExpiredProposals() {
const expirationTime = new Date(Date.now() - 24 * 60 * 60 * 1000);
await DirectorProposal.destroy({
@@ -736,7 +786,7 @@ class FalukantService extends BaseService {
},
});
}
-
+
async fetchProposals(falukantUserId, regionId) {
return DirectorProposal.findAll({
where: { employerUserId: falukantUserId },
@@ -750,19 +800,19 @@ class FalukantService extends BaseService {
{ model: FalukantPredefineFirstname, as: 'definedFirstName' },
{ model: FalukantPredefineLastname, as: 'definedLastName' },
{ model: TitleOfNobility, as: 'nobleTitle' },
- {
- model: Knowledge,
+ {
+ model: Knowledge,
as: 'knowledges',
include: [
{ model: ProductType, as: 'productType' },
- ]
+ ]
},
],
},
],
});
}
-
+
async generateProposals(falukantUserId, regionId) {
const proposalCount = Math.floor(Math.random() * 3) + 3;
for (let i = 0; i < proposalCount; i++) {
@@ -789,7 +839,7 @@ class FalukantService extends BaseService {
});
}
}
-
+
async calculateAverageKnowledge(characterId) {
const averageKnowledge = await Knowledge.findAll({
where: { characterId },
@@ -798,7 +848,7 @@ class FalukantService extends BaseService {
});
return parseFloat(averageKnowledge[0]?.avgKnowledge || 0);
}
-
+
formatProposals(proposals) {
return proposals.map((proposal) => {
const age = Math.floor((Date.now() - new Date(proposal.character.birthdate)) / (24 * 60 * 60 * 1000));
@@ -820,47 +870,40 @@ class FalukantService extends BaseService {
};
});
}
-
+
async convertProposalToDirector(hashedUserId, proposalId) {
- console.log('convert proposal to director - start');
const user = await getFalukantUserOrFail(hashedUserId);
- console.log('convert proposal to director - check user');
if (!user) {
throw new Error('User not found');
}
- console.log('convert proposal to director - find proposal', proposalId);
const proposal = await DirectorProposal.findOne(
- {
+ {
where: { id: proposalId },
include: [
{ model: FalukantCharacter, as: 'character' },
]
}
);
- console.log('convert proposal to director - check proposal');
if (!proposal || proposal.employerUserId !== user.id) {
throw new Error('Proposal does not belong to the user');
}
-
- console.log('convert proposal to director - check existing director', user, proposal);
- const existingDirector = await Director.findOne({
- where: {
- employerUserId: user.id
+ const existingDirector = await Director.findOne({
+ where: {
+ employerUserId: user.id
},
include: [
- {
- model: FalukantCharacter,
+ {
+ model: FalukantCharacter,
as: 'character',
where: {
regionId: proposal.character.regionId,
}
},
- ]
+ ]
});
if (existingDirector) {
throw new Error('A director already exists for this region');
}
- console.log('convert proposal to director - create new director');
const newDirector = await Director.create({
directorCharacterId: proposal.directorCharacterId,
employerUserId: proposal.employerUserId,
@@ -871,8 +914,8 @@ class FalukantService extends BaseService {
employerUserId: proposal.employerUserId,
},
include: [
- {
- model: FalukantCharacter,
+ {
+ model: FalukantCharacter,
as: 'character',
where: {
regionId: proposal.character.regionId,
@@ -880,13 +923,11 @@ class FalukantService extends BaseService {
},
]
});
- console.log('convert proposal to director - remove propsals');
if (regionUserDirectorProposals.length > 0) {
for (const proposal of regionUserDirectorProposals) {
await DirectorProposal.destroy();
}
}
- console.log('convert proposal to director - notify user');
notifyUser(hashedUserId, 'directorchanged');
return newDirector;
}
@@ -978,7 +1019,7 @@ class FalukantService extends BaseService {
}
const updateData = {};
updateData[settingKey] = value || false;
-
+
await Director.update(updateData, {
where: {
id: director.id,
@@ -1008,15 +1049,17 @@ class FalukantService extends BaseService {
where: {
character1Id: character.id,
},
- attributes: ['createdAt', 'widowFirstName2'],
+ attributes: ['createdAt', 'widowFirstName2', 'nextStepProgress'],
include: [
{
model: FalukantCharacter,
as: 'character2',
- attributes: ['id', 'birthdate', 'gender'],
+ 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' },
],
},
{
@@ -1030,22 +1073,28 @@ class FalukantService extends BaseService {
relationships = relationships.map((relationship) => ({
createdAt: relationship.createdAt,
widowFirstName2: relationship.widowFirstName2,
+ progress: relationship.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,
},
relationshipType: relationship.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));
- if (family.relationships.length === 0 ) {
+ const ownAge = calcAge(character.birthdate);
+ if (ownAge < 12) {
+ family.possiblePartners = [];
+ } else if (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);
+ await this.createPossiblePartners(character.id, character.gender, character.regionId, character.titleOfNobility, ownAge);
family.possiblePartners = await this.getPossiblePartners(character.id);
}
}
@@ -1073,7 +1122,6 @@ class FalukantService extends BaseService {
return proposals.map(proposal => {
const birthdate = new Date(proposal.proposedCharacter.birthdate);
const age = calcAge(birthdate);
- console.log(proposal.proposedCharacter);
return {
id: proposal.id,
requesterCharacterId: proposal.requesterCharacterId,
@@ -1087,27 +1135,32 @@ class FalukantService extends BaseService {
};
});
}
-
- async createPossiblePartners(requestingCharacterId, requestingCharacterGender, requestingRegionId, requestingCharacterTitleOfNobility) {
+
+ async createPossiblePartners(requestingCharacterId, requestingCharacterGender, requestingRegionId, requestingCharacterTitleOfNobility, ownAge) {
try {
const minTitleResult = await TitleOfNobility.findOne({
- order: [['id', 'ASC']],
+ order: [['id', 'ASC']],
attributes: ['id'],
});
if (!minTitleResult) {
throw new Error('No title of nobility found');
}
- const minTitle = minTitleResult.id;
+ const minTitle = minTitleResult.id;
+
const potentialPartners = await FalukantCharacter.findAll({
where: {
- id: { [Op.ne]: requestingCharacterId },
- gender: { [Op.ne]: requestingCharacterGender },
- regionId: requestingRegionId,
- createdAt: { [Op.lt]: new Date(new Date() - 12 * 24 * 60 * 60 * 1000) },
+ id: { [Op.ne]: requestingCharacterId },
+ gender: { [Op.ne]: requestingCharacterGender },
+ regionId: requestingRegionId,
+ createdAt: { [Op.lt]: new Date(new Date() - 12 * 24 * 60 * 60 * 1000) },
titleOfNobility: { [Op.between]: [requestingCharacterTitleOfNobility - 1, requestingCharacterTitleOfNobility + 1] }
},
+ order: [
+ [Sequelize.literal(`ABS((EXTRACT(EPOCH FROM (NOW() - "birthdate")) / 86400) - ${ownAge})`), 'ASC']
+ ],
limit: 5,
});
+
const proposals = potentialPartners.map(partner => {
const age = calcAge(partner.birthdate);
return {
@@ -1130,8 +1183,8 @@ class FalukantService extends BaseService {
throw new Error('User not found');
}
const proposal = await MarriageProposal.findOne({
- where: {
- requesterCharacterId: character.id,
+ where: {
+ requesterCharacterId: character.id,
proposedCharacterId: proposedCharacterId,
},
});
@@ -1139,7 +1192,6 @@ class FalukantService extends BaseService {
throw new Error('Proposal not found');
}
if (user.money < proposal.cost) {
- console.log(user, proposal);
throw new Error('Not enough money to accept the proposal');
}
const moneyResult = await updateFalukantUserMoney(user.id, -proposal.cost, 'Marriage cost', user.id);
@@ -1158,12 +1210,12 @@ class FalukantService extends BaseService {
relationshipTypeId: marriedType.id,
});
await MarriageProposal.destroy({
- where: { character1Id: character.id },
+ where: { requesterCharacterId: character.id },
})
- ;
+ ;
return { success: true, message: 'Marriage proposal accepted' };
}
-
+
async getGifts(hashedUserId) {
const user = await this.getFalukantUserByHashedId(hashedUserId);
const character = await FalukantCharacter.findOne({
@@ -1172,7 +1224,20 @@ class FalukantService extends BaseService {
if (!character) {
throw new Error('Character not found');
}
- let gifts = await PromotionalGift.findAll();
+ let gifts = await PromotionalGift.findAll({
+ include: [
+ {
+ model: PromotionalGiftMood,
+ as: 'promotionalgiftmoods',
+ attributes: ['mood_id', 'suitability']
+ },
+ {
+ model: PromotionalGiftCharacterTrait,
+ as: 'characterTraits',
+ attributes: ['trait_id', 'suitability']
+ }
+ ]
+ });
const lowestTitleOfNobility = await TitleOfNobility.findOne({
order: [['id', 'ASC']],
});
@@ -1181,61 +1246,107 @@ class FalukantService extends BaseService {
id: gift.id,
name: gift.name,
cost: await this.getGiftCost(gift.value, character.titleOfNobility, lowestTitleOfNobility.id),
+ moodsAffects: gift.promotionalgiftmoods,
+ charactersAffects: gift.characterTraits,
};
}));
}
async sendGift(hashedUserId, giftId) {
const user = await this.getFalukantUserByHashedId(hashedUserId);
- const lowestTitleOfNobility = await TitleOfNobility.findOne({
- order: [['id', 'ASC']],
- });
- const relation = Relationship.findOne({
- where: {
- character1Id: user.character.id,
- },
- include: [
- {
- model: RelationshipType,
- as: 'relationshipType',
- where: { tr: 'wooing' },
- }
- ],
+ const lowestTitle = await TitleOfNobility.findOne({ order: [['id', 'ASC']] });
+ const currentMoodId = user.character.moodId;
+ if (currentMoodId == null) {
+ throw new Error('moodNotSet');
+ }
+ const relation = await Relationship.findOne({
+ where: { character1Id: user.character.id },
+ include: [{
+ model: RelationshipType,
+ as: 'relationshipType',
+ where: { tr: 'wooing' }
+ }]
});
if (!relation) {
- throw new Error('User and character are not related');
+ throw new Error('notRelated');
}
- console.log(user);
- const gift = await PromotionalGift.findOne({
+ const lastGift = await PromotionalGiftLog.findOne({
+ where: { senderCharacterId: user.character.id },
+ order: [['createdAt', 'DESC']],
+ limit: 1
+ });
+ if (lastGift && (lastGift.createdAt.getTime() + 3_600_000) > Date.now()) {
+ throw new PreconditionError('tooOften');
+ }
+ const gift = await PromotionalGift.findOne({
where: { id: giftId },
include: [
{
model: PromotionalGiftCharacterTrait,
as: 'characterTraits',
- where: { trait_id: { [Op.in]: user.character.characterTraits.map(trait => trait.id) }, },
+ where: { trait_id: { [Op.in]: user.character.traits.map(t => t.id) } },
+ required: false
},
{
model: PromotionalGiftMood,
as: 'promotionalgiftmoods',
- },
+ where: { mood_id: currentMoodId },
+ required: false
+ }
]
});
- const cost = await this.getGiftCost(gift.value, user.character.titleOfNobility, lowestTitleOfNobility.id);
- if (user.money < cost) {
- console.log(user, user.money, cost);
- throw new Error('Not enough money to send the gift');
+ if (!gift) {
+ throw new Error('notFound');
}
- console.log(JSON.stringify(gift));
- const changeValue = gift.characterTraits.suitability + gift.promotionalgiftmoods.suitability - 4;
- this.updateFalukantUserMoney(user.id, -cost, 'Gift cost', user.id);
- await relation.update({ value: relation.value + changeValue });
+ const cost = await this.getGiftCost(
+ gift.value,
+ user.character.nobleTitle.id,
+ lowestTitle.id
+ );
+ if (user.money < cost) {
+ throw new PreconditionError('insufficientFunds');
+ }
+ const traits = gift.characterTraits;
+ if (!traits.length) {
+ throw new Error('noTraits');
+ }
+ const traitAvg = traits.reduce((sum, ct) => sum + ct.suitability, 0) / traits.length;
+ const moodRecord = gift.promotionalgiftmoods[0];
+ if (!moodRecord) {
+ throw new Error('noMoodData');
+ }
+ const moodSuitability = moodRecord.suitability;
+ const changeValue = Math.round(traitAvg + moodSuitability - 5);
+ await updateFalukantUserMoney(user.id, -cost, 'Gift cost', user.id);
+ await relation.update({ nextStepProgress: relation.nextStepProgress + changeValue });
await PromotionalGiftLog.create({
senderCharacterId: user.character.id,
recipientCharacterId: relation.character2Id,
- giftId: giftId,
- changeValue: changeValue,
+ giftId,
+ changeValue
});
- return { success: true, message: 'Gift sent' };
+ this.checkProposalProgress(relation);
+ return { success: true, message: 'sent' };
+ }
+
+ async checkProposalProgress(relation) {
+ const { nextStepProgress } = relation;
+ if (nextStepProgress >= 100) {
+ const engagedStatus = await RelationshipType.findOne({ where: { tr: 'engaged' } });
+ await relation.update({ nextStepProgress: 0, relationshipTypeId: engagedStatus.id });
+ const user = await User.findOne({
+ include: [{
+ model: FalukantUser,
+ as: 'falukantData',
+ include: [{
+ model: FalukantCharacter,
+ as: 'character',
+ where: { id: relation.character1Id }
+ }]
+ }]
+ });
+ await notifyUser(user.hashedId, 'familychanged');
+ }
}
async getGiftCost(value, titleOfNobility, lowestTitleOfNobility) {
@@ -1248,8 +1359,114 @@ class FalukantService extends BaseService {
}
async getHouseTypes() {
-// return House
+ // return House
}
+
+ async getMoodAffect() {
+ return PromotionalGiftMood.findAll();
+ }
+
+ async getCharacterAffect() {
+ return PromotionalGiftCharacterTrait.findAll();
+ }
+
+ async getUserHouse(hashedUserId) {
+ try {
+ const user = await User.findOne({
+ where: { hashedId: hashedUserId },
+ include: [{
+ model: FalukantUser,
+ as: 'falukantData',
+ include: [{
+ model: UserHouse,
+ as: 'userHouse',
+ include: [{
+ model: HouseType,
+ as: 'houseType',
+ attributes: ['position', 'cost']
+ }],
+ attributes: ['roofCondition', 'wallCondition', 'floorCondition', 'windowCondition']
+ }],
+ }
+ ]
+ });
+ 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);
+ return {};
+ }
+ }
+
+ async getBuyableHouses(hashedUserId) {
+ try {
+ const user = await this.getFalukantUserByHashedId(hashedUserId);
+ const houses = await BuyableHouse.findAll({
+ include: [{
+ model: HouseType,
+ as: 'houseType',
+ attributes: ['position', 'cost'],
+ where: {
+ minimumNobleTitle: {
+ [Op.lte]: user.character.nobleTitle.id
+ }
+ }
+ }],
+ attributes: ['roofCondition', 'wallCondition', 'floorCondition', 'windowCondition', 'id'],
+ order: [
+ [{ model: HouseType, as: 'houseType' }, 'position', 'DESC'],
+ ['wallCondition', 'DESC'],
+ ['roofCondition', 'DESC'],
+ ['floorCondition', 'DESC'],
+ ['windowCondition', 'DESC']
+ ]
+ });
+ return houses;
+ } catch (error) {
+ console.error('Fehler beim Laden der kaufbaren Häuser:', error);
+ throw error;
+ }
+ }
+
+ async buyUserHouse(hashedUserId, houseId) {
+ try {
+ const falukantUser = await getFalukantUserOrFail(hashedUserId);
+ const house = await BuyableHouse.findByPk(houseId, {
+ include: [{
+ model: HouseType,
+ as: 'houseType',
+ }],
+ });
+ if (!house) {
+ throw new Error('Das Haus wurde nicht gefunden.');
+ }
+ const housePrice = this.housePrice(house);
+ const oldHouse = await UserHouse.findOne({ where: { userId: falukantUser.id } });
+ if (falukantUser.money < housePrice) {
+ throw new Error('notenoughmoney.');
+ }
+ if (oldHouse) {
+ await oldHouse.destroy();
+ }
+ await UserHouse.create({
+ userId: falukantUser.id,
+ houseTypeId: house.houseTypeId,
+ });
+ await house.destroy();
+ await updateFalukantUserMoney(falukantUser.id, -housePrice, "housebuy", falukantUser.id);
+ const user = await User.findByPk(falukantUser.userId);
+ notifyUser(user.hashedId, 'falukantHouseUpdate', {});
+ return {};
+ } catch (error) {
+ console.error('Fehler beim Kaufen des Hauses:', error);
+ throw error;
+ }
+ }
+
+ housePrice(house) {
+ const houseQuality = (house.roofCondition + house.windowCondition + house.floorCondition + house.wallCondition) / 4;
+ return (house.houseType.cost / 100 * houseQuality ).toFixed(2);
+}
}
export default new FalukantService();
diff --git a/backend/utils/falukant/initializeFalukantTypes.js b/backend/utils/falukant/initializeFalukantTypes.js
index 1d89894..8621c03 100644
--- a/backend/utils/falukant/initializeFalukantTypes.js
+++ b/backend/utils/falukant/initializeFalukantTypes.js
@@ -6,6 +6,8 @@ import CharacterTrait from "../../models/falukant/type/character_trait.js";
import PromotionalGift from "../../models/falukant/type/promotional_gift.js";
import PromotionalGiftCharacterTrait from "../../models/falukant/predefine/promotional_gift_character_trait.js";
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";
export const initializeFalukantTypes = async () => {
await initializeFalukantTypeRegions();
@@ -14,6 +16,7 @@ export const initializeFalukantTypes = async () => {
await initializeFalukantCharacterTraits();
await initializeFalukantPromotionalGifts();
await initializePromotionalGiftMoodLinks();
+ await initializeFalukantHouseTypes();
};
const regionTypes = [];
@@ -204,6 +207,52 @@ const promotionalGiftMoodLinks = [
{ gift: "Horse", mood: "nervous", suitability: 4 },
];
+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' },
+];
+
+{
+ const giftNames = promotionalGifts.map(g => g.name);
+ const traitNames = characterTraits.map(t => t.name);
+
+ giftNames.forEach(giftName => {
+ traitNames.forEach(traitName => {
+ if (!promotionalGiftTraitLinks.some(l => l.gift === giftName && l.trait === traitName)) {
+ promotionalGiftTraitLinks.push({
+ gift: giftName,
+ trait: traitName,
+ suitability: Math.floor(Math.random() * 5) + 1,
+ });
+ }
+ });
+ });
+}
+
+{
+ const giftNames = promotionalGifts.map(g => g.name);
+ const moodNames = moods.map(m => m.name);
+
+ giftNames.forEach(giftName => {
+ moodNames.forEach(moodName => {
+ if (!promotionalGiftMoodLinks.some(l => l.gift === giftName && l.mood === moodName)) {
+ promotionalGiftMoodLinks.push({
+ gift: giftName,
+ mood: moodName,
+ suitability: Math.floor(Math.random() * 5) + 1,
+ });
+ }
+ });
+ });
+}
+
const initializeFalukantTypeRegions = async () => {
for (const regionType of regionTypeTrs) {
const [regionTypeRecord] = await RegionType.findOrCreate({
@@ -303,6 +352,8 @@ export const initializePromotionalGiftTraitLinks = async () => {
},
defaults: {
suitability: link.suitability,
+ gift_id: gift.id,
+ trait_id: trait.id,
},
});
}
@@ -316,15 +367,27 @@ export const initializePromotionalGiftMoodLinks = async () => {
console.error(`Gift or Mood not found for: ${link.gift}, ${link.mood}`);
continue;
}
- await PromotionalGiftMood.findOrCreate({
- where: {
- gift_id: gift.id,
- mood_id: mood.id,
- },
- defaults: {
- suitability: link.suitability,
- },
+
+ await PromotionalGiftMood.create({
+ gift_id: gift.id,
+ mood_id: mood.id,
+ suitability: link.suitability,
+ }).catch(err => {
+ if (err.name !== 'SequelizeUniqueConstraintError') throw err;
});
}
};
+export const initializeFalukantHouseTypes = async () => {
+ for (const ht of houseTypes) {
+ const [record, created] = await HouseType.findOrCreate({
+ where: { labelTr: ht.abbr },
+ defaults: {
+ cost: ht.cost,
+ imageUrl: null,
+ position: ht.position,
+ minimumNobleTitle: await TitleOfNobility.findOne({ where: { labelTr: ht.minimumTitle } }).then(title => title.id),
+ }
+ });
+ }
+};
diff --git a/dump.rdb b/dump.rdb
index b057372..7c978bd 100644
Binary files a/dump.rdb and b/dump.rdb differ
diff --git a/frontend/public/images/falukant/houses.png b/frontend/public/images/falukant/houses.png
new file mode 100644
index 0000000..f685a46
Binary files /dev/null and b/frontend/public/images/falukant/houses.png differ
diff --git a/frontend/src/components/falukant/StatusBar.vue b/frontend/src/components/falukant/StatusBar.vue
index cb59efb..e7de2f6 100644
--- a/frontend/src/components/falukant/StatusBar.vue
+++ b/frontend/src/components/falukant/StatusBar.vue
@@ -70,7 +70,7 @@ export default {
}
this.statusItems = [
{ key: "age", icon: "👶", value: age },
- { key: "wealth", icon: "💰", value: money },
+ { 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 },
];
@@ -80,12 +80,15 @@ export default {
},
async handleDaemonSocketMessage(event) {
try {
+ if (event.data === 'ping') {
+ return;
+ }
const data = JSON.parse(event.data);
if (data.event === "falukantUpdateStatus") {
this.fetchStatus();
}
} catch (error) {
- console.error("Error parsing daemonSocket message:", error);
+ console.error("Error parsing daemonSocket message:", error, event.data);
}
},
openPage(url, hasSubmenu = false) {
diff --git a/frontend/src/i18n/locales/de/falukant.json b/frontend/src/i18n/locales/de/falukant.json
index 7a64ef9..c98ba44 100644
--- a/frontend/src/i18n/locales/de/falukant.json
+++ b/frontend/src/i18n/locales/de/falukant.json
@@ -211,8 +211,22 @@
"accept": "Werbung mit diesem Partner starten",
"wooing": {
"gifts": "Werbegeschenke",
- "sendGift": "Werbegeschenk senden"
- }
+ "sendGift": "Werbegeschenk senden",
+ "gift": "Geschenk",
+ "value": "Kosten",
+ "effect": "Wirkung"
+ },
+ "giftAffect": {
+ "0": "Keiner",
+ "1": "Sehr niedrig",
+ "2": "Niedrig",
+ "3": "Mittel",
+ "4": "Hoch",
+ "5": "Sehr hoch"
+
+ },
+ "mood": "Stimmung",
+ "progress": "Zuneigung"
},
"relationships": {
"name": "Name"
@@ -242,6 +256,15 @@
"addSpouse": "Ehepartner hinzufügen",
"viewDetails": "Details anzeigen",
"remove": "Entfernen"
+ },
+ "sendgift": {
+ "error": {
+ "nogiftselected": "Bitte wähle ein Geschenk aus.",
+ "generic": "Ein unbekannter Fehler ist aufgetreten.",
+ "tooOften": "Du kannst nicht so oft Geschenke machen.",
+ "insufficientFunds": "Du hast nicht genug Geld."
+ },
+ "success": "Das Geschenk wurde überreicht."
}
},
"product": {
@@ -291,9 +314,15 @@
"changeValue": "Wertänderung",
"time": "Zeit",
"activities": {
- "Product sale": "Produktverkauf",
+ "Product sale": "Produkte verkauft",
"Production cost": "Produktionskosten",
- "Sell all products": "Alle Produkte verkaufen"
+ "Sell all products": "Alle Produkte verkauft",
+ "sell products": "Produkte verkauft",
+ "director starts production": "Direktor beginnt Produktion",
+ "Buy storage (type: field)": "Lagerplatz gekauft (Typ: Feld)",
+ "Buy storage (type: iron)": "Lagerplatz gekauft (Typ: Eisen)",
+ "Buy storage (type: stone)": "Lagerplatz gekauft (Typ: Stein)",
+ "Buy storage (type: wood)": "Lagerplatz gekauft (Typ: Holz)"
}
},
"newdirector": {
@@ -327,6 +356,63 @@
"Cat": "Katze",
"Dog": "Hund",
"Horse": "Pferd"
+ },
+ "mood": {
+ "happy": "Glücklich",
+ "sad": "Traurig",
+ "angry": "Wütend",
+ "scared": "Verängstigt",
+ "surprised": "Überrascht",
+ "normal": "Normal"
+ },
+ "character": {
+ "brave": "Mutig",
+ "kind": "Freundlich",
+ "greedy": "Gierig",
+ "wise": "Weise",
+ "loyal": "Loyal",
+ "cunning": "Listig",
+ "generous": "Großzügig",
+ "arrogant": "Arrogant",
+ "honest": "Ehrlich",
+ "ambitious": "Ehrgeizig",
+ "patient": "Geduldig",
+ "impatient": "Ungeduldig",
+ "selfish": "Egoistisch",
+ "charismatic": "Charismatisch",
+ "empathetic": "Einfühlsam",
+ "timid": "Schüchtern",
+ "stubborn": "Stur",
+ "resourceful": "Einfallsreich",
+ "reckless": "Rücksichtslos",
+ "disciplined": "Diszipliniert",
+ "optimistic": "Optimistisch",
+ "pessimistic": "Pessimistisch",
+ "manipulative": "Manipulativ",
+ "independent": "Unabhängig",
+ "dependent": "Abhängig",
+ "adventurous": "Abenteuerlustig",
+ "humble": "Bescheiden",
+ "vengeful": "Rachsüchtig",
+ "pragmatic": "Pragmatisch",
+ "idealistic": "Idealistisch"
+ },
+ "house": {
+ "title": "Haus",
+ "statusreport": "Zustand des Hauses",
+ "element": "Bereich",
+ "state": "Zustand",
+ "buyablehouses": "Kaufe ein Haus",
+ "buy": "Kaufen",
+ "price": "Kaufpreis",
+ "worth": "Restwert",
+ "sell": "Verkaufen",
+ "status": {
+ "roofCondition": "Dach",
+ "wallCondition": "Wände",
+ "floorCondition": "Böden",
+ "windowCondition": "Fenster"
+ }
}
}
}
\ No newline at end of file
diff --git a/frontend/src/router/falukantRoutes.js b/frontend/src/router/falukantRoutes.js
index dfd8c71..29e639c 100644
--- a/frontend/src/router/falukantRoutes.js
+++ b/frontend/src/router/falukantRoutes.js
@@ -3,6 +3,7 @@ import Createview from '../views/falukant/CreateView.vue';
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';
const falukantRoutes = [
{
@@ -35,6 +36,12 @@ const falukantRoutes = [
component: FamilyView,
meta: { requiresAuth: true }
},
+ {
+ path: '/falukant/house',
+ name: 'HouseView',
+ component: HouseView,
+ meta: { requiresAuth: true },
+ },
];
export default falukantRoutes;
diff --git a/frontend/src/views/falukant/FamilyView.vue b/frontend/src/views/falukant/FamilyView.vue
index 06d4161..b59603f 100644
--- a/frontend/src/views/falukant/FamilyView.vue
+++ b/frontend/src/views/falukant/FamilyView.vue
@@ -3,31 +3,50 @@
-
{{ $t('falukant.family.title') }}
-
{{ $t('falukant.family.spouse.title') }}
-
-
- | {{ $t('falukant.family.relationships.name') }} |
-
- {{ $t('falukant.titles.' + relationships[0].character2.gender + '.' +
- relationships[0].character2.nobleTitle) }}
- {{ relationships[0].character2.firstName }}
- |
-
-
- | {{ $t('falukant.family.spouse.age') }} |
- {{ relationships[0].character2.age }} |
-
-
- | {{ $t('falukant.family.spouse.status') }} |
- {{ $t('falukant.family.statuses.' + relationships[0].relationshipType) }} |
-
-
+
+
+
+ | {{ $t('falukant.family.relationships.name') }} |
+
+ {{ $t('falukant.titles.' + relationships[0].character2.gender + '.' +
+ relationships[0].character2.nobleTitle) }}
+ {{ relationships[0].character2.firstName }}
+ |
+
+
+ | {{ $t('falukant.family.spouse.age') }} |
+ {{ relationships[0].character2.age }} |
+
+
+ | {{ $t('falukant.family.spouse.mood') }} |
+ {{ $t(`falukant.mood.${relationships[0].character2.mood.tr}`) }} |
+
+
+ | {{ $t('falukant.family.spouse.status') }} |
+ {{ $t('falukant.family.statuses.' + relationships[0].relationshipType) }} |
+
+
+ | {{ $t('falukant.family.spouse.progress') }} |
+
+
+ |
+
+
+
+ - {{ $t(`falukant.character.${characteristic.tr}`) }}
+
+
{{ $t('falukant.family.spouse.wooing.gifts') }}
-
+
@@ -67,7 +89,7 @@
v-model="selectedProposalId">
{{
$t(`falukant.titles.${proposal.proposedCharacterGender}.${proposal.proposedCharacterNobleTitle}`)
- }} {{ proposal.proposedCharacterName }} |
+ }} {{ proposal.proposedCharacterName }}
{{ proposal.proposedCharacterAge }} |
{{ formatCost(proposal.cost) }} |
@@ -80,7 +102,6 @@
-
{{ $t('falukant.family.children.title') }}
@@ -131,9 +152,6 @@
-
-
-
@@ -284,4 +356,28 @@ export default {
h2 {
padding-top: 20px;
}
+
+.relationship>table,
+.relationship>ul {
+ display: inline-block;
+ margin-right: 1em;
+ vertical-align: top;
+}
+
+.relationship>ul {
+ list-style: none;
+}
+
+.progress {
+ 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;
+}
\ No newline at end of file
diff --git a/frontend/src/views/falukant/HouseView.vue b/frontend/src/views/falukant/HouseView.vue
new file mode 100644
index 0000000..4165723
--- /dev/null
+++ b/frontend/src/views/falukant/HouseView.vue
@@ -0,0 +1,259 @@
+
+
+
+
{{ $t('falukant.house.title') }}
+
+
+
+
{{ $t('falukant.house.statusreport') }}
+
+
+
+ | {{ $t('falukant.house.element') }} |
+ {{ $t('falukant.house.state') }} |
+ |
+
+
+
+
+ | {{ $t(`falukant.house.status.${index}`) }} |
+ {{ status }} % |
+ |
+
+
+ | {{ $t('falukant.house.worth') }} |
+ {{ getWorth(status) }} |
+ |
+
+
+
+
+
+
+
{{ $t('falukant.house.buyablehouses') }}
+
+
+
+
+
{{ $t('falukant.house.statusreport') }}
+
+
+
+
+ | {{ $t(`falukant.house.status.${key}`) }} |
+ {{ value }} % |
+
+
+
+
+
+
+ {{ $t('falukant.house.price') }}: {{ buyCost(house) }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/falukant/OverviewView.vue b/frontend/src/views/falukant/OverviewView.vue
index 736c95e..f42d0da 100644
--- a/frontend/src/views/falukant/OverviewView.vue
+++ b/frontend/src/views/falukant/OverviewView.vue
@@ -91,6 +91,7 @@
@@ -168,6 +169,24 @@ export default {
height: `${height}px`,
};
},
+ getHouseStyle() {
+ if (!this.falukantUser) return {};
+ const imageUrl = '/images/falukant/houses.png';
+ const housePosition = this.falukantUser.house ? this.falukantUser.house.type.position : 0;
+ const x = housePosition % 3;
+ const y = Math.floor(housePosition / 3);
+ return {
+ backgroundImage: `url(${imageUrl})`,
+ backgroundPosition: `-${x * 341}px -${y * 341}px`,
+ backgroundSize: "341px 341px",
+ width: "114px",
+ height: "114px",
+ };
+ },
+ getAgeColor(age) {
+ const ageGroup = this.getAgeGroup(age);
+ return ageGroup === 'child' ? 'blue' : ageGroup === 'teen' ? 'green' : ageGroup === 'adult' ? 'red' : 'gray';
+ },
moneyValue() {
const m = this.falukantUser?.money;
return typeof m === 'string' ? parseFloat(m) : m;
@@ -298,7 +317,16 @@ export default {
image-rendering: crisp-edges;
}
+.house {
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ background-repeat: no-repeat;
+ background-size: cover;
+ image-rendering: crisp-edges;
+}
+
h2 {
padding-top: 20px;
}
+