Skip to content

Commit

Permalink
BR format and refactor (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
mwdchang authored Aug 6, 2024
1 parent 3e995d8 commit 8d57e82
Show file tree
Hide file tree
Showing 23 changed files with 695 additions and 609 deletions.
15 changes: 8 additions & 7 deletions packages/data/src/items/lesser.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"stack": "all",
"effects": [
{
"name": "UnitEffect",
"name": "UnitAttrEffect",
"attributeMap": {
"primaryAttackPower,secondaryAttackPower,counterAttackPower": {
"rule": "percentage",
Expand Down Expand Up @@ -47,7 +47,7 @@
"stack": "all",
"effects": [
{
"name": "UnitEffect",
"name": "UnitAttrEffect",
"attributeMap": {
"attackResistances.fire": {
"rule": "",
Expand Down Expand Up @@ -100,11 +100,12 @@
{
"effectType": "BattleEffect",
"target": "self",
"filter": null,
"stack": "all",
"filters": null,
"affectedStack": "all",
"effects": [
{
"name": "UnitEffect",
"effectType": "UnitAttrEffect",
"checkResistance": false,
"attributeMap": {
"abilities": {
"rule": "add",
Expand Down Expand Up @@ -137,7 +138,7 @@
"stack": "all",
"effects": [
{
"name": "UnitEffect",
"name": "UnitAttrEffect",
"attributeMap": {
"accuracy": {
"rule": "add",
Expand Down Expand Up @@ -170,7 +171,7 @@
"stack": "all",
"effects": [
{
"name": "UnitEffect",
"name": "UnitAttrEffect",
"attributeMap": {
"primaryAttackInit,secondaryAttackInit": {
"rule": "add",
Expand Down
8 changes: 8 additions & 0 deletions packages/engine/src/base/mage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ export const createMage = (name: string, magic: string): Mage => {
return mage;
}

// For ease of testing
export const createMageTest = (name: string, magic: string, override: Partial<Mage>): Mage => {
let mage = createMage(name, magic);
mage = Object.assign(mage, override);

return mage;
}


export const totalNetPower = (mage: Mage) => {
let netpower = 0;
Expand Down
2 changes: 1 addition & 1 deletion packages/engine/src/battle/calc-accuracy-modifier.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Unit } from "shared/types/unit";
import type { Unit } from "shared/types/unit";
import { hasAbility } from "../base/unit";

export const calcAccuracyModifier = (attackingUnit: Unit, defendingUnit: Unit) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/engine/src/battle/calc-battle-orders.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _ from 'lodash';
import { BattleStack } from 'shared/types/battle';
import type { BattleStack } from 'shared/types/battle';

interface BattleOrder {
side: string,
Expand Down
46 changes: 46 additions & 0 deletions packages/engine/src/battle/calc-battle-summary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { BattleStack } from "shared/types/battle";

/**
* Sum up unit NP losses
**/
export const calcBattleSummary = (attackingArmy: BattleStack[], defendingArmy: BattleStack[]) => {
let attackerStartingNP = 0;
let defenderStartingNP = 0;
attackingArmy.forEach(stack => attackerStartingNP += stack.netPower);
defendingArmy.forEach(stack => defenderStartingNP += stack.netPower);

let attackerPowerLoss = 0;
let attackerUnitLoss = 0;
attackingArmy.forEach(stack => {
const np = stack.unit.powerRank * stack.loss;
attackerPowerLoss += np;
attackerUnitLoss += stack.loss;
// console.log('\t', stack.unit.name, stack.loss, `(net power = ${np})`);
});

console.log('=== Defender summary ===');
let defenderPowerLoss = 0;
let defenderUnitLoss = 0;
defendingArmy.forEach(stack => {
const np = stack.unit.powerRank * stack.loss;
defenderPowerLoss += np;
defenderUnitLoss += stack.loss;
// console.log('\t', stack.unit.name, stack.loss, `(net power = ${np})`);
});


return {
attacker: {
netPower: attackerStartingNP,
netPowerLoss: attackerPowerLoss,
unitsLoss: attackerUnitLoss,
armyLoss: attackingArmy.map(d => ({id: d.unit.id, size: d.loss}))
},
defender: {
netPower: defenderStartingNP,
netPowerLoss: defenderPowerLoss,
unitsLoss: defenderUnitLoss,
armyLoss: defendingArmy.map(d => ({id: d.unit.id, size: d.loss}))
}
}
}
2 changes: 1 addition & 1 deletion packages/engine/src/battle/calc-damage-multiplier.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Unit } from "shared/types/unit";
import type { Unit } from "shared/types/unit";
import { hasAbility } from "../base/unit";

export const calcDamageMultiplier = (attackingUnit: Unit, defendingUnit: Unit, attackType: string[]) => {
Expand Down
25 changes: 25 additions & 0 deletions packages/engine/src/battle/calc-fort-bonus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Mage } from "shared/types/mage";
import { totalLand } from "../base/mage";

/**
* Calculate fort bonus for unit hit points
**/
export const calcFortBonus = (defenderMage: Mage, attackType: string) => {
const defenderLand = totalLand(defenderMage);
const defenderForts = defenderMage.forts;
const fortsMin = 0.0067;
const fortsMax = 0.0250;
const fortsRatio = Math.min((defenderForts / defenderLand), fortsMax);

let base = attackType === 'regular' ? 10 : 20;
if (attackType === 'regular' && fortsRatio > fortsMin) {
const additionalBonus = 27.5 * (fortsRatio - fortsMin) / (fortsMax - fortsMin);
base += additionalBonus;
}

if (attackType === 'siege' && fortsRatio > fortsMin) {
const additionalBonus = 55 * (fortsRatio - fortsMin) / (fortsMax - fortsMin);
base += additionalBonus;
}
return base;
}
108 changes: 108 additions & 0 deletions packages/engine/src/battle/calc-landloss.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import _ from 'lodash';
import { Mage } from "shared/types/mage";
import { totalLand } from "../base/mage";

// Lose 10%
const siegePercentage = 0.10;

// Lose 5%
const regularPercenage = 0.05;

const unitsNeededPerAcre = 50;
const attackerGain = 0.33;

export const calcLandLoss = (mage: Mage, attackType: string, unitsRemaining: number) => {
const landLoss = {
farms: 0,
towns: 0,
workshops: 0,
nodes: 0,
barracks: 0,
guilds: 0,
forts: 0,
barriers: 0,
wilderness: 0,
};

const landGain = {
farms: 0,
towns: 0,
workshops: 0,
nodes: 0,
barracks: 0,
guilds: 0,
forts: 0,
barriers: 0,
wilderness: 0,
}

const buildingTypes = [
'wilderness', 'farms', 'towns',
'workshops', 'nodes', 'barracks',
'guilds', 'barriers', 'forts'
];

const takePercentage = attackType === 'siege' ? siegePercentage : regularPercenage;
const mageLand = totalLand(mage);
let landTaken = Math.ceil(Math.min(takePercentage * mageLand, unitsRemaining / unitsNeededPerAcre));
// result.land = mageLand;
// result.landTaken = landTaken;

const tempMage = _.pick(mage, buildingTypes);

const calcLoss = (buildingType: string, val: number) => {
// Handle forts, say 1500 units to take a fort
if (buildingType === 'forts') {
let maxFortTaken = Math.floor(unitsRemaining / 1500);
const forts = Math.floor(Math.min(1 + 0.1 * mage.forts, maxFortTaken));
return Math.min(forts, tempMage['forts']);
}
return Math.floor(val * landTaken / mageLand);
}

// To a first round calculation
// Do forts first, then proportionally distribute over remainder building types
landLoss.forts = calcLoss('forts', null);
landTaken -= landLoss.forts;

buildingTypes.forEach(buildingType => {
if (buildingType === 'forts') return;
landLoss[buildingType] = calcLoss(buildingType, tempMage[buildingType]);
tempMage[buildingType] -= landLoss[buildingType];
landTaken -= landLoss[buildingType];
});

// Resolve any mathematic remainder
if (landTaken > 0) {
console.log('handle remainder land', landTaken);

while (landTaken > 0) {
for (const v of buildingTypes) {
if (tempMage[v] > 0) {
landLoss[v] ++;
tempMage[v] --;
landTaken --;
break;
}
}
}
}

landGain.forts = Math.max(1, Math.floor(attackerGain * landLoss.forts));
buildingTypes.forEach(buildingType => {
if (buildingType === 'forts') return;
landGain[buildingType] = Math.floor(attackerGain * landLoss[buildingType]);
});

// Dump any remainder into wilderness
let tempTotal = 0;
buildingTypes.forEach(d => {
tempTotal += landGain[d];
});
const extra = Math.floor(attackerGain * landTaken) - tempTotal;
if (extra > 0) { landGain['wilderness'] += extra; }


// Done
return { landLoss, landGain };
}
2 changes: 1 addition & 1 deletion packages/engine/src/battle/calc-resistance.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Unit } from "shared/types/unit";
import type { Unit } from "shared/types/unit";
import { hasAbility } from "../base/unit";

export const calcResistance = (u: Unit, attackTypes: string[]) => {
Expand Down
42 changes: 42 additions & 0 deletions packages/engine/src/battle/calc-stack-healing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { BattleStack } from "shared/types/battle";
import { hasHealing, hasRegeneration } from "../base/unit";

export const calcHealing = (stack: BattleStack) => {
if (stack.size <= 0) return 0;

let totalUnitsHealed = 0;

// Healing points
if (stack.healingPoints > 0 && stack.loss > 0) {
let unitsHealed = Math.floor(stack.healingPoints / stack.unit.hitPoints);
if (unitsHealed >= stack.loss) {
unitsHealed = stack.loss;
}
totalUnitsHealed += unitsHealed;
}

// Percentage based healing
if (hasHealing(stack.unit)) {
stack.healingBuffer.push(30);
}
if (hasRegeneration(stack.unit)) {
stack.healingBuffer.push(20);
}
if (stack.healingBuffer.length > 0 && stack.loss > 0) {
let heal = 1.0;
stack.healingBuffer.forEach(hValue => {
heal = heal * (100 - hValue) / 100;
});
heal = 1 - heal;

let unitsHealed = Math.floor(heal * stack.loss);
// if (unitsHealed >= stack.loss) {
// unitsHealed = stack.loss;
// }
// stack.loss -= unitsHealed;
// stack.size += unitsHealed;
totalUnitsHealed += unitsHealed;
// console.log(`healing ${stack.unit.name} = ${unitsHealed}`);
}
return totalUnitsHealed;
}
2 changes: 1 addition & 1 deletion packages/engine/src/battle/filters-includes-stack.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _ from 'lodash';
import { BattleStack } from 'shared/types/battle';
import type { BattleStack } from 'shared/types/battle';

/**
* Check if a stack matches effect filter criteria
Expand Down
67 changes: 67 additions & 0 deletions packages/engine/src/battle/new-battle-report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { BattleReport } from "shared/types/battle";
import type { Combatant } from "shared/types/mage";
import { v4 as uuidv4 } from 'uuid';

export const newBattleReport = (attacker: Combatant, defender: Combatant, attackType: string) => {
const battleReport: BattleReport = {
id: uuidv4(),
timestamp: Date.now(),
attackType: attackType,
isSuccessful: false,

attacker: {
id: attacker.mage.id,
name: attacker.mage.name,
spellId: attacker.spellId,
itemId: attacker.itemId,
army: []
},
defender: {
id: defender.mage.id,
name: defender.mage.name,
spellId: defender.spellId,
itemId: defender.itemId,
army: []
},

// Tracking spells, heros, ... etc
preBattle: {
attacker: {
spellResult: null,
itemResult: null,
},
defender: {
spellResult: null,
itemResult: null,
}
},

// Tracking engagement
battleLogs: [],

// Units lost/gained
postBattleLogs: [],

// Summary
summary: {
attacker: {
netPower: 0,
netPowerLoss: 0,
unitsLoss: 0,
armyLoss: []
},
defender: {
netPower: 0,
netPowerLoss: 0,
unitsLoss: 0,
armyLoss: []
}
},

landResult: {
landLoss: {},
landGain: {}
}
};
return battleReport;
}
Loading

0 comments on commit 8d57e82

Please sign in to comment.