Skip to content

Merge account registration and web api into prod/world1 - #6

Closed
HarleyGilpin wants to merge 16 commits into
prod/world1from
prod/world1-web-api
Closed

HarleyGilpin wants to merge 16 commits into
prod/world1from
prod/world1-web-api

Conversation

@HarleyGilpin

Copy link
Copy Markdown
Owner

Brings GregHib#1300 (in-client account registration) and GregHib#1319 (account REST api) onto prod/world1 ahead of upstream review, so the website can register and manage accounts through the running server (HarleyGilpin/2011scape-laravel#31).

Merge

git merge feat/web-api onto origin/prod/world1. Three files conflicted, all adjacent additions with no logic overlap, resolved by keeping both sides:

File Prod side Branch side
DatabaseStorage.kt flagged(), accountId(), connectWithoutSchema() create(), SQLSTATE constants
FileStorage.kt password() create()
PlayerAccountLoader.kt hash upgrade on login prepare() for email accounts

DatabaseStorage.tables is the union: prod's PlayerCountTable plus the branch's kills / records / recent events tables.

Prod's argon2 and $2y$/$2b$ acceptance are untouched. PlayerAccountUpdater writes plain jBCrypt hashes, which prod's login already accepts. /api/status and prod's player_count writer coexist.

Ops after deploy

Root game.properties on world1 (gitignored):

web.api.enabled=true
web.api.token=<long random secret>

The webserver is started at boot, so this needs a restart rather than ::reload settings. ufw deny in 8081/tcp is already in place and Apache only proxies /proxy, so /api stays private. Then merge the site PR and set GAME_API_URL / GAME_API_TOKEN.

Deploy the server before the site: the old site keeps writing the DB directly and still works against the new server; the new site fails closed against an old server.

Verification

  • spotlessApply, compileKotlin / compileTestKotlin with -PincludeDb (prod uses the database backend) and :tools all build. Fix-up commit covers the fallout: PlayerCountReporter import (Settings.world moved to the engine), RsmodSaveMigrator gaining the new PlayerSave fields, and Storage test stubs implementing prod's password().
  • :web, :network, :database suites green. The new tests (ApiRoutesTest, PlayerAccountCreatorTest, PlayerAccountUpdaterTest, AccountDefinitionsTest, WebAccountServiceTest) pass on the merge.
  • Full :engine + :game run with fail-fast off: 3865 tests, 257 failures. Every one is pre-existing on prod/world1, not from this merge:
    • 254 are XP assertions off by the feat: dynamic xp rate scaling 1x to 5x multiplier (e.g. expected 4967.25 but was 5368.6, expected 27.0 but was 28.0).
    • EquipTest > Stack equipped items, StallsTest and TrapChestTest > Successfully steal from a stall fail identically on an untouched origin/prod/world1 checkout, and those test files are byte-identical on prod, main and this branch.
    • Untouched prod/world1 also fails :engine:compileTestKotlin outright, so its engine tests have not been running at all.
  • Not re-run here: the live curl smoke test (done on feat/web-api against a file-storage dev server); prod runs the database backend, so worth a GET /api/status and one test registration after deploy.

HarleyGilpin and others added 16 commits September 13, 2026 15:43
The 634 client's login screen already contains the "New user" form and
sends two single-reply requests: opcode 28 to check an email address and
opcode 22 to create the account. Answer both from a RegistrationServer
wired into GameServer.

- Accounts registered from the client use the email address as the
  account name; login accepts email-shaped names beyond 12 characters.
- Storage.create persists a new account atomically for files and the
  database (name/reporter columns widened to 254 with migrations).
- A display name is derived from the email and the player is prompted to
  choose their own on first login before character creation. The rename
  command now also rejects taken names.
- Password rules (5-20 alphanumeric, not guessable), minimum age and a
  per-IP creation limit are enforced server-side; registration can be
  disabled with accounts.registration.
