Skip to content

Commit 4b5ded4

Browse files
committed
Fixes MattGibney#5 proficiency points
1 parent f8bdf5a commit 4b5ded4

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,15 +87,15 @@ 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
const goldPerTurn = await this.calculateGoldPerTurn();
7992

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

8396
const authedPlayerObject: AuthedPlayerObject = Object.assign(playerObject, {
8497
attackStrength: attackStrength,
85-
defenceStrength: defenceStrength,
98+
defenseStrength: defenseStrength,
8699
attackTurns: this.attackTurns,
87100
experience: this.experience,
88101
goldInBank: this.goldInBank,
@@ -98,6 +111,8 @@ export default class PlayerModel {
98111
quantity: unit.quantity,
99112
})),
100113
structureUpgrades: this.structureUpgrades,
114+
proficiencyPoints: this.proficiencyPoints,
115+
remainingProficiencyPoints: this.proficiencyPointsRemaining,
101116
});
102117

103118
return authedPlayerObject;
@@ -153,13 +168,48 @@ export default class PlayerModel {
153168
return fortificationUpgrades[this.structureUpgrades.fortification];
154169
}
155170

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

165215
this.save();
@@ -170,47 +220,29 @@ export default class PlayerModel {
170220
(acc, unit) => acc + unit.calculateAttackStrength(),
171221
0,
172222
);
173-
if (this.race === 'human' || this.race === 'undead') {
174-
// Humans and Undead get a 5% bonus to attack strength
175-
offense *= 1.05;
176-
}
177-
if (this.class === 'fighter') {
178-
// Fighters get a 5% bonus to attack strength
179-
offense *= 1.05;
180-
}
223+
const bonus = getAttackModifier(this);
224+
offense = applyBonuses(true, offense, bonus);
181225
return Math.floor(offense);
182226
}
183227

184-
async calculateDefenceStrength(): Promise<number> {
185-
let defence = this.units.reduce(
186-
(acc, unit) => acc + unit.calculateDefenceStrength(),
228+
async calculateDefenseStrength(): Promise<number> {
229+
let defense = this.units.reduce(
230+
(acc, unit) => acc + unit.calculateDefenseStrength(),
187231
0,
188232
);
189-
if (this.race === 'elf' || this.race === 'goblin') {
190-
// Elves and Goblins get a 5% bonus to defence strength
191-
defence *= 1.05;
192-
}
193-
if (this.class === 'cleric') {
194-
// Clerics get a 5% bonus to defence strength
195-
defence *= 1.05;
196-
}
197-
198-
const fortificationBonus = this.fortification.defenceBonusPercentage;
199-
defence *= 1 + fortificationBonus / 100;
200-
201-
return Math.floor(defence);
233+
const bonus = getDefenseModifier(this);
234+
defense = applyBonuses(true, defense, bonus);
235+
return Math.floor(defense);
202236
}
203237

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

244+
const bonus = getIncomeModifier(this);
245+
goldPerTurn = applyBonuses(true, goldPerTurn, bonus);
214246
const fortificationGoldPerTurn = this.fortification.goldPerTurn;
215247
goldPerTurn += fortificationGoldPerTurn;
216248

@@ -235,12 +267,12 @@ export default class PlayerModel {
235267
const warHistoryID = `WRH-${ulid()}`;
236268

237269
const playerAttackStrength = await this.calculateAttackStrength();
238-
const targetPlayerDefenceStrength =
239-
await targetPlayer.calculateDefenceStrength();
270+
const targetPlayerDefenseStrength =
271+
await targetPlayer.calculateDefenseStrength();
240272

241273
const isVictor = this.determineIsVictor(
242274
playerAttackStrength,
243-
targetPlayerDefenceStrength,
275+
targetPlayerDefenseStrength,
244276
);
245277

246278
// Calculate XP
@@ -261,7 +293,7 @@ export default class PlayerModel {
261293
attack_turns_used: attackTurns,
262294
is_attacker_victor: false,
263295
attacker_strength: playerAttackStrength,
264-
defender_strength: targetPlayerDefenceStrength,
296+
defender_strength: targetPlayerDefenseStrength,
265297
gold_stolen: 0,
266298
created_at: new Date(),
267299
attacker_experience: 0,
@@ -293,7 +325,7 @@ export default class PlayerModel {
293325
attack_turns_used: attackTurns,
294326
is_attacker_victor: true,
295327
attacker_strength: playerAttackStrength,
296-
defender_strength: targetPlayerDefenceStrength,
328+
defender_strength: targetPlayerDefenseStrength,
297329
gold_stolen: winnings,
298330
created_at: new Date(),
299331
attacker_experience: victorExperience,
@@ -313,6 +345,7 @@ export default class PlayerModel {
313345
experience: this.experience,
314346
overall_rank: this.overallRank,
315347
structureUpgrades: this.structureUpgrades,
348+
proficiencyPoints: this.proficiencyPoints,
316349
},
317350
);
318351

@@ -337,6 +370,13 @@ export default class PlayerModel {
337370
fortification: row.structureUpgrades?.fortification || 0,
338371
housing: row.structureUpgrades?.housing || 0,
339372
};
373+
this.proficiencyPoints = {
374+
strength: row.proficiencyPoints?.strength || 0,
375+
constitution: row.proficiencyPoints?.constitution || 0,
376+
wealth: row.proficiencyPoints?.wealth || 0,
377+
dexterity: row.proficiencyPoints?.dexterity || 0,
378+
charisma: row.proficiencyPoints?.charisma || 0,
379+
};
340380
}
341381

342382
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)