Compare commits

..

3 Commits

Author SHA1 Message Date
bryanthaboi e8b7c468a4 fix ttf text erased after a box, and type names outside battle
drawBox left the color white, which tiles ignore but ttf text does not, so
every label after a box drew white on white.

typechart only loaded on entering a battle, so the summary screen got raw
type ids back instead of display names.
2026-08-04 09:39:08 -04:00
bryanthaboi 33998aab5d add ttf tiles option
lets a translation keep chosen characters on the rom tiles instead of the ttf.
needed for japanese: sizing the font for kana makes latin narrower, which
knocks the party menu numbers out of line.
2026-08-04 09:11:05 -04:00
bryanthaboi 6cfc85ca83 icons update 2026-08-04 06:52:06 -04:00
12 changed files with 207 additions and 16 deletions
+11 -2
View File
@@ -386,8 +386,17 @@ Vautour (Burpy Fresh), licensed under CC-BY 4.0 (5x11 base characters,
the registry entry: `file` for a mod-shipped TTF, `size` (the font's
design em; Plain Pixel rasterizes cleanly only at multiples of 15),
`spacing` added to every advance, `yOffset` for vertical alignment
against the 8px cell grid, and `bold`, which double-prints at a 1px
offset for fonts whose strokes read too light.
against the 8px cell grid, `bold`, which double-prints at a 1px
offset for fonts whose strokes read too light, and `tiles`, the
characters that keep their ROM tile instead of coming from the TTF.
`tiles` matters for a CJK translation. Sizing the font so a kana fills
the 8px cell leaves Latin narrower than the tile font it replaces, which
pulls the numeric columns out of line: the party menu's `:L12` stops
sitting over `34/ 34`. Naming `"0123456789/:"` keeps those on the
vanilla tiles, so numbers render exactly as they do in English while
kana still come from the font. It takes a string of characters, or a
list when a multi-character charmap sequence is meant.
See the wiki's Translations guide.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 42 KiB

-7
View File
@@ -12,13 +12,6 @@
"tintColor": "3b5ca8",
"category": "games",
"versions": [
{
"version": "0.1.65",
"date": "2026-08-03",
"size": 9376618,
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.65/gen1recomp-0.1.65-ios.ipa",
"localizedDescription": "Download the correct version for your computer below.\n\n## Issues closed\n\n- #592 Screen Orientation\n- #702 FLY overworld animation incorrect/incomplete\n- #748 Launcher Menu has overlap\n- #754 Can push boulders with STRENGTH through walls\n- #758 PC to Mac Online Multiplayer Disconnects Shortly After Starting Match\n\n## Contributors\n\n- @andrewqsantos\n- @Bortlesboat\n- @bryanthaboi\n- @castdrian\n- @johnjohto\n- @ShaneMcGovernIE"
},
{
"version": "0.1.64",
"date": "2026-08-03",
+90 -3
View File
@@ -121,6 +121,32 @@ else
say "version '$VERSION' is not X.Y.Z, shipping default engine (no stamp)"
fi
# --------------------------------------------------------------- app icon
# One source of truth for every platform's launcher icon; iOS resizes the
# same file in scripts/build_ios.sh (apply_ios_icon) and the Android res/
# drawables are generated from it too.
ICON_SRC="$ROOT/assets/logo/gen1recomp_cover.png"
# pipx installs peresed (Windows exe icon patcher) here, off the default PATH.
PATH="$PATH:$HOME/.local/bin"
make_icns() { # $1 = output .icns path
[ -f "$ICON_SRC" ] || fail "missing icon source: $ICON_SRC"
local iconset="$WORK/GameIcon.iconset" size scaled
rm -rf "$iconset"; mkdir -p "$iconset"
for size in 16 32 128 256 512; do
sips -z "$size" "$size" "$ICON_SRC" --out "$iconset/icon_${size}x${size}.png" >/dev/null
scaled=$((size * 2))
sips -z "$scaled" "$scaled" "$ICON_SRC" --out "$iconset/icon_${size}x${size}@2x.png" >/dev/null
done
iconutil -c icns "$iconset" -o "$1"
}
make_ico() { # $1 = output .ico path
[ -f "$ICON_SRC" ] || fail "missing icon source: $ICON_SRC"
magick "$ICON_SRC" -define icon:auto-resize=256,128,64,48,32,16 "$1"
}
# --------------------------------------------------------------- macOS
build_mac() {
say "building macOS app"
@@ -147,9 +173,21 @@ build_mac() {
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION" "$plist" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string $VERSION" "$plist"
if [ -f "$ROOT/assets/icon.icns" ]; then
cp "$ROOT/assets/icon.icns" "$out_app/Contents/Resources/GameIcon.icns"
# Brand the app icon. LÖVE.app resolves its icon through CFBundleIconName ->
# Assets.car first, so overwriting the loose .icns files alone changes
# nothing; the compiled asset catalog has to go and the plist has to fall
# back to CFBundleIconFile.
local icns="$ROOT/assets/icon.icns"
if [ ! -f "$icns" ]; then
icns="$WORK/GameIcon.icns"
make_icns "$icns"
fi
cp "$icns" "$out_app/Contents/Resources/GameIcon.icns"
cp "$icns" "$out_app/Contents/Resources/OS X AppIcon.icns"
rm -f "$out_app/Contents/Resources/Assets.car"
/usr/libexec/PlistBuddy -c "Delete :CFBundleIconName" "$plist" 2>/dev/null || true
/usr/libexec/PlistBuddy -c "Set :CFBundleIconFile OS X AppIcon" "$plist" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :CFBundleIconFile string 'OS X AppIcon'" "$plist"
local id="$IDENTITY"
if [ -z "$id" ]; then
@@ -223,7 +261,47 @@ build_win() {
cp "$love_dir"/*.dll "$out_dir"/
cp "$love_dir"/license.txt "$out_dir"/ 2>/dev/null || true
cat "$love_dir/love.exe" "$LOVE_FILE" > "$out_dir/$APP_NAME.exe"
# The exe's icon lives in love.exe's PE resources, so it must be patched
# BEFORE the .love is appended: peresed rewrites the whole file and would
# drop the fused bytes. peresed (pipx install pe_tools) has no .ico input,
# only raw --set-resource, so split the .ico into RT_ICON blobs plus a
# GRPICONDIR that reuses love.exe's existing resource ids (1..N, lang 1033).
local ico="$WORK/$APP_NAME.ico"
make_ico "$ico"
if command -v peresed >/dev/null 2>&1; then
local ico_parts="$WORK/ico-parts"
rm -rf "$ico_parts"; mkdir -p "$ico_parts"
python3 - "$ico" "$ico_parts" <<'PY'
import struct, sys
data = open(sys.argv[1], "rb").read()
outdir = sys.argv[2]
count = struct.unpack_from("<H", data, 4)[0]
group = struct.pack("<HHH", 0, 1, count)
for i in range(count):
w, h, colors, res, planes, bpp, size, off = struct.unpack_from("<BBBBHHII", data, 6 + 16 * i)
open("%s/icon_%d.bin" % (outdir, i + 1), "wb").write(data[off:off + size])
group += struct.pack("<BBBBHHIH", w, h, colors, res, planes, bpp, size, i + 1)
open(outdir + "/group.bin", "wb").write(group)
print(count)
PY
local n_icons args=()
n_icons=$(ls "$ico_parts" | grep -c '^icon_')
for i in $(seq 1 "$n_icons"); do
args+=(-R RT_ICON "#$i" 1033 "$ico_parts/icon_$i.bin")
done
args+=(-R RT_GROUP_ICON "#1" 1033 "$ico_parts/group.bin")
local exe_branded="$WORK/love-branded.exe"
cp "$love_dir/love.exe" "$exe_branded"
if peresed "${args[@]}" "$exe_branded" >/dev/null; then
cat "$exe_branded" "$LOVE_FILE" > "$out_dir/$APP_NAME.exe"
else
warn "peresed failed to patch the exe icon, shipping stock LÖVE icon"
cat "$love_dir/love.exe" "$LOVE_FILE" > "$out_dir/$APP_NAME.exe"
fi
else
warn "peresed not found (pipx install pe_tools), shipping stock LÖVE exe icon"
cat "$love_dir/love.exe" "$LOVE_FILE" > "$out_dir/$APP_NAME.exe"
fi
local zip_out="$DIST/win/$APP_NAME-win64.zip"
rm -f "$zip_out"
@@ -278,6 +356,15 @@ build_linux() {
unsquashfs -q -no-xattrs -o "$sfs_offset" -d "$appdir" "$love_appimage" >/dev/null
cp "$LOVE_FILE" "$appdir/game.love"
# The .desktop's Icon=love resolves against the AppDir root by basename,
# so drop the stock love.svg and provide our PNG under the same name;
# .DirIcon is what appimaged/thumbnailers show for the file itself.
[ -f "$ICON_SRC" ] || fail "missing icon source: $ICON_SRC"
rm -f "$appdir/love.svg" "$appdir/.DirIcon"
sips -z 512 512 "$ICON_SRC" --out "$appdir/love.png" >/dev/null
cp "$appdir/love.png" "$appdir/.DirIcon"
sed -i '' 's|^#FUSE_PATH="$APPDIR/my_game.love"$|FUSE_PATH="$APPDIR/game.love"|' "$appdir/AppRun"
grep -q '^FUSE_PATH="\$APPDIR/game.love"$' "$appdir/AppRun" \
|| fail "failed to enable FUSE_PATH in AppRun (upstream AppRun changed?)"
+7
View File
@@ -41,6 +41,13 @@ function Game:load()
-- render pipelines dispatch off the merged dataset; point them at the
-- one the mods just merged into before anything can draw a frame
require("src.render.Pipelines").install(Data)
-- Same reason, same moment: TypeChart caches the merged type records in an
-- upvalue, and until now only BattleState loaded it, on entering a battle.
-- Every non-battle reader of a type -- the summary screen's TYPE1/TYPE2
-- rows, the move-select TYPE/ box -- ran against an unloaded module and got
-- the raw id back instead of the display name, so a translation could not
-- reach them. Loading here means a type reads the same whoever asks first.
require("src.battle.TypeChart").load(Data)
self.input = Input
Input:init()
+6 -2
View File
@@ -1079,7 +1079,11 @@ R.font = {
f.rec{ seq = f.str, code = f.int(0) },
f.rec{ file = f.opt(f.path), size = f.opt(f.int(1)),
spacing = f.opt(f.num), yOffset = f.opt(f.num),
bold = f.opt(f.bool) },
bold = f.opt(f.bool),
-- characters that keep their ROM tile instead of coming from the
-- TTF: a string of them, or a list when a multi-character charmap
-- sequence is meant (src/render/Font.lua)
tiles = f.opt(f.union{ f.str, f.list(f.str) }) },
},
extra = function(id, value)
if fontIsCharmap(id) then
@@ -1092,7 +1096,7 @@ R.font = {
elseif fontIsTtf(id) then
-- every field optional: {} is "the bundled font at its native size"
if value.image ~= nil or value.base ~= nil then
return 'the "ttf" entry takes file/size/spacing/yOffset/bold, not a page'
return 'the "ttf" entry takes file/size/spacing/yOffset/bold/tiles, not a page'
end
elseif value.image == nil or value.base == nil then
return "a font page needs an image and a base"
+50 -2
View File
@@ -55,6 +55,34 @@ local function pagesOf(def)
return pages
end
-- ttf.tiles as a lookup keyed by charmap sequence. Accepts a plain string
-- ("0123456789"), which is split into UTF-8 characters, or a list of
-- sequences ({ "0", "1", "<PK>" }) when a multi-character macro is meant.
local function tileSet(spec)
local set = {}
if type(spec) == "table" then
for _, seq in ipairs(spec) do set[tostring(seq)] = true end
elseif type(spec) == "string" then
-- split on UTF-8 lead bytes rather than utf8Decode, which this file
-- declares further down and would be nil here
local i, n = 1, #spec
while i <= n do
local last = i
if spec:byte(i) >= 0xC0 then
local k = i + 1
while k <= n do
local b = spec:byte(k)
if b < 0x80 or b > 0xBF then break end
last, k = k, k + 1
end
end
set[spec:sub(i, last)] = true
i = last + 1
end
end
return set
end
function Font.load(data)
loadedFrom = data
local def = data.font
@@ -128,6 +156,15 @@ function Font.load(data)
-- caps line up and descenders hang below as the GB font's own do
yOffset = def.ttf.yOffset or (obj.getBaseline
and (GLYPH - 1 - obj:getBaseline()) or (GLYPH - obj:getHeight())),
-- Single characters that keep their ROM tile instead of coming from
-- the TTF. A CJK translation sizes the font so a kana fills the 8px
-- cell, which leaves Latin narrower than the tile font it replaces:
-- the numbers in a right-aligned column (the party menu's ":L12" over
-- "34/ 34") then no longer land where the 8px-per-character layout put
-- them. Naming "0123456789" here keeps digits on the vanilla tiles --
-- identical to the English build -- while kana still come from the
-- font. Sequence keys, so "é" or a "<PK>" macro can be listed too.
tiles = tileSet(def.ttf.tiles),
widths = {}, chars = {},
}
else
@@ -227,9 +264,10 @@ function Font.split(text)
for _, entry in ipairs(candidates) do
local len = #entry.seq
if text:sub(i, i + len - 1) == entry.seq then
if ttf then
if ttf and not ttf.tiles[entry.seq] then
-- single characters belong to the TTF; only multi-character
-- sequences (ligatures, <PK> macros) keep their tile mapping
-- sequences (ligatures, <PK> macros) keep their tile mapping,
-- plus anything the mod named in ttf.tiles (see Font.load)
local cp, last = utf8Decode(entry.seq, 1)
if cp and last == len then break end
end
@@ -367,8 +405,18 @@ for key, code in pairs(Font.DEFAULT_BORDER) do Font.BORDER[key] = code end
-- Draw a Game Boy style bordered box in tile coordinates.
function Font.drawBox(tx, ty, tw, th)
-- The white interior is a fill, so it needs the color; everything after it
-- is a glyph and needs the caller's. Restoring is not cosmetic: the tile
-- pages are black glyphs on transparent, so they come out black whatever
-- the color is, and leaking white here was invisible for as long as every
-- glyph was a tile. TTF text is not immune -- it draws in the current
-- color -- so a leaked white left every label printed after a box white on
-- white. On the summary screen that erased ATTACK/DEFENSE/SPEED/SPECIAL
-- and TYPE1/TYPE2 while the numbers beside them, still tiles, stayed put.
local r, g, b, a = love.graphics.getColor()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.rectangle("fill", tx * 8, ty * 8, tw * 8, th * 8)
love.graphics.setColor(r, g, b, a)
local B = Font.BORDER
Font.drawCode(B.tl, tx * 8, ty * 8)
Font.drawCode(B.tr, (tx + tw - 1) * 8, ty * 8)
+43
View File
@@ -53,6 +53,42 @@ T.eq(#Font.encode("'d"), 1, "and still consumes the whole sequence")
T.eq(Font.encode("\227\129\130")[1], BASE + 0x3042,
"kana with no charmap entry at all still gets a glyph")
-- ------------------------------------------------- ttf.tiles opts back out
-- A CJK translation sizes the font so a kana fills the 8px cell, which leaves
-- Latin narrower than the tile font it replaces and pulls the numeric columns
-- (the party menu's ":L12" over "34/ 34") out of line. ttf.tiles names the
-- characters that keep their ROM tile anyway, so numbers stay identical to the
-- English build while kana still come from the font.
Font.load({ font = { charmap = CHARMAP, ttf = { tiles = "A" } } })
T.eq(Font.encode("A")[1], 0x80, "a character in ttf.tiles keeps its tile glyph")
T.eq(Font.advanceOf(0x80), 8, "and its 8px monospace advance with it")
T.eq(Font.encode("\195\169")[1], BASE + 0xE9,
"while everything else still routes to the TTF")
-- a list form, for naming a multi-character sequence explicitly
Font.load({ font = { charmap = CHARMAP, ttf = { tiles = { "\195\169" } } } })
T.eq(Font.encode("\195\169")[1], 0xBA,
"the list form accepts a multi-byte character")
T.eq(Font.encode("A")[1], BASE + 65, "and leaves the others on the TTF")
Font.load({ font = { charmap = CHARMAP, ttf = {} } })
T.eq(Font.encode("A")[1], BASE + 65, "no tiles list means the TTF takes it back")
-- ------------------------------------------------- drawBox restores color
-- drawBox fills its interior white and used to leave the color that way.
-- Tile pages are black glyphs on transparent, so they draw black whatever the
-- color is and the leak stayed invisible for as long as every glyph was a
-- tile. TTF text draws in the current color, so every label printed after a
-- box came out white on white -- the summary screen lost ATTACK/DEFENSE/
-- SPEED/SPECIAL and TYPE1/TYPE2 while the numbers beside them survived.
love.graphics.setColor(0, 0, 0, 1)
Font.drawBox(0, 0, 4, 4)
local r, g, b, a = love.graphics.getColor()
T.eq(("%s,%s,%s,%s"):format(r, g, b, a), "0,0,0,1",
"drawBox leaves the caller's color alone")
-- metrics come straight from the font object (the stub: half the point
-- size per codepoint, doubled from U+1000 up, mimicking Plain Pixel's
-- single/double width split)
@@ -161,6 +197,13 @@ do
T.check(select(1, Schemas.check(spec, "font", "ttf",
{ file = "mods/x/f.ttf", size = 11, spacing = 1, yOffset = -3 })) == true,
"so is a fully tuned entry")
T.check(select(1, Schemas.check(spec, "font", "ttf",
{ size = 10, tiles = "0123456789/:" })) == true,
"tiles takes a string of characters")
T.check(select(1, Schemas.check(spec, "font", "ttf",
{ tiles = { "0", "<PK>" } })) == true, "or a list of charmap sequences")
T.check(Schemas.check(spec, "font", "ttf", { tiles = 10 }) == nil,
"but not a number")
T.check(Schemas.check(spec, "font", "ttf", { image = "x.png", base = 0x100 })
== nil, "a page payload under the ttf id is refused")
T.check(Schemas.check(spec, "font", "page", {}) == nil,