Accounts registered with an email address now pick their display name on
the "Character Name" panel at the end of character creation instead of
a dialogue prompt. The panel is part of interface 1028: confirming the
appearance runs the client script that shows it, six server generated
suggestions are sent as client strings, and Continue or a suggestion
click runs the submit script which returns the typed name as a string
entry. Taken or invalid names re-prompt with a message and fresh
suggestions; the dialogue prompt remains as a fallback when character
creation is disabled.
The suggestion container in the character creation name panel is hidden
in the cache behind the naming rules text, so the server now toggles the
two when the panel opens and reveals "Previous Suggestions" once a
second page exists. Suggestions are generated from the player's name or
the rejected name with a mix of styles: stem plus numbers, stem plus a
class or creature, an adjective plus the stem, and adjective plus noun.
…egHib#1296)

* Created a check to see if the player has completed Rune Mysteries before they're permitted to enter the mine.

* Update game/src/main/kotlin/content/skill/runecrafting/EssenceMine.kt

---------

Co-authored-by: potato <princess@localhost>
Co-authored-by: Greg <GregHib@users.noreply.github.com>
* Restored functionality to all(?) remaining deposit boxes

* Changed comment

* Add all deposit box names

---------

Co-authored-by: potato <princess@localhost>
Co-authored-by: GregHib <greghib@users.noreply.github.com>
* Add Biohazard quest

Ports Biohazard: Elena's distillator, Jerico's pigeons and the watchtower
distraction, the rope ladder over the wall, the mourners' poisoned stew and
headquarters, the errand boys, Guidor's test and King Lathas' confession,
plus the Combat Training Camp the quest unlocks.

The mourners lose the drop table that handed out a full outfit every kill and
gain combat definitions of their own, taken from npc 370 in RuneScape-2011;
mourner_2 drops the quarters key while the stew has them laid up, and all
seven respawn.

Shared code touched along the way:

- Gate.replaceTogether, a fixed twin of Gate.replace, which walks the second
  half off the first half's new tile and turns both halves the same way. The
  original is left alone so the gates built around its placement don't move.
- Teleport.takeOff and teleportLand accept "*" and run every registered
  handler. Ghosts Ahoy's teleportTakeOff("*") was dead, and PirateTreasure
  and LostAndFound both registered teleportLand("modern"), so one silently
  overwrote the other.
- questprep gained prep_vars, for world state a prerequisite quest leaves
  behind that marking it complete doesn't reproduce.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WQ1FECKKC3ZpsGK3hNVVnh

* Fix missing import from rebase

* Fix watchtower fence interaction

* Turn watch tower into an area

* Add missing gas mask check

* Fix unit tests

* Move redundant gate code

* Remove mocks from tests

* Fix missing quest test data

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: GregHib <greghib@users.noreply.github.com>
* Add boss tracker system

* Add additional barrows chest value message

* Track barrows chest kills

* Fix spade double digging animations

* Add boss tracker system

* Add boss kill tracking

* Add kbd trackers

* Add all boss tracker variables

* Add fight cave wave tracking

* Rename BossTracker to KillTracker

* Revert vars

* Store kills and records in player save file

* Add in-game lookup commands and fix storage tests

* Formatting
* Add in-game playtime tracking

* Add recentEvents list to player save

* Add adventurer logs system and event logging

* Add quest adventurer logs

* Add dungeoneering adventurer logs

* Add pvp adventurer logs

* Add item drop adventurer logs

* Add npc kill adventurer logs

* Add command for viewing adventurer's logs in-game
* Add Vinesweeper minigame

Winkin's Farm: dig holes with a spade to reveal how many seeds are hidden
in the eight surrounding holes, flag the ones you think hide a seed, and
gnome farmers race the rabbits to check them.

Reward shop (interface 686) is drawn by the client from enums 1589/1365,
so the server only handles clicks and the points varbit 4449; the XP
exchange uses enum 1369 for the level based rate below level 40. Tool
leprechauns and Teclyn teleport in, the portal returns to where you left.

Removes the 326 plane-1 "mud" NPC spawns that were never visible.

