KeplerC currently has ~95 registered packet handlers. Java Kepler has ~185+. This plan ports all ~90 missing handlers to achieve full feature parity. Each handler will be verified against the Java source for exact protocol compatibility. New database tables, manager structs, and query files are needed for several feature categories.
- MESSAGES array: Already 9999, no change needed
- Games: Full BattleBall + SnowStorm game logic (not just stubs)
- Packet 159 conflict: Context-based routing (GETSELECTEDBADGES in normal rooms, GETINSTANCELIST in game lobbies)
- Java verification: Check each Java handler source before writing C version
- Handlers are
.hfiles insrc/communication/incoming/{category}/containing a single function - Registered in
src/communication/message_handler.cviamessage_requests[ID] = HANDLER_NAME; - DB queries go in
src/database/queries/{category}_query.h - Game structs/managers go in
src/game/{feature}/ - All handlers take
(entity *player, incoming_message *message)signature - SQLite3 with thread-safe shared connection (
global.DB) - Outgoing messages use
om_create(header)+om_write_*()+player_send()+om_cleanup()
Low complexity, no new DB tables needed. High user-visible impact.
| ID | Name | File | Description |
|---|---|---|---|
| 56 | WHISPER | room/user/WHISPER.h |
Send whisper to specific user (like CHAT but only sent to sender+recipient) |
| 115 | GOAWAY | room/user/GOAWAY.h |
Leave room (calls room_leave) |
| 87 | CARRYITEM | room/user/CARRYITEM.h |
Carry hand item (like CARRYDRINK but for general items) |
| 89 | USEITEM | room/items/USEITEM.h |
Use carried item (transfers carry->use status) |
| 229 | SET_SOUND_SETTING | room/user/SET_SOUND_SETTING.h |
Toggle sound on/off (no-op server side, just ACK) |
| 117 | IIM | room/user/IIM.h |
In-item interaction for gamehall games (stub initially) |
- WHISPER: Read string
"username message", split on first space, find target room_user by name, send outgoing header 25 (CHAT) with whisper flag to sender+recipient only. Check ignore list. - GOAWAY: Call
room_leave(room, player)- essentially same as QUIT but explicit - CARRYITEM: Read string (item ID), set room_user status "carryd {id}", broadcast to room
- USEITEM: Transfer "carryd" status to "usei", broadcast
- SET_SOUND_SETTING: Read int (0/1), store on entity, no broadcast needed
- IIM: Read string, parse gameId + command - stub with no-op for now
src/communication/message_handler.c(add includes + registrations)- Create 6 new
.hhandler files
Essential for user experience - kick, rights management, ignore list.
CREATE TABLE IF NOT EXISTS users_mutes (
user_id INTEGER NOT NULL,
muted_id INTEGER NOT NULL,
PRIMARY KEY (user_id, muted_id)
);src/database/queries/player/ignore_query.h- CRUD for users_mutessrc/game/player/player.h- AddList *ignored_listto entity struct
| ID | Name | File | Description |
|---|---|---|---|
| 95 | KICK | room/moderation/KICK.h |
Kick user from room (requires rights or owner) |
| 155 | REMOVEALLRIGHTS | room/moderation/REMOVEALLRIGHTS.h |
Remove all rights from room |
| 360 | GET_IGNORE_LIST | user/GET_IGNORE_LIST.h |
Load ignore list on login |
| 319 | IGNORE_USER | user/IGNORE_USER.h |
Add user to ignore list |
| 322 | UNIGNORE_USER | user/UNIGNORE_USER.h |
Remove from ignore list |
| 263 | GET_USER_TAGS | room/user/GET_USER_TAGS.h |
Get user profile tags (stub) |
- KICK: Read string (username), find user in room, verify kicker has rights/ownership/rank, call room_leave on target
- REMOVEALLRIGHTS: Clear room rights list, delete all from rooms_rights table, broadcast update
- Ignore list: Load on MESSENGERINIT, cache in entity->ignored_list. WHISPER/CHAT check list before delivering. DB table
users_mutes(user_id, muted_id) - GET_USER_TAGS: Stub - send empty response (header 350)
src/communication/message_handler.csrc/game/player/player.h(add ignored_list field)src/game/player/player.c(init/cleanup ignored_list)kepler.sql(add users_mutes table)src/communication/incoming/room/user/CHAT.handSHOUT.h(add ignore check)
Small standalone handlers for user profile and navigation.
| ID | Name | File | Description |
|---|---|---|---|
| 228 | GET_ACCOUNT_PREFERENCES | user/settings/GET_ACCOUNT_PREFERENCES.h |
Send account prefs (sound on/off, etc.) |
| 9 | GETAVAILABLESETS | user/GETAVAILABLESETS.h |
Available clothing sets for user |
| 159 | GETSELECTEDBADGES | user/badges/GETSELECTEDBADGES.h |
Get a user's selected/active badges |
| 154 | GETSPACENODEUSERS | navigator/GETSPACENODEUSERS.h |
Get user count in specific room node |
| 34 | INVITE_FRIEND | messenger/INVITE_FRIEND.h |
Invite friend to current room |
| 210 | SCR_GIFT_APPROVAL | user/SCR_GIFT_APPROVAL.h |
Club gift approval (send gift items) |
- GET_ACCOUNT_PREFERENCES: Send outgoing (228) with sound/video boolean flags
- GETAVAILABLESETS: Send clothing set list string based on club status + gender
- GETSELECTEDBADGES: Read int (userId), lookup user badges, send active badges
- GETSPACENODEUSERS: Read int (roomId), count users in that room, send count
- INVITE_FRIEND: Read int (count) + int[] (userIds) + string (message), send room invite to each friend
- SCR_GIFT_APPROVAL: Stub/simple response
Dice, dimmer/moodlight, presents, wheel of fortune, item state changes.
CREATE TABLE IF NOT EXISTS items_moodlight_presets (
item_id INTEGER NOT NULL PRIMARY KEY,
current_preset INTEGER NOT NULL DEFAULT 1,
preset_1 TEXT NOT NULL DEFAULT '2,#000000,255',
preset_2 TEXT NOT NULL DEFAULT '2,#000000,255',
preset_3 TEXT NOT NULL DEFAULT '2,#000000,255'
);src/database/queries/items/moodlight_query.h- CRUD for moodlight presets
| ID | Name | File | Description |
|---|---|---|---|
| 76 | THROW_DICE | room/items/THROW_DICE.h |
Roll dice item (set to -1, schedule result) |
| 77 | DICE_OFF | room/items/DICE_OFF.h |
Turn off/close dice |
| 247 | SPIN_WHEEL_OF_FORTUNE | room/items/SPIN_WHEEL_OF_FORTUNE.h |
Spin wheel (set to -1, schedule result) |
| 78 | PRESENTOPEN | room/items/PRESENTOPEN.h |
Open gift/present item, reveal contents |
| 214 | SETITEMSTATE | room/items/SETITEMSTATE.h |
Set wall item state (on/off/numeric) |
| 341 | MSG_ROOMDIMMER_GET_PRESETS | room/dimmer/MSG_ROOMDIMMER_GET_PRESETS.h |
Get moodlight presets |
| 342 | MSG_ROOMDIMMER_SET_PRESET | room/dimmer/MSG_ROOMDIMMER_SET_PRESET.h |
Set moodlight preset |
| 343 | MSG_ROOMDIMMER_CHANGE_STATE | room/dimmer/MSG_ROOMDIMMER_CHANGE_STATE.h |
Toggle moodlight on/off |
| 28 | GETDOORFLAT | room/teleporter/GETDOORFLAT.h |
Teleporter interaction |
- THROW_DICE/DICE_OFF: Find dice item by ID, verify adjacent, set custom_data="-1" (spinning), use
uv_timer_tto schedule random result after 2s. DICE_OFF sets to "0" - SPIN_WHEEL_OF_FORTUNE: Similar to dice but 4.25s delay, random 1-20 result
- PRESENTOPEN: Parse present custom_data (delimited: saleCode|sender|?|extra|time), create reward item from sale_code, delete present, update inventory
- SETITEMSTATE: Read int (itemId) + int (state), find wall item, update custom_data, broadcast, save to DB
- Dimmer handlers: 3 presets per moodlight item stored in DB. GET loads/creates, SET validates+updates, CHANGE_STATE toggles on/off
- GETDOORFLAT: Read item ID, find linked teleporter, walk player to teleporter, warp to linked room after delay
Key dependency: Need timer/scheduled task support for dice/wheel animations. Check if hh_dispatch_timer_t from room walk timer can be reused.
Credit log and voucher redemption.
CREATE TABLE IF NOT EXISTS vouchers (
code TEXT NOT NULL PRIMARY KEY,
credits INTEGER NOT NULL DEFAULT 0,
item_id INTEGER DEFAULT NULL,
is_redeemed INTEGER NOT NULL DEFAULT 0
);| ID | Name | File | Description |
|---|---|---|---|
| 127 | GETUSERCREDITLOG | purse/GETUSERCREDITLOG.h |
Return current credit balance (simple response) |
| 129 | REDEEM_VOUCHER | purse/REDEEM_VOUCHER.h |
Validate voucher code, award credits/items |
src/database/queries/player/voucher_query.hsrc/communication/incoming/purse/directory
Full room event creation, editing, listing, and expiration.
CREATE TABLE IF NOT EXISTS rooms_events (
room_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
name TEXT NOT NULL,
description TEXT NOT NULL,
expire_time INTEGER NOT NULL
);src/game/events/event.h- Event structsrc/game/events/events_manager.h- Singleton manager with List of eventssrc/database/queries/events/event_query.h- CRUD queries- 6 handler files in
src/communication/incoming/events/
| ID | Name | File | Description |
|---|---|---|---|
| 345 | CAN_CREATE_ROOMEVENT | events/CAN_CREATE_ROOMEVENT.h |
Check if user can create event in current room |
| 346 | CREATE_ROOMEVENT | events/CREATE_ROOMEVENT.h |
Create event: read category(int), name(str), desc(str) |
| 348 | EDIT_ROOMEVENT | events/EDIT_ROOMEVENT.h |
Edit existing event |
| 347 | QUIT_ROOMEVENT | events/QUIT_ROOMEVENT.h |
End/leave event |
| 349 | GET_ROOMEVENT_TYPE_COUNT | events/GET_ROOMEVENT_TYPE_COUNT.h |
Get count per category |
| 350 | GET_ROOMEVENTS_BY_TYPE | events/GET_ROOMEVENTS_BY_TYPE.h |
List events filtered by category |
- Init: load non-expired events from DB, clean expired
- Functions: create_event, remove_event, get_events_by_category, can_create_event
- Event lifetime configurable (default 120 minutes)
In-memory CFH ticket system for moderators.
src/game/moderation/cfh/cfh.h- CallForHelp struct (callId, callerId, roomId, message, timestamp, pickerId, isOpen)src/game/moderation/cfh/cfh_manager.h- Manager with hashtable of active callssrc/database/queries/moderation/moderation_action_query.h- Audit log- 7 handler files in
src/communication/incoming/moderation/
CREATE TABLE IF NOT EXISTS moderation_audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
moderator_id INTEGER NOT NULL,
target_id INTEGER,
action_type INTEGER NOT NULL,
message TEXT,
timestamp INTEGER NOT NULL
);| ID | Name | File | Description |
|---|---|---|---|
| 200 | MODERATORACTION | moderation/MODERATORACTION.h |
Execute mod action (alert/kick/ban/mute) |
| 237 | REQUEST_CFH | moderation/REQUEST_CFH.h |
Request pending CFH list (for mods) |
| 86 | SUBMIT_CFH | moderation/SUBMIT_CFH.h |
Submit help request |
| 48 | PICK_CALLFORHELP | moderation/PICK_CALLFORHELP.h |
Mod picks up CFH ticket |
| 199 | MESSAGETOCALLER | moderation/MESSAGETOCALLER.h |
Mod replies to caller |
| 198 | CHANGECALLCATEGORY | moderation/CHANGECALLCATEGORY.h |
Change CFH category |
| 238 | DELETE_CRY | moderation/DELETE_CRY.h |
Delete/close CFH ticket |
- In-memory ConcurrentHashMap equivalent (hashtable with mutex)
- Auto-incrementing call IDs
send_to_moderators()helper: iterate players, check fuseright rank, send packet- CFH struct: id, caller_id, room_id, message, timestamp, picker_id, is_open
Full sound machine song creation, editing, and playlist management.
CREATE TABLE IF NOT EXISTS soundmachine_songs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
soundmachine_id INTEGER NOT NULL,
title TEXT NOT NULL,
length INTEGER NOT NULL DEFAULT 0,
data TEXT NOT NULL,
burnt INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS soundmachine_tracks (
soundmachine_id INTEGER NOT NULL,
track_id INTEGER NOT NULL,
slot_id INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS soundmachine_playlists (
item_id INTEGER NOT NULL,
song_id INTEGER NOT NULL,
slot_id INTEGER NOT NULL
);src/game/songs/song.h- Song structsrc/game/songs/song_manager.h- Managersrc/database/queries/songs/song_query.h- 12 handler files in
src/communication/incoming/room/trax/
| ID | Name | File |
|---|---|---|
| 244/246 | GET_SONG_LIST | Already exists (extend for 246) |
| 239 | NEW_SONG | room/trax/NEW_SONG.h |
| 219 | INSERT_SOUND_PACKAGE | room/trax/INSERT_SOUND_PACKAGE.h |
| 220 | EJECT_SOUND_PACKAGE | room/trax/EJECT_SOUND_PACKAGE.h |
| 240 | SAVE_SONG_NEW | room/trax/SAVE_SONG_NEW.h |
| 243 | UPDATE_PLAY_LIST | room/trax/UPDATE_PLAY_LIST.h |
| 221 | GET_SONG_INFO | room/trax/GET_SONG_INFO.h |
| 245 | GET_PLAY_LIST | room/trax/GET_PLAY_LIST.h |
| 248 | DELETE_SONG | room/trax/DELETE_SONG.h |
| 241 | EDIT_SONG | room/trax/EDIT_SONG.h |
| 242 | SAVE_SONG_EDIT | room/trax/SAVE_SONG_EDIT.h |
| 218 | SAVE_SONG | room/trax/SAVE_SONG.h |
| 254 | BURN_SONG | room/trax/BURN_SONG.h |
Jukebox disk management.
CREATE TABLE IF NOT EXISTS soundmachine_disks (
item_id INTEGER NOT NULL,
soundmachine_id INTEGER NOT NULL,
slot_id INTEGER NOT NULL,
song_id INTEGER NOT NULL,
burned_at INTEGER NOT NULL DEFAULT 0
);src/game/jukebox/jukebox_manager.hsrc/database/queries/songs/jukebox_query.h- 6 handler files in
src/communication/incoming/room/jukebox/
| ID | Name | File |
|---|---|---|
| 258 | GET_JUKEBOX_DISCS | room/jukebox/GET_JUKEBOX_DISCS.h |
| 259 | GET_USER_SONG_DISCS | room/jukebox/GET_USER_SONG_DISCS.h |
| 255 | ADD_JUKEBOX_DISC | room/jukebox/ADD_JUKEBOX_DISC.h |
| 256 | REMOVE_JUKEBOX_DISC | room/jukebox/REMOVE_JUKEBOX_DISC.h |
| 257 | JUKEBOX_PLAYLIST_ADD | room/jukebox/JUKEBOX_PLAYLIST_ADD.h |
| 260 | RESET_JUKEBOX | room/jukebox/RESET_JUKEBOX.h |
CREATE TABLE IF NOT EXISTS recycler_rewards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sale_code TEXT NOT NULL,
item_cost INTEGER NOT NULL DEFAULT 5,
recycling_session_time_seconds INTEGER NOT NULL DEFAULT 300,
collection_time_seconds INTEGER NOT NULL DEFAULT 3660
);
CREATE TABLE IF NOT EXISTS recycler_sessions (
user_id INTEGER NOT NULL PRIMARY KEY,
reward_id INTEGER NOT NULL,
items TEXT NOT NULL,
session_started INTEGER NOT NULL DEFAULT 0
);| ID | Name | File |
|---|---|---|
| 222 | GET_FURNI_RECYCLER_CONFIGURATION | recycler/GET_FURNI_RECYCLER_CONFIGURATION.h |
| 223 | GET_FURNI_RECYCLER_STATUS | recycler/GET_FURNI_RECYCLER_STATUS.h |
| 225 | START_FURNI_RECYCLING | recycler/START_FURNI_RECYCLING.h |
| 226 | CONFIRM_FURNI_RECYCLING | recycler/CONFIRM_FURNI_RECYCLING.h |
Smaller systems.
| ID | Name | File | Description |
|---|---|---|---|
| 111 | CHANGEWORLD | infobus/CHANGEWORLD.h |
Change world/connection (stub) |
| 112 | VOTE | infobus/VOTE.h |
Vote on infobus question |
| 113 | TRYBUS | infobus/TRYBUS.h |
Enter infobus room |
| 114 | PTM | wobblesquabble/PTM.h |
Wobble squabble game move |
| 128 | GETPETSTAT | pets/GETPETSTAT.h |
Get pet statistics |
CREATE TABLE IF NOT EXISTS items_pets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type INTEGER NOT NULL,
race INTEGER NOT NULL,
colour TEXT NOT NULL,
nature_positive INTEGER NOT NULL DEFAULT 0,
nature_negative INTEGER NOT NULL DEFAULT 0,
last_kip INTEGER NOT NULL DEFAULT 0,
last_eat INTEGER NOT NULL DEFAULT 0,
last_drink INTEGER NOT NULL DEFAULT 0,
last_playtoy INTEGER NOT NULL DEFAULT 0,
last_playuser INTEGER NOT NULL DEFAULT 0,
born INTEGER NOT NULL DEFAULT 0
);Tutorial/guide system. Mostly stubs since the client flow is self-contained.
| ID | Name | File |
|---|---|---|
| 357 | ACCEPT_TUTOR_INVITATION | welcomingparty/ACCEPT_TUTOR_INVITATION.h |
| 358 | REJECT_TUTOR_INVITATION | welcomingparty/REJECT_TUTOR_INVITATION.h |
| 356 | MSG_INVITE_TUTORS | tutorial/MSG_INVITE_TUTORS.h |
| 355 | MSG_GET_TUTORS_AVAILABLE | tutorial/MSG_GET_TUTORS_AVAILABLE.h |
| 362 | MSG_WAIT_FOR_TUTOR_INVITATIONS | tutorial/MSG_WAIT_FOR_TUTOR_INVITATIONS.h |
| 363 | MSG_CANCEL_WAIT_FOR_TUTOR_INVITATIONS | tutorial/MSG_CANCEL_WAIT_FOR_TUTOR_INVITATIONS.h |
| 313 | MSG_REMOVE_ACCOUNT_HELP_TEXT | tutorial/MSG_REMOVE_ACCOUNT_HELP_TEXT.h |
| 359 | MSG_CANCEL_TUTOR_INVITATIONS | tutorial/MSG_CANCEL_TUTOR_INVITATIONS.h |
| 249 | RESET_TUTORIAL | tutorial/RESET_TUTORIAL.h |
Most complex phase. BattleBall + SnowStorm game framework.
CREATE TABLE IF NOT EXISTS games_ranks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
game_type TEXT NOT NULL,
rank_name TEXT NOT NULL,
min_points INTEGER NOT NULL DEFAULT 0,
max_points INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS games_maps (
model TEXT NOT NULL PRIMARY KEY,
game_type TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS games_spawns (
game_type TEXT NOT NULL,
map_id INTEGER NOT NULL,
team_id INTEGER NOT NULL,
x INTEGER NOT NULL,
y INTEGER NOT NULL,
z REAL NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS games_player_stats (
user_id INTEGER NOT NULL,
game_type TEXT NOT NULL,
points_all_time INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (user_id, game_type)
);src/game/games/game.h- Base game structsrc/game/games/game_manager.h- Game instance managersrc/game/games/game_player.h- Per-player game statesrc/game/games/battleball/- BattleBall logicsrc/game/games/snowstorm/- SnowStorm logicsrc/database/queries/games/game_query.h- 13 handler files in
src/communication/incoming/games/
| ID | Name |
|---|---|
| 159 | GETINSTANCELIST (conflicts with GETSELECTEDBADGES - context-based routing) |
| 160 | OBSERVEINSTANCE |
| 161 | UNOBSERVEINSTANCE |
| 162 | INITIATECREATEGAME |
| 163 | GAMEPARAMETERVALUES |
| 165 | INITIATEJOINGAME |
| 167 | LEAVEGAME |
| 168 | KICKPLAYER |
| 169 | WATCHGAME |
| 170 | STARTGAME |
| 171 | GAMEEVENT |
| 172 | GAMERESTART |
| 173 | REQUESTFULLGAMESTATUS |
Packet 159 conflict resolution: Context-based routing in message_handler_invoke(). If player is in a game lobby room (detected via room model prefix "bb_" or "ss_"), route to GETINSTANCELIST. Otherwise route to GETSELECTEDBADGES. This requires a wrapper function registered at 159 that dispatches based on context.
Full game logic: This phase implements complete BattleBall and SnowStorm including tile maps, teams, power-ups, scoring, game loops, and win conditions. This is the largest single phase and will reference the Java Game, BattleBallGame, SnowStormGame, GamePlayer, GameTile, PowerUp classes extensively.
CREATE TABLE IF NOT EXISTS groups_details (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
owner_id INTEGER NOT NULL,
room_id INTEGER NOT NULL,
badge TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS groups_memberships (
user_id INTEGER NOT NULL,
group_id INTEGER NOT NULL,
is_pending INTEGER NOT NULL DEFAULT 0,
member_rank INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (user_id, group_id)
);| ID | Name | Description |
|---|---|---|
| 230 | GET_GROUP_BADGES | Get group badges for room users |
| 231 | GET_GROUP_INFO | Get group details by ID |
struct events_manager events_manager;
struct cfh_manager cfh_manager;
struct song_manager song_manager;
struct recycler_manager recycler_manager;
struct game_manager game_manager;
struct group_manager group_manager;List *ignored_list; // cached user IDs
int favourite_group_id; // display group
bool sound_enabled; // sound settingAfter each phase:
- Run
cmake --build buildvia msys2 bash - Fix any compilation errors
- Test with Habbo client connecting and triggering the new packet handlers
- Verify no crashes via debug logging (
configuration_get_bool("debug"))
Phases 1-5 first (core gameplay), then 6-7 (events+moderation), then 8-9 (music), then 10-14 (remaining systems). Each phase is independently testable.