feat(falukant): enhance product pricing and nobility advancement features
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
- Updated the `getAllProductPricesInRegion` method in `FalukantService` to accept additional parameters for network worth and branch ID, improving pricing calculations based on user branches. - Enhanced the nobility advancement logic in `NobilityView` to display unmet requirements clearly, providing users with better feedback on advancement conditions. - Refactored the revenue calculation in `ProductionSection` to utilize a cached product prices object, optimizing performance and reducing redundant API calls. - Updated localization files to include new translations for attack sources across multiple languages, enhancing the user experience for diverse audiences. - Removed obsolete C++ worker references and streamlined the retention logic for production logs, ensuring efficient data management.
This commit is contained in:
@@ -245,7 +245,9 @@ class FalukantController {
|
||||
if (Number.isNaN(regionId)) {
|
||||
throw new Error('regionId is required');
|
||||
}
|
||||
return this.service.getAllProductPricesInRegion(userId, regionId);
|
||||
const networkWorth = req.query.networkWorth === '1' || req.query.networkWorth === 'true';
|
||||
const branchId = req.query.branchId != null ? parseInt(req.query.branchId, 10) : null;
|
||||
return this.service.getAllProductPricesInRegion(userId, regionId, { networkWorth, branchId });
|
||||
});
|
||||
this.getProductPricesInCities = this._wrapWithUser((userId, req) => {
|
||||
const productId = parseInt(req.query.productId, 10);
|
||||
|
||||
@@ -5821,51 +5821,61 @@ class FalukantService extends BaseService {
|
||||
const oneWeekAgo = new Date(now.getTime());
|
||||
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
|
||||
if (user.lastNobilityAdvanceAt > oneWeekAgo) {
|
||||
throw new Error('too soon');
|
||||
const last = new Date(user.lastNobilityAdvanceAt);
|
||||
const retryAt = new Date(last.getTime());
|
||||
retryAt.setDate(retryAt.getDate() + 7);
|
||||
throw { status: 412, message: 'nobilityTooSoon', retryAt: retryAt.toISOString() };
|
||||
}
|
||||
}
|
||||
const nextTitle = nobility.next.toJSON();
|
||||
let fulfilled = true;
|
||||
let cost = 0;
|
||||
const unmet = [];
|
||||
for (const requirement of nextTitle.requirements) {
|
||||
let ok = true;
|
||||
switch (requirement.requirementType) {
|
||||
case 'money':
|
||||
fulfilled = fulfilled && await this.checkMoneyRequirement(user, requirement);
|
||||
ok = await this.checkMoneyRequirement(user, requirement);
|
||||
break;
|
||||
case 'cost':
|
||||
fulfilled = fulfilled && await this.checkMoneyRequirement(user, requirement);
|
||||
ok = await this.checkMoneyRequirement(user, requirement);
|
||||
cost = requirement.requirementValue;
|
||||
break;
|
||||
case 'branches':
|
||||
fulfilled = fulfilled && await this.checkBranchesRequirement(hashedUserId, requirement);
|
||||
ok = await this.checkBranchesRequirement(hashedUserId, requirement);
|
||||
break;
|
||||
case 'reputation':
|
||||
fulfilled = fulfilled && await this.checkReputationRequirement(user, requirement);
|
||||
ok = await this.checkReputationRequirement(user, requirement);
|
||||
break;
|
||||
case 'house_position':
|
||||
fulfilled = fulfilled && await this.checkHousePositionRequirement(user, requirement);
|
||||
ok = await this.checkHousePositionRequirement(user, requirement);
|
||||
break;
|
||||
case 'house_condition':
|
||||
fulfilled = fulfilled && await this.checkHouseConditionRequirement(user, requirement);
|
||||
ok = await this.checkHouseConditionRequirement(user, requirement);
|
||||
break;
|
||||
case 'office_rank_any':
|
||||
fulfilled = fulfilled && await this.checkOfficeRankAnyRequirement(user, requirement);
|
||||
ok = await this.checkOfficeRankAnyRequirement(user, requirement);
|
||||
break;
|
||||
case 'office_rank_political':
|
||||
fulfilled = fulfilled && await this.checkOfficeRankPoliticalRequirement(user, requirement);
|
||||
ok = await this.checkOfficeRankPoliticalRequirement(user, requirement);
|
||||
break;
|
||||
case 'lover_count_max':
|
||||
fulfilled = fulfilled && await this.checkLoverCountMaxRequirement(user, requirement);
|
||||
ok = await this.checkLoverCountMaxRequirement(user, requirement);
|
||||
break;
|
||||
case 'lover_count_min':
|
||||
fulfilled = fulfilled && await this.checkLoverCountMinRequirement(user, requirement);
|
||||
ok = await this.checkLoverCountMinRequirement(user, requirement);
|
||||
break;
|
||||
default:
|
||||
fulfilled = false;
|
||||
};
|
||||
ok = false;
|
||||
}
|
||||
if (!ok) {
|
||||
unmet.push({
|
||||
type: requirement.requirementType,
|
||||
required: requirement.requirementValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!fulfilled) {
|
||||
throw new Error('Requirements not fulfilled');
|
||||
if (unmet.length > 0) {
|
||||
throw { status: 412, message: 'nobilityRequirements', unmet };
|
||||
}
|
||||
const newTitle = await TitleOfNobility.findOne({
|
||||
where: { level: nobility.current.level + 1 }
|
||||
@@ -6793,13 +6803,42 @@ class FalukantService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
async getAllProductPricesInRegion(hashedUserId, regionId) {
|
||||
/**
|
||||
* Regionale Verkaufspreise (Wissen + town_product_worth), für Ertrags-/Gewinn-Tabelle.
|
||||
* @param {{ networkWorth?: boolean, branchId?: number|null }} options — bei networkWorth: MAX(worth_percent)
|
||||
* über alle Regionen der eigenen Niederlassungen (wie Daemon mit Fahrzeug / Filialnetz).
|
||||
*/
|
||||
async getAllProductPricesInRegion(hashedUserId, regionId, options = {}) {
|
||||
try {
|
||||
const networkWorth = Boolean(options.networkWorth);
|
||||
const branchId = options.branchId != null ? Number(options.branchId) : null;
|
||||
|
||||
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
||||
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
|
||||
if (!character) {
|
||||
throw new Error(`No FalukantCharacter found for user with id ${user.id}`);
|
||||
}
|
||||
|
||||
let worthRegionIds = [regionId];
|
||||
if (networkWorth) {
|
||||
if (!branchId || Number.isNaN(branchId)) {
|
||||
throw new Error('branchId is required when networkWorth is set');
|
||||
}
|
||||
const ownBranch = await Branch.findOne({
|
||||
where: { id: branchId, falukantUserId: user.id },
|
||||
attributes: ['id']
|
||||
});
|
||||
if (!ownBranch) {
|
||||
throw new Error('Branch not found or not owned by user');
|
||||
}
|
||||
const userBranches = await Branch.findAll({
|
||||
where: { falukantUserId: user.id },
|
||||
attributes: ['regionId']
|
||||
});
|
||||
const ids = [...new Set(userBranches.map((b) => b.regionId).filter((id) => id != null))];
|
||||
worthRegionIds = ids.length > 0 ? ids : [regionId];
|
||||
}
|
||||
|
||||
const [products, knowledges, townWorths] = await Promise.all([
|
||||
ProductType.findAll({ attributes: ['id', 'sellCost'] }),
|
||||
Knowledge.findAll({
|
||||
@@ -6807,17 +6846,22 @@ class FalukantService extends BaseService {
|
||||
attributes: ['productId', 'knowledge']
|
||||
}),
|
||||
TownProductWorth.findAll({
|
||||
where: { regionId: regionId },
|
||||
attributes: ['productId', 'worthPercent']
|
||||
where: { regionId: { [Op.in]: worthRegionIds } },
|
||||
attributes: ['productId', 'regionId', 'worthPercent']
|
||||
})
|
||||
]);
|
||||
|
||||
const knowledgeMap = new Map(knowledges.map(k => [k.productId, k.knowledge || 0]));
|
||||
const worthMap = new Map(townWorths.map(tw => [tw.productId, tw.worthPercent || 50]));
|
||||
const maxWorthByProduct = new Map();
|
||||
for (const tw of townWorths) {
|
||||
const w = tw.worthPercent ?? 50;
|
||||
const prev = maxWorthByProduct.get(tw.productId);
|
||||
maxWorthByProduct.set(tw.productId, prev == null ? w : Math.max(prev, w));
|
||||
}
|
||||
|
||||
const prices = {};
|
||||
for (const product of products) {
|
||||
const worthPercent = worthMap.get(product.id) ?? 50;
|
||||
const worthPercent = maxWorthByProduct.get(product.id) ?? 50;
|
||||
const knowledgeFactor = knowledgeMap.get(product.id) || 0;
|
||||
const price = calcRegionalSellPriceSync(product, knowledgeFactor, worthPercent);
|
||||
if (price !== null) prices[product.id] = price;
|
||||
@@ -6987,11 +7031,20 @@ ORDER BY r.id`,
|
||||
|
||||
for (const product of products) {
|
||||
const knowledgeFactor = knowledgeByProduct.get(product.id) || 0;
|
||||
const currentPrice = priceByProduct.get(product.id) ?? product.sellCost ?? 0;
|
||||
let currentRegionalPrice = currentPrice;
|
||||
if (currentRegionId) {
|
||||
const clientPriceRaw = priceByProduct.get(product.id);
|
||||
const clientPriceNum = clientPriceRaw != null && clientPriceRaw !== ''
|
||||
? Number(clientPriceRaw)
|
||||
: NaN;
|
||||
let currentRegionalPrice;
|
||||
if (!Number.isNaN(clientPriceNum)) {
|
||||
// Referenzpreis wie in der Ertrags-Tabelle (z. B. MAX-Worth über Filialen)
|
||||
currentRegionalPrice = clientPriceNum;
|
||||
} else if (currentRegionId) {
|
||||
const wp = worthByProductRegion.get(`${product.id}-${currentRegionId}`) ?? 50;
|
||||
currentRegionalPrice = calcRegionalSellPriceSync(product, knowledgeFactor, wp) ?? currentPrice;
|
||||
currentRegionalPrice = calcRegionalSellPriceSync(product, knowledgeFactor, wp)
|
||||
?? Number(product.sellCost) ?? 0;
|
||||
} else {
|
||||
currentRegionalPrice = Number(product.sellCost) || 0;
|
||||
}
|
||||
|
||||
const results = [];
|
||||
|
||||
Reference in New Issue
Block a user