Add raid transport feature and related updates: Introduce new raid transport functionality in FalukantService and FalukantController, including methods for retrieving raid transport regions and handling guard counts. Update frontend components to support guard count input and display related costs. Enhance localization files to include new terms for raid transport and associated metrics in English, German, and Spanish.
This commit is contained in:
@@ -252,6 +252,7 @@ class FalukantController {
|
||||
this.renovateAll = this._wrapWithUser((userId) => this.service.renovateAll(userId), { blockInDebtorsPrison: true });
|
||||
|
||||
this.getUndergroundTypes = this._wrapWithUser((userId) => this.service.getUndergroundTypes(userId));
|
||||
this.getRaidTransportRegions = this._wrapWithUser((userId) => this.service.getRaidTransportRegions(userId));
|
||||
this.getUndergroundActivities = this._wrapWithUser((userId) => this.service.getUndergroundActivities(userId));
|
||||
this.getNotifications = this._wrapWithUser((userId) => this.service.getNotifications(userId));
|
||||
this.getAllNotifications = this._wrapWithUser((userId, req) => this.service.getAllNotifications(userId, req.query.page, req.query.size));
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.transport
|
||||
ADD COLUMN IF NOT EXISTS guard_count integer NOT NULL DEFAULT 0;
|
||||
`);
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.underground
|
||||
ALTER COLUMN victim_id DROP NOT NULL;
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.transport
|
||||
DROP COLUMN IF EXISTS guard_count;
|
||||
`);
|
||||
}
|
||||
};
|
||||
@@ -25,6 +25,11 @@ Transport.init(
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
guardCount: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
@@ -38,4 +43,3 @@ Transport.init(
|
||||
|
||||
export default Transport;
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Underground.init({
|
||||
allowNull: false},
|
||||
victimId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false},
|
||||
allowNull: true},
|
||||
parameters: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true},
|
||||
|
||||
@@ -112,6 +112,7 @@ router.post('/transports', falukantController.createTransport);
|
||||
router.get('/transports/route', falukantController.getTransportRoute);
|
||||
router.get('/transports/branch/:branchId', falukantController.getBranchTransports);
|
||||
router.get('/underground/types', falukantController.getUndergroundTypes);
|
||||
router.get('/underground/raid-regions', falukantController.getRaidTransportRegions);
|
||||
router.get('/underground/activities', falukantController.getUndergroundActivities);
|
||||
router.get('/notifications', falukantController.getNotifications);
|
||||
router.get('/notifications/all', falukantController.getAllNotifications);
|
||||
|
||||
@@ -1330,8 +1330,9 @@ class FalukantService extends BaseService {
|
||||
};
|
||||
}
|
||||
|
||||
async createTransport(hashedUserId, { branchId, vehicleTypeId, vehicleIds, productId, quantity, targetBranchId }) {
|
||||
async createTransport(hashedUserId, { branchId, vehicleTypeId, vehicleIds, productId, quantity, targetBranchId, guardCount: rawGuardCount }) {
|
||||
const user = await getFalukantUserOrFail(hashedUserId);
|
||||
const guardCount = Math.max(0, Number.parseInt(rawGuardCount ?? 0, 10) || 0);
|
||||
|
||||
const sourceBranch = await Branch.findOne({
|
||||
where: { id: branchId, falukantUserId: user.id },
|
||||
@@ -1509,6 +1510,9 @@ class FalukantService extends BaseService {
|
||||
hardMax = 1;
|
||||
}
|
||||
|
||||
transportCost += guardCount * 4;
|
||||
transportCost = Math.round(transportCost * 100) / 100;
|
||||
|
||||
if (user.money < transportCost) {
|
||||
throw new PreconditionError('insufficientFunds');
|
||||
}
|
||||
@@ -1538,6 +1542,7 @@ class FalukantService extends BaseService {
|
||||
productId: isEmptyTransport ? null : productId,
|
||||
size: isEmptyTransport ? 0 : size,
|
||||
vehicleId: v.id,
|
||||
guardCount,
|
||||
},
|
||||
{ transaction: tx }
|
||||
);
|
||||
@@ -1601,6 +1606,7 @@ class FalukantService extends BaseService {
|
||||
id: t.id,
|
||||
size: t.size,
|
||||
vehicleId: t.vehicleId,
|
||||
guardCount: t.guardCount,
|
||||
})),
|
||||
};
|
||||
});
|
||||
@@ -1669,6 +1675,7 @@ class FalukantService extends BaseService {
|
||||
targetRegion: t.targetRegion,
|
||||
product: t.productType,
|
||||
size: t.size,
|
||||
guardCount: Number(t.guardCount || 0),
|
||||
vehicleId: t.vehicleId,
|
||||
vehicleType: t.vehicle?.type || null,
|
||||
createdAt: t.createdAt,
|
||||
@@ -6529,6 +6536,34 @@ ORDER BY r.id`,
|
||||
return undergroundTypes;
|
||||
}
|
||||
|
||||
async getRaidTransportRegions(hashedUserId) {
|
||||
await getFalukantUserOrFail(hashedUserId);
|
||||
|
||||
const regions = await RegionData.findAll({
|
||||
where: {
|
||||
regionTypeId: { [Op.in]: [4, 5] }
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: RegionType,
|
||||
as: 'regionType',
|
||||
attributes: ['id', 'labelTr']
|
||||
}
|
||||
],
|
||||
attributes: ['id', 'name', 'regionTypeId'],
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
|
||||
return regions
|
||||
.filter((region) => region.regionType?.labelTr !== 'town')
|
||||
.map((region) => ({
|
||||
id: region.id,
|
||||
name: region.name,
|
||||
regionTypeId: region.regionTypeId,
|
||||
regionTypeLabel: region.regionType?.labelTr || null
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
async getNotifications(hashedUserId) {
|
||||
const user = await getFalukantUserOrFail(hashedUserId);
|
||||
@@ -6768,7 +6803,7 @@ ORDER BY r.id`,
|
||||
}
|
||||
|
||||
async createUndergroundActivity(hashedUserId, payload) {
|
||||
const { typeId, victimUsername, target, goal, politicalTargets } = payload;
|
||||
const { typeId, victimUsername, target, goal, politicalTargets, regionId, bandSize } = payload;
|
||||
|
||||
// 1) Performer auflösen
|
||||
const falukantUser = await this.getFalukantUserByHashedId(hashedUserId);
|
||||
@@ -6777,42 +6812,43 @@ ORDER BY r.id`,
|
||||
}
|
||||
const performerChar = falukantUser.character;
|
||||
|
||||
// 2) Victim auflösen über Username (inner join)
|
||||
const victimChar = await FalukantCharacter.findOne({
|
||||
include: [
|
||||
{
|
||||
model: FalukantUser,
|
||||
as: 'user',
|
||||
required: true, // inner join
|
||||
attributes: [],
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'user',
|
||||
required: true, // inner join
|
||||
where: { username: victimUsername },
|
||||
attributes: []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!victimChar) {
|
||||
throw new PreconditionError('Victim character not found');
|
||||
}
|
||||
|
||||
// 3) Selbstangriff verhindern
|
||||
if (victimChar.id === performerChar.id) {
|
||||
throw new PreconditionError('Cannot target yourself');
|
||||
}
|
||||
|
||||
// 4) Typ-spezifische Validierung
|
||||
// 2) Typ-spezifische Validierung
|
||||
const undergroundType = await UndergroundType.findByPk(typeId);
|
||||
if (!undergroundType) {
|
||||
throw new Error('Invalid underground type');
|
||||
}
|
||||
|
||||
let victimChar = null;
|
||||
if (undergroundType.tr !== 'raid_transport') {
|
||||
victimChar = await FalukantCharacter.findOne({
|
||||
include: [
|
||||
{
|
||||
model: FalukantUser,
|
||||
as: 'user',
|
||||
required: true,
|
||||
attributes: [],
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'user',
|
||||
required: true,
|
||||
where: { username: victimUsername },
|
||||
attributes: []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!victimChar) {
|
||||
throw new PreconditionError('Victim character not found');
|
||||
}
|
||||
|
||||
if (victimChar.id === performerChar.id) {
|
||||
throw new PreconditionError('Cannot target yourself');
|
||||
}
|
||||
}
|
||||
|
||||
if (undergroundType.tr === 'sabotage') {
|
||||
if (!target) {
|
||||
throw new PreconditionError('Sabotage target missing');
|
||||
@@ -6832,23 +6868,67 @@ ORDER BY r.id`,
|
||||
}
|
||||
}
|
||||
|
||||
// 5) Eintrag anlegen (optional: in Transaction)
|
||||
const newEntry = await Underground.create({
|
||||
undergroundTypeId: typeId,
|
||||
performerId: performerChar.id,
|
||||
victimId: victimChar.id,
|
||||
result: {
|
||||
if (undergroundType.tr === 'raid_transport') {
|
||||
const parsedRegionId = Number.parseInt(regionId, 10);
|
||||
const parsedBandSize = Math.max(1, Number.parseInt(bandSize, 10) || 0);
|
||||
|
||||
if (!parsedRegionId) {
|
||||
throw new PreconditionError('Raid region missing');
|
||||
}
|
||||
if (!parsedBandSize) {
|
||||
throw new PreconditionError('Band size missing');
|
||||
}
|
||||
|
||||
const validRegion = await RegionData.findOne({
|
||||
where: {
|
||||
id: parsedRegionId,
|
||||
regionTypeId: { [Op.in]: [4, 5] }
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: RegionType,
|
||||
as: 'regionType',
|
||||
attributes: ['labelTr']
|
||||
}
|
||||
],
|
||||
attributes: ['id', 'name']
|
||||
});
|
||||
|
||||
if (!validRegion || validRegion.regionType?.labelTr === 'town') {
|
||||
throw new PreconditionError('Invalid raid region');
|
||||
}
|
||||
}
|
||||
|
||||
const defaultResult = undergroundType.tr === 'raid_transport'
|
||||
? {
|
||||
status: 'pending',
|
||||
outcome: null,
|
||||
attempts: 0,
|
||||
successes: 0,
|
||||
lastTargetTransportId: null,
|
||||
lastLoot: null,
|
||||
lastOutcome: null
|
||||
}
|
||||
: {
|
||||
status: 'pending',
|
||||
outcome: null,
|
||||
discoveries: null,
|
||||
visibilityDelta: 0,
|
||||
reputationDelta: 0,
|
||||
blackmailAmount: 0
|
||||
},
|
||||
};
|
||||
|
||||
const newEntry = await Underground.create({
|
||||
undergroundTypeId: typeId,
|
||||
performerId: performerChar.id,
|
||||
victimId: victimChar?.id || null,
|
||||
result: defaultResult,
|
||||
parameters: {
|
||||
target: target || null,
|
||||
goal: goal || null,
|
||||
politicalTargets: politicalTargets || null
|
||||
politicalTargets: politicalTargets || null,
|
||||
regionId: regionId || null,
|
||||
bandSize: bandSize || null
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6883,6 +6963,19 @@ ORDER BY r.id`,
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
const regionIds = [...new Set(
|
||||
activities
|
||||
.map((activity) => Number.parseInt(activity.parameters?.regionId, 10))
|
||||
.filter((id) => !Number.isNaN(id) && id > 0)
|
||||
)];
|
||||
const regions = regionIds.length
|
||||
? await RegionData.findAll({
|
||||
where: { id: regionIds },
|
||||
attributes: ['id', 'name']
|
||||
})
|
||||
: [];
|
||||
const regionMap = new Map(regions.map((region) => [region.id, region.name]));
|
||||
|
||||
return activities.map((activity) => {
|
||||
const result = activity.result || {};
|
||||
const status = result.status || (result.outcome ? 'resolved' : 'pending');
|
||||
@@ -6896,11 +6989,16 @@ ORDER BY r.id`,
|
||||
success: result.outcome === 'success',
|
||||
target: activity.parameters?.target || null,
|
||||
goal: activity.parameters?.goal || null,
|
||||
regionName: regionMap.get(Number.parseInt(activity.parameters?.regionId, 10)) || null,
|
||||
additionalInfo: {
|
||||
discoveries: result.discoveries || null,
|
||||
visibilityDelta: result.visibilityDelta ?? null,
|
||||
reputationDelta: result.reputationDelta ?? null,
|
||||
blackmailAmount: result.blackmailAmount ?? null
|
||||
blackmailAmount: result.blackmailAmount ?? null,
|
||||
bandSize: activity.parameters?.bandSize ?? null,
|
||||
attempts: result.attempts ?? null,
|
||||
successes: result.successes ?? null,
|
||||
lastOutcome: result.lastOutcome ?? null
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
-- PostgreSQL only
|
||||
|
||||
ALTER TABLE falukant_data.transport
|
||||
ADD COLUMN IF NOT EXISTS guard_count integer NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE falukant_data.underground
|
||||
ALTER COLUMN victim_id DROP NOT NULL;
|
||||
5
backend/sql/add_underground_raid_transport_type.sql
Normal file
5
backend/sql/add_underground_raid_transport_type.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- PostgreSQL only
|
||||
INSERT INTO falukant_type.underground (tr, cost)
|
||||
VALUES ('raid_transport', 9000)
|
||||
ON CONFLICT (tr) DO UPDATE
|
||||
SET cost = EXCLUDED.cost;
|
||||
@@ -663,6 +663,10 @@ const undergroundTypes = [
|
||||
"tr": "investigate_affair",
|
||||
"cost": 7000
|
||||
},
|
||||
{
|
||||
"tr": "raid_transport",
|
||||
"cost": 9000
|
||||
},
|
||||
];
|
||||
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user