From a2fcc84a4a2528011052a475693d9ebd0aa22b61 Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 10 Jul 2026 17:07:59 +0100 Subject: [PATCH 1/8] Fix run orb status invalid on first login --- game/src/main/kotlin/content/entity/player/Introduction.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/game/src/main/kotlin/content/entity/player/Introduction.kt b/game/src/main/kotlin/content/entity/player/Introduction.kt index 1e46daee17..915d27cd1c 100644 --- a/game/src/main/kotlin/content/entity/player/Introduction.kt +++ b/game/src/main/kotlin/content/entity/player/Introduction.kt @@ -25,6 +25,7 @@ class Introduction : Script { return } if (Settings["world.start.creation", true] && !player.isBot) { + player.sendVariable("movement") player["delay"] = -1 World.queue("welcome_${player.name}", 1) { player.open("character_creation") From bc4dde13c1d9576def59bcc0422543bad276341b Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 10 Jul 2026 17:36:28 +0100 Subject: [PATCH 2/8] Fix logging in requiring display name instead of account name --- .../world/gregs/voidps/engine/client/PlayerAccountLoader.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt index e7f72efe22..acb435c8ff 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt @@ -39,7 +39,7 @@ class PlayerAccountLoader( override fun exists(username: String): Boolean = storage.exists(username) - override fun password(username: String): String? = accountDefinitions.get(username)?.passwordHash + override fun password(username: String): String? = accountDefinitions.getByAccount(username)?.passwordHash /** * @return flow of instructions for the player to be controlled with From e0924454296d33563e2c10532d55384c737ab1c6 Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 10 Jul 2026 17:37:24 +0100 Subject: [PATCH 3/8] Fix being able to create an account using a display name that's already in use --- .../engine/client/PlayerAccountLoader.kt | 2 ++ .../data/definition/AccountDefinitions.kt | 18 +++++++++--------- .../voidps/network/login/AccountLoader.kt | 2 ++ .../voidps/network/login/PasswordManager.kt | 4 ++++ .../gregs/voidps/network/LoginServerTest.kt | 4 ++++ .../voidps/network/PasswordManagerTest.kt | 14 ++++++++++++++ 6 files changed, 35 insertions(+), 9 deletions(-) diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt index acb435c8ff..657d182beb 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt @@ -37,6 +37,8 @@ class PlayerAccountLoader( var update: Boolean = false + override fun used(username: String) = accountDefinitions.get(username) != null + override fun exists(username: String): Boolean = storage.exists(username) override fun password(username: String): String? = accountDefinitions.getByAccount(username)?.passwordHash diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/AccountDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/AccountDefinitions.kt index 9559df733b..18ea1dcfd5 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/AccountDefinitions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/AccountDefinitions.kt @@ -47,18 +47,18 @@ class AccountDefinitions( fun clan(displayName: String) = clans[displayName.lowercase()] - fun getByAccount(account: String): AccountDefinition? { - return get(displayNames[account.lowercase()] ?: return null) + fun getByAccount(accountName: String): AccountDefinition? { + return get(displayNames[accountName.lowercase()] ?: return null) } - fun get(key: String) = definitions[key.lowercase()] + fun get(displayName: String) = definitions[displayName.lowercase()] - fun getValue(key: String) = definitions.getValue(key.lowercase()) + fun getValue(displayName: String) = definitions.getValue(displayName.lowercase()) fun load(storage: Storage = get()): AccountDefinitions { timedLoad("account") { - for ((name, definition) in storage.names()) { - definitions[name.lowercase()] = definition + for ((_, definition) in storage.names()) { + definitions[definition.displayName.lowercase()] = definition } for (def in definitions.values) { displayNames[def.accountName.lowercase()] = def.displayName @@ -85,13 +85,13 @@ class AccountDefinitions( skip: (accountName: String) -> Boolean, ): Int { var count = 0 - for ((name, definition) in names) { + for ((_, definition) in names) { if (skip(definition.accountName)) { continue } - val existing = definitions[name.lowercase()] + val existing = definitions[definition.displayName.lowercase()] if (existing == null) { - definitions[name.lowercase()] = definition + definitions[definition.displayName.lowercase()] = definition } else if (existing == definition) { continue } else { diff --git a/network/src/main/kotlin/world/gregs/voidps/network/login/AccountLoader.kt b/network/src/main/kotlin/world/gregs/voidps/network/login/AccountLoader.kt index 2db832a876..fac756c084 100644 --- a/network/src/main/kotlin/world/gregs/voidps/network/login/AccountLoader.kt +++ b/network/src/main/kotlin/world/gregs/voidps/network/login/AccountLoader.kt @@ -8,6 +8,8 @@ import world.gregs.voidps.network.client.Instruction * Loads and checks existing accounts */ interface AccountLoader { + fun used(username: String): Boolean + fun exists(username: String): Boolean fun password(username: String): String? diff --git a/network/src/main/kotlin/world/gregs/voidps/network/login/PasswordManager.kt b/network/src/main/kotlin/world/gregs/voidps/network/login/PasswordManager.kt index cb08a20603..593bc8b245 100644 --- a/network/src/main/kotlin/world/gregs/voidps/network/login/PasswordManager.kt +++ b/network/src/main/kotlin/world/gregs/voidps/network/login/PasswordManager.kt @@ -13,6 +13,10 @@ class PasswordManager(private val account: AccountLoader) { return Response.LOGIN_SERVER_REJECTED_SESSION } val passwordHash = account.password(username) + if (passwordHash == null && account.used(username)) { + // Username already in use as a display name + return Response.INVALID_CREDENTIALS + } if (!account.exists(username)) { if (passwordHash != null) { // Failed to find account file despite AccountDefinition exists in memory (aka existed on startup) diff --git a/network/src/test/kotlin/world/gregs/voidps/network/LoginServerTest.kt b/network/src/test/kotlin/world/gregs/voidps/network/LoginServerTest.kt index bacab22dc0..5a2489aefe 100644 --- a/network/src/test/kotlin/world/gregs/voidps/network/LoginServerTest.kt +++ b/network/src/test/kotlin/world/gregs/voidps/network/LoginServerTest.kt @@ -48,6 +48,8 @@ internal class LoginServerTest { password = "\$2a\$10${"$"}iIdTrtrJ5ibgFcJToZW7ueGkymDed2Ws2FoE8JnrXPGiY2YNVa9y6" instructions = Channel(capacity = 1) accounts = object : AccountLoader { + override fun used(username: String) = false + override fun exists(username: String) = true override fun password(username: String) = password @@ -230,6 +232,8 @@ internal class LoginServerTest { val writeChannel = ByteChannel(autoFlush = true) accounts = object : AccountLoader { + override fun used(username: String) = false + override fun exists(username: String) = false override fun password(username: String) = null diff --git a/network/src/test/kotlin/world/gregs/voidps/network/PasswordManagerTest.kt b/network/src/test/kotlin/world/gregs/voidps/network/PasswordManagerTest.kt index 062f0f3544..ca13ddaf86 100644 --- a/network/src/test/kotlin/world/gregs/voidps/network/PasswordManagerTest.kt +++ b/network/src/test/kotlin/world/gregs/voidps/network/PasswordManagerTest.kt @@ -58,6 +58,17 @@ class PasswordManagerTest { assertEquals(Response.SUCCESS, result) } + @Test + fun `Can't create new player if username is in use`() { + val username = "test" + val password = "password" + accountLoader.used = true + + val result = passwordManager.validate(username, password) + + assertEquals(Response.INVALID_CREDENTIALS, result) + } + @Test fun `Existing player with no password is disabled`() { val username = "test" @@ -103,6 +114,9 @@ class PasswordManagerTest { private class TestAccountLoader : AccountLoader { val accountMap = mutableMapOf() var exists = true + var used = false + + override fun used(username: String): Boolean = used override fun exists(username: String): Boolean = exists From e63a5b0b8f6bbe2af39f0f9e66d0a0f9b44b9421 Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 10 Jul 2026 17:40:18 +0100 Subject: [PATCH 4/8] Allow spaces in message request usernames fixes #1068 --- .../kotlin/world/gregs/voidps/engine/client/EncodeExtensions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/EncodeExtensions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/EncodeExtensions.kt index 4f43ab9f6a..74543bc3d3 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/EncodeExtensions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/EncodeExtensions.kt @@ -36,7 +36,7 @@ fun Character.message( getOrPut("messages") { FixedSizeQueue(100) }.add(text) val font = get().get("p12_full") for (line in font.splitLines(Colours.replaceCustomTags(text), if (type == ChatType.Console) 600 else 484)) { - client?.message(line, type.id, tile, name, name?.lowercase(Locale.getDefault())?.replace(' ', '_')) + client?.message(line, type.id, tile, name, name?.lowercase(Locale.getDefault())) } } From 4edf80df7e49e11db4d9548f7877fb3c1f1b0b78 Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 10 Jul 2026 18:46:43 +0100 Subject: [PATCH 5/8] Some script params --- data/client/client.scripts.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/data/client/client.scripts.toml b/data/client/client.scripts.toml index f454839062..2ba7861534 100644 --- a/data/client/client.scripts.toml +++ b/data/client/client.scripts.toml @@ -39,6 +39,15 @@ id = 571 [primary_options] id = 150 +params = [ + "component_id", + "inventory_id", + "inv_width", + "inv_height", + "drag_type", # 1=swap, 2=shuffle + "inv_drag_iface", + "options", +] [secondary_options] id = 695 From 36afed8a240647e110b76b6dd160655d44cb531f Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 10 Jul 2026 19:18:02 +0100 Subject: [PATCH 6/8] Fix account unit test --- .../gregs/voidps/engine/client/PlayerAccountLoaderTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/src/test/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoaderTest.kt b/engine/src/test/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoaderTest.kt index d650eca223..981d87f91d 100644 --- a/engine/src/test/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoaderTest.kt +++ b/engine/src/test/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoaderTest.kt @@ -71,14 +71,14 @@ internal class PlayerAccountLoaderTest : KoinMock() { override fun load(accountName: String): PlayerSave? = playerSave } saveQueue = SaveQueue(storage, scope = TestScope()) - definitions = AccountDefinitions(mutableMapOf("name" to AccountDefinition("name", "oldName", "", "hash"))) + definitions = AccountDefinitions(mutableMapOf("name" to AccountDefinition("name", "oldName", "", "hash")), mutableMapOf("accountname" to "name")) accounts = mockk(relaxed = true) loader = PlayerAccountLoader(queue, storage, accounts, saveQueue, definitions, UnconfinedTestDispatcher()) } @Test fun `Get password`() { - assertEquals("hash", loader.password("name")) + assertEquals("hash", loader.password("accountName")) assertNull(loader.password("name2")) } From fef381f0706068a844436a0cf2fb5c1ebba61a4a Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 10 Jul 2026 19:26:55 +0100 Subject: [PATCH 7/8] Fix account merge tests --- .../voidps/engine/data/AccountDefinitionsReloaderTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/src/test/kotlin/world/gregs/voidps/engine/data/AccountDefinitionsReloaderTest.kt b/engine/src/test/kotlin/world/gregs/voidps/engine/data/AccountDefinitionsReloaderTest.kt index 46ed948767..85f39dbfda 100644 --- a/engine/src/test/kotlin/world/gregs/voidps/engine/data/AccountDefinitionsReloaderTest.kt +++ b/engine/src/test/kotlin/world/gregs/voidps/engine/data/AccountDefinitionsReloaderTest.kt @@ -36,7 +36,7 @@ class AccountDefinitionsReloaderTest { assertTrue(started) assertEquals(1, completed) - assertEquals("hash", definitions.get("account")?.passwordHash) + assertEquals("hash", definitions.get("Name")?.passwordHash) } @Test @@ -46,11 +46,11 @@ class AccountDefinitionsReloaderTest { assertTrue(reloader.reload()) - assertEquals("old_hash", definitions.get("account")?.passwordHash) + assertEquals("old_hash", definitions.get("Name")?.passwordHash) every { storage.names() } returns mapOf("account" to AccountDefinition("account", "Name", "", "new_hash")) every { storage.clans() } returns emptyMap() assertTrue(reloader.reload()) - assertEquals("new_hash", definitions.get("account")?.passwordHash) + assertEquals("new_hash", definitions.get("Name")?.passwordHash) } @Test @@ -66,7 +66,7 @@ class AccountDefinitionsReloaderTest { assertTrue(reloader.reload { count -> completed = count }) assertEquals(0, completed) - assertEquals("memory_hash", definitions.get("account")?.passwordHash) + assertEquals("memory_hash", definitions.get("Name")?.passwordHash) } @Test From a150436eb60a0e4b5f5a3a23daa08be2c1475d01 Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 10 Jul 2026 20:08:14 +0100 Subject: [PATCH 8/8] Fix account definitions incorrectly using account name instead of display name as the key --- .../engine/data/definition/AccountDefinitions.kt | 7 +++++-- .../engine/data/definition/AccountDefinitionsTest.kt | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/AccountDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/AccountDefinitions.kt index 18ea1dcfd5..1b619486e5 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/AccountDefinitions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/AccountDefinitions.kt @@ -89,12 +89,15 @@ class AccountDefinitions( if (skip(definition.accountName)) { continue } - val existing = definitions[definition.displayName.lowercase()] + val displayName = displayNames[definition.accountName.lowercase()]?.lowercase() ?: definition.displayName.lowercase() + val existing = definitions[displayName] if (existing == null) { - definitions[definition.displayName.lowercase()] = definition + definitions[displayName] = definition } else if (existing == definition) { continue } else { + definitions.remove(displayName) + definitions[definition.displayName.lowercase()] = existing existing.displayName = definition.displayName existing.previousName = definition.previousName existing.passwordHash = definition.passwordHash diff --git a/engine/src/test/kotlin/world/gregs/voidps/engine/data/definition/AccountDefinitionsTest.kt b/engine/src/test/kotlin/world/gregs/voidps/engine/data/definition/AccountDefinitionsTest.kt index b0b7bd043d..e28066803a 100644 --- a/engine/src/test/kotlin/world/gregs/voidps/engine/data/definition/AccountDefinitionsTest.kt +++ b/engine/src/test/kotlin/world/gregs/voidps/engine/data/definition/AccountDefinitionsTest.kt @@ -26,7 +26,7 @@ class AccountDefinitionsTest { val count = definitions.merge(mapOf("new_account" to definition), emptyMap()) { false } assertEquals(1, count) - assertSame(definition, definitions.get("new_account")) + assertSame(definition, definitions.get("Newbie")) assertEquals("Newbie", definitions.displayNames["new_account"]) } @@ -38,7 +38,7 @@ class AccountDefinitionsTest { val count = definitions.merge(mapOf("account" to definition("account", "New name", hash = "new_hash")), emptyMap()) { false } assertEquals(1, count) - val updated = definitions.get("account") + val updated = definitions.get("New name") assertSame(existing, updated) assertEquals("New name", updated?.displayName) assertEquals("new_hash", updated?.passwordHash) @@ -64,7 +64,7 @@ class AccountDefinitionsTest { } assertEquals(0, count) - assertEquals("memory_hash", definitions.get("online_account")?.passwordHash) + assertEquals("memory_hash", definitions.get("Online")?.passwordHash) } @Test @@ -73,7 +73,7 @@ class AccountDefinitionsTest { definitions.merge(emptyMap(), emptyMap()) { false } - assertEquals("hash", definitions.get("account")?.passwordHash) + assertEquals("hash", definitions.get("Name")?.passwordHash) } @Test @@ -88,7 +88,7 @@ class AccountDefinitionsTest { @Test fun `Merge updates existing clan in place preserving members`() { val existing = clan("owner_account", "Owner") - val member: Player = Player(accountName = "member_account") + val member = Player(accountName = "member_account") existing.members.add(member) definitions.merge(emptyMap(), mapOf("owner_account" to existing)) { false }