Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 44 additions & 22 deletions data/overclock_data.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
coil_multipliers:
cupronickel: 0.5
kanthal: 1
nichrome: 1.5
tungstensteel: 2.0
tpv-alloy: 2.0
hss-g: 2.5
hss-s: 3.0
naquadah: 3.5
naquadah alloy: 4
trinium: 4.5
electrum flux: 5
awakened draconium: 5.5
infinity: 6
hypogen: 6.5
eternal: 7

coil_tiers:
cupronickel: 1
kanthal: 2
nichrome: 3
tungstensteel: 4
tpv-alloy: 4
hss-g: 5
hss-s: 6
naquadah: 7
naquadah alloy: 8
trinium: 9
electrum flux: 10
awakened draconium: 11
infinity: 12
hypogen: 13
eternal: 14

coil_heat:
cupronickel: 1801
Expand All @@ -35,13 +34,30 @@ coil_heat:


pipe_casings:
bronze: 2
steel: 4
titanium: 6
tungstensteel: 8
bronze: 1
steel: 2
titanium: 3
tungstensteel: 4

pipe_casings:
tin: 1
brass: 2
electrum: 3
platinum: 4
osmium: 5
quantium: 6
fluxed electrum: 7
black plutonium: 8

# [speed +x, EU/t discount, parallels (NOT per tier)]
electromagnets:
iron: [0.1, 0.8, 8]
steel: [0.25, 0.75, 24]
neodymium: [0.5, 0.7, 48]
samarium: [1.0, 0.6, 96]
tengam: [1.5, 0.5, 256]

# [speed X, EU/t discount, parallels per tier]
# [speed +X, EU/t discount, parallels per tier]
GTpp_stats:
industrial centrifuge: [1.25, 0.9, 6]
industrial material press: [5.0, 1.0, 4]
Expand All @@ -62,6 +78,12 @@ GTpp_stats:
industrial dehydrator: [1.2, 0.5, 4]
thermic heating device: [1.2, 0.9, 8]
volcanus: [1.2, 0.9, 8]
large electric compressor: [1.0, 0.9, 2]
big barrel brewery: [0.5, 0.0, 4]
turbocan pro: [1.0, 0.0, 8]
hip: [2.5, 0.75, 4]
neutronium compressor: [0.0, 1.0, 8]


# these are special, the -1 are discarded
dangote - distillation tower: [2.5, 1, -1]
Expand Down
8 changes: 8 additions & 0 deletions src/graph/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ def isAlias(self, machine_name: str) -> bool:
['industrial dehydrator', {'utupu', 'utupu-tanuri', 'utupu tanuri', 'dehydrator++'}],
['flotation cell regulator', {'flotation cell'}],
['isamill grinding machine', {'isamill'}],
['magnetic flux exhibitor', {'mfe++', "mfe", 'industrial electromagnetic separator', 'industrial polarizer'}],
['dissection apparatus', {'extractor++', 'industrial extractor'}],
['industrial autoclave', {'autoclave++', 'industrial autoclave'}],
['fluid shaper', {'fluid solidifer++', 'industrial fluid solidifier', 'industrial tool casting machine'}],
['large electric compressor', {"compressor++", 'lec', 'industrial compressor'}],
['big barrel brewery', {'brewery++', 'industrial brewery', "bbb"}],
['turbocan pro', {"canner++", "fluid canner++", "industrial canner", "industrial fluid canner", "canner pro"}],
['hip', {"hot isostatic pressurization unit"}]
]
machine_identity_groups = [IdentityGroup(name, aliases) for name, aliases in machine_identity_groups]

Expand Down
160 changes: 136 additions & 24 deletions src/gtnh/overclocks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Standard libraries
import math
from bisect import bisect_right
from typing import Callable
from typing import Callable, Optional

# Pypi libraries
import yaml
Expand All @@ -11,7 +11,7 @@
from src.data.basicTypes import Ingredient, IngredientCollection, Recipe


def require(recipe: Recipe, requirements: tuple[str, str, str]) -> None:
def require(recipe: Recipe, requirements: list[tuple[str, type, str]]) -> None:
# requirements should be a list of [key, type, reason]
# throws RuntimeError if any of the requirements are not met
for req in requirements:
Expand All @@ -22,8 +22,6 @@ def require(recipe: Recipe, requirements: tuple[str, str, str]) -> None:


