fix: cut the welded ankle band, seat the gun in the hand, flash at the barrel
1. Ankle cuffs. The previous pass re-weighted the offending vertices to the nearer leg, which was the wrong call: the strip between the ankles is REAL geometry spanning the gap (~10 cm of ClothCAndW crossing x = -0.05 .. +0.05 at ankle height), so re-weighting only tore it in half — a visible seam that still stretched. A triangle with one corner weighted to each leg has no correct pose; it must stretch the moment the legs separate. SkinMeshRepair now deletes those triangles instead (48 on Taila: the cloth band plus its outline shell). The cut is limited to BELOW THE KNEE, taken from the skeleton's own rest pose rather than a hardcoded height, because above the knee cross-leg geometry is legitimate — the skirt and shorts genuinely span left-thigh to right-thigh weights at the crotch. 2. The M4 floated because _measure_weapon derived the grip from mesh AABBs, and the FBX guns report bind-pose bounds tens of metres across — it measured the M4 as 24 m long and pushed the gun 7.5 m in front of the character. Bounding boxes are simply not trustworthy for these meshes. The grip was already authored elsewhere: WeaponManager places the first-person viewmodel's hands at fixed points in weapon space, and every weapon marks its barrel tip with muzzle_flash.position. Those move to WeaponGrips (dependency-free, so both the weapon system and the character models can use it without dragging each other's load order along) and third person now reaches for exactly the points the viewmodel uses. Every weapon's grip now lands 0.073 m from the hand — the M4 included, down from 2.32 m — with barrel lengths that match the models (0.30 m for the MP7, 0.68 m for the DMR). 3. Muzzle flash and tracers were spawned off the viewmodel's muzzle. The viewmodel is parented to the camera, so its muzzle sits inside the player's head — in third person the flash appeared by the character's shoulder. world_muzzle() returns the muzzle of the gun actually in the character's hands whenever the character is what the viewer sees, and the networked fire-effect RPC now sends that position too, so remote players stop seeing tracers leave the shooter's head. Measured: the third-person origin sits at the held gun, 0.54 m below the head, instead of on the camera. FSM tests 11/11, spawn smoke test 0 failures. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
35ada4f34a
commit
ec6b8228da
@@ -118,12 +118,44 @@ func _fire() -> void:
|
||||
if current_ammo == 0:
|
||||
_start_reload()
|
||||
|
||||
## See BaseHitscanWeapon.world_muzzle(): in third person the viewmodel's muzzle
|
||||
## sits inside the player's head, so world effects must come off the gun the
|
||||
## character is actually holding.
|
||||
func world_muzzle() -> Node3D:
|
||||
if player and "third_person" in player and player.third_person \
|
||||
and player.has_method("get_visual_model"):
|
||||
var vis = player.get_visual_model()
|
||||
if vis and vis.has_method("get_muzzle_node"):
|
||||
var m: Node3D = vis.get_muzzle_node()
|
||||
if m:
|
||||
return m
|
||||
return muzzle_flash
|
||||
|
||||
|
||||
## Where OTHER clients should see this shot leave from — always the held gun.
|
||||
func remote_muzzle_position() -> Vector3:
|
||||
if player and player.has_method("get_visual_model"):
|
||||
var vis = player.get_visual_model()
|
||||
if vis and vis.has_method("get_muzzle_node"):
|
||||
var m: Node3D = vis.get_muzzle_node()
|
||||
if m:
|
||||
return m.global_position
|
||||
var own := world_muzzle()
|
||||
return own.global_position if own else global_position
|
||||
|
||||
|
||||
func _play_muzzle_flash() -> void:
|
||||
if muzzle_flash:
|
||||
ExplosionVFX.muzzle_flash(muzzle_flash.get_parent(), muzzle_flash.position)
|
||||
if not muzzle_flash:
|
||||
return
|
||||
var m := world_muzzle()
|
||||
ExplosionVFX.muzzle_flash(m.get_parent(), m.position)
|
||||
if m is OmniLight3D:
|
||||
var lit: OmniLight3D = m
|
||||
lit.light_energy = 8.0
|
||||
create_tween().tween_property(lit, "light_energy", 0.0, 0.05)
|
||||
if m != muzzle_flash:
|
||||
muzzle_flash.light_energy = 8.0
|
||||
var tween = create_tween()
|
||||
tween.tween_property(muzzle_flash, "light_energy", 0.0, 0.05)
|
||||
create_tween().tween_property(muzzle_flash, "light_energy", 0.0, 0.05)
|
||||
|
||||
func _shoot_projectile() -> void:
|
||||
if not camera: return
|
||||
@@ -142,7 +174,10 @@ func _shoot_projectile() -> void:
|
||||
_spawn_custom_projectile(origin, fire_dir)
|
||||
|
||||
if multiplayer.has_multiplayer_peer() and not(multiplayer.multiplayer_peer is OfflineMultiplayerPeer):
|
||||
player.server_play_fire_effects.rpc_id(1, origin, fire_dir, weapon_name, false)
|
||||
# Remote viewers see our character, so the projectile must appear to
|
||||
# leave the held gun rather than our camera.
|
||||
player.server_play_fire_effects.rpc_id(1, remote_muzzle_position(),
|
||||
fire_dir, weapon_name, false)
|
||||
|
||||
func _spawn_custom_projectile(_origin: Vector3, _fire_dir: Vector3) -> void:
|
||||
# Virtual method for custom projectiles (e.g., bouncing, homing)
|
||||
|
||||
Reference in New Issue
Block a user