- Implemented a comprehensive profit calculator for kelp processing, including smelting and crafting scenarios. - Added a graphical user interface using Tkinter for user-friendly input and output. - Included detailed calculations for profit, fuel costs, and processing times. - Supported various input formats for kelp amounts and prices, including suffixes (k/m/b) and shulker counts (s/ks/ms/bs). - Provided formatted output for results, including profit breakdowns and useful metrics.
555 lines
21 KiB
Python
555 lines
21 KiB
Python
import math
|
|
|
|
try:
|
|
import tkinter as tk
|
|
from tkinter import messagebox
|
|
from tkinter import ttk
|
|
TK_AVAILABLE = True
|
|
TK_IMPORT_ERROR = None
|
|
except Exception as exc:
|
|
tk = None
|
|
messagebox = None
|
|
ttk = None
|
|
TK_AVAILABLE = False
|
|
TK_IMPORT_ERROR = exc
|
|
|
|
# =========================
|
|
# EDIT VALUES HERE (CONFIG)
|
|
# =========================
|
|
|
|
KELP_PRICE = 61.6 # per kelp item
|
|
BLAZE_ROD_PRICE = 150 # per blaze rod
|
|
DRIED_KELP_PRICE = 86 # per dried kelp item
|
|
DRIED_KELP_BLOCK_PRICE = 751.01 # per dried kelp block
|
|
|
|
SMOKERS = 64 # amount of smokers
|
|
KELP_AMOUNT = "1s" # amount of kelp to process (items)
|
|
|
|
# =========================
|
|
# GAME CONSTANTS (DON'T EDIT)
|
|
# =========================
|
|
|
|
SECONDS_PER_ITEM_SMOKER = 5
|
|
ITEMS_PER_BLAZE_ROD = 12
|
|
DRIED_KELP_PER_BLOCK = 9
|
|
|
|
NUMBER_SUFFIXES = {
|
|
"": 1,
|
|
"k": 1_000,
|
|
"m": 1_000_000,
|
|
"b": 1_000_000_000,
|
|
}
|
|
|
|
|
|
def money(x: float) -> str:
|
|
return f"{x:,.2f}"
|
|
|
|
|
|
def format_duration(total_seconds: float) -> str:
|
|
"""Format seconds as a compact duration like 2d 3h 4m 5s."""
|
|
seconds = max(0, int(round(total_seconds)))
|
|
days, rem = divmod(seconds, 86_400)
|
|
hours, rem = divmod(rem, 3_600)
|
|
minutes, secs = divmod(rem, 60)
|
|
|
|
parts = []
|
|
if days:
|
|
parts.append(f"{days}d")
|
|
if hours:
|
|
parts.append(f"{hours}h")
|
|
if minutes:
|
|
parts.append(f"{minutes}m")
|
|
if secs or not parts:
|
|
parts.append(f"{secs}s")
|
|
|
|
return " ".join(parts)
|
|
|
|
|
|
def parse_number_with_suffix(value) -> float:
|
|
"""Parse values like 1200, 1.2k, 3m, 4B (case-insensitive)."""
|
|
if isinstance(value, (int, float)):
|
|
return float(value)
|
|
|
|
text = str(value).strip()
|
|
if not text:
|
|
raise ValueError("Value cannot be empty")
|
|
|
|
suffix = ""
|
|
if text[-1].isalpha():
|
|
suffix = text[-1].lower()
|
|
text = text[:-1].strip()
|
|
|
|
if suffix not in NUMBER_SUFFIXES:
|
|
raise ValueError("Invalid suffix. Use k, m, or b")
|
|
|
|
try:
|
|
base_value = float(text)
|
|
except ValueError as exc:
|
|
raise ValueError(f"Invalid numeric value: {value}") from exc
|
|
|
|
return base_value * NUMBER_SUFFIXES[suffix]
|
|
|
|
|
|
def parse_int_with_suffix(value, field_name: str) -> int:
|
|
parsed = parse_number_with_suffix(value)
|
|
result = int(round(parsed))
|
|
if result < 0:
|
|
raise ValueError(f"{field_name} must be >= 0")
|
|
return result
|
|
|
|
|
|
def parse_kelp_amount(value) -> int:
|
|
"""Parse kelp amount as items; supports k/m/b and shulker forms s/ks/ms/bs."""
|
|
text = str(value).strip()
|
|
if not text:
|
|
raise ValueError("KELP_AMOUNT cannot be empty")
|
|
|
|
lower_text = text.lower()
|
|
shulker_multipliers = [
|
|
("bs", 1_000_000_000),
|
|
("ms", 1_000_000),
|
|
("ks", 1_000),
|
|
("s", 1),
|
|
]
|
|
|
|
amount = None
|
|
for suffix, shulker_scale in shulker_multipliers:
|
|
if lower_text.endswith(suffix):
|
|
number_part = text[:-len(suffix)].strip()
|
|
if not number_part:
|
|
raise ValueError("Invalid shulker amount format")
|
|
try:
|
|
shulker_count = float(number_part)
|
|
except ValueError as exc:
|
|
raise ValueError("Invalid shulker amount format") from exc
|
|
amount = int(round(shulker_count * shulker_scale * 1728))
|
|
break
|
|
|
|
if amount is None:
|
|
amount = parse_int_with_suffix(text, "KELP_AMOUNT")
|
|
|
|
if amount < 0:
|
|
raise ValueError("KELP_AMOUNT must be >= 0")
|
|
|
|
return amount
|
|
|
|
|
|
def calculate(
|
|
kelp_price: float,
|
|
blaze_rod_price: float,
|
|
dried_kelp_price: float,
|
|
dried_kelp_block_price: float,
|
|
smokers: int,
|
|
kelp_amount: int,
|
|
) -> dict:
|
|
|
|
if smokers <= 0:
|
|
raise ValueError("SMOKERS must be >= 1")
|
|
if kelp_amount < 0:
|
|
raise ValueError("KELP_AMOUNT must be >= 0")
|
|
|
|
dried_kelp_out = kelp_amount # 1 kelp -> 1 dried kelp
|
|
|
|
# Fuel usage (rounded up to whole rods)
|
|
blaze_rods_used = math.ceil(kelp_amount / ITEMS_PER_BLAZE_ROD)
|
|
fuel_cost = blaze_rods_used * blaze_rod_price
|
|
|
|
# Time
|
|
total_seconds = (kelp_amount * SECONDS_PER_ITEM_SMOKER) / smokers
|
|
total_minutes = total_seconds / 60
|
|
total_hours = total_minutes / 60
|
|
|
|
# --- Scenario 1: Smelt and sell dried kelp ---
|
|
kelp_cost = kelp_amount * kelp_price
|
|
cost_smelt_only = kelp_cost + fuel_cost
|
|
revenue_smelt_only = dried_kelp_out * dried_kelp_price
|
|
profit_smelt_only = revenue_smelt_only - cost_smelt_only
|
|
|
|
# Blocks and leftovers from the smelt output
|
|
blocks_from_amount = dried_kelp_out // DRIED_KELP_PER_BLOCK
|
|
leftover_dried = dried_kelp_out % DRIED_KELP_PER_BLOCK
|
|
|
|
# --- Scenario 2: Craft blocks only (buy dried kelp for blocks) ---
|
|
cost_craft_only = (blocks_from_amount * DRIED_KELP_PER_BLOCK) * dried_kelp_price
|
|
revenue_craft_only = blocks_from_amount * dried_kelp_block_price
|
|
profit_craft_only = revenue_craft_only - cost_craft_only
|
|
|
|
# --- Scenario 3: Smelt then craft, sell blocks + leftover dried kelp ---
|
|
cost_smelt_then_craft = kelp_amount * kelp_price + fuel_cost
|
|
revenue_smelt_then_craft = (blocks_from_amount * dried_kelp_block_price) + (leftover_dried * dried_kelp_price)
|
|
profit_smelt_then_craft = revenue_smelt_then_craft - cost_smelt_then_craft
|
|
|
|
print("=== Kelp Smelting / Block Craft Profit Calculator ===")
|
|
print("\n=== Config ===")
|
|
print(f"KELP_PRICE: {kelp_price}")
|
|
print(f"BLAZE_ROD_PRICE: {blaze_rod_price}")
|
|
print(f"DRIED_KELP_PRICE: {dried_kelp_price}")
|
|
print(f"DRIED_KELP_BLOCK_PRICE: {dried_kelp_block_price}")
|
|
print(f"SMOKERS: {smokers}")
|
|
print(f"KELP_AMOUNT: {kelp_amount}")
|
|
|
|
print("\n=== Results ===")
|
|
print(f"Kelp processed: {kelp_amount}")
|
|
print(f"Dried kelp produced: {dried_kelp_out}")
|
|
print(f"Blocks craftable: {blocks_from_amount} (leftover dried kelp: {leftover_dried})")
|
|
|
|
print("\n--- Smelting logistics ---")
|
|
print(f"Blaze rods used: {blaze_rods_used} (1 rod smelts {ITEMS_PER_BLAZE_ROD} items)")
|
|
print(f"Fuel cost: {money(fuel_cost)}")
|
|
print(f"Time to smelt all: {total_seconds:,.0f} s ({total_minutes:,.2f} min, {total_hours:,.2f} hr)")
|
|
|
|
print("\n--- Profit calculations ---")
|
|
print(f"Profit (smelt -> sell dried kelp): {money(profit_smelt_only)}")
|
|
print(f"Profit (craft blocks only): {money(profit_craft_only)}")
|
|
print(f"Profit (smelt -> craft -> sell blocks + leftover dried): {money(profit_smelt_then_craft)}")
|
|
|
|
print("\n--- Useful unit breakdowns ---")
|
|
avg_fuel_cost_per_item = blaze_rod_price / ITEMS_PER_BLAZE_ROD
|
|
smelt_profit_per_kelp = (dried_kelp_price - kelp_price) - avg_fuel_cost_per_item
|
|
craft_profit_per_block = dried_kelp_block_price - (DRIED_KELP_PER_BLOCK * dried_kelp_price)
|
|
|
|
return {
|
|
"kelp_price": kelp_price,
|
|
"blaze_rod_price": blaze_rod_price,
|
|
"dried_kelp_price": dried_kelp_price,
|
|
"dried_kelp_block_price": dried_kelp_block_price,
|
|
"smokers": smokers,
|
|
"kelp_amount": kelp_amount,
|
|
"dried_kelp_out": dried_kelp_out,
|
|
"blocks_from_amount": blocks_from_amount,
|
|
"leftover_dried": leftover_dried,
|
|
"blaze_rods_used": blaze_rods_used,
|
|
"kelp_cost": kelp_cost,
|
|
"fuel_cost": fuel_cost,
|
|
"total_seconds": total_seconds,
|
|
"total_minutes": total_minutes,
|
|
"total_hours": total_hours,
|
|
"profit_smelt_only": profit_smelt_only,
|
|
"profit_craft_only": profit_craft_only,
|
|
"profit_smelt_then_craft": profit_smelt_then_craft,
|
|
"avg_fuel_cost_per_item": avg_fuel_cost_per_item,
|
|
"smelt_profit_per_kelp": smelt_profit_per_kelp,
|
|
"craft_profit_per_block": craft_profit_per_block,
|
|
}
|
|
|
|
|
|
def format_results(data: dict) -> str:
|
|
return (
|
|
"=== Kelp Smelting / Block Craft Profit Calculator ===\n\n"
|
|
"=== Config ===\n"
|
|
f"KELP_PRICE: {data['kelp_price']}\n"
|
|
f"BLAZE_ROD_PRICE: {data['blaze_rod_price']}\n"
|
|
f"DRIED_KELP_PRICE: {data['dried_kelp_price']}\n"
|
|
f"DRIED_KELP_BLOCK_PRICE: {data['dried_kelp_block_price']}\n"
|
|
f"SMOKERS: {data['smokers']}\n"
|
|
f"KELP_AMOUNT: {data['kelp_amount']}\n\n"
|
|
"=== Results ===\n"
|
|
f"Kelp processed: {data['kelp_amount']}\n"
|
|
f"Dried kelp produced: {data['dried_kelp_out']}\n"
|
|
f"Blocks craftable: {data['blocks_from_amount']} "
|
|
f"(leftover dried kelp: {data['leftover_dried']})\n\n"
|
|
"--- Smelting logistics ---\n"
|
|
f"Blaze rods used: {data['blaze_rods_used']} "
|
|
f"(1 rod smelts {ITEMS_PER_BLAZE_ROD} items)\n"
|
|
f"Fuel cost: {money(data['fuel_cost'])}\n"
|
|
f"Time to smelt all: {format_duration(data['total_seconds'])} "
|
|
f"({data['total_seconds']:,.0f} s, {data['total_minutes']:,.2f} min, {data['total_hours']:,.2f} hr)\n\n"
|
|
"--- Profit calculations ---\n"
|
|
f"Profit (smelt -> sell dried kelp): {money(data['profit_smelt_only'])}\n"
|
|
f"Profit (craft blocks only): {money(data['profit_craft_only'])}\n"
|
|
f"Profit (smelt -> craft -> sell blocks + leftover dried): "
|
|
f"{money(data['profit_smelt_then_craft'])}\n\n"
|
|
"--- Useful unit breakdowns ---\n"
|
|
f"Avg fuel cost per smelted item: {money(data['avg_fuel_cost_per_item'])}\n"
|
|
"Estimated profit per kelp smelted (avg fuel): "
|
|
f"{money(data['smelt_profit_per_kelp'])}\n"
|
|
f"Profit per dried kelp block crafted: {money(data['craft_profit_per_block'])}"
|
|
)
|
|
|
|
|
|
def main():
|
|
data = calculate(
|
|
kelp_price=parse_number_with_suffix(KELP_PRICE),
|
|
blaze_rod_price=parse_number_with_suffix(BLAZE_ROD_PRICE),
|
|
dried_kelp_price=parse_number_with_suffix(DRIED_KELP_PRICE),
|
|
dried_kelp_block_price=parse_number_with_suffix(DRIED_KELP_BLOCK_PRICE),
|
|
smokers=parse_int_with_suffix(SMOKERS, "SMOKERS"),
|
|
kelp_amount=parse_kelp_amount(KELP_AMOUNT),
|
|
)
|
|
|
|
print(format_results(data))
|
|
|
|
|
|
def launch_ui():
|
|
if not TK_AVAILABLE:
|
|
raise RuntimeError(f"Tkinter is unavailable: {TK_IMPORT_ERROR}")
|
|
|
|
root = tk.Tk()
|
|
root.title("Kelp Profit Calculator")
|
|
root.geometry("980x700")
|
|
root.minsize(900, 620)
|
|
root.configure(bg="#000000")
|
|
|
|
style = ttk.Style(root)
|
|
try:
|
|
style.theme_use("clam")
|
|
except tk.TclError:
|
|
pass
|
|
|
|
style.configure("App.TFrame", background="#000000")
|
|
style.configure("Card.TFrame", background="#111214", relief="flat")
|
|
style.configure("Header.TLabel", background="#000000", foreground="#f2f3f5", font=("Segoe UI", 21, "bold"))
|
|
style.configure("SubHeader.TLabel", background="#000000", foreground="#b5bac1", font=("Segoe UI", 10))
|
|
style.configure("FieldLabel.TLabel", background="#111214", foreground="#dbdee1", font=("Segoe UI", 10, "bold"))
|
|
style.configure("KpiLabel.TLabel", background="#111214", foreground="#949ba4", font=("Segoe UI", 9, "bold"))
|
|
style.configure("KpiValue.TLabel", background="#111214", foreground="#f2f3f5", font=("Segoe UI", 13, "bold"))
|
|
style.configure("KpiValueBest.TLabel", background="#111214", foreground="#57f287", font=("Segoe UI", 13, "bold"))
|
|
style.configure("KpiValueProfit.TLabel", background="#111214", foreground="#f2f3f5", font=("Segoe UI", 13, "bold"))
|
|
style.configure("KpiValueLoss.TLabel", background="#111214", foreground="#ed4245", font=("Segoe UI", 13, "bold"))
|
|
style.configure("Accent.TButton", font=("Segoe UI", 10, "bold"), padding=8)
|
|
style.map(
|
|
"Accent.TButton",
|
|
background=[("active", "#4752c4"), ("!disabled", "#5865f2")],
|
|
foreground=[("!disabled", "#ffffff")],
|
|
)
|
|
|
|
style.configure(
|
|
"TEntry",
|
|
fieldbackground="#1e1f22",
|
|
foreground="#f2f3f5",
|
|
bordercolor="#2b2d31",
|
|
insertcolor="#f2f3f5",
|
|
)
|
|
|
|
style.configure("Dark.Vertical.TScrollbar", background="#2b2d31", troughcolor="#1e1f22")
|
|
|
|
app = ttk.Frame(root, style="App.TFrame", padding=(22, 18, 22, 18))
|
|
app.pack(fill="both", expand=True)
|
|
app.grid_columnconfigure(0, weight=1)
|
|
app.grid_columnconfigure(1, weight=1)
|
|
app.grid_rowconfigure(2, weight=1)
|
|
|
|
ttk.Label(app, text="Kelp Profit Dashboard", style="Header.TLabel").grid(
|
|
row=0, column=0, columnspan=2, sticky="w"
|
|
)
|
|
ttk.Label(
|
|
app,
|
|
text="Smelting, crafting, and profit snapshots in one place",
|
|
style="SubHeader.TLabel",
|
|
).grid(row=1, column=0, columnspan=2, sticky="w", pady=(0, 14))
|
|
|
|
input_card = ttk.Frame(app, style="Card.TFrame", padding=(16, 16, 16, 16))
|
|
input_card.grid(row=2, column=0, sticky="nsew", padx=(0, 10))
|
|
input_card.grid_columnconfigure(0, weight=1)
|
|
input_card.grid_columnconfigure(1, weight=1)
|
|
|
|
results_card = ttk.Frame(app, style="Card.TFrame", padding=(16, 16, 16, 16))
|
|
results_card.grid(row=2, column=1, sticky="nsew", padx=(10, 0))
|
|
results_card.grid_columnconfigure(0, weight=1)
|
|
results_card.grid_rowconfigure(4, weight=1)
|
|
|
|
fields = [
|
|
("Kelp Price (k/m/b)", str(KELP_PRICE)),
|
|
("Blaze Rod Price (k/m/b)", str(BLAZE_ROD_PRICE)),
|
|
("Dried Kelp Price (k/m/b)", str(DRIED_KELP_PRICE)),
|
|
("Dried Kelp Block Price (k/m/b)", str(DRIED_KELP_BLOCK_PRICE)),
|
|
("Smokers (k/m/b)", str(SMOKERS)),
|
|
("Kelp Amount (items k/m/b or s/ks/ms/bs)", str(KELP_AMOUNT)),
|
|
]
|
|
|
|
entries = {}
|
|
|
|
ttk.Label(input_card, text="Inputs", style="FieldLabel.TLabel").grid(
|
|
row=0, column=0, columnspan=2, sticky="w", pady=(0, 10)
|
|
)
|
|
|
|
for row, (label_text, default_value) in enumerate(fields):
|
|
label = ttk.Label(input_card, text=label_text, style="FieldLabel.TLabel", anchor="w")
|
|
label.grid(row=row + 1, column=0, sticky="w", padx=(0, 8), pady=6)
|
|
|
|
entry = ttk.Entry(input_card, width=24)
|
|
entry.insert(0, default_value)
|
|
entry.grid(row=row + 1, column=1, sticky="ew", pady=6)
|
|
entries[label_text] = entry
|
|
|
|
kpi_row = ttk.Frame(results_card, style="Card.TFrame")
|
|
kpi_row.grid(row=0, column=0, sticky="ew")
|
|
kpi_row.grid_columnconfigure(0, weight=1)
|
|
kpi_row.grid_columnconfigure(1, weight=1)
|
|
kpi_row.grid_columnconfigure(2, weight=1)
|
|
|
|
kpi_1 = ttk.Frame(kpi_row, style="Card.TFrame", padding=(10, 8, 10, 8))
|
|
kpi_1.grid(row=0, column=0, sticky="nsew", padx=(0, 6))
|
|
kpi_2 = ttk.Frame(kpi_row, style="Card.TFrame", padding=(10, 8, 10, 8))
|
|
kpi_2.grid(row=0, column=1, sticky="nsew", padx=3)
|
|
kpi_3 = ttk.Frame(kpi_row, style="Card.TFrame", padding=(10, 8, 10, 8))
|
|
kpi_3.grid(row=0, column=2, sticky="nsew", padx=(6, 0))
|
|
|
|
ttk.Label(kpi_1, text="Smelt Profit", style="KpiLabel.TLabel").pack(anchor="w")
|
|
kpi_1_value = ttk.Label(kpi_1, text="0.00", style="KpiValue.TLabel")
|
|
kpi_1_value.pack(anchor="w")
|
|
|
|
ttk.Label(kpi_2, text="Craft Profit", style="KpiLabel.TLabel").pack(anchor="w")
|
|
kpi_2_value = ttk.Label(kpi_2, text="0.00", style="KpiValue.TLabel")
|
|
kpi_2_value.pack(anchor="w")
|
|
|
|
ttk.Label(kpi_3, text="Hybrid Profit", style="KpiLabel.TLabel").pack(anchor="w")
|
|
kpi_3_value = ttk.Label(kpi_3, text="0.00", style="KpiValue.TLabel")
|
|
kpi_3_value.pack(anchor="w")
|
|
|
|
metrics_row = ttk.Frame(results_card, style="Card.TFrame")
|
|
metrics_row.grid(row=1, column=0, sticky="ew", pady=(10, 0))
|
|
metrics_row.grid_columnconfigure(0, weight=1)
|
|
metrics_row.grid_columnconfigure(1, weight=1)
|
|
metrics_row.grid_columnconfigure(2, weight=1)
|
|
|
|
smelt_card = ttk.Frame(metrics_row, style="Card.TFrame", padding=(10, 8, 10, 8))
|
|
smelt_card.grid(row=0, column=0, sticky="nsew", padx=(0, 6))
|
|
fuel_card = ttk.Frame(metrics_row, style="Card.TFrame", padding=(10, 8, 10, 8))
|
|
fuel_card.grid(row=0, column=1, sticky="nsew", padx=3)
|
|
kelp_card = ttk.Frame(metrics_row, style="Card.TFrame", padding=(10, 8, 10, 8))
|
|
kelp_card.grid(row=0, column=2, sticky="nsew", padx=(6, 0))
|
|
|
|
ttk.Label(smelt_card, text="Smelt Time", style="KpiLabel.TLabel").pack(anchor="w")
|
|
smelt_time_value = ttk.Label(smelt_card, text="0m", style="KpiValueProfit.TLabel")
|
|
smelt_time_value.pack(anchor="w")
|
|
|
|
ttk.Label(fuel_card, text="Fuel Cost", style="KpiLabel.TLabel").pack(anchor="w")
|
|
fuel_cost_value = ttk.Label(fuel_card, text="0.00", style="KpiValueProfit.TLabel")
|
|
fuel_cost_value.pack(anchor="w")
|
|
|
|
ttk.Label(kelp_card, text="Kelp Cost", style="KpiLabel.TLabel").pack(anchor="w")
|
|
kelp_cost_value = ttk.Label(kelp_card, text="0.00", style="KpiValueProfit.TLabel")
|
|
kelp_cost_value.pack(anchor="w")
|
|
|
|
ttk.Separator(results_card, orient="horizontal").grid(row=2, column=0, sticky="ew", pady=(12, 10))
|
|
|
|
ttk.Label(results_card, text="Full Breakdown", style="FieldLabel.TLabel").grid(row=3, column=0, sticky="w", pady=(0, 8))
|
|
|
|
text_wrap = ttk.Frame(results_card, style="Card.TFrame")
|
|
text_wrap.grid(row=4, column=0, sticky="nsew")
|
|
text_wrap.grid_columnconfigure(0, weight=1)
|
|
text_wrap.grid_rowconfigure(0, weight=1)
|
|
|
|
result_text = tk.Text(
|
|
text_wrap,
|
|
wrap="word",
|
|
height=24,
|
|
bg="#1e1f22",
|
|
fg="#f2f3f5",
|
|
insertbackground="#f2f3f5",
|
|
relief="flat",
|
|
padx=10,
|
|
pady=10,
|
|
font=("Consolas", 10),
|
|
)
|
|
result_text.grid(row=0, column=0, sticky="nsew")
|
|
|
|
scrollbar = ttk.Scrollbar(text_wrap, orient="vertical", command=result_text.yview, style="Dark.Vertical.TScrollbar")
|
|
scrollbar.grid(row=0, column=1, sticky="ns")
|
|
result_text.configure(yscrollcommand=scrollbar.set)
|
|
|
|
result_text.tag_configure("section", foreground="#f2f3f5", font=("Consolas", 10, "bold"))
|
|
result_text.tag_configure("profit_best", foreground="#57f287", font=("Consolas", 10, "bold"))
|
|
result_text.tag_configure("profit_other", foreground="#f2f3f5", font=("Consolas", 10, "bold"))
|
|
result_text.tag_configure("loss", foreground="#ed4245", font=("Consolas", 10, "bold"))
|
|
|
|
button_row = ttk.Frame(input_card, style="Card.TFrame")
|
|
button_row.grid(row=len(fields) + 2, column=0, columnspan=2, sticky="ew", pady=(14, 2))
|
|
button_row.grid_columnconfigure(0, weight=1)
|
|
|
|
calculate_btn = ttk.Button(button_row, text="Calculate", style="Accent.TButton")
|
|
calculate_btn.grid(row=0, column=0, sticky="ew")
|
|
|
|
def on_calculate():
|
|
try:
|
|
data = calculate(
|
|
kelp_price=parse_number_with_suffix(entries["Kelp Price (k/m/b)"].get()),
|
|
blaze_rod_price=parse_number_with_suffix(entries["Blaze Rod Price (k/m/b)"].get()),
|
|
dried_kelp_price=parse_number_with_suffix(entries["Dried Kelp Price (k/m/b)"].get()),
|
|
dried_kelp_block_price=parse_number_with_suffix(entries["Dried Kelp Block Price (k/m/b)"].get()),
|
|
smokers=parse_int_with_suffix(entries["Smokers (k/m/b)"].get(), "SMOKERS"),
|
|
kelp_amount=parse_kelp_amount(entries["Kelp Amount (items k/m/b or s/ks/ms/bs)"].get()),
|
|
)
|
|
except ValueError as exc:
|
|
messagebox.showerror("Invalid input", str(exc))
|
|
return
|
|
|
|
kpi_1_value.configure(text=money(data["profit_smelt_only"]))
|
|
kpi_2_value.configure(text=money(data["profit_craft_only"]))
|
|
kpi_3_value.configure(text=money(data["profit_smelt_then_craft"]))
|
|
|
|
profits = {
|
|
"smelt": data["profit_smelt_only"],
|
|
"craft": data["profit_craft_only"],
|
|
"hybrid": data["profit_smelt_then_craft"],
|
|
}
|
|
max_profit = max(profits.values())
|
|
|
|
def kpi_style(value: float) -> str:
|
|
if value < 0:
|
|
return "KpiValueLoss.TLabel"
|
|
if value == max_profit:
|
|
return "KpiValueBest.TLabel"
|
|
return "KpiValueProfit.TLabel"
|
|
|
|
kpi_1_value.configure(style=kpi_style(profits["smelt"]))
|
|
kpi_2_value.configure(style=kpi_style(profits["craft"]))
|
|
kpi_3_value.configure(style=kpi_style(profits["hybrid"]))
|
|
|
|
smelt_time_value.configure(text=format_duration(data["total_seconds"]))
|
|
fuel_cost_value.configure(text=money(data["fuel_cost"]))
|
|
kelp_cost_value.configure(text=money(data["kelp_cost"]))
|
|
|
|
result_text.delete("1.0", tk.END)
|
|
result_text.insert(tk.END, format_results(data))
|
|
|
|
for heading in ["===", "---"]:
|
|
start = "1.0"
|
|
while True:
|
|
idx = result_text.search(heading, start, tk.END)
|
|
if not idx:
|
|
break
|
|
line_end = result_text.index(f"{idx} lineend")
|
|
result_text.tag_add("section", idx, line_end)
|
|
start = line_end
|
|
|
|
result_text.tag_remove("profit_best", "1.0", tk.END)
|
|
result_text.tag_remove("profit_other", "1.0", tk.END)
|
|
result_text.tag_remove("loss", "1.0", tk.END)
|
|
|
|
line_rules = [
|
|
("Profit (smelt -> sell dried kelp)", profits["smelt"]),
|
|
("Profit (craft blocks only)", profits["craft"]),
|
|
("Profit (smelt -> craft -> sell blocks + leftover dried)", profits["hybrid"]),
|
|
]
|
|
|
|
for label, value in line_rules:
|
|
idx = result_text.search(label, "1.0", tk.END)
|
|
if not idx:
|
|
continue
|
|
line_end = result_text.index(f"{idx} lineend")
|
|
if value < 0:
|
|
result_text.tag_add("loss", idx, line_end)
|
|
elif value == max_profit:
|
|
result_text.tag_add("profit_best", idx, line_end)
|
|
else:
|
|
result_text.tag_add("profit_other", idx, line_end)
|
|
|
|
calculate_btn.configure(command=on_calculate)
|
|
|
|
on_calculate()
|
|
root.mainloop()
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
if TK_AVAILABLE:
|
|
try:
|
|
launch_ui()
|
|
except tk.TclError:
|
|
main()
|
|
else:
|
|
print(f"UI unavailable ({TK_IMPORT_ERROR}). Falling back to CLI output.\n")
|
|
main()
|
|
except KeyboardInterrupt:
|
|
print("\nInterrupted by user. Exiting.") |