Skip to content

feat: add Photo Booth for capturing character avatars#1030

Open
HarleyGilpin wants to merge 7 commits into
GregHib:mainfrom
HarleyGilpin:feat/photo-booth
Open

feat: add Photo Booth for capturing character avatars#1030
HarleyGilpin wants to merge 7 commits into
GregHib:mainfrom
HarleyGilpin:feat/photo-booth

Conversation

@HarleyGilpin

@HarleyGilpin HarleyGilpin commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the members-only Photo Booth at Falador (NPC Iconis, id 8879; tent object 46396; interface 549). Players take a snapshot of their character which is persisted for use as a website/forum avatar.

Screencast_20260614_105551.webm
image

The avatar sprite is rendered server side using renderPhotoBooth tool.

Gradle: ./gradlew :tools:renderPhotoBooth -Pargs="--player=name --out=/tmp/avatar"

Behavior

  • Iconis: Talk-to opens the full dialogue tree (transcript-accurate); Take-picture, the dialogue's Yes, please, and the tent's Enter option all open the booth.
  • Entry: player walks to the entrance, into the tent, then the booth interface opens.
  • Take picture → confirm Yes → appearance snapshot saved, 2-hour cooldown set, tent plays the flash animation (12416), and the Booth imp says "There ya go, guv!".
  • Exit: on Close / escape / minimap / after the photo, the player walks back out to the entrance.
  • Rules: members-only (Iconis refuses on F2P), 2-hour cooldown (message shown up front, before entering), and lent items are blocked.

Snapshot data

Frozen at capture time into persisted player variables consumed later by the website (which reads the shared DB): photo_booth_time, photo_booth_male, photo_booth_looks, photo_booth_colours, photo_booth_equipment ("slot:id:equipIndex"), plus photo_booth_cooldown. Stored as short, structured values rather than a JSON/base64 blob because the player-save reader caps each string at 100 chars.

Robustness / security

  • Enter validates the object is at its real tile (2928,3323) to reject client-side object injection.
  • Logout/disconnect inside the tent saves the player at the entrance so they don't return stuck.
  • The solid 3×3 tent's walk-out is deferred one tick so the map-walk handler can't strand the player on the blocked tile.

Files

  • data/social/photo/photo_booth.{ifaces,vars,npcs,objs,anims}.toml — interface 549 components, persisted vars, Booth imp (8881), tent object, flash animation.
  • content/area/asgarnia/falador/Iconis.kt — dialogue, booth flow, snapshot, cooldown, gating.
  • content/entity/player/command/ObjectCommands.kt — ::objanim [to] admin helper (cycles object animations; used to identify the flash anim).
  • Admin command ::resetphoto clears the cooldown for testing.

Testing completed

On a members world: talked to Iconis at Falador (~2930,3326) → confirmed dialogue tree and No thank you; enter via NPC/dialogue/tent Enter → walked in, took picture, confirmed Yes → flash + imp line, snapshot vars written, walk out.

Verify: 2h cooldown blocks re-entry with a message; lent item blocks capture; minimap/Close walk the player out (not stuck); relog inside the tent returns at the entrance. On F2P, Iconis refuses.
::resetphoto resets the cooldown.

Comment thread data/social/photo/photo_booth.vars.toml
Comment thread data/social/photo/photo_booth.vars.toml Outdated
Comment thread game/src/main/kotlin/content/area/asgarnia/falador/Iconis.kt Outdated
- Add string formats to photo_booth_looks/colours/equipment vars
- Store equipment as comma-joined equipIndexes per slot, dropping slot numbers
- Use TimeUnit.HOURS.toSeconds(2) for the cooldown instead of a magic constant
HarleyGilpin and others added 4 commits June 14, 2026 13:56
Adds a standalone render tool that turns a captured photo booth snapshot
into transparent avatar PNGs, mirroring Jagex's avatar service:
- <name>_full.png  - full-body paperdoll
- <name>_chat.png  - chathead (3/4 forum-avatar angle), with a head-crop
  fallback when an item has no chathead model (e.g. full helms)

Implementation:
- Vendors only the geometry decoder from the Quill cache suite (Model.kt +
  SmartExtensions) and ports a trimmed, dependency-light renderer
  (AvatarRenderer, ModelComposer) with Gouraud smooth shading.
- PlayerModelAssembler mirrors the client BodyParts assembly; the
  equipIndex->item map is taken from the game's own ItemDefinitions so it
  always matches captured snapshots.
- Body colours are applied client-accurately from the rev-634 recolour
  palettes (BodyColourPalettes).
- Iconis flags photo_booth_dirty on capture so the renderer/website can
  pick up fresh photos.

Run via: ./gradlew :tools:renderPhotoBooth -Pargs="--player=name --out=dir"
- Mask item dialogue-head (chathead) model ids to unsigned. They decode as
  signed shorts, so worn hats/helms (e.g. party hats) were silently dropped
  from the chathead avatar; worn (full-body) models were unaffected.
- Accept '+' as a space in renderPhotoBooth --player/--players so account
  names with spaces survive -Pargs splitting.
- Move the vendored Quill model decoder into a single tools.photobooth.vendor
  package (Model.kt + SmartIo.kt) instead of squatting on the misleading
  io.blurite / net.rsprot namespaces. No rsprot dependency added.
@HarleyGilpin HarleyGilpin deleted the feat/photo-booth branch June 15, 2026 10:43
@HarleyGilpin HarleyGilpin restored the feat/photo-booth branch June 15, 2026 10:43
@HarleyGilpin HarleyGilpin reopened this Jun 15, 2026
Comment thread tools/build.gradle.kts
Comment on lines +74 to +82
tasks.register<JavaExec>("migrateRsmodSaves") {
group = "migration"
description = "Imports rsmod (rev 664) JSON player saves into void's PostgreSQL. Pass args via -Pargs=\"--dry-run --limit=5\"."
mainClass.set("world.gregs.voidps.tools.convert.RsmodSaveMigrator")
classpath = sourceSets["main"].runtimeClasspath
workingDir = rootDir
val cliArgs = (findProperty("args") as String?)?.split(" ")?.filter { it.isNotBlank() } ?: emptyList()
args = cliArgs
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accidentally commited?

@HarleyGilpin HarleyGilpin Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I had to cherry pick a commit from my production branch.

Comment thread tools/build.gradle.kts
Comment on lines +32 to +33
// Vendored Quill model renderer (photo booth avatars) decodes geometry via netty ByteBuf.
implementation("io.netty:netty-buffer:4.1.118.Final")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally ByteBuf should be replaced with Reader/ByteReader

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.

2 participants