Skip to content

Commit 573722b

Browse files
committed
Fixes MattGibney#5 proficiency points
1 parent f09363c commit 573722b

File tree

20 files changed

+711
-95
lines changed

20 files changed

+711
-95
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
exports.up = function(knex) {
2+
return knex.schema.alterTable('players', function (table) {
3+
table.jsonb('proficiencyPoints').defaultTo({
4+
strength: 0,
5+
constitution: 0,
6+
wealth: 0,
7+
dexterity: 0,
8+
charisma: 0,
9+
});
10+
});
11+
};
12+
13+
exports.down = function(knex) {
14+
return knex.schema.alterTable('players', function (table) {
15+
table.dropColumn('proficiencyPoints');
16+
});
17+
}

apps/api/src/controllers/player.ts

+24
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,28 @@ export default {
128128
);
129129
res.status(200).json(await player.serialise());
130130
}),
131+
132+
POST_proficiencyPoints: protectPrivateAPI(
133+
async (req: Request, res: Response) => {
134+
const { playerID, points } = req.body;
135+
const player = await req.ctx.modelFactory.player.fetchByID(
136+
req.ctx,
137+
playerID,
138+
);
139+
if (!player) {
140+
res.status(404).json({
141+
errors: [
142+
{
143+
code: 'player_not_found',
144+
title: 'Player not found',
145+
},
146+
],
147+
});
148+
return;
149+
}
150+
151+
await player.upgradeProficiencyPoints(points);
152+
res.status(200).json(await player.serialise());
153+
},
154+
),
131155
};

apps/api/src/controllers/structures.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Request, Response } from 'express';
22
import { APIError } from '@darkthrone/client-library';
33
import { protectPrivateAPI } from '../middleware/protectAuthenticatedRoutes';
44
import { structureUpgrades } from '@darkthrone/game-data';
5+
import { applyBonuses, getCostModifier } from '../lib/bonusHelper';
56

