Skip to content

🗡️ Two silent lookups: five melees have no swing type, and "cannon" in a name makes a kill explosive #51

Description

@longmaolab

TL;DR

Two bugs of the same shape: a lookup that quietly answers when it should admit it doesn't know. Neither throws, neither logs, and both have been shipping.

  • (A) MELEE_SWING_TYPES has 37 entries for 42 MELEE_ITEMS. The last five Admin melees fall off the end and silently take the || 'slash' default. The Spec-Ops Garrote is doing a baseball swing.
  • (B) The Kill Log decides "explosive vs gun" with one substring test, so anything whose id or type contains cannon or launcher is an explosive — the Desert Eagle brags with a blast graphic, and the Confetti Cannon fires confetti explosively. Meanwhile the Airburst Projector, which genuinely is an airburst, matches nothing.

Both came out of a new build-time tool (tools/tag-items.js) that asks a model the same questions these regexes are guessing at and diffs the answers against shipped behaviour. (A) it found with no model at all — it's just an array length.


(A) Five melees never got a swing type 🗡️

Where (verified):

  • public/game.jsconst MELEE_SWING_TYPES = [...] ends at index 36 (volt_whip)
  • public/game.jsconst MELEE_ITEMS = [...] runs to index 41
  • Read site: meleeSwingType = MELEE_SWING_TYPES[selectedMeleeIdx] || 'slash';

The catch: the || 'slash' was there to be safe, and instead it made the drift invisible. These five take a baseball swing:

idx item type
37 Karambit Admin · Combat Knife
38 Trench Bayonet Admin · Reach Knife
39 Tactical Tomahawk Admin · Throwable Axe
40 OTs-04 Bayonet Admin · Spetsnaz Blade
41 Spec-Ops Garrote Admin · Silent Kill

This is exactly the hazard #4 added a length guard for on weaponModels[]. The melee side never got one, and it drifted. MELEE_SWING_TYPES is the third parallel array in that family (MELEE_ITEMSmeleeModelsMELEE_SWING_TYPES) — the other two are still aligned at 42.

Proposed values, from each item's own reach and cooldown next to the ones already in the table:

'stab',    // 37 karambit  → short curved blade; knife (idx 7) is stab at 1.6 m / 260 ms
'thrust',  // 38 bayonet   → 3 m reach, same as spear (idx 4), which is thrust
'chop',    // 39 tomahawk  → axe head; meat_cleaver (idx 33) is chop
'stab',    // 40 ots04     → "Bayonet" by name, but 1.8 m and a 240 ms cooldown: a jab
'thrust',  // 41 garrote   → ⚠️ judgement call

⚠️ The garrote needs a human. None of the eight swing types is a strangle — thrust is the least wrong, not the right answer. Worth a playtest, and worth considering whether a garrote swing type should exist at all.

Also worth doing: extend whatever guard #4 added so this family is checked too, and drop the || 'slash' so the next gap is loud instead of quiet.


(B) "Hand Cannon" is not a cannon 💥

Where (verified): public/game.js, in the Kill Log writer:

const typeStr = ((wpn?.type || sup?.type || '') + '').toLowerCase();
const idStr = (weaponId || '').toLowerCase();
if (/explos|launcher|mortar|firework|grenade|rocket|bomb|nuke|missile|boombow|cannon|artillery/
    .test(typeStr + ' ' + idStr)) kind = 'explosive';

The catch: cannon and launcher are substrings of a lot of names that describe the shape of the tube, not how you died. Eight items are currently mis-filed:

item type why it matched
desert_eagle Admin · Hand Cannon "Hand Cannon"
hand_cannon Secondary+ id contains "cannon"
burst_cannon Heavy Burst Rifle id contains "cannon"
foam_cannon Industrial Utility id contains "cannon"
prism_launcher Bouncing Light id contains "launcher"
portal_launcher Spatial id contains "launcher"
confetti_cannon Chaos (support) id contains "cannon"

…and it misses real ones: airburst_projector (authored as grenade in PROJECTILE_KIND_BY_ID), plus land_mine, proximity_mine, magnet_mine, stasis_mine and claymore.

