Compare commits

..

4 Commits

Author SHA1 Message Date
Nayan Sawyer
48420248f2 fix bloom scrip for server and add black steel script 2026-04-05 22:02:32 -04:00
Nayan Sawyer
8971823c56 update todo 2026-04-05 21:35:02 -04:00
Nayan Sawyer
5f73428fda add calculator script for forge recipes 2026-04-05 21:33:47 -04:00
Nayan Sawyer
8356e14304 update readme with script guide, calculator section, and fix uv run in commands 2026-04-05 21:33:18 -04:00
4 changed files with 278 additions and 60 deletions

View File

@@ -11,6 +11,8 @@ against named slots and buttons so you can run repeatable forging sequences.
- [Quick Start](#quick-start) - [Quick Start](#quick-start)
- [Installation (details)](#installation-details) - [Installation (details)](#installation-details)
- [Coordinate Setup](#coordinate-setup) - [Coordinate Setup](#coordinate-setup)
- [Writing macro scripts](#writing-macro-scripts)
- [Calculating the forge recipes](#calculating-the-forge-recipes)
- [Running the Macro](#running-the-macro) - [Running the Macro](#running-the-macro)
- [Running the macro through Macro Deck](#running-the-macro-through-macro-deck) - [Running the macro through Macro Deck](#running-the-macro-through-macro-deck)
- [Shift-Click Support](#shift-click-support) - [Shift-Click Support](#shift-click-support)
@@ -41,6 +43,7 @@ changes, you can refresh the saved rectangles without fully recalibrating everyt
- `pynput` - `pynput`
- A desktop session where Python is allowed to control the mouse and keyboard - A desktop session where Python is allowed to control the mouse and keyboard
- The TerraFirmaCraft anvil UI open and visible, and an ingot with more than one page worth of recipes (like wrought iron) when you run the setup.py script - The TerraFirmaCraft anvil UI open and visible, and an ingot with more than one page worth of recipes (like wrought iron) when you run the setup.py script
- The [TFC Anvil Helper](https://www.curseforge.com/minecraft/texture-packs/tfc-anvil-helper) resource pack installed. This is not _strictly_ required, the scripts will work the same, but letter codes for different actions is based on the colors from the resource pack, so it might be difficult to write scripts without it until you've done it a million times.
- The TerraFirmaCraft anvil UI open and visible when you run the macro - The TerraFirmaCraft anvil UI open and visible when you run the macro
- [uv](https://docs.astral.sh/uv/getting-started/installation/) installed if you want to use it to install the dependencies and run the scripts - [uv](https://docs.astral.sh/uv/getting-started/installation/) installed if you want to use it to install the dependencies and run the scripts
@@ -58,13 +61,13 @@ uv sync
3. Run a full calibration once: 3. Run a full calibration once:
```powershell ```powershell
python setup.py -f uv run setup.py -f
``` ```
4. Test the macro: 4. Test the macro:
```powershell ```powershell
python main.py -s 2000 uv run main.py -s 2000
``` ```
5. Once you confirm the clicks land correctly, run longer sequences directly or from a file. (The `-s 2000` is a 2 second delay before the macro starts, you can remove it if you want the macro to start immediately). 5. Once you confirm the clicks land correctly, run longer sequences directly or from a file. (The `-s 2000` is a 2 second delay before the macro starts, you can remove it if you want the macro to start immediately).
@@ -82,8 +85,8 @@ uv sync
Then run commands with: Then run commands with:
```powershell ```powershell
uv run python setup.py --help uv run setup.py --help
uv run python main.py --help uv run main.py --help
``` ```
### Option 2: Use a normal virtual environment ### Option 2: Use a normal virtual environment
@@ -101,6 +104,8 @@ python setup.py --help
python main.py --help python main.py --help
``` ```
If you use a normal virtual environment, replace `uv run` with `python` in all the example commands in this README.
## Coordinate Setup ## Coordinate Setup
`setup.py` is interactive. It waits for you to click points on the screen and writes the `setup.py` is interactive. It waits for you to click points on the screen and writes the
@@ -113,7 +118,7 @@ Press `Esc` at any time during setup to abort.
Run this the first time, or any time the stored offsets are wrong: Run this the first time, or any time the stored offsets are wrong:
```powershell ```powershell
python setup.py -f uv run setup.py -f
``` ```
Full setup records: Full setup records:
@@ -151,7 +156,7 @@ If you already have a good `coords.json`, and only the UI position/scale changed
without `-f`: without `-f`:
```powershell ```powershell
python setup.py uv run setup.py
``` ```
In this mode: In this mode:
@@ -163,18 +168,57 @@ In this mode:
This is useful after changing resolution, window placement, or UI scale while keeping the This is useful after changing resolution, window placement, or UI scale while keeping the
same relative layout, and is a much faster way to recalibrate than a full setup. You can still perform a full setup any time you want, which will overwrite the existing coordinates file when you're done. same relative layout, and is a much faster way to recalibrate than a full setup. You can still perform a full setup any time you want, which will overwrite the existing coordinates file when you're done.
## Writing macro scripts
The scripting language is based on the colors from the TFC Anvil Helper resource pack.
| Symbol | Value | Color | Action Name |
| ------ | ----- | ----------- | ----------- |
| `L` | -3 | Light Blue | Light Hit |
| `M` | -6 | Medium Blue | Medium Hit |
| `D` | -9 | Dark Blue | Hard Hit |
| `P` | -15 | Purple | Draw |
| `G` | +2 | Green | Punch |
| `Y` | +7 | Yellow | Bend |
| `O` | +13 | Orange | Upset |
| `R` | +16 | Red | Shrink |
Other than the action letters, you can also perform the following actions:
| Symbol | Action |
| ------------------ | ------------------------------------------------------------------------------------------------ |
| `plans` | Open the plans menu |
| `weld` | Click the weld button |
| `input1` | Click the first input slot |
| `input2` | Click the second input slot |
| `rlb` | Click the left recipe scroll button on the recipe UI |
| `rrb` | Click the right recipe scroll button on the recipe UI |
| `i1` through `i27` | Click the corresponding inventory slot, top left to bottom right, in the anvil screen (9x3 grid) |
| `h1` through `h9` | Click the corresponding hotbar slot, left to right |
| `r1` through `r18` | Click the corresponding recipe slot, top left to bottom right |
## Calculating the forge recipes
You can manually figure out the optimal forge recipe with the [TFC Anvil Helper](https://www.curseforge.com/minecraft/texture-packs/tfc-anvil-helper) resource pack, but even with the helper it can be a pain to figure out the optimal recipe. This is where the `calculate_forge.py` script comes in.
It is an interactive script that will ask for three final actions and the target value (you need TFC Anvil Helper to know the target value for the action). It then calculates the optimal recipe for you, and outputs the string of anvil actions. You can copy that and run it through the macro directly, or load it up into a file with other actions to run lots at a time.
```powershell
uv run calculate_forge.py
```
## Running the macro directly ## Running the macro directly
The main runner replays a list of slot names: The main runner replays a list of slot names:
```powershell ```powershell
python main.py SLOT SLOT SLOT uv run main.py SLOT SLOT SLOT
``` ```
Example: Example:
```powershell ```powershell
python main.py -s 1500 i1 i2 i3 uv run main.py -s 1500 i1 i2 i3
``` ```
This waits for the start delay, then clicks the first three inventory slots in order. This waits for the start delay, then clicks the first three inventory slots in order.
@@ -220,7 +264,7 @@ Prefix any slot token with `_` to hold `Shift` while clicking it.
Example: Example:
```powershell ```powershell
python main.py _i1 _i2 _i3 uv run main.py _i1 _i2 _i3
``` ```
`_i2` means "shift-click `i2`". `_i2` means "shift-click `i2`".
@@ -243,7 +287,7 @@ R R Y Y Y L L L
Run it with: Run it with:
```powershell ```powershell
python main.py --file scripts\example.txt uv run main.py --file scripts\example.txt
``` ```
Token parsing rules: Token parsing rules:
@@ -281,16 +325,16 @@ Options:
Examples: Examples:
```powershell ```powershell
python main.py -s 1200 -d 150 L M D uv run main.py -s 1200 -d 150 L M D
python main.py -c alt-coords.json R R O Y uv run main.py -c alt-coords.json R R O Y
python main.py --file scripts\forge-sequence.txt uv run main.py --file scripts\forge-sequence.txt
python main.py --move-settle-ms 40 --click-hold-ms 50 --post-click-ms 40 L L L uv run main.py --move-settle-ms 40 --click-hold-ms 50 --post-click-ms 40 L L L
``` ```
### `setup.py` ### `setup.py`
```powershell ```powershell
python setup.py [options] uv run setup.py [options]
``` ```
Options: Options:
@@ -303,9 +347,9 @@ Options:
Examples: Examples:
```powershell ```powershell
python setup.py -f uv run setup.py -f
python setup.py uv run setup.py
python setup.py -f -o custom-coords.json uv run setup.py -f -o custom-coords.json
``` ```
## How `coords.json` Works ## How `coords.json` Works
@@ -343,8 +387,9 @@ That means:
### Clicks land slightly off target ### Clicks land slightly off target
- Rerun `python setup.py` - Make sure the TFC Anvil Helper resource pack is installed if you're lost with the letter codes
- If the layout changed completely, rerun `python setup.py -f` - Rerun `uv run setup.py`
- If the layout changed completely, rerun `uv run setup.py -f`
- Increase `--move-settle-ms` a little - Increase `--move-settle-ms` a little
- Increase `--click-hold-ms` if the game misses short clicks - Increase `--click-hold-ms` if the game misses short clicks
@@ -360,13 +405,13 @@ Increase:
Refresh calibration: Refresh calibration:
```powershell ```powershell
python setup.py uv run setup.py
``` ```
If that does not fix it: If that does not fix it:
```powershell ```powershell
python setup.py -f uv run setup.py -f
``` ```
### `coords.json` is missing ### `coords.json` is missing
@@ -374,7 +419,7 @@ python setup.py -f
Create it first: Create it first:
```powershell ```powershell
python setup.py -f uv run setup.py -f
``` ```
### A slot name causes an error ### A slot name causes an error
@@ -388,5 +433,5 @@ Make sure the token is one of the names listed in this README. `main.py` only un
## TODO ## TODO
- [ ] - Double check the anvil action colors without the resource pack and possibly change the letters or update the README to reflect the base mod - [x] - Double check the anvil action colors without the resource pack and possibly change the letters or update the README to reflect the base mod
- [ ] - Add support for actions other than clicking (like pressing keys) - [ ] - Add support for actions other than clicking (like pressing keys)

158
calculate_forge.py Normal file
View File

@@ -0,0 +1,158 @@
# Calculate the forge recipe for a given item
from collections import deque
import json
import time
actions = {
"L": -3,
"M": -6,
"D": -9,
"P": -15,
"G": +2,
"Y": +7,
"O": +13,
"R": +16,
}
def gen_set_of_hits(num):
set = []
if num == 1:
for i in ["L", "M", "D"]:
set.append([i])
if num == 2:
for i in ["L", "M", "D"]:
for j in ["L", "M", "D"]:
set.append([i, j])
if num == 3:
for i in ["L", "M", "D"]:
for j in ["L", "M", "D"]:
for k in ["L", "M", "D"]:
set.append([i, j, k])
return set
def find_shortest_path(target, actions, timeout_seconds=5):
# If starting at 0 and target is 0
if target == 0:
return []
# Queue stores tuples of (current_sum, path as list of action labels)
queue = deque([(0, [])])
# Visited set to prevent infinite loops and redundant work
visited = {0}
start_time = time.perf_counter()
while queue:
if time.perf_counter() - start_time >= timeout_seconds:
return None
current_sum, path = queue.popleft()
for label, value in actions.items():
next_sum = current_sum + value
if next_sum == target:
return path + [label]
if next_sum not in visited:
# Optional: limit the search range if target is reasonably small
# to prevent searching forever if a solution is impossible
if -1000 < next_sum < 1000:
visited.add(next_sum)
queue.append((next_sum, path + [label]))
return None
def calculate_recipe(last_action, second_to_last_action, third_to_last_action, target_value):
recipe = []
first_target = target_value
if last_action != "":
first_target -= actions[last_action]
if second_to_last_action != "":
first_target -= actions[second_to_last_action]
if third_to_last_action != "":
first_target -= actions[third_to_last_action]
path = find_shortest_path(first_target, actions)
if path is None:
return ["No solution found"]
recipe.extend(path)
if third_to_last_action != "":
recipe.append(third_to_last_action)
if second_to_last_action != "":
recipe.append(second_to_last_action)
if last_action != "":
recipe.append(last_action)
return recipe
def main():
# Get the three final actions and the target value from the user
print("Welcome to the forge recipe calculator!")
print("Enter the three final actions required for this recipe (L, M, D, P, G, Y, O, R). Enter 'H' if any hit is allowed for that action. Hit enter without typing anything to skip an action (for example, if only two final actions are required, enter them, and then hit enter to skip the third).")
last_action = input("Enter the last action: ")
second_to_last_action = input("Enter the second to last action: ")
third_to_last_action = input("Enter the third to last action: ")
target_value = int(input("Enter the target value for the recipe: "))
print("Calculating the recipe...")
# Calculate the recipe variants for the given actions and target value
solutions = []
if last_action == "H":
if second_to_last_action == "H":
if third_to_last_action == "H":
for hit in gen_set_of_hits(3):
solutions.append(calculate_recipe(hit[0], hit[1], hit[2], target_value))
else:
for hit in gen_set_of_hits(2):
solutions.append(calculate_recipe(hit[0], hit[1], third_to_last_action, target_value))
else:
for hit in gen_set_of_hits(1):
solutions.append(calculate_recipe(hit[0], second_to_last_action, third_to_last_action, target_value))
elif second_to_last_action == "H":
if third_to_last_action == "H":
for hit in gen_set_of_hits(2):
solutions.append(calculate_recipe(last_action, hit[0], hit[1], target_value))
else:
for hit in gen_set_of_hits(1):
solutions.append(calculate_recipe(last_action, hit[0], third_to_last_action, target_value))
elif third_to_last_action == "H":
for hit in gen_set_of_hits(1):
solutions.append(calculate_recipe(last_action, second_to_last_action, hit[0], target_value))
else:
solutions.append(calculate_recipe(last_action, second_to_last_action, third_to_last_action, target_value))
print(f"Found {len(solutions)} solutions")
if len(solutions) == 0:
print("No solutions found")
exit()
# Find the shortest recipe
lengths = [len(solution) for solution in solutions]
shortest_length_index = 0
for index, solution in enumerate(solutions):
if len(solution) < lengths[shortest_length_index]:
shortest_length_index = index
# Print the shortest recipe
recipe = solutions[shortest_length_index]
recipe_string = ""
for action in recipe:
recipe_string += action + " "
print(f"The shortest recipe is: \n{recipe_string}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,24 @@
_i1
_i2
weld
G Y R R R R R L L L
_input1
_input2
_i3
_i4
weld
G Y R R R R R L L L
_input1
_input2
_i5
_i6
weld
G Y R R R R R L L L
_input1
_input2
_i7
_i8
weld
G Y R R R R R L L L
_input1
_input2

View File

@@ -1,45 +1,36 @@
_i1 _i1
R R O Y L L L G Y O O R L L L
R R Y Y Y L L L G G Y O O R L L L
-input2 _input2
+q
_i2 _i2
R R O Y L L L G Y O O R L L L
R R Y Y Y L L L G G Y O O R L L L
-input2 _input2
+q
_i3 _i3
R R O Y L L L G Y O O R L L L
R R Y Y Y L L L G G Y O O R L L L
-input2 _input2
+q
_i4 _i4
R R O Y L L L G Y O O R L L L
R R Y Y Y L L L G G Y O O R L L L
-input2 _input2
+q
_i5 _i5
R R O Y L L L G Y O O R L L L
R R Y Y Y L L L G G Y O O R L L L
-input2 _input2
+q
_i6 _i6
R R O Y L L L G Y O O R L L L
R R Y Y Y L L L G G Y O O R L L L
-input2 _input2
+q
_i7 _i7
R R O Y L L L G Y O O R L L L
R R Y Y Y L L L G G Y O O R L L L
-input2 _input2
+q
_i8 _i8
R R O Y L L L G Y O O R L L L
R R Y Y Y L L L G G Y O O R L L L
-input2 _input2
+q
_i9 _i9
R R O Y L L L G Y O O R L L L
R R Y Y Y L L L G G Y O O R L L L
-input2 _input2
+q