feat(rust): print the weapon name the module sends in the feed
All checks were successful
PR Checks / android-build (pull_request) Successful in 14m33s
All checks were successful
PR Checks / android-build (pull_request) Successful in 14m33s
PLAN.md M20 step 4. Module-Rust#60 gives every event frame a weaponName: the game's own name for a ranged weapon, or the prefab name as words. The feed printed the prefab name as words itself, which read "with ak47u entity". weaponName() in RustFormat.kt is the Kotlin half of module-rust's format.js weapon(). It prints frame.weaponName. Against a module too old to send it, it drops the .entity / .weapon / .deployed ending before prefabName(), as the website does, so an older module's kill reads "with ak47u". Tests: a kill with weaponName, the same kill from an older module, and the fallback's edges (blank name, each ending, an ending only at the end, no weapon). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user