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.renovateAll = this._wrapWithUser((userId) => this.service.renovateAll(userId), { blockInDebtorsPrison: true });
|
||||||
|
|
||||||
this.getUndergroundTypes = this._wrapWithUser((userId) => this.service.getUndergroundTypes(userId));
|
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.getUndergroundActivities = this._wrapWithUser((userId) => this.service.getUndergroundActivities(userId));
|
||||||
this.getNotifications = this._wrapWithUser((userId) => this.service.getNotifications(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));
|
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,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
|
guardCount: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequelize,
|
sequelize,
|
||||||
@@ -38,4 +43,3 @@ Transport.init(
|
|||||||
|
|
||||||
export default Transport;
|
export default Transport;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Underground.init({
|
|||||||
allowNull: false},
|
allowNull: false},
|
||||||
victimId: {
|
victimId: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false},
|
allowNull: true},
|
||||||
parameters: {
|
parameters: {
|
||||||
type: DataTypes.JSON,
|
type: DataTypes.JSON,
|
||||||
allowNull: true},
|
allowNull: true},
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ router.post('/transports', falukantController.createTransport);
|
|||||||
router.get('/transports/route', falukantController.getTransportRoute);
|
router.get('/transports/route', falukantController.getTransportRoute);
|
||||||
router.get('/transports/branch/:branchId', falukantController.getBranchTransports);
|
router.get('/transports/branch/:branchId', falukantController.getBranchTransports);
|
||||||
router.get('/underground/types', falukantController.getUndergroundTypes);
|
router.get('/underground/types', falukantController.getUndergroundTypes);
|
||||||
|
router.get('/underground/raid-regions', falukantController.getRaidTransportRegions);
|
||||||
router.get('/underground/activities', falukantController.getUndergroundActivities);
|
router.get('/underground/activities', falukantController.getUndergroundActivities);
|
||||||
router.get('/notifications', falukantController.getNotifications);
|
router.get('/notifications', falukantController.getNotifications);
|
||||||
router.get('/notifications/all', falukantController.getAllNotifications);
|
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 user = await getFalukantUserOrFail(hashedUserId);
|
||||||
|
const guardCount = Math.max(0, Number.parseInt(rawGuardCount ?? 0, 10) || 0);
|
||||||
|
|
||||||
const sourceBranch = await Branch.findOne({
|
const sourceBranch = await Branch.findOne({
|
||||||
where: { id: branchId, falukantUserId: user.id },
|
where: { id: branchId, falukantUserId: user.id },
|
||||||
@@ -1509,6 +1510,9 @@ class FalukantService extends BaseService {
|
|||||||
hardMax = 1;
|
hardMax = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transportCost += guardCount * 4;
|
||||||
|
transportCost = Math.round(transportCost * 100) / 100;
|
||||||
|
|
||||||
if (user.money < transportCost) {
|
if (user.money < transportCost) {
|
||||||
throw new PreconditionError('insufficientFunds');
|
throw new PreconditionError('insufficientFunds');
|
||||||
}
|
}
|
||||||
@@ -1538,6 +1542,7 @@ class FalukantService extends BaseService {
|
|||||||
productId: isEmptyTransport ? null : productId,
|
productId: isEmptyTransport ? null : productId,
|
||||||
size: isEmptyTransport ? 0 : size,
|
size: isEmptyTransport ? 0 : size,
|
||||||
vehicleId: v.id,
|
vehicleId: v.id,
|
||||||
|
guardCount,
|
||||||
},
|
},
|
||||||
{ transaction: tx }
|
{ transaction: tx }
|
||||||
);
|
);
|
||||||
@@ -1601,6 +1606,7 @@ class FalukantService extends BaseService {
|
|||||||
id: t.id,
|
id: t.id,
|
||||||
size: t.size,
|
size: t.size,
|
||||||
vehicleId: t.vehicleId,
|
vehicleId: t.vehicleId,
|
||||||
|
guardCount: t.guardCount,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -1669,6 +1675,7 @@ class FalukantService extends BaseService {
|
|||||||
targetRegion: t.targetRegion,
|
targetRegion: t.targetRegion,
|
||||||
product: t.productType,
|
product: t.productType,
|
||||||
size: t.size,
|
size: t.size,
|
||||||
|
guardCount: Number(t.guardCount || 0),
|
||||||
vehicleId: t.vehicleId,
|
vehicleId: t.vehicleId,
|
||||||
vehicleType: t.vehicle?.type || null,
|
vehicleType: t.vehicle?.type || null,
|
||||||
createdAt: t.createdAt,
|
createdAt: t.createdAt,
|
||||||
@@ -6529,6 +6536,34 @@ ORDER BY r.id`,
|
|||||||
return undergroundTypes;
|
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) {
|
async getNotifications(hashedUserId) {
|
||||||
const user = await getFalukantUserOrFail(hashedUserId);
|
const user = await getFalukantUserOrFail(hashedUserId);
|
||||||
@@ -6768,7 +6803,7 @@ ORDER BY r.id`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createUndergroundActivity(hashedUserId, payload) {
|
async createUndergroundActivity(hashedUserId, payload) {
|
||||||
const { typeId, victimUsername, target, goal, politicalTargets } = payload;
|
const { typeId, victimUsername, target, goal, politicalTargets, regionId, bandSize } = payload;
|
||||||
|
|
||||||
// 1) Performer auflösen
|
// 1) Performer auflösen
|
||||||
const falukantUser = await this.getFalukantUserByHashedId(hashedUserId);
|
const falukantUser = await this.getFalukantUserByHashedId(hashedUserId);
|
||||||
@@ -6777,42 +6812,43 @@ ORDER BY r.id`,
|
|||||||
}
|
}
|
||||||
const performerChar = falukantUser.character;
|
const performerChar = falukantUser.character;
|
||||||
|
|
||||||
// 2) Victim auflösen über Username (inner join)
|
// 2) Typ-spezifische Validierung
|
||||||
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
|
|
||||||
const undergroundType = await UndergroundType.findByPk(typeId);
|
const undergroundType = await UndergroundType.findByPk(typeId);
|
||||||
if (!undergroundType) {
|
if (!undergroundType) {
|
||||||
throw new Error('Invalid underground type');
|
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 (undergroundType.tr === 'sabotage') {
|
||||||
if (!target) {
|
if (!target) {
|
||||||
throw new PreconditionError('Sabotage target missing');
|
throw new PreconditionError('Sabotage target missing');
|
||||||
@@ -6832,23 +6868,67 @@ ORDER BY r.id`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5) Eintrag anlegen (optional: in Transaction)
|
if (undergroundType.tr === 'raid_transport') {
|
||||||
const newEntry = await Underground.create({
|
const parsedRegionId = Number.parseInt(regionId, 10);
|
||||||
undergroundTypeId: typeId,
|
const parsedBandSize = Math.max(1, Number.parseInt(bandSize, 10) || 0);
|
||||||
performerId: performerChar.id,
|
|
||||||
victimId: victimChar.id,
|
if (!parsedRegionId) {
|
||||||
result: {
|
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',
|
status: 'pending',
|
||||||
outcome: null,
|
outcome: null,
|
||||||
discoveries: null,
|
discoveries: null,
|
||||||
visibilityDelta: 0,
|
visibilityDelta: 0,
|
||||||
reputationDelta: 0,
|
reputationDelta: 0,
|
||||||
blackmailAmount: 0
|
blackmailAmount: 0
|
||||||
},
|
};
|
||||||
|
|
||||||
|
const newEntry = await Underground.create({
|
||||||
|
undergroundTypeId: typeId,
|
||||||
|
performerId: performerChar.id,
|
||||||
|
victimId: victimChar?.id || null,
|
||||||
|
result: defaultResult,
|
||||||
parameters: {
|
parameters: {
|
||||||
target: target || null,
|
target: target || null,
|
||||||
goal: goal || 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']]
|
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) => {
|
return activities.map((activity) => {
|
||||||
const result = activity.result || {};
|
const result = activity.result || {};
|
||||||
const status = result.status || (result.outcome ? 'resolved' : 'pending');
|
const status = result.status || (result.outcome ? 'resolved' : 'pending');
|
||||||
@@ -6896,11 +6989,16 @@ ORDER BY r.id`,
|
|||||||
success: result.outcome === 'success',
|
success: result.outcome === 'success',
|
||||||
target: activity.parameters?.target || null,
|
target: activity.parameters?.target || null,
|
||||||
goal: activity.parameters?.goal || null,
|
goal: activity.parameters?.goal || null,
|
||||||
|
regionName: regionMap.get(Number.parseInt(activity.parameters?.regionId, 10)) || null,
|
||||||
additionalInfo: {
|
additionalInfo: {
|
||||||
discoveries: result.discoveries || null,
|
discoveries: result.discoveries || null,
|
||||||
visibilityDelta: result.visibilityDelta ?? null,
|
visibilityDelta: result.visibilityDelta ?? null,
|
||||||
reputationDelta: result.reputationDelta ?? 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",
|
"tr": "investigate_affair",
|
||||||
"cost": 7000
|
"cost": 7000
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"tr": "raid_transport",
|
||||||
|
"cost": 9000
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -207,60 +207,68 @@ Der Daemon berechnet:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
certificateScore =
|
certificateScore =
|
||||||
knowledgePoints * 0.35 +
|
knowledgePoints * 0.45 +
|
||||||
productionPoints * 0.20 +
|
productionPoints * 0.30 +
|
||||||
officePoints * 0.15 +
|
officePoints * 0.08 +
|
||||||
nobilityPoints * 0.10 +
|
nobilityPoints * 0.05 +
|
||||||
reputationPoints * 0.10 +
|
reputationPoints * 0.07 +
|
||||||
housePoints * 0.10
|
housePoints * 0.05
|
||||||
```
|
```
|
||||||
|
|
||||||
Zusätzlich gelten Mindestanforderungen je Stufe.
|
Zusätzlich gelten Mindestanforderungen je Stufe.
|
||||||
|
|
||||||
|
Balancing-Grundsatz:
|
||||||
|
|
||||||
|
- frühe und mittlere Zertifikate sollen primär über Wissen und Produktionspraxis erreichbar sein
|
||||||
|
- gesellschaftliche Faktoren wirken vor allem als Beschleuniger oder als Zugang zu hohen Zertifikaten
|
||||||
|
- vorübergehende wirtschaftliche Verlustphasen blockieren den normalen Aufstieg nicht automatisch
|
||||||
|
- ein normaler Produktionsverlust ist kein Downgrade-Grund
|
||||||
|
|
||||||
## 4.5 Mindestanforderungen je Zertifikatsstufe
|
## 4.5 Mindestanforderungen je Zertifikatsstufe
|
||||||
|
|
||||||
Eine höhere Zielstufe darf nur erreicht werden, wenn neben dem `certificateScore` auch harte Mindestgrenzen erfüllt sind.
|
Eine höhere Zielstufe darf nur erreicht werden, wenn neben dem `certificateScore` auch harte Mindestgrenzen erfüllt sind.
|
||||||
|
|
||||||
### Für Zertifikat 2
|
### Für Zertifikat 2
|
||||||
|
|
||||||
- `avgKnowledge >= 25`
|
- `avgKnowledge >= 15`
|
||||||
- `completedProductions >= 5`
|
- `completedProductions >= 4`
|
||||||
|
|
||||||
### Für Zertifikat 3
|
### Für Zertifikat 3
|
||||||
|
|
||||||
- `avgKnowledge >= 40`
|
- `avgKnowledge >= 28`
|
||||||
- `completedProductions >= 20`
|
- `completedProductions >= 15`
|
||||||
|
- kein harter Statuszwang
|
||||||
|
- Statusfaktoren dürfen den Score verbessern, sind hier aber noch nicht Pflicht
|
||||||
|
|
||||||
|
### Für Zertifikat 4
|
||||||
|
|
||||||
|
- `avgKnowledge >= 45`
|
||||||
|
- `completedProductions >= 45`
|
||||||
- mindestens einer der Statusfaktoren erfüllt:
|
- mindestens einer der Statusfaktoren erfüllt:
|
||||||
- `officePoints >= 1`
|
- `officePoints >= 1`
|
||||||
- oder `nobilityPoints >= 1`
|
- oder `nobilityPoints >= 1`
|
||||||
- oder `reputationPoints >= 2`
|
- oder `reputationPoints >= 2`
|
||||||
- oder `housePoints >= 1`
|
- oder `housePoints >= 2`
|
||||||
|
|
||||||
### Für Zertifikat 4
|
|
||||||
|
|
||||||
- `avgKnowledge >= 55`
|
|
||||||
- `completedProductions >= 60`
|
|
||||||
- mindestens zwei Statusfaktoren erfüllt
|
|
||||||
|
|
||||||
### Für Zertifikat 5
|
### Für Zertifikat 5
|
||||||
|
|
||||||
- `avgKnowledge >= 70`
|
- `avgKnowledge >= 60`
|
||||||
- `completedProductions >= 150`
|
- `completedProductions >= 110`
|
||||||
- `reputationPoints >= 3`
|
- `reputationPoints >= 2`
|
||||||
- mindestens zwei der folgenden:
|
- mindestens zwei der folgenden:
|
||||||
- `officePoints >= 2`
|
- `officePoints >= 2`
|
||||||
- `nobilityPoints >= 2`
|
- `nobilityPoints >= 1`
|
||||||
- `housePoints >= 2`
|
- `housePoints >= 2`
|
||||||
|
|
||||||
## 4.6 Ableitung der Zielstufe
|
## 4.6 Ableitung der Zielstufe
|
||||||
|
|
||||||
Vorschlag:
|
Vorschlag:
|
||||||
|
|
||||||
- `targetCertificate = 1`, wenn `certificateScore < 1.2`
|
- `targetCertificate = 1`, wenn `certificateScore < 0.9`
|
||||||
- `targetCertificate = 2`, wenn `certificateScore >= 1.2`
|
- `targetCertificate = 2`, wenn `certificateScore >= 0.9`
|
||||||
- `targetCertificate = 3`, wenn `certificateScore >= 2.1`
|
- `targetCertificate = 3`, wenn `certificateScore >= 1.8`
|
||||||
- `targetCertificate = 4`, wenn `certificateScore >= 3.0`
|
- `targetCertificate = 4`, wenn `certificateScore >= 2.8`
|
||||||
- `targetCertificate = 5`, wenn `certificateScore >= 4.0`
|
- `targetCertificate = 5`, wenn `certificateScore >= 3.8`
|
||||||
|
|
||||||
Danach werden die Mindestanforderungen geprüft.
|
Danach werden die Mindestanforderungen geprüft.
|
||||||
|
|
||||||
@@ -330,6 +338,13 @@ Für jeden Spieler mit `falukant_user`:
|
|||||||
- `falukant_user.certificate` um genau `+1` erhöhen
|
- `falukant_user.certificate` um genau `+1` erhöhen
|
||||||
9. Event an UI senden
|
9. Event an UI senden
|
||||||
|
|
||||||
|
Balancing-Hinweis für den Daemon:
|
||||||
|
|
||||||
|
- Wenn ein Spieler bereits regelmäßig produziert und verkauft, soll Zertifikat `2` früh stabil erreichbar sein.
|
||||||
|
- Zertifikat `3` soll bei solider Produktionspraxis und mittlerem Wissen ebenfalls ohne hohen Adels- oder Amtsstatus erreichbar sein.
|
||||||
|
- Hohe gesellschaftliche Faktoren sollen vor allem Zertifikat `4` und `5` deutlich erleichtern, nicht Zertifikat `2` und `3` künstlich blockieren.
|
||||||
|
- Reine Verlustphasen in der Geldhistorie sind kein eigener Gegenfaktor, solange kein echter Bankrottfall vorliegt.
|
||||||
|
|
||||||
## 6. Event-Kommunikation zwischen Daemon und UI
|
## 6. Event-Kommunikation zwischen Daemon und UI
|
||||||
|
|
||||||
## 6.1 Neues Event
|
## 6.1 Neues Event
|
||||||
|
|||||||
412
docs/FALUKANT_TRANSPORT_RAIDS_SPEC.md
Normal file
412
docs/FALUKANT_TRANSPORT_RAIDS_SPEC.md
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
# Falukant: Überfälle auf Transporte und Transportwachen
|
||||||
|
|
||||||
|
Dieses Dokument beschreibt das Zielmodell für einen neuen Untergrundtyp **Überfälle auf Transporte** sowie das ergänzende Schutzsystem **Transportwachen**.
|
||||||
|
|
||||||
|
Ziel:
|
||||||
|
|
||||||
|
- Untergrundspieler können bewaffnete Banden anheuern
|
||||||
|
- Banden lauern in geeigneten Regionen und überfallen dort zufällige Transporte
|
||||||
|
- Beute wird nicht vollständig, sondern nur teilweise erlangt
|
||||||
|
- Beute landet im nächstgelegenen Lager des Auftraggebers
|
||||||
|
- Opfer und Auftraggeber spüren wirtschaftliche und soziale Folgen
|
||||||
|
- Transporte können mit Wachen geschützt werden
|
||||||
|
- Überfallserfolg hängt später im Daemon an Bandengröße, Wachzahl, Region und Zufall
|
||||||
|
|
||||||
|
## 1. Bestandsaufnahme
|
||||||
|
|
||||||
|
Bereits vorhanden:
|
||||||
|
|
||||||
|
- Untergrundaktivitäten im Backend und UI
|
||||||
|
- Transportsystem mit Fahrzeugen, Routen, Start- und Zielniederlassung
|
||||||
|
- Lager-/Bestandssystem in Niederlassungen
|
||||||
|
- Fahrzeug- und Transportverwaltung in [BranchView.vue](/mnt/share/torsten/Programs/YourPart3/frontend/src/views/falukant/BranchView.vue)
|
||||||
|
- Untergrundformular in [UndergroundView.vue](/mnt/share/torsten/Programs/YourPart3/frontend/src/views/falukant/UndergroundView.vue)
|
||||||
|
|
||||||
|
Noch nicht vorhanden:
|
||||||
|
|
||||||
|
- Untergrundtyp `raid_transport`
|
||||||
|
- Bandengröße / Bandenkosten
|
||||||
|
- Wachen auf Transporten
|
||||||
|
- Kampfauflösung zwischen Überfall und Eskorte
|
||||||
|
- Beutetransfer in Lager
|
||||||
|
- Overworld-/Socket-Kommunikation für Überfälle
|
||||||
|
|
||||||
|
## 2. Kernidee
|
||||||
|
|
||||||
|
Ein Untergrundspieler kann eine Bande für einen Transportüberfall anheuern.
|
||||||
|
|
||||||
|
Die Bande:
|
||||||
|
|
||||||
|
- wird einer Region zugewiesen
|
||||||
|
- darf nur in Regionen vom Typ `4` oder `5` operieren
|
||||||
|
- darf nicht in `town` operieren
|
||||||
|
- lauert dort auf zufällige Transporte
|
||||||
|
|
||||||
|
Bei einem Überfall:
|
||||||
|
|
||||||
|
- wird nicht der gesamte Transport geraubt
|
||||||
|
- nur ein Teil der transportierten Ware wird erbeutet
|
||||||
|
- nur ein Teil kann tatsächlich abtransportiert werden
|
||||||
|
- die Beute wird in das nächstgelegene Lager des Auftraggebers eingebucht
|
||||||
|
|
||||||
|
Der Überfall wirkt:
|
||||||
|
|
||||||
|
- auf das Opfer wirtschaftlich und reputativ
|
||||||
|
- auf den Auftraggeber als Gewinnchance, aber auch als Risiko
|
||||||
|
|
||||||
|
## 3. Neuer Untergrundtyp
|
||||||
|
|
||||||
|
Neuer Typ:
|
||||||
|
|
||||||
|
- `raid_transport`
|
||||||
|
|
||||||
|
Geplanter UI-Name:
|
||||||
|
|
||||||
|
- `Überfälle auf Transporte`
|
||||||
|
|
||||||
|
Grundparameter:
|
||||||
|
|
||||||
|
- `type`: `raid_transport`
|
||||||
|
- `regionId`: Region, in der gelauert wird
|
||||||
|
- `bandSize`: Stärke der angeheuerten Bande
|
||||||
|
- optional später:
|
||||||
|
- `focus`: eher Waren, eher Fahrzeuge, eher schwache Transporte
|
||||||
|
|
||||||
|
## 4. Regionsregeln
|
||||||
|
|
||||||
|
Die Aktivität darf nur in Regionen starten, die:
|
||||||
|
|
||||||
|
- Regionstyp `4` oder `5` haben
|
||||||
|
- nicht `town` sind
|
||||||
|
|
||||||
|
Begründung:
|
||||||
|
|
||||||
|
- Überfälle sollen auf Wegen, Randregionen oder schlecht gesicherten Zonen stattfinden
|
||||||
|
- nicht direkt im Stadtkern
|
||||||
|
|
||||||
|
UI-Regel:
|
||||||
|
|
||||||
|
- im Untergrundformular nur zulässige Regionen anbieten
|
||||||
|
|
||||||
|
Backend-Regel:
|
||||||
|
|
||||||
|
- Region validieren
|
||||||
|
- unzulässige Region serverseitig ablehnen
|
||||||
|
|
||||||
|
## 5. Bandensystem
|
||||||
|
|
||||||
|
### 5.1 Bandengröße
|
||||||
|
|
||||||
|
Der Spieler wählt eine Bandengröße, z. B.:
|
||||||
|
|
||||||
|
- `small`
|
||||||
|
- `medium`
|
||||||
|
- `large`
|
||||||
|
|
||||||
|
Alternativ numerisch:
|
||||||
|
|
||||||
|
- `3`
|
||||||
|
- `6`
|
||||||
|
- `10`
|
||||||
|
|
||||||
|
Empfehlung für Version 1:
|
||||||
|
|
||||||
|
- numerischer Wert `bandSize`
|
||||||
|
- UI zeigt zusätzlich Stufenbezeichnung
|
||||||
|
|
||||||
|
### 5.2 Kosten
|
||||||
|
|
||||||
|
Die Kosten steigen überproportional, damit große Überfälle nicht trivial werden.
|
||||||
|
|
||||||
|
Beispielmodell:
|
||||||
|
|
||||||
|
- Grundkosten: `20`
|
||||||
|
- pro Bandit: `+12`
|
||||||
|
- Risikozuschlag: `bandSize * 2`
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
|
||||||
|
- `3` Banditen: `62`
|
||||||
|
- `6` Banditen: `104`
|
||||||
|
- `10` Banditen: `160`
|
||||||
|
|
||||||
|
Die finalen Werte sind Balancing und können später angepasst werden.
|
||||||
|
|
||||||
|
## 6. Transportwachen
|
||||||
|
|
||||||
|
### 6.1 Grundidee
|
||||||
|
|
||||||
|
Für Transporte sollen Wachen mitgeschickt werden können.
|
||||||
|
|
||||||
|
Wirkung:
|
||||||
|
|
||||||
|
- geringere Überfallchance
|
||||||
|
- höhere Abwehrchance
|
||||||
|
- geringere Beute bei erfolgreichem Überfall
|
||||||
|
|
||||||
|
### 6.2 UI-Verhalten
|
||||||
|
|
||||||
|
Beim Erstellen eines Transports:
|
||||||
|
|
||||||
|
- zusätzliches Feld `wachen`
|
||||||
|
- nur positive ganze Zahl
|
||||||
|
- sichtbare Zusatzkosten
|
||||||
|
|
||||||
|
In der Transportübersicht:
|
||||||
|
|
||||||
|
- Wachenanzahl anzeigen
|
||||||
|
|
||||||
|
### 6.3 Kosten
|
||||||
|
|
||||||
|
Wachen verursachen direkte Transportmehrkosten.
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
|
||||||
|
- `guardCount * 4`
|
||||||
|
|
||||||
|
Optional später:
|
||||||
|
|
||||||
|
- bessere Wachenstufe
|
||||||
|
- bewaffnete Eskorte
|
||||||
|
|
||||||
|
## 7. Überfallauflösung im Daemon
|
||||||
|
|
||||||
|
Der externe Daemon bleibt die führende Quelle für die eigentliche Auflösung.
|
||||||
|
|
||||||
|
Der Worker prüft periodisch:
|
||||||
|
|
||||||
|
1. aktive `raid_transport`-Aktivitäten
|
||||||
|
2. Transporte, die gerade durch passende Regionen laufen
|
||||||
|
3. ob eine Kollision zwischen Aktivität und Transport zustande kommt
|
||||||
|
|
||||||
|
### 7.1 Kandidatenprüfung
|
||||||
|
|
||||||
|
Ein Transport ist überfallbar, wenn:
|
||||||
|
|
||||||
|
- er aktiv ist
|
||||||
|
- seine Route durch die Zielregion der Bande führt oder dort endet
|
||||||
|
- er dem Auftraggeber nicht selbst gehört
|
||||||
|
|
||||||
|
### 7.2 Begegnungschance
|
||||||
|
|
||||||
|
Die Basiswahrscheinlichkeit hängt u. a. ab von:
|
||||||
|
|
||||||
|
- Bandengröße
|
||||||
|
- Regionstyp
|
||||||
|
- Transportfrequenz / Zufall
|
||||||
|
- ggf. Diskretions- oder Untergrundfaktoren
|
||||||
|
|
||||||
|
### 7.3 Kampfwert
|
||||||
|
|
||||||
|
Für Version 1 reicht ein abstrahierter Vergleich:
|
||||||
|
|
||||||
|
- `raidPower = bandSize + random(0..bandSize)`
|
||||||
|
- `guardPower = guardCount + random(0..guardCount)`
|
||||||
|
|
||||||
|
Modifikatoren:
|
||||||
|
|
||||||
|
- bessere Fahrzeuge können leicht entkommen
|
||||||
|
- große Transporte sind leichter sichtbar
|
||||||
|
|
||||||
|
Ergebnis:
|
||||||
|
|
||||||
|
- `repelled`
|
||||||
|
- `partial_success`
|
||||||
|
- `major_success`
|
||||||
|
|
||||||
|
## 8. Beute
|
||||||
|
|
||||||
|
Es darf niemals der komplette Transport verloren gehen.
|
||||||
|
|
||||||
|
### 8.1 Grundregel
|
||||||
|
|
||||||
|
Bei erfolgreichem Überfall:
|
||||||
|
|
||||||
|
- nur ein Teil der transportierten Menge wird geraubt
|
||||||
|
- nur ein Teil dieser Menge erreicht als Beute den Auftraggeber
|
||||||
|
|
||||||
|
Empfohlene Formel:
|
||||||
|
|
||||||
|
- `baseLootShare = 0.15 bis 0.45`
|
||||||
|
- bei `major_success` bis `0.60`
|
||||||
|
- Wachen senken den Wert
|
||||||
|
|
||||||
|
Zusätzlich:
|
||||||
|
|
||||||
|
- Abrunden auf ganze Mengeneinheiten
|
||||||
|
- mindestens `1`, wenn überhaupt Erfolg
|
||||||
|
|
||||||
|
### 8.2 Einlagerung
|
||||||
|
|
||||||
|
Die Beute wird in das nächstgelegene Lager des Auftraggebers eingebucht.
|
||||||
|
|
||||||
|
Priorität:
|
||||||
|
|
||||||
|
1. nächstgelegene Niederlassung des Auftraggebers
|
||||||
|
2. nur wenn dort Lager für den Produkttyp vorhanden oder anlegbar
|
||||||
|
3. falls kein geeignetes Lager existiert:
|
||||||
|
- Beute teilweise verfallen lassen
|
||||||
|
- Rest als `lost_due_to_storage`
|
||||||
|
|
||||||
|
Wichtig:
|
||||||
|
|
||||||
|
- nie stillschweigend alles gutschreiben
|
||||||
|
- Lagerkapazität berücksichtigen
|
||||||
|
|
||||||
|
## 9. Folgen
|
||||||
|
|
||||||
|
### 9.1 Für das Opfer
|
||||||
|
|
||||||
|
- Warenverlust
|
||||||
|
- optional kleiner Reputationsschaden
|
||||||
|
- Hinweis in Geld-/Transporthistorie
|
||||||
|
- evtl. Routenanpassung oder Absicherungsdruck
|
||||||
|
|
||||||
|
### 9.2 Für den Auftraggeber
|
||||||
|
|
||||||
|
- Kosten der Bande
|
||||||
|
- möglicher Beutegewinn
|
||||||
|
- optional leichter Reputations- oder Verdachtsanstieg
|
||||||
|
- Risiko von Gegenmaßnahmen in späteren Ausbaustufen
|
||||||
|
|
||||||
|
## 10. Datenmodell
|
||||||
|
|
||||||
|
Für eine erste technische Umsetzung werden voraussichtlich neue Felder benötigt.
|
||||||
|
|
||||||
|
### 10.1 Underground-Aktivität
|
||||||
|
|
||||||
|
In `underground.result` bzw. Payload:
|
||||||
|
|
||||||
|
- `bandSize`
|
||||||
|
- `attempts`
|
||||||
|
- `successes`
|
||||||
|
- `lastTargetTransportId`
|
||||||
|
- `lastLoot`
|
||||||
|
- `lastOutcome`
|
||||||
|
|
||||||
|
### 10.2 Transport
|
||||||
|
|
||||||
|
Neu empfohlen:
|
||||||
|
|
||||||
|
- `guardCount`
|
||||||
|
- optional später `guardQuality`
|
||||||
|
|
||||||
|
### 10.3 Transport-/Überfall-Log
|
||||||
|
|
||||||
|
Optional, aber sinnvoll:
|
||||||
|
|
||||||
|
- eigener Logeintrag oder JSON-Protokoll mit:
|
||||||
|
- Opfer
|
||||||
|
- Auftraggeber
|
||||||
|
- Region
|
||||||
|
- Bandengröße
|
||||||
|
- Wachen
|
||||||
|
- geraubte Mengen
|
||||||
|
- eingelagerte Mengen
|
||||||
|
|
||||||
|
## 11. Socket-Events
|
||||||
|
|
||||||
|
Empfohlene Events für die UI:
|
||||||
|
|
||||||
|
### 11.1 Überfall auf Opferseite
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event": "falukantTransportRaid",
|
||||||
|
"user_id": 123,
|
||||||
|
"reason": "transport_raided"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11.2 Überfall auf Auftraggeberseite
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event": "falukantUndergroundUpdate",
|
||||||
|
"user_id": 456,
|
||||||
|
"reason": "raid_success"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Mögliche `reason`:
|
||||||
|
|
||||||
|
- `transport_raided`
|
||||||
|
- `raid_repelled`
|
||||||
|
- `raid_success`
|
||||||
|
- `raid_partial_success`
|
||||||
|
- `raid_loot_stored`
|
||||||
|
|
||||||
|
Begleitende Events:
|
||||||
|
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
- `falukantBranchUpdate`
|
||||||
|
- optional `falukantUpdateDebt` nicht nötig
|
||||||
|
|
||||||
|
## 12. UI-Anforderungen
|
||||||
|
|
||||||
|
### 12.1 Underground
|
||||||
|
|
||||||
|
In [UndergroundView.vue](/mnt/share/torsten/Programs/YourPart3/frontend/src/views/falukant/UndergroundView.vue):
|
||||||
|
|
||||||
|
- neuer Typ `raid_transport`
|
||||||
|
- Regionsauswahl mit erlaubten Regionstypen
|
||||||
|
- Wahl der Bandengröße
|
||||||
|
- Kostenanzeige
|
||||||
|
- spätere Ergebnisanzeige:
|
||||||
|
- Erfolg / Misserfolg
|
||||||
|
- Beute
|
||||||
|
- Zielregion
|
||||||
|
|
||||||
|
### 12.2 Transport
|
||||||
|
|
||||||
|
In [BranchView.vue](/mnt/share/torsten/Programs/YourPart3/frontend/src/views/falukant/BranchView.vue):
|
||||||
|
|
||||||
|
- Wachenfeld beim Transportstart
|
||||||
|
- Wachenanzahl in Transportliste
|
||||||
|
- Hinweis, dass Wachen Überfälle erschweren, aber Kosten erhöhen
|
||||||
|
|
||||||
|
## 13. Technische Reihenfolge
|
||||||
|
|
||||||
|
### TRA1. Konzept und Typerweiterung
|
||||||
|
|
||||||
|
- `raid_transport` als Underground-Typ anlegen
|
||||||
|
- Produktions-SQL für Bestandsdatenbank
|
||||||
|
|
||||||
|
### TRA2. Lokale Projektbasis
|
||||||
|
|
||||||
|
- API akzeptiert `bandSize`
|
||||||
|
- UI unterstützt Bandengröße und erlaubte Regionen
|
||||||
|
- Transporte erhalten `guardCount`
|
||||||
|
|
||||||
|
### TRA3. Daemon-Auflösung
|
||||||
|
|
||||||
|
- Worker prüft Kollisionen zwischen Aktivität und aktiven Transporten
|
||||||
|
- Überfallausgang berechnen
|
||||||
|
- Beute teilweise einlagern
|
||||||
|
- Events senden
|
||||||
|
|
||||||
|
### TRA4. UI-Feinschliff
|
||||||
|
|
||||||
|
- Ergebnisflächen
|
||||||
|
- Logs
|
||||||
|
- klarere Rückmeldungen für Opfer und Auftraggeber
|
||||||
|
|
||||||
|
## 14. Hinweis für den Daemon
|
||||||
|
|
||||||
|
Der Daemon soll später explizit berücksichtigen:
|
||||||
|
|
||||||
|
- DB-Änderungen für `guardCount` und den neuen Underground-Typ werden projektseitig vorbereitet
|
||||||
|
- Überfälle dürfen nie Totalverlust erzeugen
|
||||||
|
- Lagerkapazität begrenzt reale Beute
|
||||||
|
- Wachen reduzieren Erfolgsquote und Beutemenge
|
||||||
|
|
||||||
|
## 15. Definition of Done
|
||||||
|
|
||||||
|
Die erste vollständige Version gilt als fertig, wenn:
|
||||||
|
|
||||||
|
1. `raid_transport` im Untergrund auswählbar ist
|
||||||
|
2. Transporte mit Wachen gestartet werden können
|
||||||
|
3. der Daemon aktive Überfälle gegen echte Transporte auflösen kann
|
||||||
|
4. das Opfer nie die komplette Fracht verliert
|
||||||
|
5. Beute im nächstgelegenen Lager des Auftraggebers landet
|
||||||
|
6. Opfer- und Auftraggeber-UI per Socket aktualisiert werden
|
||||||
@@ -147,6 +147,11 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
{{ $t('falukant.branch.transport.guardCount') }}
|
||||||
|
<input v-model.number="emptyTransportForm.guardCount" type="number" min="0" max="20" @input="loadEmptyTransportRoute">
|
||||||
|
</label>
|
||||||
|
|
||||||
<div v-if="emptyTransportForm.costLabel" class="transport-cost">
|
<div v-if="emptyTransportForm.costLabel" class="transport-cost">
|
||||||
{{ $t('falukant.branch.director.emptyTransport.cost', { cost: emptyTransportForm.costLabel }) }}
|
{{ $t('falukant.branch.director.emptyTransport.cost', { cost: emptyTransportForm.costLabel }) }}
|
||||||
</div>
|
</div>
|
||||||
@@ -200,6 +205,7 @@ export default {
|
|||||||
emptyTransportForm: {
|
emptyTransportForm: {
|
||||||
vehicleTypeId: null,
|
vehicleTypeId: null,
|
||||||
targetBranchId: null,
|
targetBranchId: null,
|
||||||
|
guardCount: 0,
|
||||||
distance: null,
|
distance: null,
|
||||||
durationHours: null,
|
durationHours: null,
|
||||||
eta: null,
|
eta: null,
|
||||||
@@ -390,8 +396,8 @@ export default {
|
|||||||
this.emptyTransportForm.routeNames = (data.regions || []).map(r => r.name);
|
this.emptyTransportForm.routeNames = (data.regions || []).map(r => r.name);
|
||||||
}
|
}
|
||||||
// Kosten für leeren Transport: 0.1
|
// Kosten für leeren Transport: 0.1
|
||||||
this.emptyTransportForm.cost = 0.1;
|
this.emptyTransportForm.cost = 0.1 + ((this.emptyTransportForm.guardCount || 0) * 4);
|
||||||
this.emptyTransportForm.costLabel = this.formatMoney(0.1);
|
this.emptyTransportForm.costLabel = this.formatMoney(this.emptyTransportForm.cost);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading transport route:', error);
|
console.error('Error loading transport route:', error);
|
||||||
this.emptyTransportForm.distance = null;
|
this.emptyTransportForm.distance = null;
|
||||||
@@ -426,11 +432,13 @@ export default {
|
|||||||
productId: null,
|
productId: null,
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
targetBranchId: this.emptyTransportForm.targetBranchId,
|
targetBranchId: this.emptyTransportForm.targetBranchId,
|
||||||
|
guardCount: this.emptyTransportForm.guardCount || 0,
|
||||||
});
|
});
|
||||||
// Formular zurücksetzen
|
// Formular zurücksetzen
|
||||||
this.emptyTransportForm = {
|
this.emptyTransportForm = {
|
||||||
vehicleTypeId: null,
|
vehicleTypeId: null,
|
||||||
targetBranchId: null,
|
targetBranchId: null,
|
||||||
|
guardCount: 0,
|
||||||
distance: null,
|
distance: null,
|
||||||
durationHours: null,
|
durationHours: null,
|
||||||
eta: null,
|
eta: null,
|
||||||
|
|||||||
@@ -90,6 +90,17 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
{{ $t('falukant.branch.transport.guardCount') }}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
v-model.number="transportForm.guardCount"
|
||||||
|
min="0"
|
||||||
|
max="20"
|
||||||
|
@input="recalcCost"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div v-if="transportForm.costLabel">
|
<div v-if="transportForm.costLabel">
|
||||||
{{ $t('falukant.branch.sale.transportCost', { cost: transportForm.costLabel }) }}
|
{{ $t('falukant.branch.sale.transportCost', { cost: transportForm.costLabel }) }}
|
||||||
</div>
|
</div>
|
||||||
@@ -138,6 +149,7 @@
|
|||||||
<th>{{ $t('falukant.branch.sale.runningEta') }}</th>
|
<th>{{ $t('falukant.branch.sale.runningEta') }}</th>
|
||||||
<th>{{ $t('falukant.branch.sale.runningRemaining') }}</th>
|
<th>{{ $t('falukant.branch.sale.runningRemaining') }}</th>
|
||||||
<th>{{ $t('falukant.branch.sale.runningVehicleCount') }}</th>
|
<th>{{ $t('falukant.branch.sale.runningVehicleCount') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.sale.runningGuards') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -164,6 +176,7 @@
|
|||||||
<td>{{ formatEta({ eta: group.eta }) }}</td>
|
<td>{{ formatEta({ eta: group.eta }) }}</td>
|
||||||
<td>{{ formatRemaining({ eta: group.eta }) }}</td>
|
<td>{{ formatRemaining({ eta: group.eta }) }}</td>
|
||||||
<td>{{ group.vehicleCount }}</td>
|
<td>{{ group.vehicleCount }}</td>
|
||||||
|
<td>{{ group.totalGuards || 0 }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -189,6 +202,7 @@
|
|||||||
vehicleTypeId: null,
|
vehicleTypeId: null,
|
||||||
targetBranchId: null,
|
targetBranchId: null,
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
|
guardCount: 0,
|
||||||
maxQuantity: 0,
|
maxQuantity: 0,
|
||||||
distance: null,
|
distance: null,
|
||||||
durationHours: null,
|
durationHours: null,
|
||||||
@@ -232,12 +246,14 @@
|
|||||||
eta: transport.eta,
|
eta: transport.eta,
|
||||||
vehicleCount: 0,
|
vehicleCount: 0,
|
||||||
totalQuantity: 0,
|
totalQuantity: 0,
|
||||||
|
totalGuards: 0,
|
||||||
transports: [],
|
transports: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const group = groups.get(key);
|
const group = groups.get(key);
|
||||||
group.vehicleCount += 1;
|
group.vehicleCount += 1;
|
||||||
|
group.totalGuards += transport.guardCount || 0;
|
||||||
if (transport.product && transport.size > 0) {
|
if (transport.product && transport.size > 0) {
|
||||||
group.totalQuantity += transport.size || 0;
|
group.totalQuantity += transport.size || 0;
|
||||||
}
|
}
|
||||||
@@ -414,7 +430,8 @@
|
|||||||
}
|
}
|
||||||
const unitValue = item.product.sellCost || 0;
|
const unitValue = item.product.sellCost || 0;
|
||||||
const totalValue = unitValue * qty;
|
const totalValue = unitValue * qty;
|
||||||
const cost = Math.max(0.1, totalValue * 0.01);
|
const guardCost = (this.transportForm.guardCount || 0) * 4;
|
||||||
|
const cost = Math.max(0.1, totalValue * 0.01) + guardCost;
|
||||||
this.transportForm.cost = cost;
|
this.transportForm.cost = cost;
|
||||||
this.transportForm.costLabel = this.formatMoney(cost);
|
this.transportForm.costLabel = this.formatMoney(cost);
|
||||||
},
|
},
|
||||||
@@ -500,6 +517,7 @@
|
|||||||
productId: source.productId,
|
productId: source.productId,
|
||||||
quantity: this.transportForm.quantity,
|
quantity: this.transportForm.quantity,
|
||||||
targetBranchId: this.transportForm.targetBranchId,
|
targetBranchId: this.transportForm.targetBranchId,
|
||||||
|
guardCount: this.transportForm.guardCount || 0,
|
||||||
});
|
});
|
||||||
await this.loadInventory();
|
await this.loadInventory();
|
||||||
await this.loadTransports();
|
await this.loadTransports();
|
||||||
|
|||||||
@@ -334,6 +334,7 @@
|
|||||||
"runningEta": "Ankunft",
|
"runningEta": "Ankunft",
|
||||||
"runningRemaining": "Restzeit",
|
"runningRemaining": "Restzeit",
|
||||||
"runningVehicleCount": "Fahrzeuge",
|
"runningVehicleCount": "Fahrzeuge",
|
||||||
|
"runningGuards": "Wachen",
|
||||||
"runningDirectionOut": "Ausgehend",
|
"runningDirectionOut": "Ausgehend",
|
||||||
"runningDirectionIn": "Eingehend"
|
"runningDirectionIn": "Eingehend"
|
||||||
},
|
},
|
||||||
@@ -408,6 +409,8 @@
|
|||||||
"transport": {
|
"transport": {
|
||||||
"title": "Transportmittel",
|
"title": "Transportmittel",
|
||||||
"placeholder": "Hier kannst du Transportmittel für deine Region kaufen oder bauen.",
|
"placeholder": "Hier kannst du Transportmittel für deine Region kaufen oder bauen.",
|
||||||
|
"guardCount": "Wachen",
|
||||||
|
"guardHint": "Zusatzkosten für Wachen: {cost}",
|
||||||
"vehicleType": "Transportmittel",
|
"vehicleType": "Transportmittel",
|
||||||
"mode": "Art",
|
"mode": "Art",
|
||||||
"modeBuy": "Kaufen (sofort verfügbar)",
|
"modeBuy": "Kaufen (sofort verfügbar)",
|
||||||
@@ -1335,7 +1338,17 @@
|
|||||||
"victimPlaceholder": "Benutzername eingeben",
|
"victimPlaceholder": "Benutzername eingeben",
|
||||||
"sabotageTarget": "Sabotageziel",
|
"sabotageTarget": "Sabotageziel",
|
||||||
"corruptGoal": "Ziel der Korruption",
|
"corruptGoal": "Ziel der Korruption",
|
||||||
"affairGoal": "Ziel der Untersuchung"
|
"affairGoal": "Ziel der Untersuchung",
|
||||||
|
"raidRegion": "Überfallregion",
|
||||||
|
"raidRegionPlaceholder": "Region wählen",
|
||||||
|
"bandSize": "Bandengröße",
|
||||||
|
"raidSummary": "Bande ({bandSize}) in {region}",
|
||||||
|
"attempts": "Versuche",
|
||||||
|
"successes": "Erfolge",
|
||||||
|
"lastOutcome": "Letztes Ergebnis",
|
||||||
|
"raidResultTitle": "Letzter Überfall",
|
||||||
|
"lastTargetTransport": "Letzter Zieltransport",
|
||||||
|
"loot": "Beute"
|
||||||
},
|
},
|
||||||
"attacks": {
|
"attacks": {
|
||||||
"target": "Angreifer",
|
"target": "Angreifer",
|
||||||
@@ -1349,7 +1362,8 @@
|
|||||||
"sabotage": "Sabotage",
|
"sabotage": "Sabotage",
|
||||||
"corrupt_politician": "Korruption",
|
"corrupt_politician": "Korruption",
|
||||||
"rob": "Raub",
|
"rob": "Raub",
|
||||||
"investigate_affair": "Liebschaft untersuchen"
|
"investigate_affair": "Liebschaft untersuchen",
|
||||||
|
"raid_transport": "Überfälle auf Transporte"
|
||||||
},
|
},
|
||||||
"targets": {
|
"targets": {
|
||||||
"house": "Wohnhaus",
|
"house": "Wohnhaus",
|
||||||
@@ -1366,6 +1380,11 @@
|
|||||||
"pending": "Ausstehend",
|
"pending": "Ausstehend",
|
||||||
"resolved": "Abgeschlossen",
|
"resolved": "Abgeschlossen",
|
||||||
"failed": "Gescheitert"
|
"failed": "Gescheitert"
|
||||||
|
},
|
||||||
|
"raidOutcomes": {
|
||||||
|
"repelled": "Abgewehrt",
|
||||||
|
"partial_success": "Teilweise erfolgreich",
|
||||||
|
"major_success": "Großer Erfolg"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -294,7 +294,26 @@
|
|||||||
},
|
},
|
||||||
"director": {
|
"director": {
|
||||||
"income": "Income",
|
"income": "Income",
|
||||||
"incomeUpdated": "Salary has been successfully updated."
|
"incomeUpdated": "Salary has been successfully updated.",
|
||||||
|
"starttransport": "May start transports",
|
||||||
|
"emptyTransport": {
|
||||||
|
"title": "Transport without products",
|
||||||
|
"description": "Move vehicles from this branch to another to use them better.",
|
||||||
|
"vehicleType": "Vehicle type",
|
||||||
|
"selectVehicle": "Select vehicle type",
|
||||||
|
"targetBranch": "Target branch",
|
||||||
|
"selectTarget": "Select target branch",
|
||||||
|
"cost": "Cost: {cost}",
|
||||||
|
"duration": "Duration: {duration}",
|
||||||
|
"arrival": "Arrival: {datetime}",
|
||||||
|
"route": "Route",
|
||||||
|
"create": "Start transport",
|
||||||
|
"success": "Transport started successfully.",
|
||||||
|
"error": "Error starting the transport."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sale": {
|
||||||
|
"runningGuards": "Guards"
|
||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"title": "Production",
|
"title": "Production",
|
||||||
@@ -328,6 +347,10 @@
|
|||||||
"raft": "Raft",
|
"raft": "Raft",
|
||||||
"sailing_ship": "Sailing ship"
|
"sailing_ship": "Sailing ship"
|
||||||
},
|
},
|
||||||
|
"transport": {
|
||||||
|
"guardCount": "Guards",
|
||||||
|
"guardHint": "Additional cost for guards: {cost}"
|
||||||
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"director": "Director",
|
"director": "Director",
|
||||||
"inventory": "Inventory",
|
"inventory": "Inventory",
|
||||||
@@ -900,7 +923,17 @@
|
|||||||
"victimPlaceholder": "Enter username",
|
"victimPlaceholder": "Enter username",
|
||||||
"sabotageTarget": "Sabotage target",
|
"sabotageTarget": "Sabotage target",
|
||||||
"corruptGoal": "Corruption goal",
|
"corruptGoal": "Corruption goal",
|
||||||
"affairGoal": "Investigation goal"
|
"affairGoal": "Investigation goal",
|
||||||
|
"raidRegion": "Raid region",
|
||||||
|
"raidRegionPlaceholder": "Select region",
|
||||||
|
"bandSize": "Band size",
|
||||||
|
"raidSummary": "Gang ({bandSize}) in {region}",
|
||||||
|
"attempts": "Attempts",
|
||||||
|
"successes": "Successes",
|
||||||
|
"lastOutcome": "Last outcome",
|
||||||
|
"raidResultTitle": "Latest raid",
|
||||||
|
"lastTargetTransport": "Latest target transport",
|
||||||
|
"loot": "Loot"
|
||||||
},
|
},
|
||||||
"attacks": {
|
"attacks": {
|
||||||
"target": "Attacker",
|
"target": "Attacker",
|
||||||
@@ -914,7 +947,8 @@
|
|||||||
"sabotage": "Sabotage",
|
"sabotage": "Sabotage",
|
||||||
"corrupt_politician": "Corruption",
|
"corrupt_politician": "Corruption",
|
||||||
"rob": "Robbery",
|
"rob": "Robbery",
|
||||||
"investigate_affair": "Investigate affair"
|
"investigate_affair": "Investigate affair",
|
||||||
|
"raid_transport": "Raid transports"
|
||||||
},
|
},
|
||||||
"targets": {
|
"targets": {
|
||||||
"house": "House",
|
"house": "House",
|
||||||
@@ -931,6 +965,11 @@
|
|||||||
"pending": "Pending",
|
"pending": "Pending",
|
||||||
"resolved": "Resolved",
|
"resolved": "Resolved",
|
||||||
"failed": "Failed"
|
"failed": "Failed"
|
||||||
|
},
|
||||||
|
"raidOutcomes": {
|
||||||
|
"repelled": "Repelled",
|
||||||
|
"partial_success": "Partial success",
|
||||||
|
"major_success": "Major success"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -322,6 +322,7 @@
|
|||||||
"runningEta": "Llegada",
|
"runningEta": "Llegada",
|
||||||
"runningRemaining": "Tiempo restante",
|
"runningRemaining": "Tiempo restante",
|
||||||
"runningVehicleCount": "Vehículos",
|
"runningVehicleCount": "Vehículos",
|
||||||
|
"runningGuards": "Guardias",
|
||||||
"runningDirectionOut": "Salida",
|
"runningDirectionOut": "Salida",
|
||||||
"runningDirectionIn": "Entrada"
|
"runningDirectionIn": "Entrada"
|
||||||
},
|
},
|
||||||
@@ -393,6 +394,8 @@
|
|||||||
"transport": {
|
"transport": {
|
||||||
"title": "Transporte",
|
"title": "Transporte",
|
||||||
"placeholder": "Aquí puedes comprar o construir medios de transporte para tu región.",
|
"placeholder": "Aquí puedes comprar o construir medios de transporte para tu región.",
|
||||||
|
"guardCount": "Guardias",
|
||||||
|
"guardHint": "Coste adicional por guardias: {cost}",
|
||||||
"vehicleType": "Medio de transporte",
|
"vehicleType": "Medio de transporte",
|
||||||
"mode": "Tipo",
|
"mode": "Tipo",
|
||||||
"modeBuy": "Comprar (disponible de inmediato)",
|
"modeBuy": "Comprar (disponible de inmediato)",
|
||||||
@@ -1259,7 +1262,17 @@
|
|||||||
"victimPlaceholder": "Introduce el nombre de usuario",
|
"victimPlaceholder": "Introduce el nombre de usuario",
|
||||||
"sabotageTarget": "Objetivo del sabotaje",
|
"sabotageTarget": "Objetivo del sabotaje",
|
||||||
"corruptGoal": "Objetivo de la corrupción",
|
"corruptGoal": "Objetivo de la corrupción",
|
||||||
"affairGoal": "Objetivo de la investigación"
|
"affairGoal": "Objetivo de la investigación",
|
||||||
|
"raidRegion": "Región de emboscada",
|
||||||
|
"raidRegionPlaceholder": "Seleccionar región",
|
||||||
|
"bandSize": "Tamaño de la banda",
|
||||||
|
"raidSummary": "Banda ({bandSize}) en {region}",
|
||||||
|
"attempts": "Intentos",
|
||||||
|
"successes": "Éxitos",
|
||||||
|
"lastOutcome": "Último resultado",
|
||||||
|
"raidResultTitle": "Último asalto",
|
||||||
|
"lastTargetTransport": "Último transporte objetivo",
|
||||||
|
"loot": "Botín"
|
||||||
},
|
},
|
||||||
"attacks": {
|
"attacks": {
|
||||||
"target": "Atacante",
|
"target": "Atacante",
|
||||||
@@ -1273,7 +1286,8 @@
|
|||||||
"sabotage": "Sabotaje",
|
"sabotage": "Sabotaje",
|
||||||
"corrupt_politician": "Corrupción",
|
"corrupt_politician": "Corrupción",
|
||||||
"rob": "Robo",
|
"rob": "Robo",
|
||||||
"investigate_affair": "Investigar relación"
|
"investigate_affair": "Investigar relación",
|
||||||
|
"raid_transport": "Asaltos a transportes"
|
||||||
},
|
},
|
||||||
"targets": {
|
"targets": {
|
||||||
"house": "Vivienda",
|
"house": "Vivienda",
|
||||||
@@ -1290,6 +1304,11 @@
|
|||||||
"pending": "Pendiente",
|
"pending": "Pendiente",
|
||||||
"resolved": "Resuelto",
|
"resolved": "Resuelto",
|
||||||
"failed": "Fallido"
|
"failed": "Fallido"
|
||||||
|
},
|
||||||
|
"raidOutcomes": {
|
||||||
|
"repelled": "Rechazado",
|
||||||
|
"partial_success": "Éxito parcial",
|
||||||
|
"major_success": "Gran éxito"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,6 +258,13 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
{{ $t('falukant.branch.transport.guardCount') }}
|
||||||
|
<input v-model.number="sendVehicleDialog.guardCount" type="number" min="0" max="20" />
|
||||||
|
</label>
|
||||||
|
<p class="transport-guards-hint">
|
||||||
|
{{ $t('falukant.branch.transport.guardHint', { cost: formatMoney((sendVehicleDialog.guardCount || 0) * 4) }) }}
|
||||||
|
</p>
|
||||||
<div class="modal-buttons">
|
<div class="modal-buttons">
|
||||||
<button @click="sendVehicles" :disabled="!sendVehicleDialog.targetBranchId">
|
<button @click="sendVehicles" :disabled="!sendVehicleDialog.targetBranchId">
|
||||||
{{ $t('falukant.branch.transport.send') }}
|
{{ $t('falukant.branch.transport.send') }}
|
||||||
@@ -407,6 +414,7 @@ export default {
|
|||||||
vehicleTypeId: null,
|
vehicleTypeId: null,
|
||||||
targetBranchId: null,
|
targetBranchId: null,
|
||||||
success: false,
|
success: false,
|
||||||
|
guardCount: 0,
|
||||||
},
|
},
|
||||||
repairVehicleDialog: {
|
repairVehicleDialog: {
|
||||||
show: false,
|
show: false,
|
||||||
@@ -991,6 +999,7 @@ export default {
|
|||||||
vehicleTypeId: null,
|
vehicleTypeId: null,
|
||||||
targetBranchId: null,
|
targetBranchId: null,
|
||||||
success: false,
|
success: false,
|
||||||
|
guardCount: 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1002,6 +1011,7 @@ export default {
|
|||||||
vehicleTypeId: vehicleTypeId,
|
vehicleTypeId: vehicleTypeId,
|
||||||
targetBranchId: null,
|
targetBranchId: null,
|
||||||
success: false,
|
success: false,
|
||||||
|
guardCount: 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1013,6 +1023,7 @@ export default {
|
|||||||
vehicleTypeId: null,
|
vehicleTypeId: null,
|
||||||
targetBranchId: null,
|
targetBranchId: null,
|
||||||
success: false,
|
success: false,
|
||||||
|
guardCount: 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1038,6 +1049,7 @@ export default {
|
|||||||
targetBranchId: this.sendVehicleDialog.targetBranchId,
|
targetBranchId: this.sendVehicleDialog.targetBranchId,
|
||||||
productId: null,
|
productId: null,
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
|
guardCount: this.sendVehicleDialog.guardCount || 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.sendVehicleDialog.vehicleIds && this.sendVehicleDialog.vehicleIds.length > 0) {
|
if (this.sendVehicleDialog.vehicleIds && this.sendVehicleDialog.vehicleIds.length > 0) {
|
||||||
|
|||||||
@@ -28,7 +28,8 @@
|
|||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t('falukant.underground.activities.victim') }}
|
{{ $t('falukant.underground.activities.victim') }}
|
||||||
<input v-model="newVictimUsername" @input="onVictimInput" type="text" class="form-control"
|
<input v-model="newVictimUsername" @input="onVictimInput" type="text" class="form-control"
|
||||||
:placeholder="$t('falukant.underground.activities.victimPlaceholder')" />
|
:placeholder="$t('falukant.underground.activities.victimPlaceholder')"
|
||||||
|
:disabled="selectedType && selectedType.tr === 'raid_transport'" />
|
||||||
</label>
|
</label>
|
||||||
<div v-if="victimSuggestions.length" class="suggestions">
|
<div v-if="victimSuggestions.length" class="suggestions">
|
||||||
<ul>
|
<ul>
|
||||||
@@ -92,6 +93,27 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<label v-if="selectedType && selectedType.tr === 'raid_transport'" class="form-label">
|
||||||
|
{{ $t('falukant.underground.activities.raidRegion') }}
|
||||||
|
<select v-model.number="newRaidRegionId" class="form-control">
|
||||||
|
<option :value="null" disabled>{{ $t('falukant.underground.activities.raidRegionPlaceholder') }}</option>
|
||||||
|
<option v-for="region in raidRegions" :key="region.id" :value="region.id">
|
||||||
|
{{ region.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label v-if="selectedType && selectedType.tr === 'raid_transport'" class="form-label">
|
||||||
|
{{ $t('falukant.underground.activities.bandSize') }}
|
||||||
|
<input
|
||||||
|
v-model.number="newRaidBandSize"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="20"
|
||||||
|
class="form-control"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
<button class="btn-create-activity" :disabled="!canCreate" @click="createActivity">
|
<button class="btn-create-activity" :disabled="!canCreate" @click="createActivity">
|
||||||
{{ $t('falukant.underground.activities.create') }}
|
{{ $t('falukant.underground.activities.create') }}
|
||||||
</button>
|
</button>
|
||||||
@@ -136,6 +158,12 @@
|
|||||||
<template v-else-if="act.type === 'investigate_affair'">
|
<template v-else-if="act.type === 'investigate_affair'">
|
||||||
{{ $t(`falukant.underground.goals.${act.goal}`) }}
|
{{ $t(`falukant.underground.goals.${act.goal}`) }}
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="act.type === 'raid_transport'">
|
||||||
|
{{ $t('falukant.underground.activities.raidSummary', {
|
||||||
|
region: act.regionName || '—',
|
||||||
|
bandSize: act.additionalInfo?.bandSize || 0
|
||||||
|
}) }}
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="act.type === 'investigate_affair' && hasAffairDetails(act)">
|
<template v-if="act.type === 'investigate_affair' && hasAffairDetails(act)">
|
||||||
@@ -180,6 +208,50 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-if="act.type === 'raid_transport'">
|
||||||
|
<div
|
||||||
|
v-if="act.additionalInfo?.lastOutcome || act.additionalInfo?.lastTargetTransportId || hasLootDetails(act)"
|
||||||
|
class="activity-details__block"
|
||||||
|
>
|
||||||
|
<div class="activity-details__label">
|
||||||
|
{{ $t('falukant.underground.activities.raidResultTitle') }}
|
||||||
|
</div>
|
||||||
|
<div class="activity-details__metrics">
|
||||||
|
<span v-if="act.additionalInfo?.lastOutcome" class="activity-metric">
|
||||||
|
{{ $t('falukant.underground.activities.lastOutcome') }}:
|
||||||
|
{{ $t(`falukant.underground.raidOutcomes.${act.additionalInfo.lastOutcome}`) }}
|
||||||
|
</span>
|
||||||
|
<span v-if="act.additionalInfo?.lastTargetTransportId" class="activity-metric">
|
||||||
|
{{ $t('falukant.underground.activities.lastTargetTransport') }}:
|
||||||
|
#{{ act.additionalInfo.lastTargetTransportId }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ul v-if="getLootDetails(act).length" class="activity-details__list">
|
||||||
|
<li v-for="entry in getLootDetails(act)" :key="entry">
|
||||||
|
{{ entry }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="activity-details__block activity-details__metrics">
|
||||||
|
<span class="activity-metric">
|
||||||
|
{{ $t('falukant.underground.activities.bandSize') }}:
|
||||||
|
{{ act.additionalInfo?.bandSize || 0 }}
|
||||||
|
</span>
|
||||||
|
<span v-if="hasNumericValue(act.additionalInfo?.attempts)" class="activity-metric">
|
||||||
|
{{ $t('falukant.underground.activities.attempts') }}:
|
||||||
|
{{ act.additionalInfo.attempts }}
|
||||||
|
</span>
|
||||||
|
<span v-if="hasNumericValue(act.additionalInfo?.successes)" class="activity-metric">
|
||||||
|
{{ $t('falukant.underground.activities.successes') }}:
|
||||||
|
{{ act.additionalInfo.successes }}
|
||||||
|
</span>
|
||||||
|
<span v-if="act.additionalInfo?.lastOutcome" class="activity-metric">
|
||||||
|
{{ $t('falukant.underground.activities.lastOutcome') }}:
|
||||||
|
{{ $t(`falukant.underground.raidOutcomes.${act.additionalInfo.lastOutcome}`) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -250,7 +322,10 @@ export default {
|
|||||||
newPoliticalTargets: [],
|
newPoliticalTargets: [],
|
||||||
newSabotageTarget: 'house',
|
newSabotageTarget: 'house',
|
||||||
newCorruptGoal: 'elect',
|
newCorruptGoal: 'elect',
|
||||||
newAffairGoal: 'expose'
|
newAffairGoal: 'expose',
|
||||||
|
raidRegions: [],
|
||||||
|
newRaidRegionId: null,
|
||||||
|
newRaidBandSize: 3
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -261,6 +336,9 @@ export default {
|
|||||||
},
|
},
|
||||||
canCreate() {
|
canCreate() {
|
||||||
if (!this.newActivityTypeId) return false;
|
if (!this.newActivityTypeId) return false;
|
||||||
|
if (this.selectedType?.tr === 'raid_transport') {
|
||||||
|
return !!this.newRaidRegionId && !!this.newRaidBandSize;
|
||||||
|
}
|
||||||
const hasUser = this.newVictimUsername.trim().length > 0;
|
const hasUser = this.newVictimUsername.trim().length > 0;
|
||||||
const hasPol = this.newPoliticalTargets.length > 0;
|
const hasPol = this.newPoliticalTargets.length > 0;
|
||||||
if (!hasUser && !hasPol) return false;
|
if (!hasUser && !hasPol) return false;
|
||||||
@@ -284,6 +362,7 @@ export default {
|
|||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.loadUndergroundTypes();
|
await this.loadUndergroundTypes();
|
||||||
|
await this.loadRaidRegions();
|
||||||
if (this.undergroundTypes.length) {
|
if (this.undergroundTypes.length) {
|
||||||
this.newActivityTypeId = this.undergroundTypes[0].id;
|
this.newActivityTypeId = this.undergroundTypes[0].id;
|
||||||
}
|
}
|
||||||
@@ -343,6 +422,11 @@ export default {
|
|||||||
if (this.selectedType.tr === 'investigate_affair') {
|
if (this.selectedType.tr === 'investigate_affair') {
|
||||||
payload.goal = this.newAffairGoal;
|
payload.goal = this.newAffairGoal;
|
||||||
}
|
}
|
||||||
|
if (this.selectedType.tr === 'raid_transport') {
|
||||||
|
payload.victimUsername = null;
|
||||||
|
payload.regionId = this.newRaidRegionId;
|
||||||
|
payload.bandSize = this.newRaidBandSize;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await apiClient.post(
|
await apiClient.post(
|
||||||
'/api/falukant/underground/activities',
|
'/api/falukant/underground/activities',
|
||||||
@@ -353,6 +437,8 @@ export default {
|
|||||||
this.newSabotageTarget = 'house';
|
this.newSabotageTarget = 'house';
|
||||||
this.newCorruptGoal = 'elect';
|
this.newCorruptGoal = 'elect';
|
||||||
this.newAffairGoal = 'expose';
|
this.newAffairGoal = 'expose';
|
||||||
|
this.newRaidRegionId = null;
|
||||||
|
this.newRaidBandSize = 3;
|
||||||
await this.loadActivities();
|
await this.loadActivities();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error creating activity', err);
|
console.error('Error creating activity', err);
|
||||||
@@ -366,6 +452,11 @@ export default {
|
|||||||
this.undergroundTypes = data;
|
this.undergroundTypes = data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async loadRaidRegions() {
|
||||||
|
const { data } = await apiClient.get('/api/falukant/underground/raid-regions');
|
||||||
|
this.raidRegions = Array.isArray(data) ? data : [];
|
||||||
|
},
|
||||||
|
|
||||||
async loadActivities() {
|
async loadActivities() {
|
||||||
this.loading.activities = true;
|
this.loading.activities = true;
|
||||||
try {
|
try {
|
||||||
@@ -407,6 +498,35 @@ export default {
|
|||||||
}).format(v);
|
}).format(v);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hasLootDetails(activity) {
|
||||||
|
return this.getLootDetails(activity).length > 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
getLootDetails(activity) {
|
||||||
|
const loot = activity?.additionalInfo?.lastLoot;
|
||||||
|
if (!loot) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (Array.isArray(loot)) {
|
||||||
|
return loot
|
||||||
|
.map((entry) => {
|
||||||
|
if (!entry) return null;
|
||||||
|
if (typeof entry === 'string') return entry;
|
||||||
|
if (typeof entry === 'object') {
|
||||||
|
const name = entry.productName || entry.product || entry.label || this.$t('falukant.underground.activities.loot');
|
||||||
|
const amount = entry.amount ?? entry.quantity ?? entry.count ?? null;
|
||||||
|
return amount != null ? `${name}: ${amount}` : String(name);
|
||||||
|
}
|
||||||
|
return String(entry);
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
if (typeof loot === 'object') {
|
||||||
|
return Object.entries(loot).map(([key, value]) => `${key}: ${value}`);
|
||||||
|
}
|
||||||
|
return [String(loot)];
|
||||||
|
},
|
||||||
|
|
||||||
hasNumericValue(value) {
|
hasNumericValue(value) {
|
||||||
return typeof value === 'number' && !Number.isNaN(value);
|
return typeof value === 'number' && !Number.isNaN(value);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user