Skip to content

Commit 552bf34

Browse files
Merge pull request #1406 from solaris-games/feature/rework-debuff-weapons
Update 251.2: weapons debuff rework
2 parents 2e6fc50 + 8d9e893 commit 552bf34

File tree

12 files changed

+186
-41
lines changed

12 files changed

+186
-41
lines changed

client/src/views/game/components/settings/GameSettings.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,10 @@
327327
:valueText="getFriendlyText(game.settings.specialGalaxy.starCaptureReward)"
328328
:value="game.settings.specialGalaxy.starCaptureReward"
329329
:compareValue="compareSettings.specialGalaxy.starCaptureReward"/>
330+
<game-setting-value title="Combat resolution: weapons malus"
331+
tooltip="Determines on which carrier a specialist must be present for its weapons debuff to affect a group of carriers"
332+
:valueText="getFriendlyText(game.settings.specialGalaxy.combatResolutionMalusStrategy)"
333+
:value="game.settings.specialGalaxy.combatResolutionMalusStrategy" />
330334
</tbody>
331335
</table>
332336
</div>
@@ -705,8 +709,9 @@ export default {
705709
'exponential': 'Exponential',
706710
'winner': 'Winner',
707711
'top_n': 'Top N',
708-
'random': 'Random',
709712
'current_research': 'Current Research',
713+
'largestCarrier': "Largest carrier",
714+
'anyCarrier': "Any carrier",
710715
}[option]
711716
712717
return text || option

common/src/api/types/common/game.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export type GameTimeMaxTurnWait = 1|5|10|30|60|120|240|360|480|600|720|1080|1440
6161
export type ReadyToQuitFraction = 0.5|0.66|0.75|0.9|1.0;
6262
export type ReadyToQuitTimerCycles = 0|1|2|3;
6363
export type ReadyToQuitVisibility = 'visible'|'anonymous'|'hidden';
64+
export type CombatResolutionMalusStrategy = 'largestCarrier' | 'anyCarrier';
6465

