big ass modding update

This commit is contained in:
bryanthaboi
2026-07-19 16:18:18 -04:00
parent b5a673b252
commit 47923d95b3
258 changed files with 31048 additions and 2310 deletions
@@ -0,0 +1,12 @@
# Changelog
Format: [keep a changelog](https://keepachangelog.com/en/1.1.0/).
Version headings match `manifest.json`'s `version`.
## 1.0.0
### Added
- Base speed patches for VENUSAUR, CHARIZARD and BLASTOISE.
- TM price halving driven off `content.items:each()`.
- Route 1 grass re-slot.
@@ -0,0 +1,59 @@
# Balance Tweaks Example
Raises the final-stage Kanto starters to 100 base speed, halves every TM
price, and re-slots the Route 1 grass table — all without copying a single
record whole.
**Persona: the Tweaker.** A player who wants one number changed and copies
an example to get there. This is the shortest complete mod in the gallery
and the one to start from.
## Try it
```sh
python3 tools/modkit.py validate mods/examples/example_balance_tweaks --base imported
luajit mods/examples/example_balance_tweaks/tests/example_balance_tweaks_test.lua
```
Then enable it: add `example_balance_tweaks = true` under `mods` in your
`options.lua`, or toggle it in the F10 mod manager.
## What it demonstrates
| Seam | Where |
|---|---|
| `content.pokemon:patch` | `main.lua` — deep-merges one leaf, leaves the rest alone |
| `content.items:each` | `main.lua` — walks the merged view to find TMs instead of listing them |
| `content.encounters:patch` | `main.lua` — a list leaf replaces wholesale even inside a patch |
| `content.<r>:get` | `main.lua` — the guard that turns a missing id into a log line, not a crash |
`patch` is the point. The legacy `mods/example_mew_starter` has to copy
every field of Mew to change two sprite paths, because api 1 only had
`override`. With `patch` you name the leaf:
```lua
mod.content.pokemon:patch("VENUSAUR", { baseStats = { speed = 100 } })
```
Everything not named — `learnset`, `types`, `evolutions`, `spriteFront`
keeps its base value, and a second mod patching `baseStats.attack` on the
same species composes with this one instead of clobbering it.
## Exact changes
- `VENUSAUR` base speed 80 → 100
- `BLASTOISE` base speed 78 → 100
- `CHARIZARD` base speed 100 → 100 (already there; kept for symmetry)
- every item whose `machine.kind == "TM"` has its `price` halved
(`TM_TOXIC` 4000 → 2000, and 49 others; HMs carry `machine.kind == "HM"`
and are left alone)
- `ROUTE_1` grass `rate` 25 → 20, slot table re-weighted to include
`SPEAROW`
Field meanings are in the generated registry reference (`modkit docs`),
section `pokemon`, `items` and `encounters`.
## Credits
Base stat and mart price tables come from the player's own imported ROM;
this mod ships numbers, not data.
@@ -0,0 +1,44 @@
-- Gallery #1 (Tweaker): pure data, no engine seams. Everything here is
-- patch + each, so no record is ever copied whole and another mod editing
-- the same species keeps its own fields.
return function(mod)
-- patch deep-merges only the leaves it names: learnset, sprites, types
-- and evolutions all survive this speed change untouched
for _, id in ipairs({ "VENUSAUR", "CHARIZARD", "BLASTOISE" }) do
if mod.content.pokemon:get(id) then
mod.content.pokemon:patch(id, { baseStats = { speed = 100 } })
else
-- degrade instead of crashing: a species mod loaded ahead of this
-- one may have removed the vanilla starter line
mod.log:warn("%s missing from the merged view; speed patch skipped", id)
end
end
-- each() walks the merged view (engine records plus every mod ahead of
-- this one), so the TM list is discovered rather than hard-coded
local halved = 0
for id, item in mod.content.items:each() do
local machine = item.machine
if machine and machine.kind == "TM" and type(item.price) == "number"
and item.price > 0 then
mod.content.items:patch(id, { price = math.floor(item.price / 2) })
halved = halved + 1
end
end
mod.log:info("halved %d TM prices", halved)
-- lists replace wholesale even inside a patch, so a re-slotted encounter
-- table is written out in full while the rate rides along as a leaf
mod.content.encounters:patch("ROUTE_1", {
grass = {
rate = 20,
slots = {
{ level = 3, species = "PIDGEY" }, { level = 3, species = "RATTATA" },
{ level = 4, species = "SPEAROW" }, { level = 2, species = "RATTATA" },
{ level = 2, species = "PIDGEY" }, { level = 3, species = "SPEAROW" },
{ level = 3, species = "PIDGEY" }, { level = 4, species = "RATTATA" },
{ level = 4, species = "PIDGEY" }, { level = 5, species = "SPEAROW" },
},
},
})
end
@@ -0,0 +1,15 @@
{
"id": "example_balance_tweaks",
"name": "Balance Tweaks Example",
"version": "1.0.0",
"api": 2,
"entry": "main.lua",
"profile": "content",
"category": "BALANCE",
"game_version": ">=1.0.0 <2.0.0",
"priority": 100,
"dependencies": [],
"optional_dependencies": [],
"conflicts": [],
"description": "Tweaker gallery entry: patch and each over the merged view, with no whole-record copies."
}
@@ -0,0 +1,21 @@
-- Sharing metadata (25-community-and-ecosystem.md 3.2). Read by tooling
-- and the manager detail pane; never by the loader's merge.
return {
summary = "Faster final starters, half-price TMs, a re-slotted Route 1.",
author = "Pokemon Gen 1 Recompilation Project",
contact = "https://github.com/bryanthaboi/pokemon-gen1-recomp-project",
tags = { "balance", "data-only", "beginner" },
differences = {
changed = {
"VENUSAUR and BLASTOISE base speed raised to 100 (CHARIZARD already was)",
"every TM item's price halved",
"Route 1 grass rate 25 -> 20 and SPEAROW added to the slot table",
},
added = {},
known = { "TM prices halve once per load, not once per install" },
},
credits = {
{ who = "pret/pokered", for_ = "the base stat and mart price tables this patches over" },
},
compat = { engine = ">=1.0.0 <2.0.0", modApi = 2 },
}
@@ -0,0 +1,28 @@
-- Standalone: luajit mods/examples/example_balance_tweaks/tests/example_balance_tweaks_test.lua
-- Loads the mod through the real headless loader and asserts its stated
-- effect against the player's imported dataset.
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.modkit")
local Data = require("src.core.Data")
Data:load()
local run = T.sdk.loadMod("mods/examples/example_balance_tweaks", { data = Data })
T.eq(#run.errors, 0, "loads clean (" .. tostring(run.errors[1]) .. ")")
T.eq(run.mod and run.mod.state, "loaded", "reached the loaded state")
T.eq(Data.pokemon.VENUSAUR.baseStats.speed, 100, "VENUSAUR speed patched")
T.eq(Data.pokemon.BLASTOISE.baseStats.speed, 100, "BLASTOISE speed patched")
-- the patch named one leaf, so everything else survived
T.check(#Data.pokemon.VENUSAUR.learnset > 0, "VENUSAUR keeps its learnset")
T.eq(Data.pokemon.VENUSAUR.types[1], "GRASS", "VENUSAUR keeps its types")
T.eq(Data.items.TM_TOXIC.price, 2000, "TM price halved")
T.eq(Data.items.POTION.price, 300, "a non-TM item is untouched")
T.eq(Data.encounters.ROUTE_1.grass.rate, 20, "Route 1 grass rate patched")
T.eq(Data.encounters.ROUTE_1.grass.slots[3].species, "SPEAROW",
"Route 1 slot table re-slotted")
run.release()
T.finish("example_balance_tweaks")