class OverclockHandler:


def __init__(
self,
parent_context, # TODO: Figure out how to type this (ProgramContext)
Expand All @@ -38,23 +36,23 @@ def __init__(
self.voltages = self.overclock_data['voltage_data']['tiers']
self.voltage_cutoffs = [32*pow(4, x) + 1 for x in range(len(self.voltages))]


def modifyGTpp(self, recipe: Recipe, MAX_PARALLEL: int = None, eut_discount: float = None) -> Recipe:
if recipe.machine not in self.overclock_data['GTpp_stats']:
raise RuntimeError('Missing OC data for GT++ multi - add to gtnhClasses/overclocks.py:GTpp_stats')

# Get per-machine boosts
SPEED_BOOST, EU_DISCOUNT, PARALLELS_PER_TIER = self.overclock_data['GTpp_stats'][recipe.machine]
SPEED_BOOST = 1/(SPEED_BOOST+1)

if eut_discount is not None:
EU_DISCOUNT = eut_discount

def calcGtpp(self, recipe: Recipe, MAX_PARALLEL: Optional[int] = None, SPEED_BOOST: float = 1, EU_DISCOUNT: float = 1, PARALLELS_PER_TIER: Optional[int] = 1) -> Recipe:
"""
A general GT++ overclocker.
SPEED_BOOST: How much to speed up the recipe, additive (0 is the base/singleblock speed)
EU_DISCOUNT: How much to discount the EUT, multiplicative (1 is the base/singleblock EUT)
Parallels are calculated as follows:
- If MAX_PARALLEL is None, it will be calculated as (tier + 1) * PARALLELS_PER_TIER
- If MAX_PARALLEL is provided, it will be used as the maximum number of parallels
"""
SPEED_MULT = 1/(SPEED_BOOST+1)
# Calculate base parallel count and clip time to 1 tick
available_eut = self.voltage_cutoffs[self.voltages.index(recipe.user_voltage)]
if MAX_PARALLEL is None:
if PARALLELS_PER_TIER is None:
raise RuntimeError('PARALLELS_PER_TIER must be set if MAX_PARALLEL is not provided')
MAX_PARALLEL = (self.voltages.index(recipe.user_voltage) + 1) * PARALLELS_PER_TIER
NEW_RECIPE_TIME = max(recipe.dur * SPEED_BOOST, 1)
NEW_RECIPE_TIME = max(recipe.dur * SPEED_MULT, 1)

# Calculate current EU/t spend
x = recipe.eut * EU_DISCOUNT
Expand All @@ -66,7 +64,7 @@ def modifyGTpp(self, recipe: Recipe, MAX_PARALLEL: int = None, eut_discount: flo
self.parent_context.log.debug(colored(recipe.machine, 'green'))
self.parent_context.log.debug(colored('Base GT++ OC stats:', 'yellow'))
self.parent_context.log.debug(colored(f'{available_eut=} {MAX_PARALLEL=} {NEW_RECIPE_TIME=} {TOTAL_EUT=} {y=}', 'yellow'))
self.parent_context.log.debug(colored(f'{SPEED_BOOST=} {EU_DISCOUNT=} {MAX_PARALLEL=}', 'yellow'))
self.parent_context.log.debug(colored(f'{SPEED_MULT=} {EU_DISCOUNT=} {MAX_PARALLEL=}', 'yellow'))

# Attempt to GT OC the entire parallel set until no energy is left
while TOTAL_EUT < available_eut:
Expand All @@ -90,6 +88,108 @@ def modifyGTpp(self, recipe: Recipe, MAX_PARALLEL: int = None, eut_discount: flo
return recipe


def modifyGTpp(self, recipe: Recipe, MAX_PARALLEL: int = None, EUT_DISCOUNT_OVERRIDE: float = None) -> Recipe:
"""
A general GT++ overclocker that looks up the machine in the GTpp_stats dict.
It also has optional MAX_PARALLEL and EUT_DISCOUNT_OVERRIDE overrides.
"""
if recipe.machine not in self.overclock_data['GTpp_stats']:
raise RuntimeError('Missing OC data for GT++ multi - add to gtnhClasses/overclocks.py:GTpp_stats')

# Get per-machine boosts
SPEED_BOOST, EU_DISCOUNT, PARALLELS_PER_TIER = self.overclock_data['GTpp_stats'][recipe.machine]

if EUT_DISCOUNT_OVERRIDE is not None:
EU_DISCOUNT = EUT_DISCOUNT_OVERRIDE

return self.calcGtpp(
recipe,
MAX_PARALLEL=MAX_PARALLEL,
SPEED_BOOST=SPEED_BOOST,
EU_DISCOUNT=EU_DISCOUNT,
PARALLELS_PER_TIER=PARALLELS_PER_TIER
)

# Magnetic Flux Exhibitor
def modifyMFE(self, recipe: Recipe) -> Recipe:
require(recipe, [
('electromagnet', str, 'calculating recipe duration, speed, and eu (e.g. "iron")')
])
magnet = recipe.electromagnet.lower()
if magnet not in self.overclock_data['electromagnets']:
raise RuntimeError(f'Unknown electromagnet "{magnet}" for MFE (Magnetic Flux Exhibitor) recipe.\nAvailable: {list(self.overclock_data["electromagnets"].keys())}')
SPEED_BOOST, EU_DISCOUNT, PARALLELS_PER_TIER = self.overclock_data['electromagnets'][recipe.magnet]
return self.calcGtpp(
recipe,
MAX_PARALLEL=1, # MFE is always single parallel
SPEED_BOOST=SPEED_BOOST,
EU_DISCOUNT=EU_DISCOUNT,
PARALLELS_PER_TIER=PARALLELS_PER_TIER
)

def modifyDissectionApparatus(self, recipe: Recipe) -> Recipe:
require(
recipe,
[
['item_pipe_casings', str, 'calculating parallels (e.g. "tin")'],
]
)
casings_dict = self.overclock_data['item_pipe_casings']
if recipe.item_pipe_casings not in casings_dict:
raise RuntimeError(f'Expected item pipe casings in {list(casings_dict)}\ngot "{recipe.item_pipe_casings}".)')
MAX_PARALLEL = casings_dict[recipe.item_pipe_casings] * 8
return self.modifyGTpp(
recipe,
MAX_PARALLEL=MAX_PARALLEL)

def modifyIndustrialAutoclave(self, recipe: Recipe) -> Recipe:
require(
recipe,
[
['coils', str, 'calculating recipe duration (eg "nichrome").'],
['pipe_casings', str, 'calculating throughput parallels (eg "steel").']
]
)

pipe_casings = self.overclock_data['pipe_casings']
if recipe.pipe_casings not in pipe_casings:
raise RuntimeError(f'Expected autoclave pipe casings in {list(pipe_casings)}\ngot "{recipe.pipe_casings}".')
if recipe.coils not in self.overclock_data['coil_tiers']:
raise RuntimeError(f'Expected autoclave coils in {list(self.overclock_data["coil_tiers"])}\ngot "{recipe.coils}".')

casing_tier = pipe_casings[recipe.pipe_casings]
MAX_PARALLEL = casing_tier * 12
SPEED_BOOST = self.overclock_data['coil_tiers'][recipe.coils] * 0.25
EU_DISCOUNT = 1 * (12 - casing_tier) / 12 # 1 is the base EUT, 12 is the max tier

return self.calcGtpp(
recipe,
MAX_PARALLEL=MAX_PARALLEL,
SPEED_BOOST=SPEED_BOOST,
EU_DISCOUNT=EU_DISCOUNT,
PARALLELS_PER_TIER=1
)

def modifyFluidShaper(self, recipe: Recipe) -> Recipe:
width_expansions = 0
if hasattr(recipe, 'width_expansions'):
width_expansions = recipe.width_expansions
else:
print("Warning: Fluid Shaper recipe missing 'width_expansions' attribute. It's technically optional, but you probably forgot it.")

parallels_per_tier = 2 + width_expansions * 3

return self.calcGtpp(
recipe,
MAX_PARALLEL=None, # Fluid Shaper has no max parallel, it just scales with width_expansions
SPEED_BOOST=2.0,
EU_DISCOUNT=0.8,
PARALLELS_PER_TIER=parallels_per_tier
)




def modifyChemPlant(self, recipe: Recipe) -> Recipe:
require(
recipe,
Expand All @@ -104,8 +204,8 @@ def modifyChemPlant(self, recipe: Recipe) -> Recipe:
if recipe.pipe_casings not in chem_plant_pipe_casings:
raise RuntimeError(f'Expected chem pipe casings in {list(chem_plant_pipe_casings)}\ngot "{recipe.pipe_casings}". (More are allowed, I just haven\'t added them yet.)')

throughput_multiplier = chem_plant_pipe_casings[recipe.pipe_casings]
coil_multiplier = self.overclock_data['coil_multipliers'][recipe.coils]
throughput_multiplier = chem_plant_pipe_casings[recipe.pipe_casings] * 2
coil_multiplier = self.overclock_data['coil_tiers'][recipe.coils] * 0.5

# Add catalyst
known_catalysts = {
Expand Down Expand Up @@ -175,7 +275,7 @@ def modifyPyrolyse(self, recipe: Recipe) -> Recipe:
)
oc_count = self.calculateStandardOC(recipe)
recipe.eut = recipe.eut * 4**oc_count
recipe.dur = recipe.dur / 2**oc_count / self.overclock_data['coil_multipliers'][recipe.coils]
recipe.dur = recipe.dur / 2**oc_count / (self.overclock_data['coil_tiers'][recipe.coils] * 0.5)

return recipe

Expand All @@ -184,7 +284,7 @@ def modifyMultiSmelter(self, recipe: Recipe) -> Recipe:
recipe.eut = 4
recipe.dur = 500
recipe = self.modifyStandard(recipe)
coil_tiering = {name: int(multiplier*2)-1 for name, multiplier in self.overclock_data['coil_multipliers'].items()}
coil_tiering = {name: int(multiplier)-1 for name, multiplier in self.overclock_data['coil_tiers'].items()}
batch_size = 8 * 2**max(4, coil_tiering[recipe.coils])
recipe.I *= batch_size
recipe.O *= batch_size
Expand Down Expand Up @@ -400,7 +500,7 @@ def modifyICO(self, recipe: Recipe) -> Recipe:
user_voltage = self.voltages.index(recipe.user_voltage)
eut_discount = 1 - 0.04 * (user_voltage + 1)

recipe = self.modifyGTpp(recipe, MAX_PARALLEL=24, eut_discount=eut_discount)
recipe = self.modifyGTpp(recipe, MAX_PARALLEL=24, EUT_DISCOUNT_OVERRIDE=eut_discount)

return recipe

Expand Down Expand Up @@ -474,7 +574,11 @@ def getOverclockFunction(self, recipe: Recipe) -> Callable[[Recipe], Recipe]:
'boldarnator': self.modifyGTpp,
'dangote - distillery': self.modifyGTpp,
'thermic heating device': self.modifyGTpp,
'volcanus': self.modifyGTpp,
'large elecric compressor': self.modifyGTpp,
'large electric compressor': self.modifyGTpp,
'big barrel brewery': self.modifyGTpp,
'turbocan pro': self.modifyGTpp,
'hip': self.modifyGTpp,

# Special GT++ multis
'dangote - distillation tower': lambda rec: self.modifyGTpp(rec, MAX_PARALLEL=12),
Expand All @@ -487,6 +591,14 @@ def getOverclockFunction(self, recipe: Recipe) -> Callable[[Recipe], Recipe]:
'isamill grinding machine': self.modifyPerfect,
"volcanus": self.modifyVolcanus,
"digester": self.modifyPerfect,
# TODO: Industrial Precision Lathe, Large Fluid Extractor, Hyper-Intensity Laser Engraver,
# Psuedostable Black Hole Containment Unit, Godforge stuff.
'magnetic flux exhibitor': self.modifyMFE,
'dissection apparatus': self.modifyDissectionApparatus,
'industrial autoclave': self.modifyIndustrialAutoclave,
'fluid shaper': self.modifyFluidShaper,
'neutronium compressor': lambda rec: self.modifyGTpp(rec, MAX_PARALLEL=8)

}

if recipe.machine in machine_overrides:
Expand Down