The fix is to ask the data, not the name. The game already knows: PROJECTILE_KIND_BY_ID records what each weapon fires, and splashRadius says outright that it detonates.

const EXPLOSIVE_KILL_WORDS = new Set([
  'explosive', 'explosives', 'grenade', 'grenades', 'bomb', 'bombs', 'nuke',
  'dynamite', 'mine', 'rocket', 'missile', 'frag', 'c4', 'mortar', 'artillery',
  'firework', 'fireworks', 'claymore',
]);
function namedAsExplosive(id, type) {
  return (String(id || '') + ' ' + String(type || '')).toLowerCase()
    .split(/[^a-z0-9]+/)               // whole words, so confetti_cannon has no cannon in it
    .some(w => EXPLOSIVE_KILL_WORDS.has(w));
}
function isExplosiveKill(weaponId, wpn, sup) {
  if (wpn) {
    const k = projectileKind(weaponId, wpn);
    if (k === 'grenade' || k === 'rocket' || wpn.splashRadius) return true;
    return namedAsExplosive(weaponId, wpn.type);   // Boombow: a bolt that detonates
  }
  if (sup) return namedAsExplosive(weaponId, sup.type);
  return false;
}

No cannon, no launcher in the word list, on purpose.

Verified over the whole catalogue (99 weapons + 55 support): 7 weapons and 1 support item stop being explosive; airburst_projector, land_mine, proximity_mine, magnet_mine, stasis_mine and claymore start; and rpg, bazooka, grenade_launcher, storm_cannon, boombow, potato_cannon, frag, c4, tac_nuke, dynamite, sticky_charge, mortar_rifle and firework_launcher are unchanged.

Two left deliberately alonepinball_launcher and potato_cannon. Both are authored as 'grenade' in PROJECTILE_KIND_BY_ID, so the table says lobbed explosive while the name says joke. That's a content call, not a bug, and it wants your eye rather than a patch.


Not in this issue, but found alongside 🔍

The same tool turned up things that are balance or taste, not bugs — filing them here only so they aren't lost:

  • ELECTRIC_WEAPONS contains the plain pistol and revolver. That set isn't cosmetic: getSecretSynergy() uses it to grant ×1.5 damage on ice zones and ×1.25 on sewer/holiday. freeze_gun is in both ELECTRIC_WEAPONS and FROST_WEAPONS; the actual volt_whip ("Long Electric") is in neither. sg8, a plain shotgun, is in FIRE_WEAPONS (×1.30 on forest).
  • 8 melee swing sounds are off for the same reason as (B) — the blade/heavy test reads only item.id, so ots04 ("Spetsnaz Blade") swings with the blunt sound.
  • _pickFinisher() falls through to FINISHERS[random] for most weapons, including storm_cannon ("Lightning Explosive"), which never gets the lightning finisher because /Explosive/ is tested before /Lightning/ in projectileKind().

Happy to split any of these into their own issue.


中文摘要

两个同一类的 bug:查表查不到时不吭声,直接给个默认值。

(A) MELEE_SWING_TYPES 只有 37 条,MELEE_ITEMS 有 42 条。最后 5 把 Admin 近战掉出数组,被 || 'slash' 兜成棒球挥棒——特工绞索在打棒球。这跟 #4weaponModels[] 加长度守卫防的是同一个毛病,近战这边没加,就漂移了。绞索那条我给的是 thrust但八种挥击类型里没有一种是"勒",需要你试手感,也可以考虑干脆加一个 garrote 类型。

(B) 击杀日志用一次子串匹配判"爆炸 vs 枪击",名字里带 cannon / launcher 就算爆炸——沙漠之鹰的击杀图标是爆炸,彩带炮"爆炸地"喷彩带;而真正的空爆发射器一个都没匹配上,地雷也全漏。修法是问数据不问名字PROJECTILE_KIND_BY_ID 本来就记了每把枪发射什么,splashRadius 直接说明它炸不炸。补给道具没有这张表,就按整词匹配而不是子串。

pinball_launcherpotato_cannon故意没动:手写表里它俩就是 'grenade',这是内容取向问题不是 bug,留给你定。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions