Skip to content

⚔️ Melee skins change your damage, and 12 base melees deal 25 whatever they advertise / 近战皮肤会改伤害,12 把基础近战不管标多少都只打 25 #52

Description

@longmaolab

TL;DR

Melee skins change your damage, and 12 base melees deal 25 no matter what they say.

emitHit sends item.id to the server as the weapon. For a skinned melee that id is the skin's id, and server.js does WEAPON_DAMAGE[data.weapon] || 25. A skin is documented in the code as "a new name and look, never a new weapon"effectiveMeleeItem() deliberately keeps the base's damage, reach, cooldown and ability — but the one number that actually decides a fight is taken from a table the skin id isn't in.

  • (A) 25 of 27 melee skins collapse to the server's || 25 default. Two more resolve to a different melee's damage.
  • (B) Independently of skins, 12 of 42 base melees have no WEAPON_DAMAGE entry at all and also deal 25. The Phase Blade advertises 90 and deals 25.

(B) is the one to fix first — it needs no skin equipped and it hits paying customers.

Found while reviewing #51; not caused by it. This is CLAUDE.md gotcha #4 ("client ↔ server must mirror WEAPON_DAMAGE") going unenforced, which is exactly what #7 proposes to fix structurally.


(B) 12 base melees deal 25 regardless of what the client says 🗡️

public/game.js declares the damage; server.js decides it. These 12 are in the first table and not the second:

melee client says server deals
phase_blade 90 25 −72%
meat_cleaver 60 25 −58%
machete 56 25 −55%
hatchet 50 25 −50%
volt_whip 50 25 −50%
pipe 44 25 −43%
cricket_bat 42 25 −40%
golf_club 40 25 −38%
fire_poker 38 25 −34%
wrench 36 25 −31%
brass_knuckles 28 25 −11%
tennis_racket 26 25 −4%

The Phase Blade is a Sci-fi P2W melee. Someone paid for 90 damage and is swinging 25. The shop, the wiki and the loadout screen all show 90, so there is no way to notice except by counting hits.

Client-side prediction disagrees too, which is why it feels like desync rather than a number being wrong: getClientWeaponDamage misses these and returns 10, so you predict 10, the server applies 25, and the health bar jumps.


(A) A cosmetic skin changes the number 🎨

emitHit(pid, …, effectiveWeaponId, …) where effectiveWeaponId = item.id, and item came from equippedMeleeItem()effectiveMeleeItem(), which sets id to the skin's id and stashes the real one in baseId.

25 of 27 skins send an id the server has never heard of and fall to || 25:

skin worn on should deal actually deals
combat_axe_firemans Combat Axe 78 25
sledge_gold_brick Sledge 70 25
sledge_anvil_on_a_stick Sledge 70 25
katana_ruler Katana 65 25
machete Katana 65 25
spear_broom_handle Spear 50 25
fire_poker Spear 50 25
chainsaw_pruning Chainsaw 45 25
(17 more)

2 skins are worse than that — they resolve to a completely different melee's entry, because the skin id happens to also be a real melee id:

skin worn on should deal actually deals why
cane Spear 50 30 WEAPON_DAMAGE['cane'] is the Walking Cane's
shovel Sledge 70 55 WEAPON_DAMAGE['shovel'] is the Shovel's

The same skin id also reaches getSecretSynergy, falloffMultiplier, headshotMultFor and INSTAKILL_HS_WEAPONS inside emitHit — so e.g. the Fire Poker skin worn on a Spear picks up FIRE_WEAPONS map synergy that the Spear itself does not have.


Suggested fix

(A) is the same shape as the swing-sound fix in #51: resolve to the base once at the top of the melee hit loop and use it for every id that goes on the wire. effectiveMeleeItem() already provides baseId.

const hitId = item.baseId || item.id;      // a skin is paint, not a weapon
let effectiveWeaponId = hitId;
// …and the same for the heavy-strike double emit, the slam, and the lunge,
// which each read item.id separately.

(B) is data: add the 12 missing ids to WEAPON_DAMAGE in server.js with the values MELEE_ITEMS already declares — and check the numbers first, since several of these have presumably been balanced by feel at 25 for a while. Going from 25 to 90 on the Phase Blade is a real balance change, not just a bug fix.

Worth more than either: a guard in tools/verify-weapons.js asserting every MELEE_ITEMS / WEAPONS id has a WEAPON_DAMAGE entry, and every BASIC_MELEE_SKINS id either has one or is never sent. That turns gotcha #4 from a thing to remember into a thing that fails the build. #7 (shared/tables.js) is the deeper version of the same idea.


中文摘要

近战皮肤会改伤害,而且 12 把基础近战不管客户端写多少都只打 25。

emitHititem.id 当武器 id 发给服务器,而装了皮肤之后这个 id 是皮肤的 id;服务器 WEAPON_DAMAGE[data.weapon] || 25 查不到就给 25。代码注释白纸黑字写着皮肤"只是换个名字和外观,绝不是换把武器",effectiveMeleeItem() 也特意保留了基础武器的伤害/距离/冷却/技能——唯独真正决定胜负的那个数字,取自一张皮肤 id 根本不在的表。

  • (A) 27 个皮肤里 25 个塌成 25 伤害;另外 2 个(caneshovel)更离谱,取到了另一把近战的数值。
  • (B) 跟皮肤无关:42 把基础近战里有 12 把压根没有 WEAPON_DAMAGE 条目,同样只打 25。相位刃标着 90,实际 25(弱了 72%)——那是把 P2W 武器,玩家是花钱买的。

建议先修 (B):不用装皮肤就中招,而且坑的是付费玩家。但改数值之前先确认手感——这几把按 25 打了很久了,直接提到 90 是实打实的平衡改动,不只是修 bug。

顺带:tools/verify-weapons.js 应该加一条守卫,强制每个武器 id 都有 WEAPON_DAMAGE 条目,把 gotcha #4 从"要记得"变成"忘了就构建失败"。#7 提的 shared/tables.js 是同一件事的彻底版。

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