6566
export type GameResearchProgressionStandard = {
6667
progression: 'standard',
@@ -141,6 +142,7 @@ export type GameSettingsSpecialGalaxy = {
141142
playerDistribution: GamePlayerDistribution;
142143
carrierSpeed: number;
143144
starCaptureReward: GameSettingEnabledDisabled;
145+
combatResolutionMalusStrategy: CombatResolutionMalusStrategy;
144146
specialistBans: {
145147
star: number[];
146148
carrier: number[];

server/db/models/schemas/game.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ const schema = new Schema({
9696
playerDistribution: { type: Types.String, required: true, enum: ['circular','random', 'circularSequential'], default: 'circular' },
9797
carrierSpeed: { type: Types.Number, required: true, min: 1, max: 25, default: 5 },
9898
starCaptureReward: { type: Types.String, required: true, enum: ['enabled', 'disabled'], default: 'enabled' },
99+
combatResolutionMalusStrategy: { type: Types.String, required: false, enum: ['largestCarrier', 'anyCarrier'], default: 'largestCarrier' },
99100
specialistBans: {
100101
star: [{ type: Types.Number, required: false }],
101102
carrier: [{ type: Types.Number, required: false }]

server/services/combat.ts

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1-
import { Carrier } from "./types/Carrier";
2-
import { Attacker, CombatCarrier, CombatPart, CombatResult, CombatResultShips, CombatStar, Defender } from "./types/Combat";
3-
import { Game } from "./types/Game";
4-
import { Player } from "./types/Player";
5-
import { Star, StarCaptureResult } from "./types/Star";
6-
import { User } from "./types/User";
1+
import {Carrier} from "./types/Carrier";
2+
import {
3+
Attacker,
4+
CombatCarrier,
5+
CombatPart,
6+
CombatResult,
7+
CombatResultShips,
8+
CombatStar,
9+
Defender
10+
} from "./types/Combat";
11+
import {CombatResolutionMalusStrategy, Game} from "./types/Game";
12+
import {Player} from "./types/Player";
13+
import {Star, StarCaptureResult} from "./types/Star";
14+
import {User} from "./types/User";
715
import DiplomacyService from "./diplomacy";
816
import GameTypeService from "./gameType";
917
import PlayerService from "./player";
@@ -164,7 +172,7 @@ export default class CombatService extends EventEmitter {
164172
}
165173

166174
_calculateEffectiveWeaponsLevels(game: Game, star: Star | null, defenders: Player[], attackers: Player[], defenderCarriers: Carrier[], attackerCarriers: Carrier[]) {
167-
let isCarrierToStarCombat = star != null;
175+
const isCarrierToStarCombat = star != null;
168176

169177
// Calculate the total number of defending ships
170178
let totalDefenders = defenderCarriers.reduce((sum, c) => sum + c.ships!, 0);
@@ -174,40 +182,39 @@ export default class CombatService extends EventEmitter {
174182
}
175183

176184
// Calculate the total number of attacking ships
177-
let totalAttackers = attackerCarriers.reduce((sum, c) => sum + c.ships!, 0);
185+
const totalAttackers = attackerCarriers.reduce((sum, c) => sum + c.ships!, 0);
178186

179187
// Calculate the defender weapons tech level based on any specialists present at stars or carriers.
180188
let defenderWeaponsTechLevel: number;
181189

182190
if (isCarrierToStarCombat) {
183191
defenderWeaponsTechLevel = this.technologyService.getStarEffectiveWeaponsLevel(game, defenders, star!, defenderCarriers);
184192
} else {
185-
defenderWeaponsTechLevel = this.technologyService.getCarriersEffectiveWeaponsLevel(game, defenders, defenderCarriers, isCarrierToStarCombat, false);
193+
defenderWeaponsTechLevel = this.technologyService.getCarriersEffectiveWeaponsLevel(game, defenders, defenderCarriers, isCarrierToStarCombat, false, 'anyCarrier');
186194
}
187-
195+
196+
const attackerMalusStrategy = isCarrierToStarCombat ? game.settings.specialGalaxy.combatResolutionMalusStrategy : 'anyCarrier';
188197
// Calculate the weapons tech level for the attacker
189-
let attackerWeaponsTechLevel = this.technologyService.getCarriersEffectiveWeaponsLevel(game, attackers, attackerCarriers, isCarrierToStarCombat, true);
198+
let attackerWeaponsTechLevel = this.technologyService.getCarriersEffectiveWeaponsLevel(game, attackers, attackerCarriers, isCarrierToStarCombat, true, attackerMalusStrategy);
190199

191200
// Check for deductions to weapons to either side
192-
let defenderWeaponsDeduction = this.technologyService.getCarriersWeaponsDebuff(attackerCarriers);
193-
let attackerWeaponsDeduction = this.technologyService.getCarriersWeaponsDebuff(defenderCarriers);
201+
const defenderWeaponsDeduction = this.technologyService.getCarriersWeaponsDebuff(attackerCarriers);
202+
const attackerWeaponsDeduction = this.technologyService.getCarriersWeaponsDebuff(defenderCarriers);
194203

195204
// Ensure that both sides fight with AT LEAST level 1 weapons
196205
defenderWeaponsTechLevel = Math.max(defenderWeaponsTechLevel - defenderWeaponsDeduction, 1);
197206
attackerWeaponsTechLevel = Math.max(attackerWeaponsTechLevel - attackerWeaponsDeduction, 1);
198207

199208
// Check to see if weapons tech should be swapped (joker specialist)
200-
let defenderSwapWeapons = this._shouldSwapWeaponsTech(defenderCarriers);
201-
let attackerSwapWeapons = this._shouldSwapWeaponsTech(attackerCarriers);
209+
const defenderSwapWeapons = this._shouldSwapWeaponsTech(defenderCarriers);
210+
const attackerSwapWeapons = this._shouldSwapWeaponsTech(attackerCarriers);
202211

203-
let shouldSwapWeaponsTech = (isCarrierToStarCombat && attackerSwapWeapons && !defenderSwapWeapons) || // Attacker controls controls the weapon swap in c2s combat unless both have jokers
212+
const shouldSwapWeaponsTech = (isCarrierToStarCombat && attackerSwapWeapons && !defenderSwapWeapons) || // Attacker controls controls the weapon swap in c2s combat unless both have jokers
204213
(!isCarrierToStarCombat && attackerSwapWeapons !== defenderSwapWeapons); // In c2c combat, swap weapons unless both have jokers
205214

206215
if (shouldSwapWeaponsTech) {
207216
let oldDefenderWeaponsTechLevel = defenderWeaponsTechLevel;
208-
let oldAttackerWeaponsTechLevel = attackerWeaponsTechLevel;
209-
210-
defenderWeaponsTechLevel = oldAttackerWeaponsTechLevel;
217+
defenderWeaponsTechLevel = attackerWeaponsTechLevel;
211218
attackerWeaponsTechLevel = oldDefenderWeaponsTechLevel;
212219
}
213220

server/services/repository.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default class Repository<T> {
1919
return await this.model.findById(id, select).exec();
2020
}
2121

22-
async find(query, select?: any | null, sort?: any | null, limit?: number | null, skip?: number | null): Promise<T[]> {
22+
async find(query, select?: any | null, sort?: any | null, limit?: number | null, skip?: number | null, defaults: boolean = true): Promise<T[]> {
2323
// TODO: The allowDiskUse() method was added to Mongoose in 5.12.8 (https://github.com/Automattic/mongoose/issues/10177), but
2424
// the Typescript type definition for the method was only added in 6.0.9 (https://github.com/Automattic/mongoose/pull/10791).
2525
// This horrible bodge ensures that this all works with Typescript, and can be removed once we update Mongoose to version 6.0.9 or above.
@@ -29,7 +29,7 @@ export default class Repository<T> {
2929
.sort(sort)
3030
.skip(skip!) // We lie and say skip won't be null. The reality is that skip() can accept null values just fine.
3131
.limit(limit!) // We lie and say limit won't be null. The reality is that limit() can accept null values just fine.
32-
.lean({ defaults: true }) as T1)
32+
.lean({ defaults }) as T1)
3333
.allowDiskUse(true)
3434
.exec() as T[];
3535
}

server/services/technology.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Carrier } from "./types/Carrier";
2-
import { Game } from "./types/Game";
2+
import {CombatResolutionMalusStrategy, Game} from "./types/Game";
33
import { Player, PlayerTechnologyLevels, ResearchTypeNotRandom } from "./types/Player";
44
import { Star } from "./types/Star";
55
import SpecialistService from "./specialist";
66
import GameTypeService from "./gameType";
7+
import {maxBy, maxOf} from "./utils";
78

89
const DEFAULT_TECHNOLOGIES: ResearchTypeNotRandom[] = [
910
'terraforming',
@@ -160,7 +161,7 @@ export default class TechnologyService {
160161
return 0;
161162
}
162163

163-
getCarrierWeaponsBuff(carrier: Carrier, isCarrierToStarCombat: boolean, isAttacker: boolean, allyCount: number) {
164+
getCarrierWeaponsBuff(carrier: Carrier, isCarrierToStarCombat: boolean, isAttacker: boolean, allyCount: number, strategy: CombatResolutionMalusStrategy, isLargestCarrier: boolean) {
164165
const buffs: number[] = [];
165166

166167
if (carrier.specialistId) {
@@ -191,7 +192,12 @@ export default class TechnologyService {
191192
}
192193

193194
if (specialist.modifiers.local.weapons) {
194-
buffs.push(specialist.modifiers.local.weapons);
195+
const isDebuff = specialist.modifiers.local.weapons < 0;
196+
const isMalusCarrier = (strategy === 'anyCarrier') || (strategy === 'largestCarrier' && isLargestCarrier);
197+
198+
if (!isDebuff || isMalusCarrier) {
199+
buffs.push(specialist.modifiers.local.weapons);
200+
}
195201
}
196202
}
197203
}
@@ -235,22 +241,28 @@ export default class TechnologyService {
235241
let buffs: number[] = [];
236242

237243
if (carriersInOrbit.length) {
238-
buffs = carriersInOrbit.map(c => this.getCarrierWeaponsBuff(c, true, false, defenders.length));
244+
buffs = carriersInOrbit.map(c => this.getCarrierWeaponsBuff(c, true, false, defenders.length, 'anyCarrier', false));
239245
}
240246

241247
buffs.push(this.getStarWeaponsBuff(star));
242248

243249
return this._calculateActualWeaponsBuff(weapons, buffs, defenderBonus);
244250
}
245251

246-
getCarriersEffectiveWeaponsLevel(game: Game, players: Player[], carriers: Carrier[], isCarrierToStarCombat: boolean, isAttacker: boolean) {
247-
let weapons = players.sort((a, b) => b.research.weapons.level - a.research.weapons.level)[0].research.weapons.level;
252+
getCarriersEffectiveWeaponsLevel(game: Game, players: Player[], carriers: Carrier[], isCarrierToStarCombat: boolean, isAttacker: boolean, strategy: CombatResolutionMalusStrategy) {
253+
const weapons = players.sort((a, b) => b.research.weapons.level - a.research.weapons.level)[0].research.weapons.level;
248254

249255
if (!carriers.length) {
250256
return weapons;
251257
}
252258

253-
let buffs = carriers.map(c => this.getCarrierWeaponsBuff(c, isCarrierToStarCombat, isAttacker, players.length));
259+
const largestCarrierShips = maxBy(c => c.ships || 0, carriers);
260+
261+
const buffs = carriers.map(c => {
262+
const isLargest = (c.ships || 0) === (largestCarrierShips || 0);
263+
264+
return this.getCarrierWeaponsBuff(c, isCarrierToStarCombat, isAttacker, players.length, strategy, isLargest)
265+
});
254266

255267
return this._calculateActualWeaponsBuff(weapons, buffs, 0);
256268
}

server/services/types/Game.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export type GameTimeMaxTurnWait = 1|5|10|30|60|120|240|360|480|600|720|1080|1440
6262
export type ReadyToQuitFraction = 0.5|0.66|0.75|0.9|1.0;
6363
export type ReadyToQuitTimerCycles = 0|1|2|3;
6464
export type ReadyToQuitVisibility = 'visible'|'anonymous'|'hidden';
65+
export type CombatResolutionMalusStrategy = 'largestCarrier' | 'anyCarrier';
6566

6667
export type GameResearchProgressionStandard = {
6768
progression: 'standard',
@@ -142,6 +143,7 @@ export interface GameSettings {
142143
playerDistribution: GamePlayerDistribution;
143144
carrierSpeed: number;
144145
starCaptureReward: GameSettingEnabledDisabled;
146+
combatResolutionMalusStrategy: CombatResolutionMalusStrategy;
145147
specialistBans: {
146148
star: number[];
147149
carrier: number[];

server/services/utils.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function intersectionOfSets<T>(a: Set<T>, b: Set<T>): Set<T> {
1313
return new Set(Array.from(a).filter(x => b.has(x)));
1414
}
1515

16-
export function maxBy<T>(max: (T) => number, list: T[]): number {
16+
export function maxBy<T>(max: (arg0: T) => number, list: T[]): number {
1717
let lastScore = Number.MIN_SAFE_INTEGER;
1818
for (let el of list) {
1919
const elScore = max(el);
@@ -25,6 +25,21 @@ export function maxBy<T>(max: (T) => number, list: T[]): number {
2525
return lastScore;
2626
}
2727

28+
export function maxOf<T>(max: (arg0: T) => number, list: T[]): T | undefined {
29+
let lastScore = Number.MIN_SAFE_INTEGER;
30+
let largest: T | undefined = undefined;
31+
32+
for (let el of list) {
33+
const elScore = max(el);
34+
if (elScore > lastScore) {
35+
lastScore = elScore;
36+
largest = el;
37+
}
38+
}
39+
40+
return largest;
41+
}
42+
2843
export function minBy<T>(min: (T) => number, list: T[]): number {
2944
let lastScore = Number.MAX_SAFE_INTEGER;
3045
for (let el of list) {

server/spec/combat.spec.ts

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,13 @@ describe('combat', () => {
124124
// --------------------------
125125

126126
it('should calculate carrier to star combat - carriers vs. star garrison - defender wins', async () => {
127-
const game = { };
127+
const game = {
128+
settings: {
129+
specialGalaxy: {
130+
combatResolutionMalusStrategy: 'anyCarrier'
131+
}
132+
}
133+
};
128134

129135
const star = {
130136
shipsActual: 10
@@ -161,7 +167,13 @@ describe('combat', () => {
161167
});
162168

163169
it('should calculate carrier to star combat - carriers vs. star garrison - attacker wins', async () => {
164-
const game = { };
170+
const game = {
171+
settings: {
172+
specialGalaxy: {
173+
combatResolutionMalusStrategy: 'anyCarrier'
174+
}
175+
}
176+
};
165177

166178
const star = {
167179
shipsActual: 10
@@ -198,7 +210,13 @@ describe('combat', () => {
198210
});
199211

200212
it('should calculate carrier to star combat - carriers vs. carriers - defender wins', async () => {
201-
const game = { };
213+
const game = {
214+
settings: {
215+
specialGalaxy: {
216+
combatResolutionMalusStrategy: 'anyCarrier'
217+
}
218+
}
219+
};
202220

203221
const star = {
204222
shipsActual: 0
@@ -239,7 +257,13 @@ describe('combat', () => {
239257
});
240258

241259
it('should calculate carrier to star combat - carriers vs. carriers - attacker wins', async () => {
242-
const game = { };
260+
const game = {
261+
settings: {
262+
specialGalaxy: {
263+
combatResolutionMalusStrategy: 'anyCarrier'
264+
}
265+
}
266+
};
243267

244268
const star = {
245269
shipsActual: 0
@@ -282,7 +306,13 @@ describe('combat', () => {
282306
// --------------------------
283307

284308
it('should calculate carrier to carrier combat - mutual destruction', async () => {
285-
const game = { };
309+
const game = {
310+
settings: {
311+
specialGalaxy: {
312+
combatResolutionMalusStrategy: 'anyCarrier'
313+
}
314+
}
315+
};
286316

287317
const defenders = [];
288318

@@ -319,7 +349,13 @@ describe('combat', () => {
319349
});
320350

321351
it('should calculate carrier to carrier combat - defender wins', async () => {
322-
const game = { };
352+
const game = {
353+
settings: {
354+
specialGalaxy: {
355+
combatResolutionMalusStrategy: 'anyCarrier'
356+
}
357+
}
358+
};
323359

324360
const defenders = [];
325361

@@ -356,7 +392,13 @@ describe('combat', () => {
356392
});
357393

358394
it('should calculate carrier to carrier combat - attacker wins', async () => {
359-
const game = { };
395+
const game = {
396+
settings: {
397+
specialGalaxy: {
398+
combatResolutionMalusStrategy: 'anyCarrier'
399+
}
400+
}
401+
};
360402

361403
const defenders = [];
362404

@@ -393,7 +435,13 @@ describe('combat', () => {
393435
});
394436

395437
it('should destroy carriers in carrier to carrier combat if they cannot withstand a single blow - defender wins', async () => {
396-
const game = { };
438+
const game = {
439+
settings: {
440+
specialGalaxy: {
441+
combatResolutionMalusStrategy: 'anyCarrier'
442+
}
443+
}
444+
};
397445

398446
const defenders = [];
399447

@@ -427,7 +475,13 @@ describe('combat', () => {
427475
});
428476

429477
it('should destroy carriers in carrier to carrier combat if they cannot withstand a single blow - attacker wins', async () => {
430-
const game = { };
478+
const game = {
479+
settings: {
480+
specialGalaxy: {
481+
combatResolutionMalusStrategy: 'anyCarrier'
482+
}
483+
}
484+
};
431485

432486
const defenders = [];
433487

0 commit comments

Comments
 (0)