diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 2f77410..75c73fa 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -106,6 +106,14 @@ async function getBranchOrFail(userId, branchId) { return branch; } +const BRANCH_EXPANSION_EXPONENT = 1.2; +const BRANCH_TYPE_COST_PRESSURE = { + production: { same: 1.15, other: 0.45 }, + store: { same: 1.1, other: 0.5 }, + fullstack: { same: 1.35, other: 0.7 }, + default: { same: 1.05, other: 0.5 }, +}; + const POLITICAL_OFFICE_RANKS = { assessor: 1, councillor: 1, @@ -572,6 +580,22 @@ class FalukantService extends BaseService { { tr: "drunkOfLife", method: "healthDrunkOfLife", cost: 5000000 } ]; + _getBranchCostPressure(targetLabel) { + return BRANCH_TYPE_COST_PRESSURE[targetLabel] || BRANCH_TYPE_COST_PRESSURE.default; + } + + _calculateBranchActionCost(baseCost, targetLabel, ownedTypeLabels = []) { + const normalizedTarget = String(targetLabel || '').trim().toLowerCase(); + const { same, other } = this._getBranchCostPressure(normalizedTarget); + const weightedCount = ownedTypeLabels.reduce((sum, label) => { + const normalizedLabel = String(label || '').trim().toLowerCase(); + return sum + (normalizedLabel === normalizedTarget ? same : other); + }, 0); + const exponentBase = Math.max(1, 1 + weightedCount); + const rawCost = Number(baseCost || 0) * Math.pow(exponentBase, BRANCH_EXPANSION_EXPONENT); + return Math.round(rawCost * 100) / 100; + } + static RECURSIVE_REGION_SEARCH = ` WITH RECURSIVE ancestors AS ( SELECT @@ -1258,12 +1282,15 @@ class FalukantService extends BaseService { if (!branchType) { throw new Error(`Unknown branchTypeId ${branchTypeId}`); } - const existingCount = await Branch.count({ - where: { falukantUserId: user.id } + const existingBranches = await Branch.findAll({ + where: { falukantUserId: user.id }, + include: [{ model: BranchType, as: 'branchType', attributes: ['labelTr'] }], + attributes: ['id'] }); - const exponentBase = Math.max(existingCount, 1); - const rawCost = branchType.baseCost * Math.pow(exponentBase, 1.2); - const cost = Math.round(rawCost * 100) / 100; + const ownedTypeLabels = existingBranches + .map((branch) => branch.branchType?.labelTr) + .filter(Boolean); + const cost = this._calculateBranchActionCost(branchType.baseCost, branchType.labelTr, ownedTypeLabels); if (user.money < cost) { throw new PreconditionError('insufficientFunds'); } @@ -1283,7 +1310,18 @@ class FalukantService extends BaseService { async getBranchTypes(hashedUserId) { const user = await getFalukantUserOrFail(hashedUserId); const branchTypes = await BranchType.findAll(); - return branchTypes; + const ownedBranches = await Branch.findAll({ + where: { falukantUserId: user.id }, + include: [{ model: BranchType, as: 'branchType', attributes: ['labelTr'] }], + attributes: ['id'] + }); + const ownedTypeLabels = ownedBranches + .map((branch) => branch.branchType?.labelTr) + .filter(Boolean); + return branchTypes.map((branchType) => ({ + ...branchType.get({ plain: true }), + createCost: this._calculateBranchActionCost(branchType.baseCost, branchType.labelTr, ownedTypeLabels), + })); } @@ -1311,7 +1349,7 @@ class FalukantService extends BaseService { const user = await getFalukantUserOrFail(hashedUserId); const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: user.id }, - include: [{ model: BranchType, as: 'branchType', attributes: ['id', 'labelTr'] }], + include: [{ model: BranchType, as: 'branchType', attributes: ['id', 'labelTr', 'baseCost'] }], }); if (!branch) { throw new Error('Branch not found'); @@ -1333,8 +1371,25 @@ class FalukantService extends BaseService { if (!targetType) { throw new Error(`Target branch type '${targetLabel}' not found`); } - - // Für den Moment ohne zusätzliche Kosten – kann später erweitert werden + const ownedBranches = await Branch.findAll({ + where: { falukantUserId: user.id }, + include: [{ model: BranchType, as: 'branchType', attributes: ['labelTr'] }], + attributes: ['id'] + }); + const ownedTypeLabels = ownedBranches + .filter((entry) => entry.id !== branch.id) + .map((entry) => entry.branchType?.labelTr) + .filter(Boolean); + const baseUpgradeCost = Math.max(targetType.baseCost - (branch.branchType?.baseCost || 0), targetType.baseCost * 0.6); + const upgradeCost = this._calculateBranchActionCost(baseUpgradeCost, targetType.labelTr, ownedTypeLabels); + if (user.money < upgradeCost) { + throw new PreconditionError('insufficientFunds'); + } + await updateFalukantUserMoney( + user.id, + -upgradeCost, + 'upgrade_branch' + ); branch.branchTypeId = targetType.id; await branch.save(); diff --git a/frontend/src/dialogues/falukant/CreateBranchDialog.vue b/frontend/src/dialogues/falukant/CreateBranchDialog.vue index 6641cad..8383632 100644 --- a/frontend/src/dialogues/falukant/CreateBranchDialog.vue +++ b/frontend/src/dialogues/falukant/CreateBranchDialog.vue @@ -257,6 +257,9 @@ }, computeBranchCost(type) { + if (Number.isFinite(Number(type?.createCost))) { + return Math.round(Number(type.createCost) * 100) / 100; + } const total = this.cities.reduce((sum, city) => sum + city.branches.length, 0); const factor = Math.pow(Math.max(total, 1), 1.2); const raw = type.baseCost * factor;