diff --git a/app/world-6/summoning/content.tsx b/app/world-6/summoning/content.tsx
index 4a573d86..dbc07e4d 100644
--- a/app/world-6/summoning/content.tsx
+++ b/app/world-6/summoning/content.tsx
@@ -17,6 +17,8 @@ import { SummoningUpgrades } from '../../../components/world-6/summoning/summoni
import { SummoningBattles } from '../../../components/world-6/summoning/summoningBattles';
import { useAppDataStore } from '../../../lib/providers/appDataStoreProvider';
import { useShallow } from 'zustand/react/shallow';
+import TipDisplay from '../../../components/base/TipDisplay';
+import { CircleInformation } from 'grommet-icons';
function Summoning() {
const { theData } = useAppDataStore(useShallow(
@@ -38,7 +40,7 @@ function Summoning() {
{
summoning.summonEssences.filter(essence => essence.displayEssence == true).map((essence) => (
-
+
}
/>
+
+ {essence.stoneBossVictories} Stone kills
+
+ You can find the stone to start the boss battle here :
+ {summoning.getEssenceSummoningStoneBossmap(essence.color)}
+
+ }
+ >
+
+
+
))
}
diff --git a/components/world-6/summoning/summoningUpgrades.tsx b/components/world-6/summoning/summoningUpgrades.tsx
index 2b81f0e2..8b4329e0 100644
--- a/components/world-6/summoning/summoningUpgrades.tsx
+++ b/components/world-6/summoning/summoningUpgrades.tsx
@@ -1,4 +1,5 @@
import { Box, Grid, Stack, Text } from "grommet";
+import { Star } from 'grommet-icons';
import ShadowBox from "../../base/ShadowBox";
import IconImage from "../../base/IconImage";
import TextAndLabel, { ComponentAndLabel } from "../../base/TextAndLabel";
@@ -6,6 +7,7 @@ import { nFormatter } from "../../../data/utility";
import { Summoning as SummoningDomain, SummonUpgrade, SummonEssence } from '../../../data/domain/world-6/summoning';
import TabButton from "../../base/TabButton";
import { useState } from "react";
+import TipDisplay from "../../base/TipDisplay";
const ColorSection = ({ colorUpgrades, allUpgrades, essence }: { colorUpgrades: SummonUpgrade[], allUpgrades: SummonUpgrade[], essence: SummonEssence | undefined }) => {
if (!essence || colorUpgrades.length == 0) {
@@ -43,6 +45,14 @@ const ColorSection = ({ colorUpgrades, allUpgrades, essence }: { colorUpgrades:
{upgrade.data.name}
{upgrade.getLevelDisplay()}
+ {upgrade.isDoubled &&
+
+
+ }
diff --git a/data/domain/idleonData.tsx b/data/domain/idleonData.tsx
index 36888d8d..594c9e38 100644
--- a/data/domain/idleonData.tsx
+++ b/data/domain/idleonData.tsx
@@ -48,7 +48,7 @@ import { Rift } from "./world-4/rift";
import { Equinox, updateEquinoxBar } from './world-3/equinox';
import { POExtra } from './world-2/postoffice';
import { Sneaking, updateSneaking } from './world-6/sneaking';
-import { Summoning, updateSummoningLevelAndBonusesFromIt, updateSummoningWinnerBonusBoost, updateSummoningWinnerImpact } from './world-6/summoning';
+import { Summoning, updateSummoningUpgrades, updateSummoningWinnerBonusBoost, updateSummoningWinnerImpact } from './world-6/summoning';
import { Farming, updateFarmingCropScientistBonuses, updateFarmingDisplayData, updateFarmingLevel } from './world-6/farming';
import { StarSigns, updateInfinityStarSigns, updateStarSignsUnlocked } from './starsigns';
import { IslandExpeditions } from './world-2/islandExpedition';
@@ -236,6 +236,7 @@ export const initAccountDataKeys = (allItems: Item[]) => {
// ORDER IS IMPORTANT, the keys are not relevant as data doesn't get persisted.
// This allows for multiple calls that touch the same data to happen in the same map (artifacts + sailing for example)
const postProcessingMap: Record = {
+ "updateCompanionImpact": (doc: Cloudsave, accountData: Map) => updateCompanionImpact(accountData),
"updateOrionGlobalBonus": (doc: Cloudsave, accountData: Map) => updateOrionGlobalBonus(accountData),
"updatePoppyGlobalBonus": (doc: Cloudsave, accountData: Map) => updatePoppyGlobalBonus(accountData),
"updateBubba": (doc: Cloudsave, accountData: Map) => updateBubba(accountData),
@@ -244,18 +245,16 @@ const postProcessingMap: Record = {
"updateLegendTalents": (doc: Cloudsave, accountData: Map) => updateLegendTalents(accountData),
"updatePlayerDeathnote": (doc: Cloudsave, accountData: Map) => updatePlayerDeathnote(accountData),
"updatePlayerSpecialTalents": (doc: Cloudsave, accountData: Map) => updatePlayerSpecialTalents(accountData),
- "summoningLevel": (doc: Cloudsave, accountData: Map) => updateSummoningLevelAndBonusesFromIt(accountData),
+ "emperorBonuses": (doc: Cloudsave, accountData: Map) => updateEmperorBonuses(accountData),
"summoningWinnerBonus": (doc: Cloudsave, accountData: Map) => updateSummoningWinnerBonusBoost(accountData),
"summoningWinnerImpact": (doc: Cloudsave, accountData: Map) => updateSummoningWinnerImpact(accountData),
"starSignsUnlocked": (doc: Cloudsave, accountData: Map) => updateStarSignsUnlocked(accountData),
"farmingLevel": (doc: Cloudsave, accountData: Map) => updateFarmingLevel(accountData),
- "updateCompanionImpact": (doc: Cloudsave, accountData: Map) => updateCompanionImpact(accountData),
"divinity": (doc: Cloudsave, accountData: Map) => updateDivinity(accountData),
"updatePlayerTalentLevelWithoutESBonus": (doc: Cloudsave, accountData: Map) => updatePlayerTalentLevelExceptESBonus(accountData),
"family": (doc: Cloudsave, accountData: Map) => calculateFamily(accountData),
"updatePlayerTalentLevelESBonus": (doc: Cloudsave, accountData: Map) => updatePlayerTalentLevelESBonus(accountData),
"arcaneCultistImpact": (doc: Cloudsave, accountData: Map) => updateArcaneCultistImpact(accountData),
- "emperorBonuses": (doc: Cloudsave, accountData: Map) => updateEmperorBonuses(accountData),
"updateAllShinies": (doc: Cloudsave, accountData: Map) => updateAllShinyEffects(accountData),
"updateInfinityStarSigns": (doc: Cloudsave, accountData: Map) => updateInfinityStarSigns(accountData),
"updateSuperbitImpcats": (doc: Cloudsave, accountData: Map) => updateSuperbitImpacts(accountData),
@@ -299,6 +298,7 @@ const postProcessingMap: Record = {
// I really really hate this.
const postPostProcessingMap: Record = {
+ "updateSummoningUpgrades": (doc: Cloudsave, accountData: Map) => updateSummoningUpgrades(accountData),
"updateWorshipTotemsSoulGainBonuses": (doc: Cloudsave, accountData: Map) => updateTotemsBonuses(accountData),
"updateOrionFeatherRate": (doc: Cloudsave, accountData: Map) => updateOrionFeatherRate(accountData),
"updatePoppyFishRate": (doc: Cloudsave, accountData: Map) => updatePoppyFishRate(accountData),
diff --git a/data/domain/world-2/arcade.tsx b/data/domain/world-2/arcade.tsx
index 1d32c522..d6240d4e 100644
--- a/data/domain/world-2/arcade.tsx
+++ b/data/domain/world-2/arcade.tsx
@@ -1,4 +1,4 @@
-import { lavaFunc, range } from "../../utility"
+import { lavaFunc, nFormatter, range } from "../../utility"
import { Achievement } from "../achievements";
import { Alchemy } from "./alchemy/alchemy";
import { Domain, RawData } from "../base/domain";
@@ -76,7 +76,7 @@ export class ArcadeBonus {
}
const finalBonus = baseBonus * multiplier;
- return round ? Math.round(finalBonus) : finalBonus;
+ return round ? parseFloat(nFormatter(finalBonus)) : finalBonus;
}
getBonusText = (_: boolean = true) => {
diff --git a/data/domain/world-4/tome.tsx b/data/domain/world-4/tome.tsx
index d34261b5..5d00fc5d 100644
--- a/data/domain/world-4/tome.tsx
+++ b/data/domain/world-4/tome.tsx
@@ -45,6 +45,7 @@ import { TomeEpilogueBonusModel } from '../model/tomeEpilogueBonusModel';
import { EquipmentSets } from '../misc/equipmentSets';
import { Grimoire } from '../grimoire';
import { Bubba } from '../world-3/bubba';
+import { CoralReef } from '../world-7/coralReef';
export enum TomeScoreColors {
Platinum = "#6EE3FF",
@@ -364,6 +365,7 @@ export const updateTomeScores = (data: Map) => {
const equipmentSet = data.get("equipmentSets") as EquipmentSets;
const grimoire = data.get("grimoire") as Grimoire;
const bubba = data.get("bubba") as Bubba;
+ const coralReef = data.get("coralReef") as CoralReef;
// Calculate how many trophy and obols have been found
const slab = data.get("slab") as Slab;
@@ -524,7 +526,6 @@ export const updateTomeScores = (data: Map) => {
const totalSummoningUpgradeLevels = summoning.summonUpgrades.reduce((sum, upgrade) => sum + upgrade.level, 0);
// Sum of summoning victories
- // TODO : update summoning to include the new color battles and upgrades
const summoningVictories = summoning.summonBattles.getTotalVictories();
// Number of Ninja floors unlocked
@@ -545,8 +546,7 @@ export const updateTomeScores = (data: Map) => {
}
// Total of all summoning boss victories
- // TODO once implemented
- const totalSummoningBossesVictories = 0;
+ const totalSummoningBossesVictories = summoning.summonEssences.reduce((sum, essence) => sum += essence.stoneBossVictories, 0);
// Number of Jade Emporium upgrades bought
const jadeEmporiumUpgradesBought = sneaking.jadeUpgrades.filter(upgrade => upgrade.display && upgrade.purchased).length;
@@ -983,8 +983,7 @@ export const updateTomeScores = (data: Map) => {
break;
case 97:
// Total coral reef upgrades
- // TODO : get the values once implemented
- line.updateAllPlayersCurrentValue(0);
+ line.updateAllPlayersCurrentValue(coralReef.upgrades.reduce((sum, upgrade) => sum += upgrade.level, 0));
break;
case 98:
// Deepest delve depth reach in a single run
diff --git a/data/domain/world-6/emperor.tsx b/data/domain/world-6/emperor.tsx
index 7f18069a..d5e2bce7 100644
--- a/data/domain/world-6/emperor.tsx
+++ b/data/domain/world-6/emperor.tsx
@@ -74,8 +74,6 @@ export class Emperor extends Domain {
return 135e13 * Math.pow(1.7, this.emperorKills);
}
-
-
/**
* Calculate daily emperor tries
*/
diff --git a/data/domain/world-6/farming.tsx b/data/domain/world-6/farming.tsx
index 2402dbfe..b9b9432d 100644
--- a/data/domain/world-6/farming.tsx
+++ b/data/domain/world-6/farming.tsx
@@ -834,7 +834,7 @@ export const updateFarmingDisplayData = (data: Map) => {
const skillMastery = rift.bonuses.find(bonus => bonus.name == "Skill Mastery") as SkillMastery;
// Update Min and Max possible quantity to collect from one fully grown crop
- const gemInstagrowPurchase = gemStore.purchases.find(purchase => purchase.index == 140)?.pucrhased ?? 0;
+ const gemInstagrowPurchase = gemStore.purchases.find(purchase => purchase.no == 139)?.pucrhased ?? 0;
farming.updatePossibleQuantityToCollect(farming.getMarketUpgradeBonusValue(1), gemInstagrowPurchase);
// Update growth speed for displayng when crops will be ready
diff --git a/data/domain/world-6/summoning.tsx b/data/domain/world-6/summoning.tsx
index 25ccd4e0..adaf5c79 100644
--- a/data/domain/world-6/summoning.tsx
+++ b/data/domain/world-6/summoning.tsx
@@ -17,8 +17,10 @@ import { TaskBoard } from "../tasks";
import { Achievement } from "../achievements";
import { Equinox } from "../world-3/equinox";
import { Cooking } from "../world-4/cooking";
-import { Emperor } from "../world-6/emperor";
+import { Emperor } from "./emperor";
import { EquipmentSets } from "../misc/equipmentSets";
+import { Tesseract } from "../tesseract";
+import { GemStore } from "../gemPurchases";
const WhiteBattleOrder = [
"Pet1", "Pet2", "Pet3", "Pet0", "Pet4", "Pet6", "Pet5", "Pet10", "Pet11"
@@ -35,7 +37,7 @@ const EndlessModeBonusIndexes = [
// engine.GameAttribute.h.CustomList.h.SummonEnemies[10]
const EndlessModeBonusIncrease = [
- 1,3,1,12,1,7,2,4,15,10,1,4,18,2,4,3,20,25,2,20,5,30,24,4,1,2,2,35,9,26,5,5,40,1,45,50,2,6,30,3
+ 1,3,1,12,1,7,2,4,15,3,1,4,18,2,4,3,2,2,2,20,5,4,24,4,1,2,3,5,9,26,5,5,3,1,5,8,2,6,30,3
]
// engine.GameAttribute.h.CustomList.h.SummonEnemies[11]
@@ -66,7 +68,7 @@ export enum SummonEssenceColor {
Purple = 4,
Red = 5,
Cyan = 6,
- FutureContent3 = 7,
+ Teal = 7,
FutureContent4 = 8,
Endless = 9
}
@@ -76,18 +78,23 @@ export class SummonUpgrade {
level: number = 0;
shouldBeDisplayed: boolean = true;
bonusMultiplyer: number = 1;
+ isDoubled: boolean = false;
+ doublerBonus: number = 1;
+ stoneBossBonus: number = 1;
+ totalCostReduction: number = 1;
- constructor(public index: number, public data: SummonUpgradeModel, level: number = 0) {
+ constructor(public index: number, public data: SummonUpgradeModel, level: number = 0, doubled: boolean = false) {
this.shouldBeDisplayed = (data.name != "Name");
this.level = level;
+ this.isDoubled = doubled;
}
nextLevelCost = (): number => {
- return this.data.cost * Math.pow(this.data.costExponent, this.level);
+ return this.totalCostReduction * this.data.cost * Math.pow(this.data.costExponent, this.level);
}
getBaseBonus = (level: number = this.level): number => {
- return level * this.data.bonusQty
+ return level * this.data.bonusQty * this.stoneBossBonus * this.doublerBonus;
}
getFullBonus = (level: number = this.level): number => {
@@ -123,11 +130,14 @@ export class SummonUpgrade {
}
getBonusText = (level: number = this.level): string => {
- // This bonus is special so we make a special case
if (this.index == 2) {
+ // This bonus is special so we make a special case
return this.data.bonus.slice(0, this.data.bonus.indexOf('@')) + "Cost (and level) reset by cycle of 4 days (but you keep summoned familiars)";
+ } else if ([49, 57, 72, 75].indexOf(this.index) >= 0) {
+ // Those are the cost reduction upgrades, so we need to format the bonus to get the same display as in-game
+ return this.data.bonus.replace(/@/, '\r\n').replace(/{/, nFormatter(this.getBaseBonus(level), "CommaNotation")).replace(/}/, nFormatter((1 - 1 / (1 + this.getFullBonus(level) / 100)) * 100, "MultiplierInfo"));
} else {
- return this.data.bonus.replace(/@/, '\r\n').replace(/{/, this.getBaseBonus(level).toString()).replace(/}/, this.getFullBonus(level).toString());
+ return this.data.bonus.replace(/@/, '\r\n').replace(/{/, nFormatter(this.getBaseBonus(level), "CommaNotation")).replace(/}/, nFormatter(this.getFullBonus(level), "CommaNotation"));
}
}
@@ -136,7 +146,6 @@ export class SummonUpgrade {
}
}
-
export class SummonBonus {
bonusValue: number = 0;
pristineCharmBonus: number = 1;
@@ -144,11 +153,10 @@ export class SummonBonus {
taskBoardBonus: number = 0;
achievement379Bonus: number = 0;
achievement373Bonus: number = 0;
- summoning32Bonus: number = 0;
-
- // More bonuses
+ summoning31Bonus: number = 0;
godshardSetBonus: number = 0;
emperorBonus: number = 0;
+ gemItemPurchased: number = 0;
constructor(public index: number, public data: SummonEnemyBonusModel, bonusValue: number = 0) {
this.bonusValue = bonusValue;
@@ -157,18 +165,17 @@ export class SummonBonus {
getBonus = (): number => {
switch (true)
{
- // +1 from in-game indexes as it starts from 1 instead of 0
- case this.data.bonusId == 21:
- case this.data.bonusId == 23:
- case this.data.bonusId == 25:
- case this.data.bonusId == 32:
+ case this.index == 20:
+ case this.index == 22:
+ case this.index == 24:
+ case this.index == 31:
return this.bonusValue;
- case this.data.bonusId == 20:
- return 3.5 * this.bonusValue * this.pristineCharmBonus * (1 + (this.artifactBonus + Math.min(10, this.taskBoardBonus) + this.achievement379Bonus + this.achievement373Bonus + this.godshardSetBonus) / 100);
- case 21 <= this.data.bonusId && 34 >= this.data.bonusId:
- return this.bonusValue * this.pristineCharmBonus * (1 + (this.artifactBonus + Math.min(10, this.taskBoardBonus) + this.achievement379Bonus + this.achievement373Bonus + this.summoning32Bonus + this.godshardSetBonus + this.emperorBonus) / 100);
+ case this.index == 19:
+ return 3.5 * this.bonusValue * this.pristineCharmBonus * (1 + 10 * this.gemItemPurchased / 100) * (1 + (this.artifactBonus + Math.min(10, this.taskBoardBonus) + this.achievement379Bonus + this.achievement373Bonus + this.godshardSetBonus) / 100);
+ case 20 <= this.index && 33 >= this.index:
+ return this.bonusValue * this.pristineCharmBonus * (1 + 10 * this.gemItemPurchased / 100) * (1 + (this.artifactBonus + Math.min(10, this.taskBoardBonus) + this.achievement379Bonus + this.achievement373Bonus + this.summoning31Bonus + this.godshardSetBonus + this.emperorBonus) / 100);
default:
- return 3.5 * this.bonusValue * this.pristineCharmBonus * (1 + (this.artifactBonus + Math.min(10, this.taskBoardBonus) + this.achievement379Bonus + this.achievement373Bonus + this.summoning32Bonus + this.godshardSetBonus + this.emperorBonus) / 100);
+ return 3.5 * this.bonusValue * this.pristineCharmBonus * (1 + 10 * this.gemItemPurchased / 100) * (1 + (this.artifactBonus + Math.min(10, this.taskBoardBonus) + this.achievement379Bonus + this.achievement373Bonus + this.summoning31Bonus + this.godshardSetBonus + this.emperorBonus) / 100);
}
}
@@ -192,7 +199,7 @@ export class SummonBonus {
getBonusText = (): string => {
// Can't have the two at the same time, so no worries with displaying two times the bonus
- return this.data.bonus.replace(/{/, nFormatter(this.getBonus())).replace(/, notateNumber(1 + this.getBonus() / 100));
+ return this.data.bonus.replace(/{/, nFormatter(this.getBonus())).replace(/, nFormatter(1 + this.getBonus() / 100, "MultiplierInfo"));
}
}
@@ -203,6 +210,7 @@ export interface SummonEssence {
displayEssence: boolean,
unlocked: boolean,
victories: number,
+ stoneBossVictories: number,
battles: SummonEnemyModel[]
}
@@ -328,6 +336,10 @@ export class Summoning extends Domain {
// Multiply bonus by cyan victory
upgrade.bonusMultiplyer = (this.summonEssences?.find(essence => essence.color == SummonEssenceColor.Cyan)?.victories ?? 0);
break;
+ case 73:
+ // Multiply bonus by teal victory
+ upgrade.bonusMultiplyer = (this.summonEssences?.find(essence => essence.color == SummonEssenceColor.Teal)?.victories ?? 0);
+ break;
case 62:
case 63:
case 64:
@@ -341,13 +353,15 @@ export class Summoning extends Domain {
case 65:
case 66:
case 67:
+ case 80:
// Multiply bonus by summoning level
upgrade.bonusMultiplyer = this.summoningLevel;
break;
case 60:
case 61:
+ case 75:
// Multiply bonus for every 100 total summoning upgrades purchased
- upgrade.bonusMultiplyer = Math.floor(this.summonUpgrades.reduce((sum, upgrade) => sum + upgrade.level, 0)/100);
+ upgrade.bonusMultiplyer = Math.max(0, Math.floor(this.summonUpgrades.reduce((sum, upgrade) => sum + upgrade.level, 0)/100));
break;
default:
upgrade.bonusMultiplyer = 1;
@@ -358,16 +372,16 @@ export class Summoning extends Domain {
updatePlayersUnitStats = () => {
const healthFlatBonus = [1,10,35,37];
- const healthFlat = (1 + this.summonUpgrades.filter(upgrade => healthFlatBonus.indexOf(upgrade.index) > -1)?.reduce((sum, upgrade) => sum + upgrade.getFullBonus(), 0));
- this.summonBattles.playerUnitsHP = healthFlat * (1 + (this.summonUpgrades.find(upgrade => upgrade.index == 20)?.getFullBonus() ?? 0) / 100 )
- * (1 + ((this.summonUpgrades.find(upgrade => upgrade.index == 50)?.getFullBonus() ?? 0) + (this.summonUpgrades.find(upgrade => upgrade.index == 59)?.getFullBonus() ?? 0) + (this.summonUpgrades.find(upgrade => upgrade.index == 63)?.getFullBonus() ?? 0)) / 100 )
- * (1 + (this.summonUpgrades.find(upgrade => upgrade.index == 61)?.getFullBonus() ?? 0) / 100);
+ const healthFlat = 2 + (1 + this.summonUpgrades.filter(upgrade => healthFlatBonus.indexOf(upgrade.index) > -1)?.reduce((sum, upgrade) => sum + upgrade.getFullBonus(), 0));
+ this.summonBattles.playerUnitsHP = healthFlat * (1 + (this.getUpgradeBonusFromIndex(20) + this.getUpgradeBonusFromIndex(81)) / 100)
+ * (1 + (this.getUpgradeBonusFromIndex(50) + this.getUpgradeBonusFromIndex(59) + this.getUpgradeBonusFromIndex(63)) / 100)
+ * (1 + this.getUpgradeBonusFromIndex(61) / 100);
const attackFlatBonus = [3,12,21,31];
const attackFlat = (1 + this.summonUpgrades.filter(upgrade => attackFlatBonus.indexOf(upgrade.index) > -1)?.reduce((sum, upgrade) => sum + upgrade.getFullBonus(), 0));
- this.summonBattles.playerUnitsAtk = attackFlat * (1 + (this.summonUpgrades.find(upgrade => upgrade.index == 43)?.getFullBonus() ?? 0) / 100 )
- * (1 + ((this.summonUpgrades.find(upgrade => upgrade.index == 51)?.getFullBonus() ?? 0) + (this.summonUpgrades.find(upgrade => upgrade.index == 56)?.getFullBonus() ?? 0) + (this.summonUpgrades.find(upgrade => upgrade.index == 64)?.getFullBonus() ?? 0)) / 100 )
- * (1 + (this.summonUpgrades.find(upgrade => upgrade.index == 60)?.getFullBonus() ?? 0) / 100);
+ this.summonBattles.playerUnitsAtk = attackFlat * (1 + (this.getUpgradeBonusFromIndex(43) + this.getUpgradeBonusFromIndex(74)) / 100)
+ * (1 + (this.getUpgradeBonusFromIndex(51) + this.getUpgradeBonusFromIndex(56) + this.getUpgradeBonusFromIndex(64)) / 100)
+ * (1 + this.getUpgradeBonusFromIndex(60) / 100);
// This bonus is based on attack damage of units, so can't update it before
const sharpenedSpikeUpgrade = this.summonUpgrades.find(upgrade => upgrade.index == 68);
@@ -379,7 +393,9 @@ export class Summoning extends Domain {
getRawKeys(): RawData[] {
return [
{ key: "Summon", perPlayer: false, default: [] },
- { key: "OptLacc", perPlayer: false, default: [] }
+ { key: "OptLacc", perPlayer: false, default: [] },
+ { key: "Holes", perPlayer: false, default:[] },
+ { key: "KRbest", perPlayer: false, default:[] },
]
}
@@ -391,20 +407,22 @@ export class Summoning extends Domain {
const summoning = data.get(this.dataKey) as Summoning;
const summoningData = data.get("Summon") as any[];
const optionList = data.get("OptLacc") as number[];
+ const holeData = data.get("Holes") as any[];
+ const killroyKillsData = data.get("KRbest") as Record;
// Defend against old accounts and people without any summoning data.
if (summoningData.length == 0) {
return;
}
+ const doublerData = (holeData[28] || []) as number[];
+
summoning.summonUpgrades = [];
- summoning.summonUpgrades = initSummonUpgradeRepo()
- .map(
- base => new SummonUpgrade(base.index, base.data, summoningData[0][base.index] ?? 0)
+ summoning.summonUpgrades = initSummonUpgradeRepo().map(
+ base => new SummonUpgrade(base.index, base.data, summoningData[0][base.index] ?? 0, doublerData.includes(base.index))
);
- summoning.summonBonuses = initSummonEnemyBonusRepo()
- .map(
+ summoning.summonBonuses = initSummonEnemyBonusRepo().map(
base => new SummonBonus(base.index, base.data)
);
@@ -479,6 +497,7 @@ export class Summoning extends Domain {
let unlocked: boolean = false;
let displayBattle: boolean = false;
let displayEssence: boolean = false;
+ const stoneBossVictories = killroyKillsData[`SummzTrz${index}`] ?? 0;
const essenceOwned: number = index < essences.length ? essences[index] : 0;
switch(index) {
case SummonEssenceColor.White:
@@ -516,6 +535,12 @@ export class Summoning extends Domain {
displayBattle = true;
displayEssence = true;
break;
+ case SummonEssenceColor.Teal:
+ // TODO : update this once Spelunking is correctly implemented
+ unlocked = false;
+ displayBattle = true;
+ displayEssence = true;
+ break;
case SummonEssenceColor.Endless:
// Need to update this once we know which upgrade unlock this
unlocked = (this.summonUpgrades?.find(upgrade => upgrade.index == 70)?.level ?? 0) > 0;
@@ -523,7 +548,6 @@ export class Summoning extends Domain {
// There's no real Endless essence, so never display it
displayEssence = false;
break;
- case SummonEssenceColor.FutureContent3:
case SummonEssenceColor.FutureContent4:
default:
unlocked = false;
@@ -542,7 +566,7 @@ export class Summoning extends Domain {
colorBattles = summoning.summonBattles.allBattles[index];
}
- summoning.summonEssences.push({ color: index, quantity: essenceOwned, unlocked: unlocked, displayBattles: displayBattle, displayEssence: displayEssence, victories: colorVictories, battles: colorBattles });
+ summoning.summonEssences.push({ color: index, quantity: essenceOwned, unlocked: unlocked, displayBattles: displayBattle, displayEssence: displayEssence, victories: colorVictories, battles: colorBattles, stoneBossVictories: stoneBossVictories });
}
summoning.updateUnlockedUpgrades();
@@ -553,6 +577,40 @@ export class Summoning extends Domain {
summoning.summonFamiliarRaw = summoningData[4];
}
+ getUpgradeBonusFromIndex(index: number): number {
+ return this.summonUpgrades.find(upgrade => upgrade.index == index)?.getFullBonus() || 0;
+ }
+
+ getSummoningWinnerBonusFromIndex(index: number): number {
+ return this.summonBonuses.find(bonus => bonus.index == index)?.getBonus() || 0;
+ }
+
+ getEssenceSummoningStoneBossmap(index: number): string {
+ switch (index) {
+ case SummonEssenceColor.White: return "Bamboo Laboredge (W6)";
+ case SummonEssenceColor.Green: return "Lightway Path (W6)";
+ case SummonEssenceColor.Yellow: return "Yolrock Basin (W6)";
+ case SummonEssenceColor.Blue: return "Equinox Valley (W3)";
+ case SummonEssenceColor.Purple: return "Jelly Cube Bridge (W4)";
+ case SummonEssenceColor.Red: return "Crawly Catacombs (W5)";
+ case SummonEssenceColor.Cyan: return "Emperor's Castle Doorstep (W6)";
+ default: return "We don't know yet";
+ }
+ }
+
+ getEssenceSummoningStoneBossName(index: number): string {
+ switch (index) {
+ case SummonEssenceColor.White: return "The Aetherdoot";
+ case SummonEssenceColor.Green: return "The Grover Glunko";
+ case SummonEssenceColor.Yellow: return "The Shimmerlord";
+ case SummonEssenceColor.Blue: return "The Freezerslush";
+ case SummonEssenceColor.Purple: return "The Hexermush";
+ case SummonEssenceColor.Red: return "The Cinderdomeo";
+ case SummonEssenceColor.Cyan: return "The Zephyeror";
+ default: return "No boss yet";
+ }
+ }
+
static getSummoningStoneIcon(color: SummonEssenceColor): ImageData {
if (color == SummonEssenceColor.Endless) {
return {
@@ -594,20 +652,6 @@ export class Summoning extends Domain {
}
}
-export const updateSummoningLevelAndBonusesFromIt = (data: Map) => {
- const summoning = data.get("summoning") as Summoning;
- const players = data.get("players") as Player[];
-
- const levels: number[] = [];
- players.forEach(player => {
- levels.push(player.skills.get(SkillsIndex.Summoning)?.level ?? 0);
- })
- summoning.summoningLevel = Math.max(...levels);
-
- summoning.updateSecondaryBonus();
- summoning.updatePlayersUnitStats();
-}
-
export const updateSummoningWinnerBonusBoost = (data: Map) => {
const summoning = data.get("summoning") as Summoning;
const sneaking = data.get("sneaking") as Sneaking;
@@ -616,6 +660,7 @@ export const updateSummoningWinnerBonusBoost = (data: Map) => {
const achievements = data.get("achievements") as Achievement[];
const emperor = data.get("emperor") as Emperor;
const equipmentSets = data.get("equipmentSets") as EquipmentSets;
+ const gemStore = data.get("gems") as GemStore;
const crystalComb = sneaking.pristineCharms?.find(charm => charm.data.itemId == 8);
const charmBonus = (crystalComb && crystalComb.unlocked) ? (1 + crystalComb.data.x1 / 100) : 1;
@@ -626,9 +671,10 @@ export const updateSummoningWinnerBonusBoost = (data: Map) => {
const taskBonus = taskboard.merits[44].getBonus();
const emperorBonus = emperor.emperorBonuses[8].getBonus();
const godshardSetBonus = equipmentSets.getSetBonus("GODSHARD_SET", undefined, true);
+ const gemItemPurchased = gemStore.purchases.find(purchase => purchase.no == 11)?.pucrhased || 0;
// this bonus isn't affected by any boost, so we can already calculate it here
- const summonBonus = (summoning.summonBonuses.find(bonus => bonus.data.bonusId == 32)?.getBonus() ?? 0);
+ const summonBonus = summoning.getSummoningWinnerBonusFromIndex(31);
summoning.summonBonuses.forEach(bonus => {
bonus.pristineCharmBonus = charmBonus;
@@ -636,10 +682,13 @@ export const updateSummoningWinnerBonusBoost = (data: Map) => {
bonus.taskBoardBonus = taskBonus;
bonus.achievement373Bonus = achiev373;
bonus.achievement379Bonus = achiev379;
- bonus.summoning32Bonus = summonBonus;
+ bonus.summoning31Bonus = summonBonus;
bonus.godshardSetBonus = godshardSetBonus;
bonus.emperorBonus = emperorBonus;
+ bonus.gemItemPurchased = gemItemPurchased;
});
+
+ return summoning;
}
// Kinda like what have been done for breeding Shiny with updateAllShinyEffects(), easier to manage this way
@@ -649,7 +698,7 @@ export const updateSummoningWinnerImpact = (data: Map) => {
const cooking = data.get("cooking") as Cooking;
// Equinox Max Level
- const bonusEquinoxLevel = (summoning.summonBonuses.find(bonus => bonus.data.bonusId == 25)?.getBonus() ?? 0);
+ const bonusEquinoxLevel = (summoning.summonBonuses.find(bonus => bonus.index == 24)?.getBonus() ?? 0);
// Don't bother if == 0
if (bonusEquinoxLevel > 0) {
equinox.upgrades.forEach((upgrade) => {
@@ -659,9 +708,69 @@ export const updateSummoningWinnerImpact = (data: Map) => {
}
// Meal Bonus
- const mealBonus = (summoning.summonBonuses.find(bonus => bonus.data.bonusId == 27)?.getBonus() ?? 0);
+ const mealBonus = (summoning.summonBonuses.find(bonus => bonus.index == 26)?.getBonus() ?? 0);
cooking.meals.forEach(meal => {
meal.winnerBonus = mealBonus;
});
+
+ return summoning;
+}
+
+export const updateSummoningUpgrades= (data: Map) => {
+ const summoning = data.get("summoning") as Summoning;
+ const players = data.get("players") as Player[];
+ const tesseract = data.get("tesseract") as Tesseract;
+
+ // Update upgrade bonuses
+ const levels: number[] = [];
+ players.forEach(player => {
+ levels.push(player.skills.get(SkillsIndex.Summoning)?.level ?? 0);
+ })
+ summoning.summoningLevel = Math.max(...levels);
+
+ const talentBonus597 = players.reduce((value, player) => {
+ const talentBonus = player.getTalentBonus(597);
+
+ if (talentBonus > value) {
+ return talentBonus;
+ } else {
+ return value;
+ }
+ }, 0);
+ const doublerBonus = 2 + Math.max(0, talentBonus597 / 100 - 1) + summoning.getUpgradeBonusFromIndex(78) / 100;
+
+ summoning.summonUpgrades.forEach(upgrade => {
+ upgrade.doublerBonus = upgrade.isDoubled ? doublerBonus : 1;
+ upgrade.stoneBossBonus = 1 + (parseInt(upgrade.data.filler) == 1 ? summoning.summonEssences.find(essence => essence.color == upgrade.data.colour)?.stoneBossVictories || 0 : 0);
+ });
+ summoning.updateSecondaryBonus();
+ summoning.updatePlayersUnitStats();
+
+ // Update cost reduction for upgrades
+ const talentBonus595 = players.reduce((value, player) => {
+ const talentBonus = player.getTalentBonus(595);
+
+ if (talentBonus > value) {
+ return talentBonus;
+ } else {
+ return value;
+ }
+ }, 0);
+
+ const costReductionFromTalent595 = 1 / (1 + Math.max(0, Math.floor(summoning.summonUpgrades.reduce((sum, upgrade) => sum + upgrade.level, 0)/100)) * talentBonus595 / 100);
+ const costReductionFromTesseract54 = 1 / (1 + tesseract.getUpgradeBonus(54) * (summoning.summonEssences?.find(essence => essence.color == SummonEssenceColor.Endless)?.victories ?? 0) / 100);
+ const costReductionFromUpgrade49 = 1 / (1 + summoning.getUpgradeBonusFromIndex(49) / 100);
+ const costReductionFromUpgrade57 = 1 / (1 + summoning.getUpgradeBonusFromIndex(57) / 100);
+ const costReductionFromUpgrade72 = 1 / (1 + summoning.getUpgradeBonusFromIndex(72) / 100);
+ const costReductionFromUpgrade75 = 1 / (1 + summoning.getUpgradeBonusFromIndex(75) / 100);
+
+ const totalCostReduction = costReductionFromTalent595 * costReductionFromTesseract54 * costReductionFromUpgrade49 * costReductionFromUpgrade57
+ * costReductionFromUpgrade72 * costReductionFromUpgrade75;
+
+ summoning.summonUpgrades.forEach(upgrade => {
+ upgrade.totalCostReduction = totalCostReduction;
+ });
+
+ return summoning;
}
diff --git a/docs/TESTING_IMPLEMENTATION.md b/docs/TESTING_IMPLEMENTATION.md
index 25c2e161..c7c8ace7 100644
--- a/docs/TESTING_IMPLEMENTATION.md
+++ b/docs/TESTING_IMPLEMENTATION.md
@@ -306,13 +306,19 @@ boat.getSpeedValue({withCaptain: false})
### Precision Handling
-Use appropriate precision for floating-point comparisons:
+99% of cases will use a tolerance of 0.
+
+We can use a few percentage of tolerance for very big numbers of
+unstable calculations.
+
+This will only be done on a case by case basis and very rarely.
+
```typescript
-// For percentages and most calculations
-expect(calculated).toBeCloseTo(extracted, 2); // 2 decimal places
+// For most calculations
+expect(domainValue).toMatchLiveGame(liveValue, 0);
-// For very precise calculations
-expect(calculated).toBeCloseTo(extracted, 5); // 5 decimal places
+// For very rare inconsistent calculations
+expect(domainValue).toMatchLiveGame(liveValue, 0.01);
```
## Troubleshooting
diff --git a/tests/configs/summoning-win-bonus.json b/tests/configs/summoning-win-bonus.json
new file mode 100644
index 00000000..4c05b97f
--- /dev/null
+++ b/tests/configs/summoning-win-bonus.json
@@ -0,0 +1,81 @@
+{
+ "description": "Extract data for summoning winner bonus (WinBonus) calculations",
+ "target": "Summoning WinBonus",
+ "extractions": [
+ {
+ "label": "pristine_charm_bonus_8",
+ "expression": "idleon.callFunction(\"Ninja\", \"PristineBon\", 8, 0)",
+ "description": "Pristine charm bonus for summoning (index 8 - Crystal Comb)"
+ },
+ {
+ "label": "gem_item_purchased_11",
+ "expression": "idleon.engine.getGameAttribute(\"GemItemsPurchased\")[11]",
+ "description": "Gem shop purchase 11 (Winner Bonuses)"
+ },
+ {
+ "label": "artifact_bonus_32",
+ "expression": "idleon.callFunction(\"Sailing\", \"ArtifactBonus\", 32, 0)",
+ "description": "Sailing artifact 32 bonus (summoning winner bonus)"
+ },
+ {
+ "label": "task_board_bonus_2_5_4",
+ "expression": "idleon.engine.getGameAttribute(\"Tasks\")[2][5][4]",
+ "description": "Task board merit bonus (max 10) for summoning winners"
+ },
+ {
+ "label": "achievement_379_status",
+ "expression": "idleon.callFunction(\"AchieveStatus\", 379)",
+ "description": "Achievement 379 completion status (winner bonus +1%)"
+ },
+ {
+ "label": "achievement_373_status",
+ "expression": "idleon.callFunction(\"AchieveStatus\", 373)",
+ "description": "Achievement 373 completion status (winner bonus +1%)"
+ },
+ {
+ "label": "godshard_set_bonus",
+ "expression": "idleon.callFunction(\"GetSetBonus\", \"GODSHARD_SET\", \"Bonus\", 0, 0)",
+ "description": "Godshard equipment set bonus for summoning winners"
+ },
+ {
+ "label": "emperor_bonus_8",
+ "expression": "idleon.callFunction(\"Thingies\", \"EmperorBon\", 8, 0)",
+ "description": "Emperor bonus 8 (summoning winner bonus)"
+ },
+ {
+ "label": "win_bonus_19",
+ "expression": "idleon.callFunction(\"Summoning\", \"WinBonus\", 19, 0)",
+ "description": "Winner bonus 19 (uses 3.5x multiplier, no recursive bonus)"
+ },
+ {
+ "label": "win_bonus_20",
+ "expression": "idleon.callFunction(\"Summoning\", \"WinBonus\", 20, 0)",
+ "description": "Winner bonus 20 (raw value only, no multipliers)"
+ },
+ {
+ "label": "win_bonus_22",
+ "expression": "idleon.callFunction(\"Summoning\", \"WinBonus\", 22, 0)",
+ "description": "Winner bonus 22 (raw value only, no multipliers)"
+ },
+ {
+ "label": "win_bonus_24",
+ "expression": "idleon.callFunction(\"Summoning\", \"WinBonus\", 24, 0)",
+ "description": "Winner bonus 24 (raw value only, no multipliers)"
+ },
+ {
+ "label": "win_bonus_26",
+ "expression": "idleon.callFunction(\"Summoning\", \"WinBonus\", 26, 0)",
+ "description": "Winner bonus 26 (Meal bonus - uses all multipliers + bonus 31)"
+ },
+ {
+ "label": "win_bonus_31",
+ "expression": "idleon.callFunction(\"Summoning\", \"WinBonus\", 31, 0)",
+ "description": "Winner bonus 31"
+ },
+ {
+ "label": "win_bonus_5",
+ "expression": "idleon.callFunction(\"Summoning\", \"WinBonus\", 5, 0)",
+ "description": "Winner bonus 5 (uses 3.5x multiplier and all bonuses including recursive bonus 31)"
+ }
+ ]
+}
diff --git a/tests/domains/alchemy/diamondchef.test.ts b/tests/domains/alchemy/diamondchef.test.ts
index b5349fd1..314c723e 100644
--- a/tests/domains/alchemy/diamondchef.test.ts
+++ b/tests/domains/alchemy/diamondchef.test.ts
@@ -7,7 +7,7 @@
import { loadExtractionResults, validateExtractionHealth, getExtractedValue } from '../../utils/live-game-data-loader';
import { loadGameDataFromSave } from '../../utils/cloudsave-loader';
-import { Alchemy, CauldronIndex, DiamonChefBubble } from '../../../data/domain/alchemy';
+import { Alchemy, CauldronIndex, DiamonChefBubble } from '../../../data/domain/world-2/alchemy/alchemy';
import { lavaFunc } from '../../../data/utility';
const saveName = 'latest';
diff --git a/tests/domains/cooking/meal-bonus-parameters.test.ts b/tests/domains/cooking/meal-bonus-parameters.test.ts
index a14ba7fe..cd5f8d20 100644
--- a/tests/domains/cooking/meal-bonus-parameters.test.ts
+++ b/tests/domains/cooking/meal-bonus-parameters.test.ts
@@ -15,10 +15,10 @@
import { loadExtractionResults, validateExtractionHealth, getExtractedValue } from '../../utils/live-game-data-loader';
import { loadGameDataFromSave } from '../../utils/cloudsave-loader';
-import { Lab } from '../../../data/domain/lab';
-import { Breeding } from '../../../data/domain/breeding';
+import { Lab } from '../../../data/domain/world-4/lab';
+import { Breeding } from '../../../data/domain/world-4/breeding';
import { Summoning } from '../../../data/domain/world-6/summoning';
-import { Cooking } from '../../../data/domain/cooking';
+import { Cooking } from '../../../data/domain/world-4/cooking';
const saveName = 'latest';
const extractionResultsName = 'cooking-meal-bonus-data.json';
@@ -47,7 +47,7 @@ const parameterSpecs = {
extractionKey: 'win_bonus_26',
domainExtractor: (gameData: Map) => {
const summoning = gameData.get("summoning") as Summoning;
- return summoning.summonBonuses.find(bonus => bonus.data.bonusId === 26)?.getBonus() ?? 0;
+ return summoning.summonBonuses.find(bonus => bonus.index === 26)?.getBonus() ?? 0;
}
},
diff --git a/tests/domains/cooking/meal-bonus.test.ts b/tests/domains/cooking/meal-bonus.test.ts
index 94cee2be..1124bdf9 100644
--- a/tests/domains/cooking/meal-bonus.test.ts
+++ b/tests/domains/cooking/meal-bonus.test.ts
@@ -7,7 +7,7 @@
import { loadExtractionResults, validateExtractionHealth } from '../../utils/live-game-data-loader';
import { loadGameDataFromSave } from '../../utils/cloudsave-loader';
-import { Cooking } from '../../../data/domain/cooking';
+import { Cooking } from '../../../data/domain/world-4/cooking';
const saveName = 'latest';
const extractionResultsName = 'cooking-meal-bonus-data.json';
diff --git a/tests/domains/cooking/meal-speed-parameters.test.ts b/tests/domains/cooking/meal-speed-parameters.test.ts
index c909beef..ef0fce27 100644
--- a/tests/domains/cooking/meal-speed-parameters.test.ts
+++ b/tests/domains/cooking/meal-speed-parameters.test.ts
@@ -8,19 +8,19 @@
import { loadExtractionResults, validateExtractionHealth, getExtractedValue } from '../../utils/live-game-data-loader';
import { loadGameDataFromSave } from '../../utils/cloudsave-loader';
import { ParameterTestSpec } from '../../utils/parameter-test-config';
-import { Cooking } from '../../../data/domain/cooking';
+import { Cooking } from '../../../data/domain/world-4/cooking';
import { StarSigns } from '../../../data/domain/starsigns';
import { CropScientistBonusText, Farming } from '../../../data/domain/world-6/farming';
import { Player } from '../../../data/domain/player';
import { Votes } from '../../../data/domain/world-2/votes';
-import { Alchemy } from '../../../data/domain/alchemy';
+import { Alchemy } from '../../../data/domain/world-2/alchemy/alchemy';
import { UpgradeVault } from '../../../data/domain/upgradeVault';
-import { AtomCollider } from '../../../data/domain/atomCollider';
-import { TotalizerBonus, Worship } from '../../../data/domain/worship';
-import { Sailing } from '../../../data/domain/sailing';
-import { Arcade } from '../../../data/domain/arcade';
+import { AtomCollider } from '../../../data/domain/world-3/construction/atomCollider';
+import { TotalizerBonus, Worship } from '../../../data/domain/world-3/worship';
+import { Sailing } from '../../../data/domain/world-5/sailing/sailing';
+import { Arcade } from '../../../data/domain/world-2/arcade';
import { Stamp } from '../../../data/domain/world-1/stamps';
-import { Lab } from '../../../data/domain/lab';
+import { Lab } from '../../../data/domain/world-4/lab';
import { Summoning } from '../../../data/domain/world-6/summoning';
import { Hole } from '../../../data/domain/world-5/hole/hole';
import { Card } from '../../../data/domain/cards';
@@ -235,8 +235,7 @@ const cookingParameterSpecs: Record = {
extractionKey: 'summoning_win_bonus_15',
domainExtractor: (gameData) => {
const summoning = gameData.get("summoning") as Summoning;
- // Our bonus index starts at 1, so we get 16 instead of 15.
- const winnerBonus = summoning.summonBonuses.find(bonus => bonus.data.bonusId == 16)?.getBonus() ?? 0;
+ const winnerBonus = summoning.summonBonuses.find(bonus => bonus.index == 15)?.getBonus() ?? 0;
return winnerBonus;
}
},
diff --git a/tests/domains/cooking/meal-speed.test.ts b/tests/domains/cooking/meal-speed.test.ts
index 24ed8396..1c44bf65 100644
--- a/tests/domains/cooking/meal-speed.test.ts
+++ b/tests/domains/cooking/meal-speed.test.ts
@@ -7,7 +7,7 @@
import { loadExtractionResults, getExtractedValue, validateExtractionHealth } from '../../utils/live-game-data-loader';
import { loadGameDataFromSave } from '../../utils/cloudsave-loader';
-import { Cooking } from '../../../data/domain/cooking';
+import { Cooking } from '../../../data/domain/world-4/cooking';
// TODO: Make it possible to test multiple save / extraction results.
const saveName = 'latest';
diff --git a/tests/domains/sailing/speed-parameters.test.ts b/tests/domains/sailing/speed-parameters.test.ts
index bf048ed8..592f6d4b 100644
--- a/tests/domains/sailing/speed-parameters.test.ts
+++ b/tests/domains/sailing/speed-parameters.test.ts
@@ -1,19 +1,19 @@
import { loadExtractionResults, validateExtractionHealth, getExtractedValue } from '../../utils/live-game-data-loader';
import { loadGameDataFromSave } from '../../utils/cloudsave-loader';
import { ParameterTestSpec } from '../../utils/parameter-test-config';
-import { Sailing } from '../../../data/domain/sailing';
-import { Divinity } from '../../../data/domain/divinity';
+import { Sailing } from '../../../data/domain/world-5/sailing/sailing';
+import { Divinity } from '../../../data/domain/world-5/divinity';
import { Card } from '../../../data/domain/cards';
-import { Alchemy } from '../../../data/domain/alchemy';
+import { Alchemy } from '../../../data/domain/world-2/alchemy/alchemy';
import { Votes } from '../../../data/domain/world-2/votes';
import { Stamp } from '../../../data/domain/world-1/stamps';
-import { PlayerStatues } from '../../../data/domain/statues';
-import { Cooking } from '../../../data/domain/cooking';
-import { Rift, SkillMastery } from '../../../data/domain/rift';
-import { Worship, TotalizerBonus } from '../../../data/domain/worship';
+import { PlayerStatues } from '../../../data/domain/world-1/statues';
+import { Cooking } from '../../../data/domain/world-4/cooking';
+import { Rift, SkillMastery } from '../../../data/domain/world-4/rift';
+import { Worship, TotalizerBonus } from '../../../data/domain/world-3/worship';
import { StarSigns } from '../../../data/domain/starsigns';
import { SkillsIndex } from '../../../data/domain/SkillsIndex';
-import { SlabInfluencedArtifact } from '../../../data/domain/sailing/artifacts';
+import { SlabInfluencedArtifact } from '../../../data/domain/world-5/sailing/artifacts';
// TODO: Make it possible to test multiple save / extraction results.
const saveName = 'latest';
diff --git a/tests/domains/summoning/win-bonus-parameters.test.ts b/tests/domains/summoning/win-bonus-parameters.test.ts
new file mode 100644
index 00000000..78b0e6c8
--- /dev/null
+++ b/tests/domains/summoning/win-bonus-parameters.test.ts
@@ -0,0 +1,140 @@
+/**
+ * Summoning Winner Bonus Parameter Validation
+ *
+ * Tests our summoning winner bonus (WinBonus) calculations against live game data.
+ *
+ * Formula (from game code):
+ * WinBonus calculation has multiple branches based on bonus index:
+ *
+ * 1. Indices 20, 22, 24, 31: Return raw bonus value only
+ * bonusValue
+ *
+ * 2. Index 19: Uses 3.5x multiplier without recursive bonus 31
+ * 3.5 * bonusValue * pristineCharm * (1 + gemPurchase/100) *
+ * (1 + (artifact + min(10, taskBonus) + achiev379 + achiev373 + godshardSet) / 100)
+ *
+ * 3. Indices 20-33 (excluding the raw-only ones): Uses all multipliers including recursive bonus 31
+ * bonusValue * pristineCharm * (1 + gemPurchase/100) *
+ * (1 + (artifact + min(10, taskBonus) + achiev379 + achiev373 + winBonus31 + emperorBonus + godshardSet) / 100)
+ *
+ * 4. Default (all others): Uses 3.5x multiplier with all bonuses including recursive bonus 31
+ * 3.5 * bonusValue * pristineCharm * (1 + gemPurchase/100) *
+ * (1 + (artifact + min(10, taskBonus) + achiev379 + achiev373 + winBonus31 + emperorBonus + godshardSet) / 100)
+ *
+ * Cross-domain dependencies:
+ * - Sneaking: Pristine charm bonus (Crystal Comb)
+ * - Sailing: Artifact 32 bonus
+ * - TaskBoard: Merit bonus (capped at 10)
+ * - Achievements: Completion status for 373 and 379
+ * - EquipmentSets: Godshard set bonus
+ * - Emperor: Emperor bonus 8
+ * - GemStore: Gem purchase 11
+ */
+
+import { loadExtractionResults, validateExtractionHealth, getExtractedValue } from '../../utils/live-game-data-loader';
+import { loadGameDataFromSave } from '../../utils/cloudsave-loader';
+import type { Sneaking } from '../../../data/domain/world-6/sneaking';
+import type { Sailing } from '../../../data/domain/world-5/sailing/sailing';
+import type { TaskBoard } from '../../../data/domain/tasks';
+import type { Achievement } from '../../../data/domain/achievements';
+import type { EquipmentSets } from '../../../data/domain/misc/equipmentSets';
+import type { Emperor } from '../../../data/domain/world-6/emperor';
+import type { GemStore } from '../../../data/domain/gemPurchases';
+
+const saveName = 'latest';
+const extractionResultsName = 'summoning-win-bonus-data.json';
+
+const parameterSpecs = {
+ pristine_charm_bonus_8: {
+ description: 'Pristine charm bonus 8 (Crystal Comb - summoning winner bonus)',
+ extractionKey: 'pristine_charm_bonus_8',
+ domainExtractor: (gameData: Map) => {
+ const sneaking = gameData.get("sneaking") as Sneaking;
+ const crystalComb = sneaking.pristineCharms?.find(charm => charm.data.itemId === 8);
+ return (crystalComb && crystalComb.unlocked) ? crystalComb.data.x1 : 0;
+ }
+ },
+
+ gem_item_purchased_11: {
+ description: 'Gem shop purchase 11 (Winner Bonuses)',
+ extractionKey: 'gem_item_purchased_11',
+ domainExtractor: (gameData: Map) => {
+ const gemStore = gameData.get("gems") as GemStore;
+ return gemStore.purchases.find(purchase => purchase.no === 11)?.pucrhased ?? 0;
+ }
+ },
+
+ artifact_bonus_32: {
+ description: 'Sailing artifact 32 bonus (summoning winner bonus)',
+ extractionKey: 'artifact_bonus_32',
+ domainExtractor: (gameData: Map) => {
+ const sailing = gameData.get("sailing") as Sailing;
+ return sailing.artifacts[32]?.getBonus() ?? 0;
+ }
+ },
+
+ task_board_bonus_2_5_4: {
+ description: 'Task board merit bonus for summoning winners (capped at 10 in formula)',
+ extractionKey: 'task_board_bonus_2_5_4',
+ domainExtractor: (gameData: Map) => {
+ const taskboard = gameData.get("taskboard") as TaskBoard;
+ // Merit 44 is index [2][5] in the game data structure
+ return taskboard.merits[44]?.getBonus() ?? 0;
+ }
+ },
+
+ achievement_379_status: {
+ description: 'Achievement 379 completion status (adds +1% if completed)',
+ extractionKey: 'achievement_379_status',
+ domainExtractor: (gameData: Map) => {
+ const achievements = gameData.get("achievements") as Achievement[];
+ return achievements[379]?.completed ? 1 : 0;
+ }
+ },
+
+ achievement_373_status: {
+ description: 'Achievement 373 completion status (adds +1% if completed)',
+ extractionKey: 'achievement_373_status',
+ domainExtractor: (gameData: Map) => {
+ const achievements = gameData.get("achievements") as Achievement[];
+ return achievements[373]?.completed ? 1 : 0;
+ }
+ },
+
+ godshard_set_bonus: {
+ description: 'Godshard equipment set bonus for summoning winners',
+ extractionKey: 'godshard_set_bonus',
+ domainExtractor: (gameData: Map) => {
+ const equipmentSets = gameData.get("equipmentSets") as EquipmentSets;
+ return equipmentSets.getSetBonus("GODSHARD_SET", undefined, true);
+ }
+ },
+
+ emperor_bonus_8: {
+ description: 'Emperor bonus 8 (summoning winner bonus)',
+ extractionKey: 'emperor_bonus_8',
+ domainExtractor: (gameData: Map) => {
+ const emperor = gameData.get("emperor") as Emperor;
+ return emperor.emperorBonuses[8]?.getBonus() ?? 0;
+ }
+ }
+};
+
+describe('Summoning Domain - Winner Bonus - Parameters', () => {
+ let extractionResults: any;
+ let gameData: Map;
+
+ beforeAll(() => {
+ extractionResults = loadExtractionResults(extractionResultsName);
+ validateExtractionHealth(extractionResults);
+ gameData = loadGameDataFromSave(saveName);
+ });
+
+ Object.entries(parameterSpecs).forEach(([_, spec]) => {
+ it(`validates ${spec.description}`, () => {
+ const liveValue = getExtractedValue(extractionResults, spec.extractionKey);
+ const domainValue = spec.domainExtractor(gameData);
+ expect(domainValue).toMatchLiveGame(liveValue, 0);
+ });
+ });
+});
diff --git a/tests/domains/summoning/win-bonus.test.ts b/tests/domains/summoning/win-bonus.test.ts
new file mode 100644
index 00000000..f2ebb104
--- /dev/null
+++ b/tests/domains/summoning/win-bonus.test.ts
@@ -0,0 +1,74 @@
+/**
+ * Summoning Winner Bonus Calculation Validation
+ *
+ * Tests our Summoning winner bonus calculations against live game data.
+ */
+
+import { loadExtractionResults, validateExtractionHealth, getExtractedValue } from '../../utils/live-game-data-loader';
+import { loadGameDataFromSave } from '../../utils/cloudsave-loader';
+import type { Summoning } from '../../../data/domain/world-6/summoning';
+
+const saveName = 'latest';
+const extractionResultsName = 'summoning-win-bonus-data.json';
+
+describe('Summoning Domain - Winner Bonus Calculations', () => {
+ let extractionResults: any;
+ let gameData: Map;
+ let summoning: Summoning;
+
+ beforeAll(() => {
+ extractionResults = loadExtractionResults(extractionResultsName);
+ validateExtractionHealth(extractionResults);
+ gameData = loadGameDataFromSave(saveName);
+ summoning = gameData.get("summoning") as Summoning;
+ });
+
+ it('validates winner bonus 19 (3.5x multiplier, no recursive bonus)', () => {
+ const liveValue = getExtractedValue(extractionResults, 'win_bonus_19');
+ const domainValue = summoning.summonBonuses.find(bonus => bonus.index === 19)?.getBonus() ?? 0;
+
+ expect(domainValue).toMatchLiveGame(liveValue, 0);
+ });
+
+ it('validates winner bonus 20 (raw value only, no multipliers)', () => {
+ const liveValue = getExtractedValue(extractionResults, 'win_bonus_20');
+ const domainValue = summoning.summonBonuses.find(bonus => bonus.index === 20)?.getBonus() ?? 0;
+
+ expect(domainValue).toMatchLiveGame(liveValue, 0);
+ });
+
+ it('validates winner bonus 22 (raw value only, no multipliers)', () => {
+ const liveValue = getExtractedValue(extractionResults, 'win_bonus_22');
+ const domainValue = summoning.summonBonuses.find(bonus => bonus.index === 22)?.getBonus() ?? 0;
+
+ expect(domainValue).toMatchLiveGame(liveValue, 0);
+ });
+
+ it('validates winner bonus 24 (raw value only, no multipliers)', () => {
+ const liveValue = getExtractedValue(extractionResults, 'win_bonus_24');
+ const domainValue = summoning.summonBonuses.find(bonus => bonus.index === 24)?.getBonus() ?? 0;
+
+ expect(domainValue).toMatchLiveGame(liveValue, 0);
+ });
+
+ it('validates winner bonus 26 (Meal bonus - uses all multipliers + bonus 31)', () => {
+ const liveValue = getExtractedValue(extractionResults, 'win_bonus_26');
+ const domainValue = summoning.summonBonuses.find(bonus => bonus.index === 26)?.getBonus() ?? 0;
+
+ expect(domainValue).toMatchLiveGame(liveValue, 0);
+ });
+
+ it('validates winner bonus 31 (raw value only - used in other calculations)', () => {
+ const liveValue = getExtractedValue(extractionResults, 'win_bonus_31');
+ const domainValue = summoning.summonBonuses.find(bonus => bonus.index === 31)?.getBonus() ?? 0;
+
+ expect(domainValue).toMatchLiveGame(liveValue, 0);
+ });
+
+ it('validates winner bonus 5 (3.5x multiplier with all bonuses including recursive bonus 31)', () => {
+ const liveValue = getExtractedValue(extractionResults, 'win_bonus_5');
+ const domainValue = summoning.summonBonuses.find(bonus => bonus.index === 5)?.getBonus() ?? 0;
+
+ expect(domainValue).toMatchLiveGame(liveValue, 0);
+ });
+});
diff --git a/tests/results/summoning-win-bonus-data.json b/tests/results/summoning-win-bonus-data.json
new file mode 100644
index 00000000..c1055284
--- /dev/null
+++ b/tests/results/summoning-win-bonus-data.json
@@ -0,0 +1,104 @@
+{
+ "timestamp": "2026-02-12T12:42:13.696Z",
+ "extractions": {
+ "pristine_charm_bonus_8": {
+ "expression": "idleon.callFunction(\"Ninja\", \"PristineBon\", 8, 0)",
+ "result": 30,
+ "description": "Pristine charm bonus for summoning (index 8 - Crystal Comb)",
+ "extractedAt": "2026-02-12T12:42:13.744Z"
+ },
+ "gem_item_purchased_11": {
+ "expression": "idleon.engine.getGameAttribute(\"GemItemsPurchased\")[11]",
+ "result": 5,
+ "description": "Gem shop purchase 11 (Winner Bonuses)",
+ "extractedAt": "2026-02-12T12:42:13.748Z"
+ },
+ "artifact_bonus_32": {
+ "expression": "idleon.callFunction(\"Sailing\", \"ArtifactBonus\", 32, 0)",
+ "result": 100,
+ "description": "Sailing artifact 32 bonus (summoning winner bonus)",
+ "extractedAt": "2026-02-12T12:42:13.755Z"
+ },
+ "task_board_bonus_2_5_4": {
+ "expression": "idleon.engine.getGameAttribute(\"Tasks\")[2][5][4]",
+ "result": 10,
+ "description": "Task board merit bonus (max 10) for summoning winners",
+ "extractedAt": "2026-02-12T12:42:13.759Z"
+ },
+ "achievement_379_status": {
+ "expression": "idleon.callFunction(\"AchieveStatus\", 379)",
+ "result": 1,
+ "description": "Achievement 379 completion status (winner bonus +1%)",
+ "extractedAt": "2026-02-12T12:42:13.762Z"
+ },
+ "achievement_373_status": {
+ "expression": "idleon.callFunction(\"AchieveStatus\", 373)",
+ "result": 1,
+ "description": "Achievement 373 completion status (winner bonus +1%)",
+ "extractedAt": "2026-02-12T12:42:13.765Z"
+ },
+ "godshard_set_bonus": {
+ "expression": "idleon.callFunction(\"GetSetBonus\", \"GODSHARD_SET\", \"Bonus\", 0, 0)",
+ "result": 0,
+ "description": "Godshard equipment set bonus for summoning winners",
+ "extractedAt": "2026-02-12T12:42:13.773Z"
+ },
+ "emperor_bonus_8": {
+ "expression": "idleon.callFunction(\"Thingies\", \"EmperorBon\", 8, 0)",
+ "result": 1,
+ "description": "Emperor bonus 8 (summoning winner bonus)",
+ "extractedAt": "2026-02-12T12:42:13.776Z"
+ },
+ "win_bonus_19": {
+ "expression": "idleon.callFunction(\"Summoning\", \"WinBonus\", 19, 0)",
+ "result": 43.407000000000004,
+ "description": "Winner bonus 19 (uses 3.5x multiplier, no recursive bonus)",
+ "extractedAt": "2026-02-12T12:42:13.779Z"
+ },
+ "win_bonus_20": {
+ "expression": "idleon.callFunction(\"Summoning\", \"WinBonus\", 20, 0)",
+ "result": 3,
+ "description": "Winner bonus 20 (raw value only, no multipliers)",
+ "extractedAt": "2026-02-12T12:42:13.791Z"
+ },
+ "win_bonus_22": {
+ "expression": "idleon.callFunction(\"Summoning\", \"WinBonus\", 22, 0)",
+ "result": 10,
+ "description": "Winner bonus 22 (raw value only, no multipliers)",
+ "extractedAt": "2026-02-12T12:42:13.794Z"
+ },
+ "win_bonus_24": {
+ "expression": "idleon.callFunction(\"Summoning\", \"WinBonus\", 24, 0)",
+ "result": 6,
+ "description": "Winner bonus 24 (raw value only, no multipliers)",
+ "extractedAt": "2026-02-12T12:42:13.796Z"
+ },
+ "win_bonus_26": {
+ "expression": "idleon.callFunction(\"Summoning\", \"WinBonus\", 26, 0)",
+ "result": 98.2215,
+ "description": "Winner bonus 26 (Meal bonus - uses all multipliers + bonus 31)",
+ "extractedAt": "2026-02-12T12:42:13.806Z"
+ },
+ "win_bonus_31": {
+ "expression": "idleon.callFunction(\"Summoning\", \"WinBonus\", 31, 0)",
+ "result": 6,
+ "description": "Winner bonus 31",
+ "extractedAt": "2026-02-12T12:42:13.809Z"
+ },
+ "win_bonus_5": {
+ "expression": "idleon.callFunction(\"Summoning\", \"WinBonus\", 5, 0)",
+ "result": 1972.9710000000002,
+ "description": "Winner bonus 5 (uses 3.5x multiplier and all bonuses including recursive bonus 31)",
+ "extractedAt": "2026-02-12T12:42:13.811Z"
+ }
+ },
+ "errors": {},
+ "metadata": {
+ "serverStatus": {
+ "server": "running",
+ "cdpConnected": true,
+ "injected": true,
+ "gameReady": true
+ }
+ }
+}
\ No newline at end of file