Skip to content

Commit 5ae1725

Browse files
authored
Merge pull request #36 from squirelworks/claude/project-thread-kas0am-zelda-online2
Zelda Online 2: Java rebuild with the GServ lobby, 0.0.1
2 parents a4018cd + b6ac977 commit 5ae1725

19 files changed

Lines changed: 2449 additions & 0 deletions
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: zelda-online2
2+
3+
# Zelda Online 2 and its GServ lobby (Java). Built and tested on Linux and Windows on every push
4+
# and pull request that touches it. A tag zelda-online2-Z3V103-v<version>
5+
# (or a manual run) also makes a DRAFT release with both jars; Squirrel
6+
# reviews it and presses Publish.
7+
on:
8+
push:
9+
branches: [main]
10+
tags: ['zelda-online2-Z3V103-v*']
11+
paths: ['zelda-online2-Z3V103/**', '.github/workflows/zelda-online2.yml']
12+
pull_request:
13+
paths: ['zelda-online2-Z3V103/**', '.github/workflows/zelda-online2.yml']
14+
workflow_dispatch:
15+
inputs:
16+
version:
17+
description: Version to release as a draft, e.g. v0.0.1 (empty = build only)
18+
required: false
19+
default: ''
20+
21+
concurrency:
22+
group: zelda-online2-${{ github.ref }}
23+
cancel-in-progress: true
24+
25+
permissions:
26+
contents: write
27+
28+
jobs:
29+
build:
30+
strategy:
31+
fail-fast: false
32+
matrix:
33+
os: [ubuntu-latest, windows-latest]
34+
runs-on: ${{ matrix.os }}
35+
steps:
36+
- uses: actions/checkout@v4
37+
- uses: actions/setup-java@v4
38+
with:
39+
distribution: temurin
40+
java-version: '21'
41+
- name: Build and test
42+
shell: bash
43+
run: zelda-online2-Z3V103/build.sh
44+
- uses: actions/upload-artifact@v4
45+
if: runner.os == 'Linux'
46+
with:
47+
name: zelda-online2
48+
path: |
49+
build/zelda-online2-Z3V103/ZeldaOnline2.jar
50+
build/zelda-online2-Z3V103/GServ.jar
51+
build/zelda-online2-Z3V103/smoke.png
52+
53+
release:
54+
needs: build
55+
if: startsWith(github.ref, 'refs/tags/zelda-online2-Z3V103-v') || (github.event_name == 'workflow_dispatch' && inputs.version != '')
56+
runs-on: ubuntu-latest
57+
steps:
58+
- uses: actions/checkout@v4
59+
- uses: actions/download-artifact@v4
60+
with:
61+
name: zelda-online2
62+
path: dist-in
63+
- name: Pack
64+
env:
65+
IN_VERSION: ${{ inputs.version }}
66+
TAG: ${{ github.ref_name }}
67+
run: |
68+
B=zelda-online2-Z3V103
69+
if [ -n "$IN_VERSION" ]; then V="$IN_VERSION"; else V="v${TAG##*-v}"; fi
70+
OUT="$B-$V"
71+
mkdir -p "dist/$OUT"
72+
cp dist-in/ZeldaOnline2.jar dist-in/GServ.jar "$B/README.md" "$B/RELEASE_NOTES.md" "$B/make_assets.py" "dist/$OUT/"
73+
(cd dist && zip -qr "$OUT.zip" "$OUT")
74+
echo "TAG_NAME=$B-$V" >> "$GITHUB_ENV"
75+
echo "TITLE=$(head -1 "$B/RELEASE_NOTES.md" | sed 's/^# *//') $V" >> "$GITHUB_ENV"
76+
- uses: softprops/action-gh-release@v2
77+
with:
78+
tag_name: ${{ env.TAG_NAME }}
79+
name: ${{ env.TITLE }}
80+
body_path: zelda-online2-Z3V103/RELEASE_NOTES.md
81+
target_commitish: ${{ github.sha }}
82+
draft: true
83+
files: dist/*.zip

‎zelda-online2-Z3V103/README.md‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Zelda Online 2 (`Z3-V1.03`)
2+
3+
Zelda Online 2, the "local version" of July 1998, rebuilt, together with the
4+
GServ lobby it shipped with. The original is a Java 1.1 application by
5+
Stefan Knorr: a start window with "One-Player mode" and "Multiplayer mode",
6+
a server address field and the player's options, then the game in a
7+
512x448 view. Multiplayer goes through GServ on port 14801, one lobby room
8+
per level. It is the step between Zelda Online 1 and the first Delphi Graal.
9+
10+
This build is written in Java, like the original, and targets Java 8 class
11+
files, so any Java 8 or newer runs it. It uses only AWT.
12+
13+
> **Not affiliated.** graal-delphi is an independent fan project. It is not
14+
> affiliated with, endorsed by, or connected to GraalOnline or
15+
> graalonline.com, Cyberjoueurs, or Stéphane Portha ("unixmad").
16+
17+
No original code, graphics or levels are in this folder. The original
18+
package includes its author's source; none of it was read for this build or
19+
copied into it. The code is written from the notes below.
20+
21+
## Folder name
22+
23+
There is no version token in 1998, so the folder carries the level format
24+
the game ships its levels in: `Z3-V1.03`.
25+
26+
## Sources
27+
28+
| Area | Notes in [graal_analysis](https://github.com/squirelworks/graal_analysis) |
29+
|---|---|
30+
| What the package is, start window, options, files | `client-internals/precursors-and-fan-builds.md` |
31+
| GServ lines, ids, parties, room names | `client-internals/precursors-and-fan-builds.md` ("Network"), `protocol/relay-1998.md` |
32+
| D payloads (P, C, N, S, T, O, I), sender id, number encoding, level leader | `protocol/relay-1998.md` |
33+
| Tick, walking, push timing, chat and name drawing | `client-internals/precursor-runs-2026-10-30.md` |
34+
| Board, links, baddies, signs | `formats/level-binary.md`, `formats/sign-text.md` |
35+
36+
## Classes
37+
38+
| Class | What it does |
39+
|---|---|
40+
| `zo2.ZeldaOnline2` | start window (modes, address, nickname, own name, colours), game window, chat line, headless mode |
41+
| `zo2.Net` | the game's side of the lobby: room per level, party on the first `+`, state push on `P`, D payloads, level leader |
42+
| `zo2.Relay` | the text-line socket |
43+
| `zo2.Game` | the 50 ms tick: walking, collision, corner slide, push counter, links, signs |
44+
| `zo2.Renderer` | the view; chat and names as bold serif text with a white offset copy, no bubble |
45+
| `zo2.Level`, `SignText`, `Arrays`, `Assets`, `StandIn`, `TileSheet`, `Colours` | files, stand-ins, tiles, colours |
46+
| `gserv.GServ` | the lobby: ids, games, `N O S + - P C V D H` |
47+
48+
## Build, test, run
49+
50+
```
51+
zelda-online2-Z3V103/build.sh # needs a JDK (11 or newer to build)
52+
java -jar build/zelda-online2-Z3V103/GServ.jar # the lobby, port 14801
53+
java -jar build/zelda-online2-Z3V103/ZeldaOnline2.jar
54+
```
55+
56+
The address field is pre-filled with `listserver.graal.in`; type
57+
`127.0.0.1` for a GServ on the same machine. Arrow keys walk, Enter opens
58+
the chat line (multiplayer), Space or A reads a sign.
59+
60+
Flags: `--one-player`, `--server HOST[:PORT]`, `--nick NAME`, `--data DIR`,
61+
`--level NAME`, `--keys rrdd.. --dump f.png` (headless). `ZO2_DATA=dir`
62+
makes the test program also decode every `.zelda` in that folder.
63+
64+
## Where the files go
65+
66+
The jars live in their folder and find their files from there:
67+
68+
```
69+
Zelda Online 2/
70+
ZeldaOnline2.jar GServ.jar
71+
graal-assets/zelda-online2/ (from the assets zip)
72+
pics1.gif arrays.dat levels/*.zelda
73+
```
74+
75+
It looks in `--data`, then the jar's folder (and its `levels/` and
76+
`game/`), then `graal-assets/zelda-online2/`, then `graal-assets/common/`,
77+
and else uses its stand-ins. The original package can also simply be
78+
unpacked next to the jar. `make_assets.py SRC OUT.zip` makes the assets
79+
zip from your own copy: `pics1.gif`, `arrays.dat` and the levels only.
80+
81+
## What works (0.0.1)
82+
83+
- The start window with both modes and the options; the game window.
84+
- Levels, links, signs, walking, collision, corner slide and push timing.
85+
- Multiplayer: room per level (`Zlevel11.6` for `level1.zelda`), party on
86+
the first `+`, the state push on `P`, positions every 2 ticks, chat,
87+
nickname, colours, leaving the room on a level change, the level leader.
88+
- The GServ lobby. The rebuilt client was also run against the original
89+
GServ server on loopback: both players got ids, joined a party and saw
90+
each other's nickname and position.
91+
92+
## Not yet
93+
94+
The player sprite from `link.gif` (a stand-in figure is drawn, coloured by
95+
the options), sword, bushes, lifting, baddies, chests, board changes over
96+
the wire, the password save (its alphabet and mask are not in the notes
97+
yet), sound. The colour list is the rebuild's own. The original start
98+
position is not known; the rebuild starts near the middle of `level1`.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Zelda Online 2
2+
3+
A rebuild of Zelda Online 2 (July 1998) and the GServ lobby it came with:
4+
the Java game that took Zelda Online multiplayer. It is written in Java, as
5+
the original was, and runs on any Java 8 or newer. This is a first, early
6+
release.
7+
8+
> **Not affiliated.** graal-delphi is an independent fan project. It is not
9+
> affiliated with, endorsed by, or connected to GraalOnline or
10+
> graalonline.com, Cyberjoueurs, or Stéphane Portha ("unixmad").
11+
12+
The zip holds no original code, graphics or levels. The game carries simple
13+
stand-in graphics and two small levels of its own, so it runs with nothing
14+
next to it.
15+
16+
## Running it
17+
18+
Unzip, then run `java -jar ZeldaOnline2.jar` (or double-click it). The
19+
start window offers One-Player mode and Multiplayer mode, with your
20+
nickname and colours.
21+
22+
For multiplayer, start the lobby with `java -jar GServ.jar` on one machine
23+
(port 14801), then type that machine's address in the start window. Every
24+
level is its own room: players meet when they are on the same level.
25+
26+
Arrow keys walk. In multiplayer, Enter opens the chat line.
27+
28+
## Your own files
29+
30+
Put your Zelda Online 2 files in the same folder as the jars:
31+
32+
- run `make_assets.py` on your copy (`python3 make_assets.py SRC assets.zip`)
33+
and unzip the result next to the jars;
34+
- or unpack the original package next to them.
35+
36+
## What works
37+
38+
- The start window, one-player and multiplayer play on the original levels.
39+
- Walking, collision, level links and signs at the original speed.
40+
- Seeing other players on the same level, with their names, chat and
41+
colours; the lobby itself.
42+
43+
## Not yet
44+
45+
The real player sprite, the sword, bushes, baddies, chests, the password
46+
save and sound.

‎zelda-online2-Z3V103/build.sh‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/sh
2+
# Build Zelda Online 2 and its GServ lobby and its tests with a JDK, then run the tests.
3+
# zelda-online2-Z3V103/build.sh build and test
4+
# Output: build/zelda-online2-Z3V103/ZeldaOnline2.jar and GServ.jar
5+
# The classes target Java 8 class files, so any Java 8 or newer runs it.
6+
set -eu
7+
HERE=$(cd "$(dirname "$0")" && pwd)
8+
NAME=$(basename "$HERE")
9+
OUT="$HERE/../build/$NAME"
10+
mkdir -p "$OUT"
11+
OUT=$(cd "$OUT" && pwd)
12+
SEP=:
13+
# Git Bash on Windows: the JDK wants Windows paths and ';' between them
14+
if command -v cygpath >/dev/null 2>&1; then OUT=$(cygpath -m "$OUT"); SEP=';'; fi
15+
rm -rf "$OUT/classes" "$OUT/test-classes"
16+
mkdir -p "$OUT/classes" "$OUT/test-classes"
17+
cd "$HERE"
18+
javac -encoding UTF-8 --release 8 -Xlint:-options -nowarn -d "$OUT/classes" $(find src -name '*.java')
19+
printf 'Main-Class: zo2.ZeldaOnline2\n' > "$OUT/manifest.txt"
20+
jar cfm "$OUT/ZeldaOnline2.jar" "$OUT/manifest.txt" -C "$OUT/classes" zo2
21+
printf 'Main-Class: gserv.GServ\n' > "$OUT/manifest-gserv.txt"
22+
jar cfm "$OUT/GServ.jar" "$OUT/manifest-gserv.txt" -C "$OUT/classes" gserv
23+
javac -encoding UTF-8 --release 8 -Xlint:-options -nowarn -cp "$OUT/classes" -d "$OUT/test-classes" $(find tests -name '*.java')
24+
java -Djava.awt.headless=true -cp "$OUT/classes$SEP$OUT/test-classes" zo2.TestZO2
25+
# smoke: the jar starts, walks and writes a frame
26+
java -Djava.awt.headless=true -jar "$OUT/ZeldaOnline2.jar" --one-player --keys rrrrrrrrdddd --dump "$OUT/smoke.png"
27+
test -s "$OUT/smoke.png"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env python3
2+
"""Make the assets zip for the Zelda Online 2 rebuild from your own copy.
3+
4+
make_assets.py SRC OUT.zip
5+
6+
SRC is an unpacked Zelda Online 2 (the package, or its game/ folder; the
7+
levels are found anywhere under it). The zip gets only what the rebuild reads, and nothing else:
8+
9+
graal-assets/zelda-online2/pics1.gif the tile sheet
10+
graal-assets/zelda-online2/arrays.dat tile types
11+
graal-assets/zelda-online2/levels/*.zelda every level
12+
13+
Unzip it next to ZeldaOnline2.jar. Class files, sources and pages are never
14+
copied. The zip is your own files: it is not part of this repository.
15+
"""
16+
import os
17+
import sys
18+
import zipfile
19+
20+
LABEL = 'zelda-online2'
21+
TOP = {'pics1.gif', 'arrays.dat'}
22+
23+
24+
def main():
25+
if len(sys.argv) != 3:
26+
sys.exit(__doc__)
27+
src, out = sys.argv[1], sys.argv[2]
28+
picked = {}
29+
for d, _, names in os.walk(src):
30+
for n in sorted(names):
31+
low = n.lower()
32+
p = os.path.join(d, n)
33+
if low in TOP:
34+
picked.setdefault(low, p)
35+
elif low.endswith('.zelda'):
36+
picked.setdefault('levels/' + low, p)
37+
missing = [n for n in sorted(TOP) if n not in picked]
38+
if missing:
39+
print('not found in SRC (the built-in stand-ins are used instead):', ', '.join(missing))
40+
root = 'graal-assets/' + LABEL + '/'
41+
with zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as z:
42+
for rel in sorted(picked):
43+
z.write(picked[rel], root + rel)
44+
print('%s: %d files' % (out, len(picked)))
45+
46+
47+
if __name__ == '__main__':
48+
main()

0 commit comments

Comments
 (0)