Skip to content

Commit 60666fc

Browse files
committed
Fixes MattGibney#5 proficiency points
1 parent f09363c commit 60666fc

File tree

20 files changed

+715
-95
lines changed

20 files changed

+715
-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

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

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

+81-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 as 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,50 @@ 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+
console.log('this.proficiencyPoints', this.proficiencyPoints);
201+
await this.save();
202+
console.log('this.proficiencyPoints', this.proficiencyPoints);
203+
}
204+
154205
async upgradeStructure(
155206
type: keyof typeof structureUpgrades,
156207
desiredUpgrade: StructureUpgrade,
157208
) {
158209
this.ctx.logger.debug({ type }, 'Upgrading structure');
159-
160-
this.gold -= desiredUpgrade.cost;
210+
const bonus = getCostModifier(this);
211+
const cost = applyBonuses(false, desiredUpgrade.cost, bonus);
212+
this.gold -= Math.floor(cost);
161213
this.structureUpgrades[type] += 1;
162214

163215
this.save();
@@ -168,47 +220,29 @@ export default class PlayerModel {
168220
(acc, unit) => acc + unit.calculateAttackStrength(),
169221
0,
170222
);
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-
}
223+
const bonus = getAttackModifier(this);
224+
offense = applyBonuses(true, offense, bonus);
179225
return Math.floor(offense);
180226
}
181227

182-
async calculateDefenceStrength(): Promise<number> {
183-
let defence = this.units.reduce(
184-
(acc, unit) => acc + unit.calculateDefenceStrength(),
228+
async calculateDefenseStrength(): Promise<number> {
229+
let defense = this.units.reduce(
230+
(acc, unit) => acc + unit.calculateDefenseStrength(),
185231
0,
186232
);
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);
233+
const bonus = getDefenseModifier(this);
234+
defense = applyBonuses(true, defense, bonus);
235+
return Math.floor(defense);
200236
}
201237

202238
async calculateGoldPerTurn(): Promise<number> {
203239
let goldPerTurn = this.units.reduce(
204240
(acc, unit) => acc + unit.calculateGoldPerTurn(),
205241
0,
206242
);
207-
if (this.class === 'thief') {
208-
// Thieves get a 5% bonus to gold per turn
209-
goldPerTurn *= 1.05;
210-
}
211243

244+
const bonus = getIncomeModifier(this);
245+
goldPerTurn = applyBonuses(true, goldPerTurn, bonus);
212246
const fortificationGoldPerTurn = this.fortification.goldPerTurn;
213247
goldPerTurn += fortificationGoldPerTurn;
214248

@@ -233,12 +267,12 @@ export default class PlayerModel {
233267
const warHistoryID = `WRH-${ulid()}`;
234268

235269
const playerAttackStrength = await this.calculateAttackStrength();
236-
const targetPlayerDefenceStrength =
237-
await targetPlayer.calculateDefenceStrength();
270+
const targetPlayerDefenseStrength =
271+
await targetPlayer.calculateDefenseStrength();
238272

239273
const isVictor = this.determineIsVictor(
240274
playerAttackStrength,
241-
targetPlayerDefenceStrength,
275+
targetPlayerDefenseStrength,
242276
);
243277

244278
// Calculate XP
@@ -259,7 +293,7 @@ export default class PlayerModel {
259293
attack_turns_used: attackTurns,
260294
is_attacker_victor: false,
261295
attacker_strength: playerAttackStrength,
262-
defender_strength: targetPlayerDefenceStrength,
296+
defender_strength: targetPlayerDefenseStrength,
263297
gold_stolen: 0,
264298
created_at: new Date(),
265299
attacker_experience: 0,
@@ -291,7 +325,7 @@ export default class PlayerModel {
291325
attack_turns_used: attackTurns,
292326
is_attacker_victor: true,
293327
attacker_strength: playerAttackStrength,
294-
defender_strength: targetPlayerDefenceStrength,
328+
defender_strength: targetPlayerDefenseStrength,
295329
gold_stolen: winnings,
296330
created_at: new Date(),
297331
attacker_experience: victorExperience,
@@ -311,6 +345,7 @@ export default class PlayerModel {
311345
experience: this.experience,
312346
overall_rank: this.overallRank,
313347
structureUpgrades: this.structureUpgrades,
348+
proficiencyPoints: this.proficiencyPoints,
314349
},
315350
);
316351

@@ -335,6 +370,13 @@ export default class PlayerModel {
335370
fortification: row.structureUpgrades?.fortification || 0,
336371
housing: row.structureUpgrades?.housing || 0,
337372
};
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+
};
338380
}
339381

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