* Drop the leprechaun teleport animation

Animation 201 belongs to the player skeleton and rendered as a broken
pose on the leprechaun. The teleport plays only the curse cast graphic
and projectile on the caster, then the impact graphic and sound on the
player once they land.

* Play animation 11705 for the leprechaun teleport

* Wait for the leprechaun's cast before teleporting

shoot() returns the projectile time in client cycles, so the delay must
be converted to game ticks; two ticks minimum so the animation and cast
graphic are visible before the player moves.

* Teleport once the leprechaun's cast animation has finished

* Fire the teleport projectile during the leprechaun's cast

* Play the curse impact on the player before teleporting

* Fix gnome npc dialogue heads

* Fix npc reloading

* Optimise object lookup

* Fix flag buying plural

* Move some methods around

* Add safety check for tests

* Simplify populating

---------

Co-authored-by: GregHib <greghib@users.noreply.github.com>
* Fix GameObjects.clear deallocating the wrong zone

clear(zone) passed absolute tile coordinates to deallocateZone, which
builds its key with Zone.id and so expects zone coordinates. Every other
key goes through Zone.tileIndex, which does the shr 3 first.

Zone.id masks with 0x7ff, so the stray x8 wrapped rather than going out
of range and produced a valid but wrong key: the zone passed in was never
cleared, and an 8x8 tile block of a real region was deleted instead,
always at a multiple of 64. Nothing put those objects back - the map is
only loaded at startup, and send() skips empty entries so re-syncing a
client had nothing to replay. Clients kept rendering them from cache and
every click logged "Invalid object" until the next restart.

deallocateZone now takes a Zone so the unit can't be mistaken again.

clear(zone) also drops the zone's replacements and cancels its timers,
which are keyed separately from the map. An orphaned replacement is
unreachable by reset(zone), and a revert firing after the zone was
discarded would write back into it.

GameObjectHashMap.remove no longer stores anything for a tile it doesn't
hold. It defaulted an absent index to -1 and wrote `-1 and mask.inv()`,
inserting a value that reads as neither empty nor replaced, so the tile
returned a phantom object, was skipped by re-syncs, and could not be
repaired.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Skip instance teardown for regions outside the instance pool

clearInstance deletes objects, collision, npcs and floor items outright
with no reload to put them back, using whatever region id the player's
"instance" variable holds. That variable is persisted with the save, so a
stale or wrong id would tear a permanent hole in the live map.

Every current writer stores a pooled region, so this changes no existing
flow; it just stops the worst outcome if one ever doesn't. The player is
still moved out, since a non-instance region has nothing to tear down.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Exposes /api on the existing ktor webserver so a website or launcher can
create accounts, check names, reset passwords, change display names and read
server status without a restart or a scheduled account reload. Off by
default; requests need "Authorization: Bearer <web.api.token>".

- :web defines AccountService and the routes; game wires WebAccountService
  onto the engine, mirroring how RegistrationServer uses AccountCreator.
- PlayerAccountCreator gains a direct create for username or email names
  (email needs an explicit display name); PlayerAccountUpdater applies
  password and display name changes to online and offline accounts.
- Account creation is rate limited per end-user address from
  X-Forwarded-For, sent by the authenticated caller.
- Player.rename/nameTaken and Settings.world/worldName move to the engine;
  AccountDefinitions.update now re-keys the owner's clan.
- The webserver starts after content load and the webclient zip is optional
  when only the api is enabled.
# Conflicts:
#	database/src/main/kotlin/world/gregs/voidps/storage/DatabaseStorage.kt
#	engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt
#	engine/src/main/kotlin/world/gregs/voidps/engine/data/file/FileStorage.kt
- PlayerCountReporter: Settings.world now lives on the Settings companion.
- RsmodSaveMigrator: PlayerSave gained kills, records and recentEvents.
- Storage test stubs implement password() from the prod hash-upgrade path.
@HarleyGilpin
HarleyGilpin deleted the branch prod/world1 September 17, 2026 23:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants