Skip to content

Commit aa03933

Browse files
committed
eggsss
1 parent aebefc6 commit aa03933

File tree

8 files changed

+364
-48
lines changed

8 files changed

+364
-48
lines changed

src/components/Profile/MiscPanel.tsx

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import { useGameData } from '../../hooks/useGameData';
44
import { useForgeUpgradeStats } from '../../hooks/useForgeCalculator';
55
import { Card } from '../UI/Card';
66
import { Button } from '../UI/Button';
7+
import { SpriteIcon } from '../UI/SpriteIcon';
78
import { Plus, Minus } from 'lucide-react';
89

910
export function MiscPanel() {
1011
const { profile, updateNestedProfile } = useProfile();
1112
const { data: petConfig } = useGameData<any>('PetBaseConfig.json');
1213
const { data: forgeData } = useGameData<any>('ForgeUpgradeLibrary.json');
14+
const { data: forgeConfig } = useGameData<any>('ForgeConfig.json');
1315

1416
// Determine max forge level from config
1517
const maxForgeLevel = forgeData ? Math.max(...Object.keys(forgeData).map(Number)) : 99;
@@ -140,24 +142,18 @@ export function MiscPanel() {
140142
</div>
141143

142144
{/* Stats */}
143-
<div className="grid grid-cols-2 gap-2">
144-
<div className="flex flex-col items-center bg-bg-input py-2 rounded border border-border">
145-
<span className="text-text-muted font-bold mb-1">Hammers (Total)</span>
146-
<span className="font-bold text-text-primary">
147-
{new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(upgradeStats.hammersToUpgrade)}
148-
</span>
149-
{upgradeStats.freeForgeChance > 0 && (
150-
<span className="text-[9px] text-text-muted line-through">
151-
{new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(upgradeStats.rawHammersNeeded)}
152-
</span>
153-
)}
154-
</div>
155-
145+
<div className="grid grid-cols-1 gap-2">
156146
<div className="flex flex-col items-center bg-bg-input py-2 rounded border border-border">
157147
<span className="text-text-muted font-bold mb-1">Time</span>
158148
<span className="font-bold text-text-primary">
159149
{formatTime(upgradeStats.totalTimeSeconds)}
160150
</span>
151+
{forgeConfig && upgradeStats.totalTimeSeconds > 0 && (
152+
<div className="flex items-center gap-1 mt-0.5 text-accent-primary font-bold">
153+
<SpriteIcon name="GemSquare" size={12} />
154+
{Math.ceil(upgradeStats.totalTimeSeconds * (forgeConfig.ForgeGemSkipCostPerSecond || 0.013))}
155+
</div>
156+
)}
161157
</div>
162158
</div>
163159
</div>

src/hooks/useEggsCalculator.ts

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ export interface EggOptimizationResult {
1010
hatchPoints: number;
1111
mergePoints: number;
1212
timeUsed: number;
13+
baseTimeUsed: number;
14+
gemTimeUsed: number;
1315
timeLeft: number;
1416
timeline: Timeline;
17+
totalGemsUsed: number;
1518
}
1619

1720
export interface TimelineEvent {
@@ -20,6 +23,7 @@ export interface TimelineEvent {
2023
endTime: number; // minutes
2124
duration: number; // minutes
2225
efficiency: number; // Points Per Second
26+
gemCost?: number;
2327
}
2428

2529
export type Timeline = TimelineEvent[][];
@@ -209,10 +213,11 @@ export function useEggsCalculator() {
209213
}, [guildWarConfig]);
210214

211215
// --- Optimization Logic ---
216+
const { data: forgeConfig } = useGameData<any>('ForgeConfig.json');
217+
212218
const optimization = useMemo((): EggOptimizationResult | null => {
213-
if (!hatchValuesProfile || !warPoints) return null;
219+
if (!hatchValuesProfile || !warPoints || !forgeConfig) return null;
214220

215-
const totalMinutesAvailable = timeLimitHours * 60;
216221
const rarities = ['Common', 'Rare', 'Epic', 'Legendary', 'Ultimate', 'Mythic'];
217222

218223
// 1. Prepare Pool of All Eggs
@@ -265,6 +270,10 @@ export function useEggsCalculator() {
265270
let hPoints = 0;
266271
let mPoints = 0;
267272

273+
let totalGemCost = 0;
274+
const gemLimit = profile.misc.useGemsInCalculators ? profile.misc.gemCount : 0;
275+
const gemCostPerSecond = forgeConfig.PetGemSkipCostPerSecond || 0.003;
276+
268277
// 3. Greedy Assignment (Least Loaded / Earliest Finish)
269278
// User wants to balance slots ("non tutto sul primo") and minimize makespan.
270279
// Since we sorted by Time Descending (Big Rocks), using parameters of LPT (Longest Processing Time)
@@ -276,10 +285,24 @@ export function useEggsCalculator() {
276285
let minCurrentTime = Number.MAX_VALUE;
277286

278287
for (let i = 0; i < availableSlots; i++) {
279-
// Must fit within limit
280-
if (slots[i] + egg.time <= totalMinutesAvailable) {
281-
// We want the LEAST loaded slot to balance
282-
if (slots[i] < minCurrentTime) {
288+
// Check if this slot is better (earlier)
289+
if (slots[i] < minCurrentTime) {
290+
// Tentatively check if we can afford it with gems
291+
const startTime = slots[i];
292+
const endTime = startTime + egg.time;
293+
const baseMinutesAvailable = timeLimitHours * 60;
294+
295+
let potentialGemCost = 0;
296+
if (endTime > baseMinutesAvailable) {
297+
const gemMinutes = Math.min(egg.time, endTime - Math.max(startTime, baseMinutesAvailable));
298+
if (gemMinutes > 0) {
299+
potentialGemCost = Math.ceil((gemMinutes * 60) * gemCostPerSecond);
300+
}
301+
}
302+
303+
// Only consider this slot if we can afford the gems AND it fits within the base time limit
304+
// OR if it exceeds base time but we have gems for it.
305+
if (endTime <= baseMinutesAvailable || (totalGemCost + potentialGemCost <= gemLimit)) {
283306
minCurrentTime = slots[i];
284307
bestSlotIdx = i;
285308
}
@@ -291,18 +314,31 @@ export function useEggsCalculator() {
291314
const startTime = slots[bestSlotIdx];
292315
const endTime = startTime + egg.time;
293316

317+
// Re-calculate Gem Cost (it should be the same as the check, but cleaner to recalc)
318+
const baseMinutesAvailable = timeLimitHours * 60;
319+
let gemCost = 0;
320+
321+
if (endTime > baseMinutesAvailable) {
322+
const gemMinutes = Math.min(egg.time, endTime - Math.max(startTime, baseMinutesAvailable));
323+
if (gemMinutes > 0) {
324+
gemCost = Math.ceil((gemMinutes * 60) * gemCostPerSecond);
325+
}
326+
}
327+
294328
// Commit
295329
slots[bestSlotIdx] = endTime;
296330
timeline[bestSlotIdx].push({
297331
rarity: egg.rarity,
298332
startTime,
299333
endTime,
300334
duration: egg.time,
301-
efficiency: egg.eff
335+
efficiency: egg.eff,
336+
gemCost
302337
});
303338
toOpen[egg.rarity] = (toOpen[egg.rarity] || 0) + 1;
304339
hPoints += egg.hPoints;
305340
mPoints += egg.mPoints;
341+
totalGemCost += gemCost;
306342
}
307343
}
308344

@@ -313,12 +349,15 @@ export function useEggsCalculator() {
313349
totalPoints: hPoints + mPoints,
314350
hatchPoints: hPoints,
315351
mergePoints: mPoints,
316-
timeUsed: makeSpan, // This will now be <= totalMinutesAvailable
317-
timeLeft: totalMinutesAvailable - makeSpan,
318-
timeline
352+
timeUsed: makeSpan,
353+
baseTimeUsed: Math.min(makeSpan, (timeLimitHours * 60)),
354+
gemTimeUsed: Math.max(0, makeSpan - (timeLimitHours * 60)),
355+
timeLeft: Math.max(0, (timeLimitHours * 60) - makeSpan),
356+
timeline,
357+
totalGemsUsed: totalGemCost
319358
};
320359

321-
}, [ownedEggs, timeLimitHours, availableSlots, hatchValuesProfile, warPoints]);
360+
}, [ownedEggs, timeLimitHours, availableSlots, hatchValuesProfile, warPoints, forgeConfig, profile.misc.gemCount, profile.misc.useGemsInCalculators]);
322361

323362
// --- Tech Tree Bonus (Additive Chance) ---
324363
const eggDungeonBonus = useMemo(() => {

src/hooks/useTreeOptimizer.ts

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface TechUpgrade {
1515
points: number;
1616
tier: number;
1717
sprite_rect?: { x: number; y: number; width: number; height: number };
18+
gemCost?: number;
1819
}
1920

2021
export function useTreeOptimizer() {
@@ -77,8 +78,10 @@ export function useTreeOptimizer() {
7778
};
7879

7980
// 4. Optimization Logic
81+
const { data: forgeConfig } = useGameData<any>('ForgeConfig.json');
82+
8083
const optimization = useMemo(() => {
81-
if (!mapping || !library || !upgradeLibrary || !dayConfig) return null;
84+
if (!mapping || !library || !upgradeLibrary || !dayConfig || !forgeConfig) return null;
8285

8386
// Map Tier -> Points
8487
const tierPoints: Record<number, number> = {
@@ -107,25 +110,31 @@ export function useTreeOptimizer() {
107110
}
108111

109112
let totalPoints = 0;
110-
let timeRemainingSeconds = timeLimitHours * 3600;
113+
const baseTimeLimitSeconds = timeLimitHours * 3600;
114+
115+
// Track resources
116+
let accumulatedTimeSeconds = 0;
117+
let accumulatedGemCost = 0;
118+
const gemLimit = (profile.misc.useGemsInCalculators ? profile.misc.gemCount : 0);
111119
let potionsRemaining = potions;
120+
112121
const actions: TechUpgrade[] = [];
122+
const gemCostPerSecond = forgeConfig.TechTreeGemSkipCostPerSecond || 0.003;
113123

114124
// Simple Greedy Simulation
115-
// While we have time and potions, find all "available" upgrades
116-
// Selection criteria: Max (Points / Duration) to fit most inside 1h or 24h?
117-
// Actually Max Points / Duration is usually best for time-limited ranking
118-
119125
const maxIter = 500; // Safety break
120126
let iter = 0;
121127

122-
while (timeRemainingSeconds > 0 && potionsRemaining > 0 && iter < maxIter) {
128+
// We continue as long as we can potentially perform an action.
129+
// Determining "can perform" is handled inside by the search for a candidate.
130+
while (iter < maxIter) {
123131
iter++;
124-
const possibleUpgrades: TechUpgrade[] = [];
125132

126133
// Calculate current bonuses
127134
const bonuses = calculateTechBonuses(currentTree);
128135

136+
const possibleUpgrades: TechUpgrade[] = [];
137+
129138
// Find all available upgrades
130139
Object.entries(mapping.trees || {}).forEach(([treeName, treeDef]: [string, any]) => {
131140
treeDef.nodes.forEach((node: any) => {
@@ -137,7 +146,7 @@ export function useTreeOptimizer() {
137146
if (currentLvl < maxLvl) {
138147
// Check requirements
139148
const reqsMet = (node.requirements || []).every((reqId: number) => {
140-
return (currentTree[treeName]?.[reqId] || 0) >= 1; // Usually need lvl 1 to unlock next
149+
return (currentTree[treeName]?.[reqId] || 0) >= 1;
141150
});
142151

143152
if (reqsMet) {
@@ -173,36 +182,76 @@ export function useTreeOptimizer() {
173182
if (possibleUpgrades.length === 0) break;
174183

175184
// Sort by efficiency (Points / Duration)
176-
// If duration is 0, give it high priority
177185
possibleUpgrades.sort((a, b) => (b.points / (b.duration || 1)) - (a.points / (a.duration || 1)));
178186

179-
// Find first one that fits budget
180-
const best = possibleUpgrades.find(upg => upg.cost <= potionsRemaining && upg.duration <= timeRemainingSeconds);
187+
// Find first one that fits budget (Potions AND Gems)
188+
const best = possibleUpgrades.find(upg => {
189+
// Check Potion Cost
190+
if (upg.cost > potionsRemaining) return false;
191+
192+
// Check Gem Cost
193+
const startTime = accumulatedTimeSeconds;
194+
const endTime = startTime + upg.duration;
195+
let neededGems = 0;
196+
197+
if (endTime > baseTimeLimitSeconds) {
198+
const overlap = Math.min(upg.duration, endTime - Math.max(startTime, baseTimeLimitSeconds));
199+
if (overlap > 0) {
200+
neededGems = Math.ceil(overlap * gemCostPerSecond);
201+
}
202+
}
203+
204+
if (neededGems > (gemLimit - accumulatedGemCost)) return false;
205+
206+
return true;
207+
});
181208

182209
if (best) {
183-
actions.push(best);
210+
// Re-calculate gem cost to attach (could optimize by returning it from find, but this is cheap)
211+
const startTime = accumulatedTimeSeconds;
212+
const endTime = startTime + best.duration;
213+
let gemCost = 0;
214+
if (endTime > baseTimeLimitSeconds) {
215+
const overlap = Math.min(best.duration, endTime - Math.max(startTime, baseTimeLimitSeconds));
216+
if (overlap > 0) {
217+
gemCost = Math.ceil(overlap * gemCostPerSecond);
218+
}
219+
}
220+
221+
actions.push({ ...best, gemCost });
222+
184223
totalPoints += best.points;
185224
potionsRemaining -= best.cost;
186-
timeRemainingSeconds -= best.duration;
225+
accumulatedTimeSeconds += best.duration;
226+
accumulatedGemCost += gemCost;
227+
187228
// Update virtual tree
188229
if (!currentTree[best.tree]) currentTree[best.tree] = {};
189230
currentTree[best.tree][best.nodeId] = best.toLevel;
190231
} else {
191-
// No more upgrades fit
232+
// No upgrades fit our remaining resources
192233
break;
193234
}
194235
}
195236

237+
// Calculate total time used
238+
const usedSeconds = accumulatedTimeSeconds;
239+
const gemTimeSeconds = Math.max(0, usedSeconds - baseTimeLimitSeconds);
240+
const baseTimeSeconds = Math.min(usedSeconds, baseTimeLimitSeconds);
241+
196242
return {
197243
totalPoints,
198244
actions,
199-
timeUsed: (timeLimitHours * 3600 - timeRemainingSeconds) / 3600,
245+
timeUsed: usedSeconds / 3600,
246+
baseTimeUsed: baseTimeSeconds / 3600,
247+
gemTimeUsed: gemTimeSeconds / 3600,
200248
potionsUsed: potions - potionsRemaining,
201249
remainingPotions: potionsRemaining,
202-
finalBonuses: calculateTechBonuses(currentTree)
250+
finalBonuses: calculateTechBonuses(currentTree),
251+
totalGemsUsed: accumulatedGemCost
203252
};
204253

205-
}, [mapping, library, upgradeLibrary, dayConfig, treeMode, profile.techTree, timeLimitHours, potions]);
254+
}, [mapping, library, upgradeLibrary, dayConfig, treeMode, profile.techTree, timeLimitHours, potions, forgeConfig, profile.misc.gemCount, profile.misc.useGemsInCalculators]);
206255

207256
const applyUpgrades = (selectedActions: TechUpgrade[]) => {
208257
if (selectedActions.length === 0) return;

0 commit comments

Comments
 (0)