Update product definitions and revenue calculations in Falukant: Adjust product sell costs and production times for better balance. Refactor revenue calculations to focus on profit per minute instead of revenue per minute. Enhance localization files to include new terms related to product unlocks and certificate levels in English, German, and Spanish, improving user experience across languages.

This commit is contained in:
Torsten Schulz (local)
2026-03-26 20:19:49 +01:00
parent 01849c8ffe
commit e0c3b472db
10 changed files with 303 additions and 43 deletions

View File

@@ -97,16 +97,26 @@ async function getBranchOrFail(userId, branchId) {
return branch;
}
/**
* Gesamtkosten für eine Produktionscharge (früher: quantity * category * 6 pro Einheit).
* Pro Einheit jetzt 2 * Kategorie (z.B. Kat.1 = 2, Kat.2 = 4), damit Nettoerlös und
* typischer town_product_worth (oft 4060 %, siehe Model-Hook) zusammenpassen.
*/
function productionCostTotal(quantity, category) {
const PRODUCTION_COST_BASE = 6.0;
const PRODUCTION_COST_PER_PRODUCT_CATEGORY = 1.0;
const PRODUCTION_HEADROOM_DISCOUNT_PER_STEP = 0.035;
const PRODUCTION_HEADROOM_DISCOUNT_CAP = 0.14;
function productionPieceCost(certificate, category) {
const c = Math.max(1, Number(category) || 1);
const cert = Math.max(1, Number(certificate) || 1);
const raw = PRODUCTION_COST_BASE + (c * PRODUCTION_COST_PER_PRODUCT_CATEGORY);
const headroom = Math.max(0, cert - c);
const discount = Math.min(
headroom * PRODUCTION_HEADROOM_DISCOUNT_PER_STEP,
PRODUCTION_HEADROOM_DISCOUNT_CAP
);
return raw * (1 - discount);
}
function productionCostTotal(quantity, category, certificate) {
const q = Math.min(100, Math.max(1, Number(quantity) || 1));
const perUnit = 2 * c;
return q * perUnit;
return q * productionPieceCost(certificate, category);
}
/** Regionaler Nachfragewert: sehr niedrige Werte aus der DB sonst unspielbar; Decke nach oben bei 100. */
@@ -2053,7 +2063,7 @@ class FalukantService extends BaseService {
}
if (!p) throw new Error('Product not found');
quantity = Math.min(100, quantity);
const cost = productionCostTotal(quantity, p.category);
const cost = productionCostTotal(quantity, p.category, u.certificate);
if (u.money < cost) throw new Error('notenoughmoney');
const r = await updateFalukantUserMoney(u.id, -cost, 'Production cost', u.id);
if (!r.success) throw new Error('Failed to update money');

View File

@@ -0,0 +1,51 @@
BEGIN;
UPDATE falukant_type.product
SET sell_cost = CASE label_tr
WHEN 'wheat' THEN 7
WHEN 'grain' THEN 7
WHEN 'carrot' THEN 4
WHEN 'fish' THEN 7
WHEN 'meat' THEN 7
WHEN 'leather' THEN 7
WHEN 'wood' THEN 7
WHEN 'stone' THEN 7
WHEN 'milk' THEN 5
WHEN 'cheese' THEN 5
WHEN 'bread' THEN 5
WHEN 'beer' THEN 22
WHEN 'iron' THEN 24
WHEN 'copper' THEN 24
WHEN 'spices' THEN 42
WHEN 'salt' THEN 24
WHEN 'sugar' THEN 24
WHEN 'vinegar' THEN 24
WHEN 'cotton' THEN 24
WHEN 'wine' THEN 24
WHEN 'gold' THEN 40
WHEN 'diamond' THEN 40
WHEN 'furniture' THEN 40
WHEN 'clothing' THEN 40
WHEN 'jewelry' THEN 58
WHEN 'painting' THEN 58
WHEN 'book' THEN 58
WHEN 'weapon' THEN 58
WHEN 'armor' THEN 58
WHEN 'shield' THEN 58
WHEN 'horse' THEN 78
WHEN 'ox' THEN 78
ELSE sell_cost
END,
production_time = CASE label_tr
WHEN 'carrot' THEN 2
ELSE production_time
END
WHERE label_tr IN (
'wheat', 'grain', 'carrot', 'fish', 'meat', 'leather', 'wood', 'stone',
'milk', 'cheese', 'bread', 'beer', 'iron', 'copper', 'spices', 'salt',
'sugar', 'vinegar', 'cotton', 'wine', 'gold', 'diamond', 'furniture',
'clothing', 'jewelry', 'painting', 'book', 'weapon', 'armor', 'shield',
'horse', 'ox'
);
COMMIT;

View File

@@ -250,37 +250,36 @@ async function initializeFalukantProducts() {
const baseProducts = [
{ labelTr: 'wheat', category: 1, productionTime: 2, sellCost: 7 },
{ labelTr: 'grain', category: 1, productionTime: 2, sellCost: 7 },
// Wie Weizen/Getreide (kein Debug-Tempo mehr); Verkaufspreis wie Milch/Brot (Kat. 1, schnelle Ware)
{ labelTr: 'carrot', category: 1, productionTime: 2, sellCost: 6 },
{ labelTr: 'carrot', category: 1, productionTime: 2, sellCost: 4 },
{ labelTr: 'fish', category: 1, productionTime: 2, sellCost: 7 },
{ labelTr: 'meat', category: 1, productionTime: 2, sellCost: 7 },
{ labelTr: 'leather', category: 1, productionTime: 2, sellCost: 7 },
{ labelTr: 'wood', category: 1, productionTime: 2, sellCost: 7 },
{ labelTr: 'stone', category: 1, productionTime: 2, sellCost: 7 },
{ labelTr: 'milk', category: 1, productionTime: 1, sellCost: 6 },
{ labelTr: 'cheese', category: 1, productionTime: 1, sellCost: 6 },
{ labelTr: 'bread', category: 1, productionTime: 1, sellCost: 6 },
{ labelTr: 'beer', category: 2, productionTime: 3, sellCost: 6 },
{ labelTr: 'iron', category: 2, productionTime: 4, sellCost: 15 },
{ labelTr: 'copper', category: 2, productionTime: 4, sellCost: 15 },
{ labelTr: 'spices', category: 2, productionTime: 8, sellCost: 30 },
{ labelTr: 'salt', category: 2, productionTime: 4, sellCost: 15 },
{ labelTr: 'sugar', category: 2, productionTime: 4, sellCost: 15 },
{ labelTr: 'vinegar', category: 2, productionTime: 4, sellCost: 15 },
{ labelTr: 'cotton', category: 2, productionTime: 4, sellCost: 15 },
{ labelTr: 'wine', category: 2, productionTime: 4, sellCost: 15 },
{ labelTr: 'gold', category: 3, productionTime: 4, sellCost: 30 },
{ labelTr: 'diamond', category: 3, productionTime: 4, sellCost: 30 },
{ labelTr: 'furniture', category: 3, productionTime: 4, sellCost: 30 },
{ labelTr: 'clothing', category: 3, productionTime: 4, sellCost: 30 },
{ labelTr: 'jewelry', category: 4, productionTime: 5, sellCost: 60 },
{ labelTr: 'painting', category: 4, productionTime: 5, sellCost: 60 },
{ labelTr: 'book', category: 4, productionTime: 5, sellCost: 60 },
{ labelTr: 'weapon', category: 4, productionTime: 5, sellCost: 60 },
{ labelTr: 'armor', category: 4, productionTime: 5, sellCost: 60 },
{ labelTr: 'shield', category: 4, productionTime: 5, sellCost: 60 },
{ labelTr: 'horse', category: 5, productionTime: 5, sellCost: 60 },
{ labelTr: 'ox', category: 5, productionTime: 5, sellCost: 60 },
{ labelTr: 'milk', category: 1, productionTime: 1, sellCost: 5 },
{ labelTr: 'cheese', category: 1, productionTime: 1, sellCost: 5 },
{ labelTr: 'bread', category: 1, productionTime: 1, sellCost: 5 },
{ labelTr: 'beer', category: 2, productionTime: 3, sellCost: 22 },
{ labelTr: 'iron', category: 2, productionTime: 4, sellCost: 24 },
{ labelTr: 'copper', category: 2, productionTime: 4, sellCost: 24 },
{ labelTr: 'spices', category: 2, productionTime: 8, sellCost: 42 },
{ labelTr: 'salt', category: 2, productionTime: 4, sellCost: 24 },
{ labelTr: 'sugar', category: 2, productionTime: 4, sellCost: 24 },
{ labelTr: 'vinegar', category: 2, productionTime: 4, sellCost: 24 },
{ labelTr: 'cotton', category: 2, productionTime: 4, sellCost: 24 },
{ labelTr: 'wine', category: 2, productionTime: 4, sellCost: 24 },
{ labelTr: 'gold', category: 3, productionTime: 4, sellCost: 40 },
{ labelTr: 'diamond', category: 3, productionTime: 4, sellCost: 40 },
{ labelTr: 'furniture', category: 3, productionTime: 4, sellCost: 40 },
{ labelTr: 'clothing', category: 3, productionTime: 4, sellCost: 40 },
{ labelTr: 'jewelry', category: 4, productionTime: 5, sellCost: 58 },
{ labelTr: 'painting', category: 4, productionTime: 5, sellCost: 58 },
{ labelTr: 'book', category: 4, productionTime: 5, sellCost: 58 },
{ labelTr: 'weapon', category: 4, productionTime: 5, sellCost: 58 },
{ labelTr: 'armor', category: 4, productionTime: 5, sellCost: 58 },
{ labelTr: 'shield', category: 4, productionTime: 5, sellCost: 58 },
{ labelTr: 'horse', category: 5, productionTime: 5, sellCost: 78 },
{ labelTr: 'ox', category: 5, productionTime: 5, sellCost: 78 },
];
const productsToInsert = baseProducts.map(p => ({