67
export default {
78
POST_upgradeStructure: protectPrivateAPI(
@@ -49,7 +50,9 @@ export default {
4950
}
5051
}
5152

52-
if (req.ctx.authedPlayer.gold < nextStructureUpgrade.cost) {
53+
const bonus = getCostModifier(req.ctx.authedPlayer);
54+
const cost = applyBonuses(false, nextStructureUpgrade.cost, bonus);
55+
if (req.ctx.authedPlayer.gold < Math.floor(cost)) {
5356
res.status(400).send({
5457
errors: [
5558
{

apps/api/src/daos/player.ts

+7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ export type PlayerRow = {
2222
fortification: number;
2323
housing: number;
2424
};
25+
proficiencyPoints: {
26+
strength: number;
27+
constitution: number;
28+
wealth: number;
29+
dexterity: number;
30+
charisma: number;
31+
};
2532
};
2633

2734
export default class PlayerDao {

apps/api/src/lib/bonusHelper.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { classBonuses, raceBonuses } from '@darkthrone/game-data';
2+
import PlayerModel from '../models/player';
3+
4+
export function getAttackModifier(player: PlayerModel): number {
5+
let bonus = 0;
6+
bonus += raceBonuses[player.race]?.offense || 0;
7+
bonus += classBonuses[player.class]?.offense || 0;
8+
bonus += player.proficiencyPoints.strength;
9+
return bonus;
10+
}
11+
12+
export function getIncomeModifier(player: PlayerModel): number {
13+
let bonus = 0;
14+
bonus += raceBonuses[player.race]?.income || 0;
15+
bonus += classBonuses[player.class]?.income || 0;
16+
bonus += player.proficiencyPoints.wealth;
17+
return bonus;
18+
}
19+
20+
export function getCostModifier(player: PlayerModel): number {
21+
let bonus = 0;
22+
bonus += player.proficiencyPoints.charisma;
23+
return bonus;
24+
}
25+
26+
export function getDefenseModifier(player: PlayerModel): number {
27+
let bonus = 0;
28+
bonus += raceBonuses[player.race]?.defense || 0;
29+
bonus += classBonuses[player.class]?.defense || 0;
30+
bonus += player.fortification.defenseBonusPercentage;
31+
bonus += player.proficiencyPoints.constitution;
32+
return bonus;
33+
}
34+
35+
export function calculateIntBonus(
36+
isAdditive: boolean,
37+
...bonuses: number[]
38+
): number {
39+
let total = 0;
40+
bonuses.reduce((_acc, bonus) => (total += bonus), 0);
41+
isAdditive ? (total = 100 + total) : (total = 100 - total);
42+
return total;
43+
}
44+
45+
export function applyBonuses(
46+
isAdditive: boolean,
47+
stat: number,
48+
...bonus: number[]
49+
): number {
50+
const bonusInt = calculateIntBonus(isAdditive, ...bonus);
51+
return Math.floor(stat * bonusInt) / 100;
52+
}

apps/api/src/models/player.ts

+79-39
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ import {
2323
} from '@darkthrone/game-data';
2424
import { getRandomNumber } from '../utils';
2525
import { Paginator } from '../lib/paginator';
26+
import {
27+
getAttackModifier,
28+
getDefenseModifier,
29+
getIncomeModifier,
30+
getCostModifier,
31+
applyBonuses,
32+
} from '../lib/bonusHelper';
2633

2734
export default class PlayerModel {
2835
private ctx: Context;
@@ -44,14 +51,20 @@ export default class PlayerModel {
4451
fortification: number;
4552
housing: number;
4653
};
54+
public proficiencyPoints: {
55+
strength: number;
56+
constitution: number;
57+
wealth: number;
58+
dexterity: number;
59+
charisma: number;
60+
};
4761

4862
public units: PlayerUnitsModel[];
4963

5064
constructor(ctx: Context, data: PlayerRow, units: PlayerUnitsModel[]) {
5165
this.ctx = ctx;
52-
53-
this.populateFromRow(data);
5466
this.units = units;
67+
this.populateFromRow(data);
5568
}
5669

5770
async serialise(): Promise<PlayerObject | AuthedPlayerObject> {
@@ -74,14 +87,14 @@ export default class PlayerModel {
7487
if (!isAuthed) return playerObject;
7588

7689
const attackStrength = await this.calculateAttackStrength();
77-
const defenceStrength = await this.calculateDefenceStrength();
90+
const defenseStrength = await this.calculateDefenseStrength();
7891

7992
const date24HoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
8093
const depositHistory = await this.fetchBankHistory(date24HoursAgo);
8194

8295
const authedPlayerObject: AuthedPlayerObject = Object.assign(playerObject, {
8396
attackStrength: attackStrength,
84-
defenceStrength: defenceStrength,
97+
defenseStrength: defenseStrength,
8598
attackTurns: this.attackTurns,
8699
experience: this.experience,
87100
goldInBank: this.goldInBank,
@@ -96,6 +109,8 @@ export default class PlayerModel {
96109
quantity: unit.quantity,
97110
})),
98111
structureUpgrades: this.structureUpgrades,
112+
proficiencyPoints: this.proficiencyPoints,
113+
remainingProficiencyPoints: this.proficiencyPointsRemaining,
99114
});
100115

101116
return authedPlayerObject;
@@ -151,13 +166,48 @@ export default class PlayerModel {
151166
return fortificationUpgrades[this.structureUpgrades.fortification];
152167
}
153168

169+
get proficiencyPointsTotal(): number {
170+
return Object.values(this.proficiencyPoints).reduce(
171+
(acc, points) => acc + points,
172+
0,
173+
);
174+
}
175+
176+
get proficiencyPointsRemaining(): number {
177+
// The player gets 1 proficiency point per level
178+
// Since the player starts at level 1, we subtract 1 from the level
179+
return this.level - 1 - this.proficiencyPointsTotal;
180+
}
181+
182+
async upgradeProficiencyPoints(points: {
183+
strength: number;
184+
constitution: number;
185+
wealth: number;
186+
dexterity: number;
187+
charisma: number;
188+
}) {
189+
this.ctx.logger.debug({ points }, 'Upgrading proficiency points');
190+
const totalPoints = Object.values(points).reduce(
191+
(acc, point) => acc + point,
192+
0,
193+
);
194+
195+
if (totalPoints > this.proficiencyPointsRemaining) {
196+
throw new Error('Not enough proficiency points');
197+
}
198+
199+
this.proficiencyPoints = points;
200+
await this.save();
201+
}
202+
154203
async upgradeStructure(
155204
type: keyof typeof structureUpgrades,
156205
desiredUpgrade: StructureUpgrade,
157206
) {
158207
this.ctx.logger.debug({ type }, 'Upgrading structure');
159-
160-
this.gold -= desiredUpgrade.cost;
208+
const bonus = getCostModifier(this);
209+
const cost = applyBonuses(false, desiredUpgrade.cost, bonus);
210+
this.gold -= Math.floor(cost);
161211
this.structureUpgrades[type] += 1;
162212

163213
this.save();
@@ -168,47 +218,29 @@ export default class PlayerModel {
168218
(acc, unit) => acc + unit.calculateAttackStrength(),
169219
0,
170220
);
171-
if (this.race === 'human' || this.race === 'undead') {
172-
// Humans and Undead get a 5% bonus to attack strength
173-
offense *= 1.05;
174-
}
175-
if (this.class === 'fighter') {
176-
// Fighters get a 5% bonus to attack strength
177-
offense *= 1.05;
178-
}
221+
const bonus = getAttackModifier(this);
222+
offense = applyBonuses(true, offense, bonus);
179223
return Math.floor(offense);
180224
}
181225

182-
async calculateDefenceStrength(): Promise<number> {
183-
let defence = this.units.reduce(
184-
(acc, unit) => acc + unit.calculateDefenceStrength(),
226+
async calculateDefenseStrength(): Promise<number> {
227+
let defense = this.units.reduce(
228+
(acc, unit) => acc + unit.calculateDefenseStrength(),
185229
0,
186230
);
187-
if (this.race === 'elf' || this.race === 'goblin') {
188-
// Elves and Goblins get a 5% bonus to defence strength
189-
defence *= 1.05;
190-
}
191-
if (this.class === 'cleric') {
192-
// Clerics get a 5% bonus to defence strength
193-
defence *= 1.05;
194-
}
195-
196-
const fortificationBonus = this.fortification.defenceBonusPercentage;
197-
defence *= 1 + fortificationBonus / 100;
198-
199-
return Math.floor(defence);
231+
const bonus = getDefenseModifier(this);
232+
defense = applyBonuses(true, defense, bonus);
233+
return Math.floor(defense);
200234
}
201235

202236
async calculateGoldPerTurn(): Promise<number> {
203237
let goldPerTurn = this.units.reduce(
204238
(acc, unit) => acc + unit.calculateGoldPerTurn(),
205239
0,
206240
);
207-
if (this.class === 'thief') {
208-
// Thieves get a 5% bonus to gold per turn
209-
goldPerTurn *= 1.05;
210-
}
211241

242+
const bonus = getIncomeModifier(this);
243+
goldPerTurn = applyBonuses(true, goldPerTurn, bonus);
212244
const fortificationGoldPerTurn = this.fortification.goldPerTurn;
213245
goldPerTurn += fortificationGoldPerTurn;
214246

@@ -233,12 +265,12 @@ export default class PlayerModel {
233265
const warHistoryID = `WRH-${ulid()}`;
234266

235267
const playerAttackStrength = await this.calculateAttackStrength();
236-
const targetPlayerDefenceStrength =
237-
await targetPlayer.calculateDefenceStrength();
268+
const targetPlayerDefenseStrength =
269+
await targetPlayer.calculateDefenseStrength();
238270

239271
const isVictor = this.determineIsVictor(
240272
playerAttackStrength,
241-
targetPlayerDefenceStrength,
273+
targetPlayerDefenseStrength,
242274
);
243275

244276
// Calculate XP
@@ -259,7 +291,7 @@ export default class PlayerModel {
259291
attack_turns_used: attackTurns,
260292
is_attacker_victor: false,
261293
attacker_strength: playerAttackStrength,
262-
defender_strength: targetPlayerDefenceStrength,
294+
defender_strength: targetPlayerDefenseStrength,
263295
gold_stolen: 0,
264296
created_at: new Date(),
265297
attacker_experience: 0,
@@ -291,7 +323,7 @@ export default class PlayerModel {
291323
attack_turns_used: attackTurns,
292324
is_attacker_victor: true,
293325
attacker_strength: playerAttackStrength,
294-
defender_strength: targetPlayerDefenceStrength,
326+
defender_strength: targetPlayerDefenseStrength,
295327
gold_stolen: winnings,
296328
created_at: new Date(),
297329
attacker_experience: victorExperience,
@@ -311,6 +343,7 @@ export default class PlayerModel {
311343
experience: this.experience,
312344
overall_rank: this.overallRank,
313345
structureUpgrades: this.structureUpgrades,
346+
proficiencyPoints: this.proficiencyPoints,
314347
},
315348
);
316349

@@ -335,6 +368,13 @@ export default class PlayerModel {
335368
fortification: row.structureUpgrades?.fortification || 0,
336369
housing: row.structureUpgrades?.housing || 0,
337370
};
371+
this.proficiencyPoints = {
372+
strength: row.proficiencyPoints?.strength || 0,
373+
constitution: row.proficiencyPoints?.constitution || 0,
374+
wealth: row.proficiencyPoints?.wealth || 0,
375+
dexterity: row.proficiencyPoints?.dexterity || 0,
376+
charisma: row.proficiencyPoints?.charisma || 0,
377+
};
338378
}
339379

340380
static async fetchAllForUser(ctx: Context, user: UserModel) {

apps/api/src/models/playerUnits.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ export default class PlayerUnitsModel {
3232
return UnitTypes[this.unitType].attack * this.quantity;
3333
}
3434

35-
calculateDefenceStrength(): number {
36-
return UnitTypes[this.unitType].defence * this.quantity;
35+
calculateDefenseStrength(): number {
36+
return UnitTypes[this.unitType].defense * this.quantity;
3737
}
3838

3939
calculateGoldPerTurn(): number {

apps/api/src/router.ts

+3
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,7 @@ router.post('/bank/withdraw', BankingController.POST_withdraw);
6868
// Structures
6969
router.post('/structures/upgrade', StructuresController.POST_upgradeStructure);
7070

71+
// ProficiencyPoints
72+
router.post('/proficiency-points', PlayersController.POST_proficiencyPoints);
73+
7174
export default router;

0 commit comments

Comments
 (0)