diff --git a/app/src/main/java/com/runicgateway/app/ui/rust/RustFeed.kt b/app/src/main/java/com/runicgateway/app/ui/rust/RustFeed.kt index fdf2c63..354c093 100644 --- a/app/src/main/java/com/runicgateway/app/ui/rust/RustFeed.kt +++ b/app/src/main/java/com/runicgateway/app/ui/rust/RustFeed.kt @@ -161,7 +161,7 @@ fun describe(row: RustEventDto): FeedLine { */ private fun death(row: RustEventDto, name: String?): FeedLine { val where = listOfNotNull( - row.str("weapon")?.let { "with ${prefabName(it)}" }, + row.str("weapon")?.let { "with ${weaponName(it, row.str("weaponName"))}" }, row.num("distance")?.let { "${Math.round(it)}m" }, row.str("grid"), if (row.flag("sleeping")) "while sleeping" else null, diff --git a/app/src/main/java/com/runicgateway/app/ui/rust/RustFormat.kt b/app/src/main/java/com/runicgateway/app/ui/rust/RustFormat.kt index bd6b8ae..8afe24f 100644 --- a/app/src/main/java/com/runicgateway/app/ui/rust/RustFormat.kt +++ b/app/src/main/java/com/runicgateway/app/ui/rust/RustFormat.kt @@ -159,6 +159,23 @@ fun prefabName(name: String?): String { return name.replace(Regex("[_.]+"), " ").trim() } +/** The endings a held entity's prefab name carries that no player says. */ +private val WEAPON_ENDING = Regex("[._](entity|weapon|deployed)$") + +/** + * The weapon in a kill, the Kotlin half of `module-rust`'s `format.js weapon()`. + * The module sends `weaponName` (Module-Rust#60), which is the game's own name for a + * gun or the prefab name as words (`server/model/titles/weaponNames.js`), and the + * app prints that. A module too old to send it gets the same fallback here: the + * `.entity` / `.weapon` / `.deployed` ending is dropped, then [prefabName] turns the + * rest into words. So the app never prints "ak47u entity". + */ +fun weaponName(weapon: String?, weaponName: String?): String { + if (weapon.isNullOrBlank()) return "" + if (!weaponName.isNullOrBlank()) return weaponName + return prefabName(weapon.trim().replace(WEAPON_ENDING, "")) +} + /** * An Economics balance (`docs/modules/rust/PLAN_REDESIGNS.md` §11.2.2): grouped, * at most two places, and a dash for one the server never reported — not "0", diff --git a/app/src/test/java/com/runicgateway/app/ui/rust/RustFeedTest.kt b/app/src/test/java/com/runicgateway/app/ui/rust/RustFeedTest.kt index 35e79ed..be172c8 100644 --- a/app/src/test/java/com/runicgateway/app/ui/rust/RustFeedTest.kt +++ b/app/src/test/java/com/runicgateway/app/ui/rust/RustFeedTest.kt @@ -41,6 +41,35 @@ class RustFeedTest { assertTrue(line.detail.contains("42m")) } + @Test + fun `a kill prints the weapon name the module sends`() { + // Module-Rust#60: the module reads the game's own name off its table and + // sends it beside the prefab, which stays the counting key. + val line = describe( + row( + "player.death", + """{"name":"Bob","attackerType":"player","attackerName":"Alice", + "weapon":"ak47u.entity","weaponName":"Assault Rifle"}""", + ), + ) + + assertEquals("with Assault Rifle", line.detail) + } + + @Test + fun `an older module's kill drops the prefab ending rather than printing it`() { + // A module from before #60 sends no `weaponName`. The feed once said + // "with ak47u entity"; the ending is dropped the way the website does it. + val line = describe( + row( + "player.death", + """{"name":"Bob","attackerType":"player","attackerName":"Alice","weapon":"ak47u.entity"}""", + ), + ) + + assertEquals("with ak47u", line.detail) + } + @Test fun `a fall is a death by nobody, not a kill`() { // The failure this distinction exists to avoid: `HitInfo` is legitimately diff --git a/app/src/test/java/com/runicgateway/app/ui/rust/RustFormatTest.kt b/app/src/test/java/com/runicgateway/app/ui/rust/RustFormatTest.kt index 06e8650..0f2dc9e 100644 --- a/app/src/test/java/com/runicgateway/app/ui/rust/RustFormatTest.kt +++ b/app/src/test/java/com/runicgateway/app/ui/rust/RustFormatTest.kt @@ -117,6 +117,21 @@ class RustFormatTest { assertEquals("", prefabName(null)) } + @Test + fun `a weapon prints the module's name, or the prefab without its ending`() { + assertEquals("Assault Rifle", weaponName("ak47u.entity", "Assault Rifle")) + // An older module sends no name; neither does one that sends a blank. + assertEquals("ak47u", weaponName("ak47u.entity", null)) + assertEquals("ak47u", weaponName("ak47u.entity", "")) + assertEquals("salvaged sword", weaponName("salvaged_sword.weapon", null)) + assertEquals("autoturret", weaponName("autoturret.deployed", null)) + assertEquals("rifle ak", weaponName("rifle.ak", null)) + // The ending is dropped only at the end. + assertEquals("entity thing", weaponName("entity.thing", null)) + // No weapon, no name, even when a name came along. + assertEquals("", weaponName(null, "Assault Rifle")) + } + @Test fun `a nameless player shows a shortened id, not the word unknown`() { // A steam id is not a name and does not look like one, which is the point: