diff --git a/game/neo/scripts/game_sounds_weapons.txt b/game/neo/scripts/game_sounds_weapons.txt index ad9ac75c4f..556a743384 100644 --- a/game/neo/scripts/game_sounds_weapons.txt +++ b/game/neo/scripts/game_sounds_weapons.txt @@ -22,6 +22,37 @@ "wave" ")weapons/milso/milso_reload.wav" } +// weapon_milso reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_milso.reload_bolt" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/milso/milso_reload_bolt.wav" +} + +// weapon_milso reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_milso.reload_drop" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/milso/milso_reload_drop.wav" +} + +"weapon_milso.reload_seat" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/milso/milso_reload_seat.wav" +} + //------------------ "weapon_milso.empty" @@ -78,6 +109,37 @@ "wave" ")weapons/tachi/tachi_reload.wav" } +// weapon_tachi reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_tachi.reload_bolt" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/tachi/tachi_reload_bolt.wav" +} + +// weapon_tachi reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_tachi.reload_drop" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/tachi/tachi_reload_drop.wav" +} + +"weapon_tachi.reload_seat" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/tachi/tachi_reload_seat.wav" +} + //------------------ "weapon_tachi.empty" @@ -144,6 +206,35 @@ "wave" ")weapons/kyla/kyla_reload.wav" } +// weapon_kyla reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_kyla.reload_drop" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/kyla/kyla_reload_drop.wav" +} + +"weapon_kyla.reload_seat" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/kyla/kyla_reload_seat.wav" +} + +"weapon_kyla.reload_seat2" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/kyla/kyla_reload_seat2.wav" +} + //------------------ "weapon_kyla.empty" @@ -203,6 +294,35 @@ "wave" ")weapons/aa13/aa13_reload.wav" } +// weapon_aa13 reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_aa13.reload_drop" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/aa13/aa13_reload_drop.wav" +} + +"weapon_aa13.reload_seat" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/aa13/aa13_reload_seat.wav" +} + +"weapon_aa13.reload_bolt" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/aa13/aa13_reload_bolt.wav" +} + //------------------ "weapon_aa13.empty" @@ -353,6 +473,35 @@ "wave" ")weapons/mpn45/mpn45_reload.wav" } +// weapon_mpn45 reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_mpn45.reload_drop" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/mpn45/mpn45_reload_drop.wav" +} + +"weapon_mpn45.reload_seat" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/mpn45/mpn45_reload_seat.wav" +} + +"weapon_mpn45.reload_bolt" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/mpn45/mpn45_reload_bolt.wav" +} + //------------------- "weapon_mpn45.empty" @@ -429,6 +578,35 @@ "wave" ")weapons/srm/srm_reload.wav" } +// weapon_srm reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_srm.reload_drop" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/srm/srm_reload_drop.wav" +} + +"weapon_srm.reload_seat" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/srm/srm_reload_seat.wav" +} + +"weapon_srm.reload_bolt" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/srm/srm_reload_bolt.wav" +} + //------------------- "weapon_srm.empty" @@ -514,6 +692,44 @@ "wave" ")weapons/jitte/jitte_reload.wav" } +// weapon_jitte reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_jitte.reload_drop" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/jitte/jitte_reload_drop.wav" +} + +"weapon_jitte.reload_drop2" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/jitte/jitte_reload_drop2.wav" +} + +"weapon_jitte.reload_seat" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/jitte/jitte_reload_seat.wav" +} + +"weapon_jitte.reload_bolt" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/jitte/jitte_reload_bolt.wav" +} + //------------------- "weapon_jitte.empty" @@ -597,6 +813,53 @@ "wave" ")weapons/m41/m41_reload.wav" } +// weapon_m41 reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_m41.reload_drop" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/m41/m41_reload_drop.wav" +} + +"weapon_m41.reload_seat" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/m41/m41_reload_seat.wav" +} + +"weapon_m41.reload_seat2" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/m41/m41_reload_seat2.wav" +} + +"weapon_m41.reload_slap" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/m41/m41_reload_slap.wav" +} + +"weapon_m41.reload_bolt" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/m41/m41_reload_bolt.wav" +} + //------------------- "weapon_m41.empty" @@ -688,6 +951,44 @@ "wave" ")weapons/srs/srs_reload.wav" } +// weapon_srs reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_srs.reload_drop" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/srs/srs_reload_drop.wav" +} + +"weapon_srs.reload_seat" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/srs/srs_reload_seat.wav" +} + +"weapon_srs.reload_bolt" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/srs/srs_reload_bolt.wav" +} + +"weapon_srs.reload_bolt2" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/srs/srs_reload_bolt2.wav" +} + //------------------- "weapon_srs.charge" @@ -770,6 +1071,44 @@ "wave" ")weapons/zr68/zr68_reload.wav" } +// ZR68 reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_zr68.reload_drop" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/zr68/zr68_reload_drop.wav" +} + +"weapon_zr68.reload_seat" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/zr68/zr68_reload_seat.wav" +} + +"weapon_zr68.reload_slap" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/zr68/zr68_reload_slap.wav" +} + +"weapon_zr68.reload_bolt" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/zr68/zr68_reload_bolt.wav" +} + //------------------- "weapon_zr68l.reload" @@ -794,6 +1133,44 @@ "wave" ")weapons/zr68/zr68l_reload.wav" } +// ZR68L reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_zr68l.reload_drop" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/zr68/zr68l_reload_drop.wav" +} + +"weapon_zr68l.reload_seat" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/zr68/zr68l_reload_seat.wav" +} + +"weapon_zr68l.reload_slap" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/zr68/zr68l_reload_slap.wav" +} + +"weapon_zr68l.reload_bolt" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/zr68/zr68l_reload_bolt.wav" +} + //------------------- "weapon_zr68.empty" @@ -905,6 +1282,44 @@ "wave" ")weapons/mx/mx_reload.wav" } +// weapon_mx reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_mx.reload_drop" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/mx/mx_reload_drop.wav" +} + +"weapon_mx.reload_seat" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/mx/mx_reload_seat.wav" +} + +"weapon_mx.reload_slap" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/mx/mx_reload_slap.wav" +} + +"weapon_mx.reload_bolt" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/mx/mx_reload_bolt.wav" +} + //------------------- "weapon_mx.empty" @@ -987,6 +1402,53 @@ "wave" ")weapons/pz/pz_reload.wav" } +// weapon_pz reload in per-phase pieces (research): each file starts 0.02 s before its first transient + +"weapon_pz.reload_drop" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/pz/pz_reload_drop.wav" +} + +"weapon_pz.reload_drop2" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/pz/pz_reload_drop2.wav" +} + +"weapon_pz.reload_seat" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/pz/pz_reload_seat.wav" +} + +"weapon_pz.reload_slap" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/pz/pz_reload_slap.wav" +} + +"weapon_pz.reload_bolt" +{ + "channel" "CHAN_ITEM" + "soundlevel" "SNDLVL_NORM" + "volume" "1.0" + "pitch" "PITCH_NORM" + "wave" ")weapons/pz/pz_reload_bolt.wav" +} + //------------------- "weapon_pz.empty" diff --git a/game/neo/scripts/weapon_aa13.txt b/game/neo/scripts/weapon_aa13.txt index def63c9983..4d32c9570e 100644 --- a/game/neo/scripts/weapon_aa13.txt +++ b/game/neo/scripts/weapon_aa13.txt @@ -6,6 +6,7 @@ WeaponData "Damage" "25" "Bullets" "5" "CycleTime" "0.333" + // the charging handle is home 0.333 before the end, the alternative value // Weapon data is loaded by both the Game and Client DLLs. "printname" "AA13" @@ -19,6 +20,52 @@ WeaponData "bucket" "0" "bucket_position" "3" + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "1.033" "sound" "weapon_aa13.reload_drop" "at" "0.374" } + "seat" { "credit" "3.367" "sound" "weapon_aa13.reload_seat" "at" "2.905" } + "bolt" { "credit" "4.333" "sound" "weapon_aa13.reload_bolt" "at" "3.764" } + } + "reload_partial" + { + "drop" { "credit" "1.033" "sound" "weapon_aa13.reload_drop" "at" "0.374" } + "seat" { "credit" "3.367" "sound" "weapon_aa13.reload_seat" "at" "2.905" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "2.600" "sound" "weapon_aa13.reload_seat" "at" "2.138" } + "bolt" { "credit" "3.567" "sound" "weapon_aa13.reload_bolt" "at" "2.997" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "2.600" "sound" "weapon_aa13.reload_seat" "at" "2.138" } + } + "reload_from_seat_dry" + { + "bolt" { "credit" "1.067" "sound" "weapon_aa13.reload_bolt" "at" "0.497" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "2.567" "sound" "weapon_aa13.reload_seat" "at" "2.105" } + "bolt" { "credit" "3.533" "sound" "weapon_aa13.reload_bolt" "at" "2.964" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "2.567" "sound" "weapon_aa13.reload_seat" "at" "2.105" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "2.200" "sound" "weapon_aa13.reload_seat" "at" "1.738" } + "bolt" { "credit" "3.167" "sound" "weapon_aa13.reload_bolt" "at" "2.597" } + } + } + "clip_size" "16" "default_clip" "48" "autoswitchfrom" "0" diff --git a/game/neo/scripts/weapon_jitte.txt b/game/neo/scripts/weapon_jitte.txt index c450c6f29a..95dd95420c 100644 --- a/game/neo/scripts/weapon_jitte.txt +++ b/game/neo/scripts/weapon_jitte.txt @@ -13,6 +13,53 @@ WeaponData "Damage" "15" "Penetration" "12.0" "CycleTime" "0.090909" // time between shots + // the charging handle is home 0.400 before the end, the alternative value + + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "1.367" "sound" "weapon_jitte.reload_drop" "at" "0.089" "sound2" "weapon_jitte.reload_drop2" "at2" "1.395" } + "seat" { "credit" "2.100" "sound" "weapon_jitte.reload_seat" "at" "1.733" } + "bolt" { "credit" "2.900" "sound" "weapon_jitte.reload_bolt" "at" "2.391" } + } + "reload_partial" + { + "drop" { "credit" "1.367" "sound" "weapon_jitte.reload_drop" "at" "0.089" "sound2" "weapon_jitte.reload_drop2" "at2" "1.395" } + "seat" { "credit" "2.100" "sound" "weapon_jitte.reload_seat" "at" "1.733" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.300" "sound" "weapon_jitte.reload_seat" "at" "0.933" } + "bolt" { "credit" "2.100" "sound" "weapon_jitte.reload_bolt" "at" "1.591" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.300" "sound" "weapon_jitte.reload_seat" "at" "0.933" } + } + "reload_from_seat_dry" + { + "bolt" { "credit" "0.867" "sound" "weapon_jitte.reload_bolt" "at" "0.358" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.567" "sound" "weapon_jitte.reload_seat" "at" "1.200" } + "bolt" { "credit" "2.367" "sound" "weapon_jitte.reload_bolt" "at" "1.858" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.567" "sound" "weapon_jitte.reload_seat" "at" "1.200" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "1.367" "sound" "weapon_jitte.reload_seat" "at" "1.000" } + "bolt" { "credit" "2.167" "sound" "weapon_jitte.reload_bolt" "at" "1.658" } + } + } "TPMuzzleFlashScale" "0.5" diff --git a/game/neo/scripts/weapon_jittescoped.txt b/game/neo/scripts/weapon_jittescoped.txt index a7c32e13d1..da2cc5c093 100644 --- a/game/neo/scripts/weapon_jittescoped.txt +++ b/game/neo/scripts/weapon_jittescoped.txt @@ -13,6 +13,53 @@ WeaponData "Damage" "15" "Penetration" "20.0" "CycleTime" "0.090909" // time between shots + // the charging handle is home 0.400 before the end, the alternative value + + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "1.367" "sound" "weapon_jitte.reload_drop" "at" "0.089" "sound2" "weapon_jitte.reload_drop2" "at2" "1.395" } + "seat" { "credit" "2.100" "sound" "weapon_jitte.reload_seat" "at" "1.733" } + "bolt" { "credit" "2.900" "sound" "weapon_jitte.reload_bolt" "at" "2.391" } + } + "reload_partial" + { + "drop" { "credit" "1.367" "sound" "weapon_jitte.reload_drop" "at" "0.089" "sound2" "weapon_jitte.reload_drop2" "at2" "1.395" } + "seat" { "credit" "2.100" "sound" "weapon_jitte.reload_seat" "at" "1.733" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.300" "sound" "weapon_jitte.reload_seat" "at" "0.933" } + "bolt" { "credit" "2.100" "sound" "weapon_jitte.reload_bolt" "at" "1.591" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.300" "sound" "weapon_jitte.reload_seat" "at" "0.933" } + } + "reload_from_seat_dry" + { + "bolt" { "credit" "0.867" "sound" "weapon_jitte.reload_bolt" "at" "0.358" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.567" "sound" "weapon_jitte.reload_seat" "at" "1.200" } + "bolt" { "credit" "2.367" "sound" "weapon_jitte.reload_bolt" "at" "1.858" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.567" "sound" "weapon_jitte.reload_seat" "at" "1.200" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "1.367" "sound" "weapon_jitte.reload_seat" "at" "1.000" } + "bolt" { "credit" "2.167" "sound" "weapon_jitte.reload_bolt" "at" "1.658" } + } + } "TPMuzzleFlashScale" "0.5" diff --git a/game/neo/scripts/weapon_kyla.txt b/game/neo/scripts/weapon_kyla.txt index ab9152032c..d7f972ae97 100644 --- a/game/neo/scripts/weapon_kyla.txt +++ b/game/neo/scripts/weapon_kyla.txt @@ -14,6 +14,26 @@ WeaponData "Penetration" "10.0" "CycleTime" "0.363636" // time between shoots + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "0" + "reload" + { + "drop" { "credit" "0.800" "sound" "weapon_kyla.reload_drop" "at" "0.232" } + "seat" { "credit" "1.333" "sound" "weapon_kyla.reload_seat" "at" "1.216" "sound2" "weapon_kyla.reload_seat2" "at2" "1.673" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.733" "sound" "weapon_kyla.reload_seat" "at" "1.616" "sound2" "weapon_kyla.reload_seat2" "at2" "2.073" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.633" "sound" "weapon_kyla.reload_seat" "at" "1.516" "sound2" "weapon_kyla.reload_seat2" "at2" "1.973" } + } + } + "TPMuzzleFlashScale" "0.6" "clip_size" "6" diff --git a/game/neo/scripts/weapon_m41.txt b/game/neo/scripts/weapon_m41.txt index 29acafbfcf..3d2e6eed5c 100644 --- a/game/neo/scripts/weapon_m41.txt +++ b/game/neo/scripts/weapon_m41.txt @@ -16,6 +16,68 @@ WeaponData "Penetration" "32.0" "CycleTime" "0.166666" // time between shots + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "0.633" "sound" "weapon_m41.reload_drop" "at" "0.624" } + "seat" { "credit" "1.300" "sound" "weapon_m41.reload_seat" "at" "1.302" "sound2" "weapon_m41.reload_seat2" "at2" "1.858" } + "slap" { "credit" "2.467" "sound" "weapon_m41.reload_slap" "at" "2.511" } + "bolt" { "credit" "3.367" "sound" "weapon_m41.reload_bolt" "at" "3.166" } + } + "reload_partial" + { + "drop" { "credit" "0.633" "sound" "weapon_m41.reload_drop" "at" "0.624" } + "seat" { "credit" "1.300" "sound" "weapon_m41.reload_seat" "at" "1.302" "sound2" "weapon_m41.reload_seat2" "at2" "1.858" } + "slap" { "credit" "2.467" "sound" "weapon_m41.reload_slap" "at" "2.511" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.233" "sound" "weapon_m41.reload_seat" "at" "1.235" "sound2" "weapon_m41.reload_seat2" "at2" "1.791" } + "slap" { "credit" "2.400" "sound" "weapon_m41.reload_slap" "at" "2.444" } + "bolt" { "credit" "3.300" "sound" "weapon_m41.reload_bolt" "at" "3.099" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.233" "sound" "weapon_m41.reload_seat" "at" "1.235" "sound2" "weapon_m41.reload_seat2" "at2" "1.791" } + "slap" { "credit" "2.400" "sound" "weapon_m41.reload_slap" "at" "2.444" } + } + "reload_from_seat_dry" + { + "slap" { "credit" "0.867" "sound" "weapon_m41.reload_slap" "at" "0.911" } + "bolt" { "credit" "1.700" "sound" "weapon_m41.reload_bolt" "at" "1.499" } + } + "reload_from_seat_partial" + { + "slap" { "credit" "0.867" "sound" "weapon_m41.reload_slap" "at" "0.911" } + } + "reload_from_slap_dry" + { + "bolt" { "credit" "0.867" "sound" "weapon_m41.reload_bolt" "at" "0.666" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.533" "sound" "weapon_m41.reload_seat" "at" "1.535" "sound2" "weapon_m41.reload_seat2" "at2" "2.091" } + "slap" { "credit" "2.700" "sound" "weapon_m41.reload_slap" "at" "2.744" } + "bolt" { "credit" "3.600" "sound" "weapon_m41.reload_bolt" "at" "3.399" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.533" "sound" "weapon_m41.reload_seat" "at" "1.535" "sound2" "weapon_m41.reload_seat2" "at2" "2.091" } + "slap" { "credit" "2.700" "sound" "weapon_m41.reload_slap" "at" "2.744" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "1.267" "sound" "weapon_m41.reload_seat" "at" "1.269" "sound2" "weapon_m41.reload_seat2" "at2" "1.825" } + "slap" { "credit" "2.433" "sound" "weapon_m41.reload_slap" "at" "2.478" } + "bolt" { "credit" "3.333" "sound" "weapon_m41.reload_bolt" "at" "3.133" } + } + } + "TPMuzzleFlashScale" "0.5" "clip_size" "15" diff --git a/game/neo/scripts/weapon_m41l.txt b/game/neo/scripts/weapon_m41l.txt index 303ba2c82d..afc551abe9 100644 --- a/game/neo/scripts/weapon_m41l.txt +++ b/game/neo/scripts/weapon_m41l.txt @@ -16,6 +16,68 @@ WeaponData "Penetration" "32.0" "CycleTime" "0.5" // time between shots + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "0.633" "sound" "weapon_m41.reload_drop" "at" "0.624" } + "seat" { "credit" "1.300" "sound" "weapon_m41.reload_seat" "at" "1.302" "sound2" "weapon_m41.reload_seat2" "at2" "1.858" } + "slap" { "credit" "2.467" "sound" "weapon_m41.reload_slap" "at" "2.511" } + "bolt" { "credit" "3.367" "sound" "weapon_m41.reload_bolt" "at" "3.166" } + } + "reload_partial" + { + "drop" { "credit" "0.633" "sound" "weapon_m41.reload_drop" "at" "0.624" } + "seat" { "credit" "1.300" "sound" "weapon_m41.reload_seat" "at" "1.302" "sound2" "weapon_m41.reload_seat2" "at2" "1.858" } + "slap" { "credit" "2.467" "sound" "weapon_m41.reload_slap" "at" "2.511" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.233" "sound" "weapon_m41.reload_seat" "at" "1.235" "sound2" "weapon_m41.reload_seat2" "at2" "1.791" } + "slap" { "credit" "2.400" "sound" "weapon_m41.reload_slap" "at" "2.444" } + "bolt" { "credit" "3.300" "sound" "weapon_m41.reload_bolt" "at" "3.099" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.233" "sound" "weapon_m41.reload_seat" "at" "1.235" "sound2" "weapon_m41.reload_seat2" "at2" "1.791" } + "slap" { "credit" "2.400" "sound" "weapon_m41.reload_slap" "at" "2.444" } + } + "reload_from_seat_dry" + { + "slap" { "credit" "0.867" "sound" "weapon_m41.reload_slap" "at" "0.911" } + "bolt" { "credit" "1.700" "sound" "weapon_m41.reload_bolt" "at" "1.499" } + } + "reload_from_seat_partial" + { + "slap" { "credit" "0.867" "sound" "weapon_m41.reload_slap" "at" "0.911" } + } + "reload_from_slap_dry" + { + "bolt" { "credit" "0.867" "sound" "weapon_m41.reload_bolt" "at" "0.666" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.533" "sound" "weapon_m41.reload_seat" "at" "1.535" "sound2" "weapon_m41.reload_seat2" "at2" "2.091" } + "slap" { "credit" "2.700" "sound" "weapon_m41.reload_slap" "at" "2.744" } + "bolt" { "credit" "3.600" "sound" "weapon_m41.reload_bolt" "at" "3.399" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.533" "sound" "weapon_m41.reload_seat" "at" "1.535" "sound2" "weapon_m41.reload_seat2" "at2" "2.091" } + "slap" { "credit" "2.700" "sound" "weapon_m41.reload_slap" "at" "2.744" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "1.267" "sound" "weapon_m41.reload_seat" "at" "1.269" "sound2" "weapon_m41.reload_seat2" "at2" "1.825" } + "slap" { "credit" "2.433" "sound" "weapon_m41.reload_slap" "at" "2.478" } + "bolt" { "credit" "3.333" "sound" "weapon_m41.reload_bolt" "at" "3.133" } + } + } + "TPMuzzleFlashScale" "0.25" "clip_size" "10" diff --git a/game/neo/scripts/weapon_m41s.txt b/game/neo/scripts/weapon_m41s.txt index af971caae6..e40fd1a36f 100644 --- a/game/neo/scripts/weapon_m41s.txt +++ b/game/neo/scripts/weapon_m41s.txt @@ -16,6 +16,68 @@ WeaponData "Penetration" "20.0" "CycleTime" "0.166666" // time between shots + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "0.633" "sound" "weapon_m41.reload_drop" "at" "0.624" } + "seat" { "credit" "1.300" "sound" "weapon_m41.reload_seat" "at" "1.302" "sound2" "weapon_m41.reload_seat2" "at2" "1.858" } + "slap" { "credit" "2.467" "sound" "weapon_m41.reload_slap" "at" "2.511" } + "bolt" { "credit" "3.367" "sound" "weapon_m41.reload_bolt" "at" "3.166" } + } + "reload_partial" + { + "drop" { "credit" "0.633" "sound" "weapon_m41.reload_drop" "at" "0.624" } + "seat" { "credit" "1.300" "sound" "weapon_m41.reload_seat" "at" "1.302" "sound2" "weapon_m41.reload_seat2" "at2" "1.858" } + "slap" { "credit" "2.467" "sound" "weapon_m41.reload_slap" "at" "2.511" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.233" "sound" "weapon_m41.reload_seat" "at" "1.235" "sound2" "weapon_m41.reload_seat2" "at2" "1.791" } + "slap" { "credit" "2.400" "sound" "weapon_m41.reload_slap" "at" "2.444" } + "bolt" { "credit" "3.300" "sound" "weapon_m41.reload_bolt" "at" "3.099" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.233" "sound" "weapon_m41.reload_seat" "at" "1.235" "sound2" "weapon_m41.reload_seat2" "at2" "1.791" } + "slap" { "credit" "2.400" "sound" "weapon_m41.reload_slap" "at" "2.444" } + } + "reload_from_seat_dry" + { + "slap" { "credit" "0.867" "sound" "weapon_m41.reload_slap" "at" "0.911" } + "bolt" { "credit" "1.700" "sound" "weapon_m41.reload_bolt" "at" "1.499" } + } + "reload_from_seat_partial" + { + "slap" { "credit" "0.867" "sound" "weapon_m41.reload_slap" "at" "0.911" } + } + "reload_from_slap_dry" + { + "bolt" { "credit" "0.867" "sound" "weapon_m41.reload_bolt" "at" "0.666" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.533" "sound" "weapon_m41.reload_seat" "at" "1.535" "sound2" "weapon_m41.reload_seat2" "at2" "2.091" } + "slap" { "credit" "2.700" "sound" "weapon_m41.reload_slap" "at" "2.744" } + "bolt" { "credit" "3.600" "sound" "weapon_m41.reload_bolt" "at" "3.399" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.533" "sound" "weapon_m41.reload_seat" "at" "1.535" "sound2" "weapon_m41.reload_seat2" "at2" "2.091" } + "slap" { "credit" "2.700" "sound" "weapon_m41.reload_slap" "at" "2.744" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "1.267" "sound" "weapon_m41.reload_seat" "at" "1.269" "sound2" "weapon_m41.reload_seat2" "at2" "1.825" } + "slap" { "credit" "2.433" "sound" "weapon_m41.reload_slap" "at" "2.478" } + "bolt" { "credit" "3.333" "sound" "weapon_m41.reload_bolt" "at" "3.133" } + } + } + "TPMuzzleFlashScale" "0.5" "clip_size" "15" diff --git a/game/neo/scripts/weapon_milso.txt b/game/neo/scripts/weapon_milso.txt index 8e05550834..83cae37492 100644 --- a/game/neo/scripts/weapon_milso.txt +++ b/game/neo/scripts/weapon_milso.txt @@ -14,6 +14,52 @@ WeaponData "Penetration" "12.0" "CycleTime" "0.212121" + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "0.467" "sound" "weapon_milso.reload_drop" "at" "0.292" } + "seat" { "credit" "1.167" "sound" "weapon_milso.reload_seat" "at" "1.243" } + } + "reloadempty" + { + "drop" { "credit" "0.467" "sound" "weapon_milso.reload_drop" "at" "0.292" } + "seat" { "credit" "1.167" "sound" "weapon_milso.reload_seat" "at" "1.243" } + "bolt" { "credit" "2.067" "sound" "weapon_milso.reload_bolt" "at" "2.060" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.167" "sound" "weapon_milso.reload_seat" "at" "1.243" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.167" "sound" "weapon_milso.reload_seat" "at" "1.243" } + "bolt" { "credit" "2.067" "sound" "weapon_milso.reload_bolt" "at" "2.060" } + } + "reload_from_seat_dry" + { + "bolt" { "credit" "0.267" "sound" "weapon_milso.reload_bolt" "at" "0.260" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.500" "sound" "weapon_milso.reload_seat" "at" "1.576" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.500" "sound" "weapon_milso.reload_seat" "at" "1.576" } + "bolt" { "credit" "2.400" "sound" "weapon_milso.reload_bolt" "at" "2.393" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "1.233" "sound" "weapon_milso.reload_seat" "at" "1.310" } + "bolt" { "credit" "2.133" "sound" "weapon_milso.reload_bolt" "at" "2.127" } + } + } + "TPMuzzleFlashScale" "0.5" "clip_size" "12" diff --git a/game/neo/scripts/weapon_mpn.txt b/game/neo/scripts/weapon_mpn.txt index c85eb6a752..1ff223bf16 100644 --- a/game/neo/scripts/weapon_mpn.txt +++ b/game/neo/scripts/weapon_mpn.txt @@ -13,6 +13,53 @@ WeaponData "Damage" "12" "Penetration" "12.0" "CycleTime" "0.075757" // time between shots + // the charging handle is home 0.400 before the end, the alternative value + + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "0.633" "sound" "weapon_mpn45.reload_drop" "at" "0.596" } + "seat" { "credit" "1.467" "sound" "weapon_mpn45.reload_seat" "at" "1.447" } + "bolt" { "credit" "2.800" "sound" "weapon_mpn45.reload_bolt" "at" "2.374" } + } + "reload_partial" + { + "drop" { "credit" "0.633" "sound" "weapon_mpn45.reload_drop" "at" "0.596" } + "seat" { "credit" "1.467" "sound" "weapon_mpn45.reload_seat" "at" "1.447" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.600" "sound" "weapon_mpn45.reload_seat" "at" "1.580" } + "bolt" { "credit" "2.933" "sound" "weapon_mpn45.reload_bolt" "at" "2.507" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.600" "sound" "weapon_mpn45.reload_seat" "at" "1.580" } + } + "reload_from_seat_dry" + { + "bolt" { "credit" "1.067" "sound" "weapon_mpn45.reload_bolt" "at" "0.641" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.900" "sound" "weapon_mpn45.reload_seat" "at" "1.880" } + "bolt" { "credit" "3.233" "sound" "weapon_mpn45.reload_bolt" "at" "2.807" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.900" "sound" "weapon_mpn45.reload_seat" "at" "1.880" } + } + "fire_chamber_onehand" + { + "fire_at" "0.200" + "seat" { "credit" "1.300" "sound" "weapon_mpn45.reload_seat" "at" "1.280" } + "bolt" { "credit" "2.633" "sound" "weapon_mpn45.reload_bolt" "at" "2.207" } + } + } "TPMuzzleFlashScale" "0.5" diff --git a/game/neo/scripts/weapon_mpn_unsilenced.txt b/game/neo/scripts/weapon_mpn_unsilenced.txt index 8bd6e69fcc..c3340e9a9a 100644 --- a/game/neo/scripts/weapon_mpn_unsilenced.txt +++ b/game/neo/scripts/weapon_mpn_unsilenced.txt @@ -13,6 +13,53 @@ WeaponData "Damage" "12" "Penetration" "12.0" "CycleTime" "0.075757" // time between shots + // the charging handle is home 0.400 before the end, the alternative value + + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "0.633" "sound" "weapon_mpn45.reload_drop" "at" "0.596" } + "seat" { "credit" "1.467" "sound" "weapon_mpn45.reload_seat" "at" "1.447" } + "bolt" { "credit" "2.800" "sound" "weapon_mpn45.reload_bolt" "at" "2.374" } + } + "reload_partial" + { + "drop" { "credit" "0.633" "sound" "weapon_mpn45.reload_drop" "at" "0.596" } + "seat" { "credit" "1.467" "sound" "weapon_mpn45.reload_seat" "at" "1.447" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.600" "sound" "weapon_mpn45.reload_seat" "at" "1.580" } + "bolt" { "credit" "2.933" "sound" "weapon_mpn45.reload_bolt" "at" "2.507" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.600" "sound" "weapon_mpn45.reload_seat" "at" "1.580" } + } + "reload_from_seat_dry" + { + "bolt" { "credit" "1.067" "sound" "weapon_mpn45.reload_bolt" "at" "0.641" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.900" "sound" "weapon_mpn45.reload_seat" "at" "1.880" } + "bolt" { "credit" "3.233" "sound" "weapon_mpn45.reload_bolt" "at" "2.807" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.900" "sound" "weapon_mpn45.reload_seat" "at" "1.880" } + } + "fire_chamber_onehand" + { + "fire_at" "0.200" + "seat" { "credit" "1.300" "sound" "weapon_mpn45.reload_seat" "at" "1.280" } + "bolt" { "credit" "2.633" "sound" "weapon_mpn45.reload_bolt" "at" "2.207" } + } + } "TPMuzzleFlashScale" "0.5" diff --git a/game/neo/scripts/weapon_mx.txt b/game/neo/scripts/weapon_mx.txt index 1eaabb29e8..8983315bf7 100644 --- a/game/neo/scripts/weapon_mx.txt +++ b/game/neo/scripts/weapon_mx.txt @@ -16,6 +16,68 @@ WeaponData "Penetration" "38.0" "CycleTime" "0.106060" // time between shots + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "0.467" "sound" "weapon_mx.reload_drop" "at" "0.354" } + "seat" { "credit" "1.300" "sound" "weapon_mx.reload_seat" "at" "1.284" } + "slap" { "credit" "1.700" "sound" "weapon_mx.reload_slap" "at" "1.725" } + "bolt" { "credit" "2.500" "sound" "weapon_mx.reload_bolt" "at" "2.200" } + } + "reload_partial" + { + "drop" { "credit" "0.467" "sound" "weapon_mx.reload_drop" "at" "0.354" } + "seat" { "credit" "1.300" "sound" "weapon_mx.reload_seat" "at" "1.284" } + "slap" { "credit" "1.700" "sound" "weapon_mx.reload_slap" "at" "1.725" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.167" "sound" "weapon_mx.reload_seat" "at" "1.151" } + "slap" { "credit" "1.567" "sound" "weapon_mx.reload_slap" "at" "1.592" } + "bolt" { "credit" "2.367" "sound" "weapon_mx.reload_bolt" "at" "2.067" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.167" "sound" "weapon_mx.reload_seat" "at" "1.151" } + "slap" { "credit" "1.567" "sound" "weapon_mx.reload_slap" "at" "1.592" } + } + "reload_from_seat_dry" + { + "slap" { "credit" "0.600" "sound" "weapon_mx.reload_slap" "at" "0.625" } + "bolt" { "credit" "1.400" "sound" "weapon_mx.reload_bolt" "at" "1.100" } + } + "reload_from_seat_partial" + { + "slap" { "credit" "0.600" "sound" "weapon_mx.reload_slap" "at" "0.625" } + } + "reload_from_slap_dry" + { + "bolt" { "credit" "1.300" "sound" "weapon_mx.reload_bolt" "at" "1.000" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.400" "sound" "weapon_mx.reload_seat" "at" "1.384" } + "slap" { "credit" "1.800" "sound" "weapon_mx.reload_slap" "at" "1.825" } + "bolt" { "credit" "2.600" "sound" "weapon_mx.reload_bolt" "at" "2.300" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.400" "sound" "weapon_mx.reload_seat" "at" "1.384" } + "slap" { "credit" "1.800" "sound" "weapon_mx.reload_slap" "at" "1.825" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "1.133" "sound" "weapon_mx.reload_seat" "at" "1.117" } + "slap" { "credit" "1.533" "sound" "weapon_mx.reload_slap" "at" "1.558" } + "bolt" { "credit" "2.333" "sound" "weapon_mx.reload_bolt" "at" "2.033" } + } + } + "TPMuzzleFlashScale" "0.3" "clip_size" "30" diff --git a/game/neo/scripts/weapon_mx_silenced.txt b/game/neo/scripts/weapon_mx_silenced.txt index b8b7c86556..7f2c54e969 100644 --- a/game/neo/scripts/weapon_mx_silenced.txt +++ b/game/neo/scripts/weapon_mx_silenced.txt @@ -14,6 +14,68 @@ WeaponData "Penetration" "18.0" "CycleTime" "0.106060" // time between shots + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "0.467" "sound" "weapon_mx.reload_drop" "at" "0.354" } + "seat" { "credit" "1.300" "sound" "weapon_mx.reload_seat" "at" "1.284" } + "slap" { "credit" "1.700" "sound" "weapon_mx.reload_slap" "at" "1.725" } + "bolt" { "credit" "2.500" "sound" "weapon_mx.reload_bolt" "at" "2.200" } + } + "reload_partial" + { + "drop" { "credit" "0.467" "sound" "weapon_mx.reload_drop" "at" "0.354" } + "seat" { "credit" "1.300" "sound" "weapon_mx.reload_seat" "at" "1.284" } + "slap" { "credit" "1.700" "sound" "weapon_mx.reload_slap" "at" "1.725" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.167" "sound" "weapon_mx.reload_seat" "at" "1.151" } + "slap" { "credit" "1.567" "sound" "weapon_mx.reload_slap" "at" "1.592" } + "bolt" { "credit" "2.367" "sound" "weapon_mx.reload_bolt" "at" "2.067" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.167" "sound" "weapon_mx.reload_seat" "at" "1.151" } + "slap" { "credit" "1.567" "sound" "weapon_mx.reload_slap" "at" "1.592" } + } + "reload_from_seat_dry" + { + "slap" { "credit" "0.600" "sound" "weapon_mx.reload_slap" "at" "0.625" } + "bolt" { "credit" "1.400" "sound" "weapon_mx.reload_bolt" "at" "1.100" } + } + "reload_from_seat_partial" + { + "slap" { "credit" "0.600" "sound" "weapon_mx.reload_slap" "at" "0.625" } + } + "reload_from_slap_dry" + { + "bolt" { "credit" "1.300" "sound" "weapon_mx.reload_bolt" "at" "1.000" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.400" "sound" "weapon_mx.reload_seat" "at" "1.384" } + "slap" { "credit" "1.800" "sound" "weapon_mx.reload_slap" "at" "1.825" } + "bolt" { "credit" "2.600" "sound" "weapon_mx.reload_bolt" "at" "2.300" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.400" "sound" "weapon_mx.reload_seat" "at" "1.384" } + "slap" { "credit" "1.800" "sound" "weapon_mx.reload_slap" "at" "1.825" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "1.133" "sound" "weapon_mx.reload_seat" "at" "1.117" } + "slap" { "credit" "1.533" "sound" "weapon_mx.reload_slap" "at" "1.558" } + "bolt" { "credit" "2.333" "sound" "weapon_mx.reload_bolt" "at" "2.033" } + } + } + "TPMuzzleFlashScale" "0.3" "clip_size" "30" diff --git a/game/neo/scripts/weapon_pz.txt b/game/neo/scripts/weapon_pz.txt index c67473d642..65279b6ef0 100644 --- a/game/neo/scripts/weapon_pz.txt +++ b/game/neo/scripts/weapon_pz.txt @@ -15,6 +15,69 @@ WeaponData "Damage" "20" "Penetration" "28.0" "CycleTime" "0.090909" // time between shots + // the charging handle is home 0.400 before the end, the alternative value + + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "1.500" "sound" "weapon_pz.reload_drop" "at" "0.335" "sound2" "weapon_pz.reload_drop2" "at2" "2.383" } + "seat" { "credit" "3.800" "sound" "weapon_pz.reload_seat" "at" "3.382" } + "slap" { "credit" "4.667" "sound" "weapon_pz.reload_slap" "at" "4.461" } + "bolt" { "credit" "5.367" "sound" "weapon_pz.reload_bolt" "at" "4.802" } + } + "reload_partial" + { + "drop" { "credit" "1.500" "sound" "weapon_pz.reload_drop" "at" "0.335" "sound2" "weapon_pz.reload_drop2" "at2" "2.383" } + "seat" { "credit" "3.800" "sound" "weapon_pz.reload_seat" "at" "3.382" } + "slap" { "credit" "4.667" "sound" "weapon_pz.reload_slap" "at" "4.461" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "2.433" "sound" "weapon_pz.reload_seat" "at" "2.015" } + "slap" { "credit" "3.300" "sound" "weapon_pz.reload_slap" "at" "3.094" } + "bolt" { "credit" "4.000" "sound" "weapon_pz.reload_bolt" "at" "3.435" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "2.433" "sound" "weapon_pz.reload_seat" "at" "2.015" } + "slap" { "credit" "3.300" "sound" "weapon_pz.reload_slap" "at" "3.094" } + } + "reload_from_seat_dry" + { + "slap" { "credit" "0.733" "sound" "weapon_pz.reload_slap" "at" "0.528" } + "bolt" { "credit" "1.433" "sound" "weapon_pz.reload_bolt" "at" "0.869" } + } + "reload_from_seat_partial" + { + "slap" { "credit" "0.733" "sound" "weapon_pz.reload_slap" "at" "0.528" } + } + "reload_from_slap_dry" + { + "bolt" { "credit" "1.000" "sound" "weapon_pz.reload_bolt" "at" "0.435" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "2.767" "sound" "weapon_pz.reload_seat" "at" "2.349" } + "slap" { "credit" "3.633" "sound" "weapon_pz.reload_slap" "at" "3.428" } + "bolt" { "credit" "4.333" "sound" "weapon_pz.reload_bolt" "at" "3.769" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "2.767" "sound" "weapon_pz.reload_seat" "at" "2.349" } + "slap" { "credit" "3.633" "sound" "weapon_pz.reload_slap" "at" "3.428" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "2.500" "sound" "weapon_pz.reload_seat" "at" "2.082" } + "slap" { "credit" "3.367" "sound" "weapon_pz.reload_slap" "at" "3.161" } + "bolt" { "credit" "4.067" "sound" "weapon_pz.reload_bolt" "at" "3.502" } + } + } "TPMuzzleFlashScale" "0.3" diff --git a/game/neo/scripts/weapon_srm.txt b/game/neo/scripts/weapon_srm.txt index e697644a27..5076fa1e5c 100644 --- a/game/neo/scripts/weapon_srm.txt +++ b/game/neo/scripts/weapon_srm.txt @@ -13,6 +13,53 @@ WeaponData "Damage" "12" "Penetration" "20.0" "CycleTime" "0.060606" // time between shots + // the charging handle is home 0.167 before the end, the alternative value + + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "0.633" "sound" "weapon_srm.reload_drop" "at" "0.496" } + "seat" { "credit" "1.733" "sound" "weapon_srm.reload_seat" "at" "1.634" } + "bolt" { "credit" "2.900" "sound" "weapon_srm.reload_bolt" "at" "2.537" } + } + "reload_partial" + { + "drop" { "credit" "0.633" "sound" "weapon_srm.reload_drop" "at" "0.496" } + "seat" { "credit" "1.733" "sound" "weapon_srm.reload_seat" "at" "1.634" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.700" "sound" "weapon_srm.reload_seat" "at" "1.601" } + "bolt" { "credit" "2.867" "sound" "weapon_srm.reload_bolt" "at" "2.504" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.700" "sound" "weapon_srm.reload_seat" "at" "1.601" } + } + "reload_from_seat_dry" + { + "bolt" { "credit" "1.000" "sound" "weapon_srm.reload_bolt" "at" "0.637" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.600" "sound" "weapon_srm.reload_seat" "at" "1.501" } + "bolt" { "credit" "2.767" "sound" "weapon_srm.reload_bolt" "at" "2.404" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.600" "sound" "weapon_srm.reload_seat" "at" "1.501" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "1.333" "sound" "weapon_srm.reload_seat" "at" "1.234" } + "bolt" { "credit" "2.500" "sound" "weapon_srm.reload_bolt" "at" "2.137" } + } + } "TPMuzzleFlashScale" "0.5" diff --git a/game/neo/scripts/weapon_srm_s.txt b/game/neo/scripts/weapon_srm_s.txt index 8d190812a7..d67a86f761 100644 --- a/game/neo/scripts/weapon_srm_s.txt +++ b/game/neo/scripts/weapon_srm_s.txt @@ -13,6 +13,53 @@ WeaponData "Damage" "12" "Penetration" "10.0" "CycleTime" "0.090909" // time between shots + // the charging handle is home 0.167 before the end, the alternative value + + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "0.633" "sound" "weapon_srm.reload_drop" "at" "0.496" } + "seat" { "credit" "1.733" "sound" "weapon_srm.reload_seat" "at" "1.634" } + "bolt" { "credit" "2.900" "sound" "weapon_srm.reload_bolt" "at" "2.537" } + } + "reload_partial" + { + "drop" { "credit" "0.633" "sound" "weapon_srm.reload_drop" "at" "0.496" } + "seat" { "credit" "1.733" "sound" "weapon_srm.reload_seat" "at" "1.634" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.700" "sound" "weapon_srm.reload_seat" "at" "1.601" } + "bolt" { "credit" "2.867" "sound" "weapon_srm.reload_bolt" "at" "2.504" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.700" "sound" "weapon_srm.reload_seat" "at" "1.601" } + } + "reload_from_seat_dry" + { + "bolt" { "credit" "1.000" "sound" "weapon_srm.reload_bolt" "at" "0.637" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.600" "sound" "weapon_srm.reload_seat" "at" "1.501" } + "bolt" { "credit" "2.767" "sound" "weapon_srm.reload_bolt" "at" "2.404" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.600" "sound" "weapon_srm.reload_seat" "at" "1.501" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "1.333" "sound" "weapon_srm.reload_seat" "at" "1.234" } + "bolt" { "credit" "2.500" "sound" "weapon_srm.reload_bolt" "at" "2.137" } + } + } "TPMuzzleFlashScale" "0.5" diff --git a/game/neo/scripts/weapon_srs.txt b/game/neo/scripts/weapon_srs.txt index 4d7ba65f34..74d3d9c5b8 100644 --- a/game/neo/scripts/weapon_srs.txt +++ b/game/neo/scripts/weapon_srs.txt @@ -16,6 +16,51 @@ WeaponData "Penetration" "65.0" "CycleTime" "1.4" + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "1.133" "sound" "weapon_srs.reload_drop" "at" "0.453" } + "seat" { "credit" "1.867" "sound" "weapon_srs.reload_seat" "at" "1.711" } + "bolt" { "credit" "3.067" "sound" "weapon_srs.reload_bolt" "at" "2.501" "sound2" "weapon_srs.reload_bolt2" "at2" "3.000" } + } + "reload_partial" + { + "drop" { "credit" "1.133" "sound" "weapon_srs.reload_drop" "at" "0.453" } + "seat" { "credit" "1.867" "sound" "weapon_srs.reload_seat" "at" "1.711" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.067" "sound" "weapon_srs.reload_seat" "at" "0.911" } + "bolt" { "credit" "2.267" "sound" "weapon_srs.reload_bolt" "at" "1.701" "sound2" "weapon_srs.reload_bolt2" "at2" "2.200" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.067" "sound" "weapon_srs.reload_seat" "at" "0.911" } + } + "reload_from_seat_dry" + { + "bolt" { "credit" "0.967" "sound" "weapon_srs.reload_bolt" "at" "0.401" "sound2" "weapon_srs.reload_bolt2" "at2" "0.900" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.567" "sound" "weapon_srs.reload_seat" "at" "1.411" } + "bolt" { "credit" "2.767" "sound" "weapon_srs.reload_bolt" "at" "2.201" "sound2" "weapon_srs.reload_bolt2" "at2" "2.700" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.567" "sound" "weapon_srs.reload_seat" "at" "1.411" } + } + // the bolt cycle after a shot: "bolt" is when the bolt is home with the next round (Bolt bone, charge f25) + "charge" + { + "bolt" { "credit" "0.833" } + } + } + "TPMuzzleFlashScale" "0.25" "clip_size" "5" diff --git a/game/neo/scripts/weapon_supa7.txt b/game/neo/scripts/weapon_supa7.txt index 55315864d4..e4f2454b69 100644 --- a/game/neo/scripts/weapon_supa7.txt +++ b/game/neo/scripts/weapon_supa7.txt @@ -5,6 +5,9 @@ WeaponData // Weapon characteristics: "Damage" "18" "Bullets" "8" + // when the fire animation's pump is home with the next round (Pump bone: altfire f18, fire f30) + "ChamberAfterShot" "0.600" + "ChamberAfterSlugShot" "1.000" "CycleTime" "0.8" // Weapon data is loaded by both the Game and Client DLLs. diff --git a/game/neo/scripts/weapon_tachi.txt b/game/neo/scripts/weapon_tachi.txt index 21c7909c8b..d0a5336aff 100644 --- a/game/neo/scripts/weapon_tachi.txt +++ b/game/neo/scripts/weapon_tachi.txt @@ -14,6 +14,55 @@ WeaponData "bucket" "1" "bucket_position" "0" + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "0.467" "sound" "weapon_tachi.reload_drop" "at" "0.266" } + "seat" { "credit" "1.167" "sound" "weapon_tachi.reload_seat" "at" "1.413" } + "bolt" { "credit" "2.067" "sound" "weapon_tachi.reload_bolt" "at" "2.060" } + } + "reloadempty" + { + "drop" { "credit" "0.467" "sound" "weapon_tachi.reload_drop" "at" "0.266" } + "seat" { "credit" "1.167" "sound" "weapon_tachi.reload_seat" "at" "1.413" } + "bolt" { "credit" "2.067" "sound" "weapon_tachi.reload_bolt" "at" "2.060" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.133" "sound" "weapon_tachi.reload_seat" "at" "1.380" } + "bolt" { "credit" "2.033" "sound" "weapon_tachi.reload_bolt" "at" "2.027" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.133" "sound" "weapon_tachi.reload_seat" "at" "1.380" } + "bolt" { "credit" "2.033" "sound" "weapon_tachi.reload_bolt" "at" "2.027" } + } + "reload_from_seat_dry" + { + "bolt" { "credit" "0.267" "sound" "weapon_tachi.reload_bolt" "at" "0.260" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.633" "sound" "weapon_tachi.reload_seat" "at" "1.880" } + "bolt" { "credit" "2.533" "sound" "weapon_tachi.reload_bolt" "at" "2.527" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.633" "sound" "weapon_tachi.reload_seat" "at" "1.880" } + "bolt" { "credit" "2.533" "sound" "weapon_tachi.reload_bolt" "at" "2.527" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "1.367" "sound" "weapon_tachi.reload_seat" "at" "1.613" } + "bolt" { "credit" "2.267" "sound" "weapon_tachi.reload_bolt" "at" "2.260" } + } + } + "clip_size" "15" "default_clip" "45" "autoswitchfrom" "0" diff --git a/game/neo/scripts/weapon_zr68c.txt b/game/neo/scripts/weapon_zr68c.txt index 4ce959e1c1..fea659d5e3 100644 --- a/game/neo/scripts/weapon_zr68c.txt +++ b/game/neo/scripts/weapon_zr68c.txt @@ -16,6 +16,68 @@ WeaponData "Penetration" "28.0" "CycleTime" "0.090909" // time between shots + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "0.700" "sound" "weapon_zr68.reload_drop" "at" "0.146" } + "seat" { "credit" "1.333" "sound" "weapon_zr68.reload_seat" "at" "1.294" } + "slap" { "credit" "1.700" "sound" "weapon_zr68.reload_slap" "at" "1.729" } + "bolt" { "credit" "2.267" "sound" "weapon_zr68.reload_bolt" "at" "2.029" } + } + "reload_partial" + { + "drop" { "credit" "0.700" "sound" "weapon_zr68.reload_drop" "at" "0.146" } + "seat" { "credit" "1.333" "sound" "weapon_zr68.reload_seat" "at" "1.294" } + "slap" { "credit" "1.700" "sound" "weapon_zr68.reload_slap" "at" "1.729" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.233" "sound" "weapon_zr68.reload_seat" "at" "1.194" } + "slap" { "credit" "1.600" "sound" "weapon_zr68.reload_slap" "at" "1.629" } + "bolt" { "credit" "2.167" "sound" "weapon_zr68.reload_bolt" "at" "1.929" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.233" "sound" "weapon_zr68.reload_seat" "at" "1.194" } + "slap" { "credit" "1.600" "sound" "weapon_zr68.reload_slap" "at" "1.629" } + } + "reload_from_seat_dry" + { + "slap" { "credit" "0.767" "sound" "weapon_zr68.reload_slap" "at" "0.796" } + "bolt" { "credit" "1.333" "sound" "weapon_zr68.reload_bolt" "at" "1.096" } + } + "reload_from_seat_partial" + { + "slap" { "credit" "0.767" "sound" "weapon_zr68.reload_slap" "at" "0.796" } + } + "reload_from_slap_dry" + { + "bolt" { "credit" "1.233" "sound" "weapon_zr68.reload_bolt" "at" "0.996" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.433" "sound" "weapon_zr68.reload_seat" "at" "1.394" } + "slap" { "credit" "1.800" "sound" "weapon_zr68.reload_slap" "at" "1.829" } + "bolt" { "credit" "2.367" "sound" "weapon_zr68.reload_bolt" "at" "2.129" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.433" "sound" "weapon_zr68.reload_seat" "at" "1.394" } + "slap" { "credit" "1.800" "sound" "weapon_zr68.reload_slap" "at" "1.829" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "1.167" "sound" "weapon_zr68.reload_seat" "at" "1.127" } + "slap" { "credit" "1.533" "sound" "weapon_zr68.reload_slap" "at" "1.562" } + "bolt" { "credit" "2.100" "sound" "weapon_zr68.reload_bolt" "at" "1.862" } + } + } + "TPMuzzleFlashScale" "0.5" "clip_size" "30" diff --git a/game/neo/scripts/weapon_zr68l.txt b/game/neo/scripts/weapon_zr68l.txt index 7a32a52377..3dbe10cf2c 100644 --- a/game/neo/scripts/weapon_zr68l.txt +++ b/game/neo/scripts/weapon_zr68l.txt @@ -16,6 +16,68 @@ WeaponData "Penetration" "32.0" "CycleTime" "0.5" // time between shots + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "0.700" "sound" "weapon_zr68l.reload_drop" "at" "0.183" } + "seat" { "credit" "1.333" "sound" "weapon_zr68l.reload_seat" "at" "1.335" } + "slap" { "credit" "1.700" "sound" "weapon_zr68l.reload_slap" "at" "1.691" } + "bolt" { "credit" "2.267" "sound" "weapon_zr68l.reload_bolt" "at" "1.996" } + } + "reload_partial" + { + "drop" { "credit" "0.700" "sound" "weapon_zr68l.reload_drop" "at" "0.183" } + "seat" { "credit" "1.333" "sound" "weapon_zr68l.reload_seat" "at" "1.335" } + "slap" { "credit" "1.700" "sound" "weapon_zr68l.reload_slap" "at" "1.691" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.267" "sound" "weapon_zr68l.reload_seat" "at" "1.268" } + "slap" { "credit" "1.633" "sound" "weapon_zr68l.reload_slap" "at" "1.624" } + "bolt" { "credit" "2.200" "sound" "weapon_zr68l.reload_bolt" "at" "1.929" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.267" "sound" "weapon_zr68l.reload_seat" "at" "1.268" } + "slap" { "credit" "1.633" "sound" "weapon_zr68l.reload_slap" "at" "1.624" } + } + "reload_from_seat_dry" + { + "slap" { "credit" "0.800" "sound" "weapon_zr68l.reload_slap" "at" "0.791" } + "bolt" { "credit" "1.367" "sound" "weapon_zr68l.reload_bolt" "at" "1.096" } + } + "reload_from_seat_partial" + { + "slap" { "credit" "0.800" "sound" "weapon_zr68l.reload_slap" "at" "0.791" } + } + "reload_from_slap_dry" + { + "bolt" { "credit" "1.233" "sound" "weapon_zr68l.reload_bolt" "at" "0.963" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.533" "sound" "weapon_zr68l.reload_seat" "at" "1.535" } + "slap" { "credit" "1.900" "sound" "weapon_zr68l.reload_slap" "at" "1.891" } + "bolt" { "credit" "2.467" "sound" "weapon_zr68l.reload_bolt" "at" "2.196" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.533" "sound" "weapon_zr68l.reload_seat" "at" "1.535" } + "slap" { "credit" "1.900" "sound" "weapon_zr68l.reload_slap" "at" "1.891" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "1.267" "sound" "weapon_zr68l.reload_seat" "at" "1.268" } + "slap" { "credit" "1.633" "sound" "weapon_zr68l.reload_slap" "at" "1.624" } + "bolt" { "credit" "2.200" "sound" "weapon_zr68l.reload_bolt" "at" "1.929" } + } + } + "TPMuzzleFlashScale" "0.5" "clip_size" "10" diff --git a/game/neo/scripts/weapon_zr68s.txt b/game/neo/scripts/weapon_zr68s.txt index f38e687f26..56adc700a1 100644 --- a/game/neo/scripts/weapon_zr68s.txt +++ b/game/neo/scripts/weapon_zr68s.txt @@ -18,6 +18,68 @@ WeaponData "Penetration" "18.0" "CycleTime" "0.106060" // time between shots + // Reload phases, generated from segments.csv by segments_build.py (ntre proposals/0057): + // credit = when the phase counts, at = when its sound piece's first transient plays, both from the sequence start + "ReloadPhases" + { + "ChamberedRound" "1" + "reload" + { + "drop" { "credit" "0.700" "sound" "weapon_zr68.reload_drop" "at" "0.146" } + "seat" { "credit" "1.333" "sound" "weapon_zr68.reload_seat" "at" "1.294" } + "slap" { "credit" "1.700" "sound" "weapon_zr68.reload_slap" "at" "1.729" } + "bolt" { "credit" "2.267" "sound" "weapon_zr68.reload_bolt" "at" "2.029" } + } + "reload_partial" + { + "drop" { "credit" "0.700" "sound" "weapon_zr68.reload_drop" "at" "0.146" } + "seat" { "credit" "1.333" "sound" "weapon_zr68.reload_seat" "at" "1.294" } + "slap" { "credit" "1.700" "sound" "weapon_zr68.reload_slap" "at" "1.729" } + } + "reload_from_drop_dry" + { + "seat" { "credit" "1.233" "sound" "weapon_zr68.reload_seat" "at" "1.194" } + "slap" { "credit" "1.600" "sound" "weapon_zr68.reload_slap" "at" "1.629" } + "bolt" { "credit" "2.167" "sound" "weapon_zr68.reload_bolt" "at" "1.929" } + } + "reload_from_drop_partial" + { + "seat" { "credit" "1.233" "sound" "weapon_zr68.reload_seat" "at" "1.194" } + "slap" { "credit" "1.600" "sound" "weapon_zr68.reload_slap" "at" "1.629" } + } + "reload_from_seat_dry" + { + "slap" { "credit" "0.767" "sound" "weapon_zr68.reload_slap" "at" "0.796" } + "bolt" { "credit" "1.333" "sound" "weapon_zr68.reload_bolt" "at" "1.096" } + } + "reload_from_seat_partial" + { + "slap" { "credit" "0.767" "sound" "weapon_zr68.reload_slap" "at" "0.796" } + } + "reload_from_slap_dry" + { + "bolt" { "credit" "1.233" "sound" "weapon_zr68.reload_bolt" "at" "0.996" } + } + "draw_resume_drop_dry" + { + "seat" { "credit" "1.433" "sound" "weapon_zr68.reload_seat" "at" "1.394" } + "slap" { "credit" "1.800" "sound" "weapon_zr68.reload_slap" "at" "1.829" } + "bolt" { "credit" "2.367" "sound" "weapon_zr68.reload_bolt" "at" "2.129" } + } + "draw_resume_drop_partial" + { + "seat" { "credit" "1.433" "sound" "weapon_zr68.reload_seat" "at" "1.394" } + "slap" { "credit" "1.800" "sound" "weapon_zr68.reload_slap" "at" "1.829" } + } + "fire_chamber_onehand" + { + "fire_at" "0.133" + "seat" { "credit" "1.167" "sound" "weapon_zr68.reload_seat" "at" "1.127" } + "slap" { "credit" "1.533" "sound" "weapon_zr68.reload_slap" "at" "1.562" } + "bolt" { "credit" "2.100" "sound" "weapon_zr68.reload_bolt" "at" "1.862" } + } + } + "TPMuzzleFlashScale" "0.5" "clip_size" "30" diff --git a/src/game/client/CMakeLists.txt b/src/game/client/CMakeLists.txt index cd2703666e..c20ddfac2a 100644 --- a/src/game/client/CMakeLists.txt +++ b/src/game/client/CMakeLists.txt @@ -1757,6 +1757,7 @@ set(UNITY_SOURCE_NEO_SHARED ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_penetration_resistance.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_player_shared.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_player_spawnpoint.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_playeranimstate.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_predicted_viewmodel.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_predicted_viewmodel_muzzleflash.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_shot_manipulator.cpp @@ -1787,6 +1788,7 @@ target_sources_grouped( ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_penetration_resistance.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_player_shared.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_player_spawnpoint.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_playeranimstate.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_predicted_viewmodel.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_predicted_viewmodel_muzzleflash.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_shot_manipulator.h @@ -1896,14 +1898,11 @@ target_sources_grouped( TARGET client NAME "Source Files\\Shared\\Multiplayer" FILES - ${CMAKE_SOURCE_DIR}/game/shared/Multiplayer/multiplayer_animstate.cpp - ${CMAKE_SOURCE_DIR}/game/shared/Multiplayer/multiplayer_animstate.h ) set(UNITY_SOURCE_HL2MP ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_gamerules.cpp ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_player_shared.cpp - ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_playeranimstate.cpp ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_weapon_parse.cpp hl2mp/c_hl2mp_player.cpp hl2mp/c_te_hl2mp_shotgun_shot.cpp @@ -1921,7 +1920,6 @@ target_sources_grouped( FILES ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_gamerules.h ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_player_shared.h - ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_playeranimstate.h ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_weapon_parse.h hl2mp/c_hl2mp_player.h hl2mp/clientmode_hl2mpnormal.h diff --git a/src/game/client/hl2mp/c_hl2mp_player.cpp b/src/game/client/hl2mp/c_hl2mp_player.cpp index 0b267d9fb0..8b760b2d37 100644 --- a/src/game/client/hl2mp/c_hl2mp_player.cpp +++ b/src/game/client/hl2mp/c_hl2mp_player.cpp @@ -188,7 +188,7 @@ C_HL2MP_Player::C_HL2MP_Player() : m_PlayerAnimState( this ), m_iv_angEyeAngles( m_EntClientFlags |= ENTCLIENTFLAG_DONTUSEIK; #endif #ifdef NEO - m_PlayerAnimState = CreateHL2MPPlayerAnimState( this ); + m_PlayerAnimState = CreateNEOPlayerAnimState( this ); #endif m_blinkTimer.Invalidate(); diff --git a/src/game/client/hl2mp/c_hl2mp_player.h b/src/game/client/hl2mp/c_hl2mp_player.h index aaa99399c0..9e15e066dd 100644 --- a/src/game/client/hl2mp/c_hl2mp_player.h +++ b/src/game/client/hl2mp/c_hl2mp_player.h @@ -9,7 +9,7 @@ #define HL2MP_PLAYER_H #pragma once -#include "hl2mp_playeranimstate.h" +#include "neo_playeranimstate.h" #include "c_basehlplayer.h" #include "hl2mp_player_shared.h" #include "beamdraw.h" @@ -145,7 +145,7 @@ class C_HL2MP_Player : public C_BaseHLPlayer C_HL2MP_Player( const C_HL2MP_Player & ); #ifdef NEO - CHL2MPPlayerAnimState *m_PlayerAnimState; + CNEOPlayerAnimState *m_PlayerAnimState; #else CPlayerAnimState m_PlayerAnimState; #endif diff --git a/src/game/client/neo/ui/neo_hud_ammo.cpp b/src/game/client/neo/ui/neo_hud_ammo.cpp index b8038ac8ab..0768863a2d 100644 --- a/src/game/client/neo/ui/neo_hud_ammo.cpp +++ b/src/game/client/neo/ui/neo_hud_ammo.cpp @@ -6,6 +6,9 @@ #include "iclientmode.h" #include #include +#include +#include "VGuiMatSurface/IMatSystemSurface.h" +#include "cdll_client_int.h" #include #include #include @@ -150,14 +153,16 @@ void CNEOHud_Ammo::DrawAmmo() const int fireModeWidth = 0, fireModeHeight = 0; int magSizeMax = 0; int magSizeCurrent = 0; + // a gun with a chamber counts the magazine in the row, and the round icon of the fire-mode glyph shows the chamber + const bool hasChamber = activeWep->HasChamberSlot(); if ((activeWep->UsesClipsForAmmo1() && !(activeWep->GetNeoWepBits() & NEO_WEP_THROWABLE)) || (activeWep->GetNeoWepBits() & NEO_WEP_BALC)) { char fireModeText[2]{ '\0' }; ammoChar = activeWep->GetNEOWpnData().szBulletCharacter; - magSizeMax = activeWep->GetMaxClip1(); - magSizeCurrent = activeWep->Clip1(); + magSizeMax = hasChamber ? activeWep->MagazineCapacity() : activeWep->GetMaxRoundsInGun(); + magSizeCurrent = hasChamber ? activeWep->RoundsInMagazine() : activeWep->Clip1(); if(activeWep) { @@ -176,10 +181,16 @@ void CNEOHud_Ammo::DrawAmmo() const g_pVGuiLocalize->ConvertANSIToUnicode(fireModeText, unicodeFireModeText, sizeof(unicodeFireModeText)); surface()->DrawSetTextFont(m_hBulletFont); - surface()->DrawSetTextPos(icon_xpos + xpos, icon_ypos + ypos); - surface()->DrawPrintText(unicodeFireModeText, V_wcslen(unicodeFireModeText)); - surface()->GetTextSize(m_hBulletFont, unicodeFireModeText, fireModeWidth, fireModeHeight); + if (hasChamber) + { + DrawFireModeWithChamber(unicodeFireModeText, fireModeHeight, activeWep->IsRoundChambered()); + } + else + { + surface()->DrawSetTextPos(icon_xpos + xpos, icon_ypos + ypos); + surface()->DrawPrintText(unicodeFireModeText, V_wcslen(unicodeFireModeText)); + } } } else { @@ -206,8 +217,15 @@ void CNEOHud_Ammo::DrawAmmo() const surface()->DrawSetTextFont(m_hBulletFont); surface()->DrawSetTextPos(digit_xpos + xpos, digit_ypos + ypos); wchar_t bullets[22]; - V_swprintf_safe(bullets, L"%i/%i", magSizeCurrent, magSizeMax); - surface()->DrawPrintText(bullets, (int)(magSizeCurrent == 0 ? 1 : log10(magSizeCurrent) + 1) + (int)(log10(magSizeMax) + 1) + 1); + if (hasChamber && activeWep->IsRoundChambered()) + { + V_swprintf_safe(bullets, L"%i+1/%i", magSizeCurrent, magSizeMax); + } + else + { + V_swprintf_safe(bullets, L"%i/%i", magSizeCurrent, magSizeMax); + } + surface()->DrawPrintText(bullets, V_wcslen(bullets)); return; } @@ -224,6 +242,7 @@ void CNEOHud_Ammo::DrawAmmo() const const int maxBulletsWeCanDisplayWithPlus = bulletWidth == 0 ? 0 : ((maxSpaceAvailableForBullets - plusWidth) / bulletWidth); const bool bulletsOverflowing = maxBulletsWeCanDisplay < magSizeMax; + const int magSizeFull = magSizeMax; if(bulletsOverflowing) { @@ -248,7 +267,7 @@ void CNEOHud_Ammo::DrawAmmo() const if (magSizeMax > 0) bullets[magSizeMax - 1] = '+'; - if(maxClip == magSizeCurrent) + if(magSizeFull == magSizeCurrent) { magAmountToDrawFilled = magSizeMax; } else if(magSizeMax - 1 < magSizeCurrent) @@ -280,6 +299,36 @@ void CNEOHud_Ammo::DrawAmmo() const } } +// The fire-mode glyph's top rows are a round icon (bullet or shell), its letter sits below them +static constexpr float FIRE_MODE_ROUND_ICON_HEIGHT = 0.184f; + +// Draw the fire-mode glyph with its round icon lit while a round is chambered, in the empty color otherwise; +// the letter keeps the text color. Two passes, each clipped to its part of the glyph +void CNEOHud_Ammo::DrawFireModeWithChamber(const wchar_t *pszGlyph, int iGlyphTall, bool bChambered) const +{ + const int iX = icon_xpos + xpos; + const int iY = icon_ypos + ypos; + int iScreenX = 0, iScreenY = 0; + vgui::ipanel()->GetAbsPos(GetVPanel(), iScreenX, iScreenY); + const int iSplitY = iScreenY + iY + static_cast(iGlyphTall * FIRE_MODE_ROUND_ICON_HEIGHT + 0.5f); + + int iLeft, iTop, iRight, iBottom; + bool bClippingDisabled; + g_pMatSystemSurface->GetClippingRect(iLeft, iTop, iRight, iBottom, bClippingDisabled); + + g_pMatSystemSurface->SetClippingRect(iLeft, iTop, iRight, Min(iBottom, iSplitY)); + surface()->DrawSetTextColor(bChambered ? ammo_color : emptied_ammo_color); + surface()->DrawSetTextPos(iX, iY); + surface()->DrawPrintText(pszGlyph, V_wcslen(pszGlyph)); + + g_pMatSystemSurface->SetClippingRect(iLeft, Max(iTop, iSplitY), iRight, iBottom); + surface()->DrawSetTextColor(ammo_text_color); + surface()->DrawSetTextPos(iX, iY); + surface()->DrawPrintText(pszGlyph, V_wcslen(pszGlyph)); + + g_pMatSystemSurface->SetClippingRect(iLeft, iTop, iRight, iBottom); +} + void CNEOHud_Ammo::DrawNeoHudElement() { auto *localPlayer = C_NEO_Player::GetLocalNEOPlayer(); diff --git a/src/game/client/neo/ui/neo_hud_ammo.h b/src/game/client/neo/ui/neo_hud_ammo.h index 7ea1901233..742eebf925 100644 --- a/src/game/client/neo/ui/neo_hud_ammo.h +++ b/src/game/client/neo/ui/neo_hud_ammo.h @@ -27,6 +27,7 @@ class CNEOHud_Ammo : public CNEOHud_ChildElement, public CHudElement, public vgu private: void DrawAmmo() const; + void DrawFireModeWithChamber(const wchar_t *pszGlyph, int iGlyphTall, bool bChambered) const; void DrawHeatMeter(C_NEOBaseCombatWeapon* activeWep) const; private: diff --git a/src/game/server/CMakeLists.txt b/src/game/server/CMakeLists.txt index fec1fd9998..20980013ca 100644 --- a/src/game/server/CMakeLists.txt +++ b/src/game/server/CMakeLists.txt @@ -1423,6 +1423,7 @@ set(UNITY_SOURCE_NEO_SHARED ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_penetration_resistance.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_player_shared.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_player_spawnpoint.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_playeranimstate.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_predicted_viewmodel.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_predicted_viewmodel_muzzleflash.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_shot_manipulator.cpp @@ -1480,6 +1481,7 @@ target_sources_grouped( ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_penetration_resistance.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_player_shared.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_player_spawnpoint.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_playeranimstate.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_predicted_viewmodel.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_predicted_viewmodel_muzzleflash.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_shot_manipulator.h @@ -1740,14 +1742,11 @@ target_sources_grouped( TARGET server NAME "Source Files\\Shared\\Multiplayer" FILES - ${CMAKE_SOURCE_DIR}/game/shared/Multiplayer/multiplayer_animstate.cpp - ${CMAKE_SOURCE_DIR}/game/shared/Multiplayer/multiplayer_animstate.h ) set(UNITY_SOURCE_HL2MP ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_gamerules.cpp ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_player_shared.cpp - ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_playeranimstate.cpp ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_weapon_parse.cpp hl2mp/hl2mp_client.cpp hl2mp/hl2mp_cvars.cpp @@ -1766,7 +1765,6 @@ target_sources_grouped( FILES ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_gamerules.h ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_player_shared.h - ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_playeranimstate.h ${CMAKE_SOURCE_DIR}/game/shared/hl2mp/hl2mp_weapon_parse.h hl2mp/hl2mp_gameinterface.h hl2mp/hl2mp_player.h diff --git a/src/game/server/hl2mp/hl2mp_player.cpp b/src/game/server/hl2mp/hl2mp_player.cpp index d9e8c9bcc5..d73f33a6af 100644 --- a/src/game/server/hl2mp/hl2mp_player.cpp +++ b/src/game/server/hl2mp/hl2mp_player.cpp @@ -193,7 +193,7 @@ CHL2MP_Player::CHL2MP_Player() : m_PlayerAnimState( this ) { #ifdef NEO //Tony; create our player animation state. - m_PlayerAnimState = CreateHL2MPPlayerAnimState( this ); + m_PlayerAnimState = CreateNEOPlayerAnimState( this ); #endif UseClientSideAnimation(); diff --git a/src/game/server/hl2mp/hl2mp_player.h b/src/game/server/hl2mp/hl2mp_player.h index e5e0b60a14..463d9d2374 100644 --- a/src/game/server/hl2mp/hl2mp_player.h +++ b/src/game/server/hl2mp/hl2mp_player.h @@ -16,7 +16,7 @@ class CHL2MP_Player; #include "hl2_player.h" #include "simtimer.h" #include "soundenvelope.h" -#include "hl2mp_playeranimstate.h" +#include "neo_playeranimstate.h" #include "hl2mp_player_shared.h" #include "hl2mp_gamerules.h" #include "utldict.h" @@ -177,7 +177,7 @@ class CHL2MP_Player : public CHL2_Player private: #ifdef NEO - CHL2MPPlayerAnimState *m_PlayerAnimState; + CNEOPlayerAnimState *m_PlayerAnimState; #else CPlayerAnimState m_PlayerAnimState; #endif diff --git a/src/game/server/neo/neo_ghost_boundary.cpp b/src/game/server/neo/neo_ghost_boundary.cpp index 9f38e5e589..31f5e0b96e 100644 --- a/src/game/server/neo/neo_ghost_boundary.cpp +++ b/src/game/server/neo/neo_ghost_boundary.cpp @@ -102,7 +102,7 @@ void CNEO_GhostBoundary::Think() } CBasePlayer* pPlayer = UTIL_PlayerByIndex(iJuggernautIndex); - if (pPlayer->GetFlags() & FL_ONGROUND) + if (pPlayer && (pPlayer->GetFlags() & FL_ONGROUND)) { m_vecLastObjectivePos = pPlayer->GetAbsOrigin(); } @@ -120,8 +120,9 @@ void CNEO_GhostBoundary::Think() return; } + // The ghoster index is only refreshed after entities think, so it can still name a player who just disconnected CBasePlayer* pPlayer = UTIL_PlayerByIndex(iGhosterIndex); - if (pPlayer->GetFlags() & FL_ONGROUND) + if (pPlayer && (pPlayer->GetFlags() & FL_ONGROUND)) { m_vecLastObjectivePos = pPlayer->GetAbsOrigin(); } diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index 4a5278e505..e7c3471bfe 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -2386,6 +2386,12 @@ void CNEO_Player::AddPoints(int score, bool bAllowNegativeScore, bool bIgnorePla ConVar cl_neo_spec_auto_observe_killer_if_observing_victim("cl_neo_spec_auto_observe_killer_if_observing_victim", "1", FCVAR_CLIENTDLL | FCVAR_ARCHIVE | FCVAR_USERINFO, "If the current observer target is killed when in eye or following, switch observer target to the killer", true, 0, true, 1); void CNEO_Player::Event_Killed( const CTakeDamageInfo &info ) { + // a death cuts a reload short, whether or not the weapon drops + if (auto *pNeoWeapon = dynamic_cast(GetActiveWeapon())) + { + pNeoWeapon->StopReloadOnDrop(); + } + CBaseEntity* pAttacker = info.GetAttacker(); if (pAttacker && pAttacker->IsPlayer()) // we can only have players as a spectate target atm { @@ -2990,6 +2996,7 @@ void CNEO_Player::Weapon_Drop( CBaseCombatWeapon *pWeapon, return; // Return early to not drop weapon } + neoWeapon->StopReloadOnDrop(); pWeapon->m_bInReload = false; pWeapon->StopWeaponSound(RELOAD_NPC); diff --git a/src/game/shared/base_playeranimstate.cpp b/src/game/shared/base_playeranimstate.cpp index 0126f016ae..7f4950894c 100644 --- a/src/game/shared/base_playeranimstate.cpp +++ b/src/game/shared/base_playeranimstate.cpp @@ -234,6 +234,17 @@ void CBasePlayerAnimState::RestartMainSequence() } +#ifdef NEO +void CBasePlayerAnimState::OnNewModel() +{ + // the aim layer transitioners keep the previous sequences to blend out of them, + // and after a model swap those indices address a foreign sequence table + m_IdleSequenceTransitioner.RemoveAll(); + m_SequenceTransitioner.RemoveAll(); + ClearAnimationState(); +} +#endif + void CBasePlayerAnimState::ComputeSequences( CStudioHdr *pStudioHdr ) { VPROF( "CBasePlayerAnimState::ComputeSequences" ); diff --git a/src/game/shared/base_playeranimstate.h b/src/game/shared/base_playeranimstate.h index 95e2c73a4f..de0a68be97 100644 --- a/src/game/shared/base_playeranimstate.h +++ b/src/game/shared/base_playeranimstate.h @@ -162,6 +162,11 @@ abstract_class CBasePlayerAnimState : virtual public IPlayerAnimState void RestartMainSequence(); +#ifdef NEO + // A sequence index is only meaningful for the model it was computed against + void OnNewModel(); +#endif + // Helpers for the derived classes to use. protected: diff --git a/src/game/shared/basecombatweapon_shared.cpp b/src/game/shared/basecombatweapon_shared.cpp index e72e024be8..f9c993e6d8 100644 --- a/src/game/shared/basecombatweapon_shared.cpp +++ b/src/game/shared/basecombatweapon_shared.cpp @@ -2443,7 +2443,13 @@ bool CBaseCombatWeapon::SetIdealActivity( Activity ideal ) int nextSequence = FindTransitionSequence( GetSequence(), m_nIdealSequence, NULL ); // Don't use transitions when we're deploying +#ifdef NEO + // or reloading: the reload ends after the sequence's duration, + // so a transition played first (such as the Tachi's lowtoidle after a sprint) would cut the reload short + if ( ideal != ACT_VM_DRAW && ideal != ACT_VM_RELOAD && IsWeaponVisible() && nextSequence != m_nIdealSequence ) +#else if ( ideal != ACT_VM_DRAW && IsWeaponVisible() && nextSequence != m_nIdealSequence ) +#endif { //Set our activity to the next transitional animation SetActivity( ACT_TRANSITION ); diff --git a/src/game/shared/hl2mp/hl2mp_playeranimstate.cpp b/src/game/shared/hl2mp/hl2mp_playeranimstate.cpp deleted file mode 100644 index 9e441acd87..0000000000 --- a/src/game/shared/hl2mp/hl2mp_playeranimstate.cpp +++ /dev/null @@ -1,1015 +0,0 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -#include "cbase.h" -#include "tier0/vprof.h" -#include "animation.h" -#include "studio.h" -#include "apparent_velocity_helper.h" -#include "utldict.h" - -#include "hl2mp_playeranimstate.h" -#include "base_playeranimstate.h" -#include "datacache/imdlcache.h" - -#ifdef CLIENT_DLL -#include "c_hl2mp_player.h" -#ifdef NEO -#include "c_neo_player.h" -#endif -#else -#include "hl2mp_player.h" -#ifdef NEO -#include "neo_player.h" -#endif -#endif - -#ifdef NEO -#include "shareddefs.h" - -#define DEFAULT_IDLE_NAME "Idle_Upper_" -#define DEFAULT_CROUCH_IDLE_NAME "Crouch_Idle_Upper_" -#define DEFAULT_CROUCH_WALK_NAME "Crouch_Walk_Upper_" -#define DEFAULT_WALK_NAME "Walk_Upper_" -#define DEFAULT_RUN_NAME "Run_Upper_" - -#define DEFAULT_FIRE_IDLE_NAME "Idle_Shoot_" -#define DEFAULT_FIRE_CROUCH_NAME "Crouch_Idle_Shoot_" -#define DEFAULT_FIRE_CROUCH_WALK_NAME "Crouch_Walk_Shoot_" -#define DEFAULT_FIRE_WALK_NAME "Walk_Shoot_" -#define DEFAULT_FIRE_RUN_NAME "Run_Shoot_" -#endif - -#include "filesystem.h" -#include "engine/ivdebugoverlay.h" - -#define HL2MP_RUN_SPEED 320.0f -#define HL2MP_WALK_SPEED 75.0f -#define HL2MP_CROUCHWALK_SPEED 110.0f - -extern ConVar sv_showanimstate; -extern ConVar showanimstate_log; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pPlayer - -// Output : CMultiPlayerAnimState* -//----------------------------------------------------------------------------- -CHL2MPPlayerAnimState* CreateHL2MPPlayerAnimState( CHL2MP_Player *pPlayer ) -{ - MDLCACHE_CRITICAL_SECTION(); - - // Setup the movement data. - MultiPlayerMovementData_t movementData; - movementData.m_flBodyYawRate = 720.0f; - movementData.m_flRunSpeed = HL2MP_RUN_SPEED; - movementData.m_flWalkSpeed = HL2MP_WALK_SPEED; - movementData.m_flSprintSpeed = -1.0f; - - // Create animation state for this player. - CHL2MPPlayerAnimState *pRet = new CHL2MPPlayerAnimState( pPlayer, movementData ); - - // Specific HL2MP player initialization. - pRet->InitHL2MPAnimState( pPlayer ); - - return pRet; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : - -//----------------------------------------------------------------------------- -CHL2MPPlayerAnimState::CHL2MPPlayerAnimState() -{ - m_pHL2MPPlayer = NULL; - - // Don't initialize HL2MP specific variables here. Init them in InitHL2MPAnimState() -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pPlayer - -// &movementData - -//----------------------------------------------------------------------------- -CHL2MPPlayerAnimState::CHL2MPPlayerAnimState( CBasePlayer *pPlayer, MultiPlayerMovementData_t &movementData ) -: CMultiPlayerAnimState( pPlayer, movementData ) -{ - m_pHL2MPPlayer = NULL; - - // Don't initialize HL2MP specific variables here. Init them in InitHL2MPAnimState() -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : - -//----------------------------------------------------------------------------- -CHL2MPPlayerAnimState::~CHL2MPPlayerAnimState() -{ -} - -//----------------------------------------------------------------------------- -// Purpose: Initialize HL2MP specific animation state. -// Input : *pPlayer - -//----------------------------------------------------------------------------- -void CHL2MPPlayerAnimState::InitHL2MPAnimState( CHL2MP_Player *pPlayer ) -{ - m_pHL2MPPlayer = pPlayer; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHL2MPPlayerAnimState::ClearAnimationState( void ) -{ - BaseClass::ClearAnimationState(); -} - -int CHL2MPPlayerAnimState::CalcSequenceIndex(const char* pBaseName, ...) -{ - char szFullName[512]; - va_list marker; - va_start(marker, pBaseName); - Q_vsnprintf(szFullName, sizeof(szFullName), pBaseName, marker); - va_end(marker); - CBaseAnimatingOverlay* pPlayer = GetBasePlayer(); - int iSequence = pPlayer->LookupSequence(szFullName); - - // Show warnings if we can't find anything here. - if (iSequence == -1) - { - static CUtlDict dict; - if (dict.Find(szFullName) == -1) - { - dict.Insert(szFullName, 0); - Warning("CalcSequenceIndex: can't find '%s'.\n", szFullName); - } - - iSequence = 0; - } - - //DevMsg("CalcSeqIdx: \"%s\": %d\n", szFullName, iSequence); - - return iSequence; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : actDesired - -// Output : Activity -//----------------------------------------------------------------------------- -Activity CHL2MPPlayerAnimState::TranslateActivity( Activity actDesired ) -{ - // Hook into baseclass when / if hl2mp player models get swim animations. - Activity translateActivity = actDesired; //BaseClass::TranslateActivity( actDesired ); - if ( GetHL2MPPlayer()->GetActiveWeapon() ) - { - bool required = false; - translateActivity = GetHL2MPPlayer()->GetActiveWeapon()->ActivityOverride( translateActivity, &required); - } - return translateActivity; -} - -#ifdef NEO -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pStudioHdr - -//----------------------------------------------------------------------------- -void CHL2MPPlayerAnimState::ComputeSequences(CStudioHdr* pStudioHdr) -{ - VPROF("CBasePlayerAnimState::ComputeSequences"); - - // Lower body (walk/run/idle). - ComputeMainSequence(); - - // The groundspeed interpolator uses the main sequence info. - UpdateInterpolators(); - ComputeGestureSequence(pStudioHdr); -} - -#endif //NEO -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHL2MPPlayerAnimState::Update( float eyeYaw, float eyePitch ) -{ - // Profile the animation update. - VPROF( "CHL2MPPlayerAnimState::Update" ); - - // Get the HL2MP player. - CHL2MP_Player *pHL2MPPlayer = GetHL2MPPlayer(); - if ( !pHL2MPPlayer ) - return; - - // Get the studio header for the player. - CStudioHdr *pStudioHdr = pHL2MPPlayer->GetModelPtr(); - if ( !pStudioHdr ) - return; - - // Check to see if we should be updating the animation state - dead, ragdolled? - if ( !ShouldUpdateAnimState() ) - { - ClearAnimationState(); - return; - } - - // Store the eye angles. - m_flEyeYaw = AngleNormalize( eyeYaw ); - m_flEyePitch = AngleNormalize( eyePitch ); - - // Compute the player sequences. - ComputeSequences( pStudioHdr ); - - if ( SetupPoseParameters( pStudioHdr ) ) - { - // Pose parameter - what direction are the player's legs running in. - ComputePoseParam_MoveYaw( pStudioHdr ); - - // Pose parameter - Torso aiming (up/down). - ComputePoseParam_AimPitch( pStudioHdr ); - - // Pose parameter - Torso aiming (rotation). - ComputePoseParam_AimYaw( pStudioHdr ); - } - -#ifdef CLIENT_DLL - if ( C_BasePlayer::ShouldDrawLocalPlayer() ) - { - m_pHL2MPPlayer->SetPlaybackRate( 1.0f ); - } -#endif - -#ifdef CLIENT_DLL - if (cl_showanimstate.GetInt() == m_pHL2MPPlayer->entindex()) - { - DebugShowAnimStateFull(5); - } - else if (cl_showanimstate.GetInt() == -2) - { - C_BasePlayer* targetPlayer = C_BasePlayer::GetLocalPlayer(); - - if (targetPlayer && (targetPlayer->GetObserverMode() == OBS_MODE_IN_EYE || targetPlayer->GetObserverMode() == OBS_MODE_CHASE)) - { - C_BaseEntity* target = targetPlayer->GetObserverTarget(); - - if (target && target->IsPlayer()) - { - targetPlayer = ToBasePlayer(target); - } - } - - if (m_pHL2MPPlayer == targetPlayer) - { - DebugShowAnimStateFull(6); - } - } -#else - if (sv_showanimstate.GetInt() == m_pHL2MPPlayer->entindex()) - { - DebugShowAnimState(20); - } -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : event - -//----------------------------------------------------------------------------- -void CHL2MPPlayerAnimState::DoAnimationEvent( PlayerAnimEvent_t event, int nData ) -{ - Activity iGestureActivity = ACT_INVALID; - - switch( event ) - { - case PLAYERANIMEVENT_ATTACK_PRIMARY: - { -#ifdef NEO - AddToGestureSlot(GESTURE_SLOT_ATTACK_AND_RELOAD, CalcFireSequence(), true); -#else - // Weapon primary fire. - if ( m_pHL2MPPlayer->GetFlags() & FL_DUCKING ) - RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_ATTACK_CROUCH_PRIMARYFIRE ); - else - RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_ATTACK_STAND_PRIMARYFIRE ); - - iGestureActivity = ACT_VM_PRIMARYATTACK; -#endif - break; - } - - case PLAYERANIMEVENT_VOICE_COMMAND_GESTURE: - { - if ( !IsGestureSlotActive( GESTURE_SLOT_ATTACK_AND_RELOAD ) ) - RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, (Activity)nData ); - - break; - } - case PLAYERANIMEVENT_ATTACK_SECONDARY: - { -#ifdef NEO - AddToGestureSlot(GESTURE_SLOT_ATTACK_AND_RELOAD, CalcFireSequence(), true); -#else - // Weapon secondary fire. - if ( m_pHL2MPPlayer->GetFlags() & FL_DUCKING ) - RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_ATTACK_CROUCH_SECONDARYFIRE ); - else - RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_ATTACK_STAND_SECONDARYFIRE ); - - iGestureActivity = ACT_VM_PRIMARYATTACK; -#endif - break; - } - case PLAYERANIMEVENT_ATTACK_PRE: - { - if ( m_pHL2MPPlayer->GetFlags() & FL_DUCKING ) - { - // Weapon pre-fire. Used for minigun windup, sniper aiming start, etc in crouch. - iGestureActivity = ACT_MP_ATTACK_CROUCH_PREFIRE; - } - else - { - // Weapon pre-fire. Used for minigun windup, sniper aiming start, etc. - iGestureActivity = ACT_MP_ATTACK_STAND_PREFIRE; - } - - RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, iGestureActivity, false ); - - break; - } - case PLAYERANIMEVENT_ATTACK_POST: - { - RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_ATTACK_STAND_POSTFIRE ); - break; - } - - case PLAYERANIMEVENT_RELOAD: - { -#ifdef NEO - AddToGestureSlot(GESTURE_SLOT_ATTACK_AND_RELOAD, CalcReloadSequence(), true); -#else - // Weapon reload. - if ( GetBasePlayer()->GetFlags() & FL_DUCKING ) - RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_RELOAD_CROUCH ); - else - RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_RELOAD_STAND ); -#endif - break; - } - case PLAYERANIMEVENT_RELOAD_LOOP: - { - // Weapon reload. - if ( GetBasePlayer()->GetFlags() & FL_DUCKING ) - RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_RELOAD_CROUCH_LOOP ); - else - RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_RELOAD_STAND_LOOP ); - break; - } - case PLAYERANIMEVENT_RELOAD_END: - { - // Weapon reload. - if ( GetBasePlayer()->GetFlags() & FL_DUCKING ) - RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_RELOAD_CROUCH_END ); - else - RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_RELOAD_STAND_END ); - break; - } - default: - { - BaseClass::DoAnimationEvent( event, nData ); - break; - } - } - -#ifdef CLIENT_DLL - // Make the weapon play the animation as well - if ( iGestureActivity != ACT_INVALID ) - { - CBaseCombatWeapon *pWeapon = GetHL2MPPlayer()->GetActiveWeapon(); - if ( pWeapon ) - { -// pWeapon->EnsureCorrectRenderingModel(); - pWeapon->SendWeaponAnim( iGestureActivity ); -// // Force animation events! -// pWeapon->ResetEventsParity(); // reset event parity so the animation events will occur on the weapon. - pWeapon->DoAnimationEvents( pWeapon->GetModelPtr() ); - } - } -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *idealActivity - -//----------------------------------------------------------------------------- -bool CHL2MPPlayerAnimState::HandleSwimming( Activity &idealActivity ) -{ - bool bInWater = BaseClass::HandleSwimming( idealActivity ); - - return bInWater; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *idealActivity - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CHL2MPPlayerAnimState::HandleMoving( Activity &idealActivity ) -{ -#ifdef NEO - if (m_pHL2MPPlayer->IsSprinting()) - { - idealActivity = ACT_RUN; - } - else - { - Vector vecVelocity; - GetOuterAbsVelocity(vecVelocity); - if (vecVelocity.Length() > 0.5) - { - idealActivity = ACT_WALK; - } - } - - return true; -#else - return BaseClass::HandleMoving( idealActivity ); -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *idealActivity - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CHL2MPPlayerAnimState::HandleDucking( Activity &idealActivity ) -{ - if ( m_pHL2MPPlayer->GetFlags() & FL_DUCKING ) - { - if ( GetOuterXYSpeed() < MOVING_MINIMUM_SPEED ) - { -#ifdef NEO - idealActivity = ACT_CROUCHIDLE; -#else - idealActivity = ACT_MP_CROUCH_IDLE; -#endif - } - else - { -#ifdef NEO - idealActivity = ACT_RUN_CROUCH; -#else - idealActivity = ACT_MP_CROUCHWALK; -#endif - } - - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: -bool CHL2MPPlayerAnimState::HandleJumping( Activity &idealActivity ) -{ - Vector vecVelocity; - GetOuterAbsVelocity( vecVelocity ); - - if ( m_bJumping ) - { - static bool bNewJump = false; //Tony; hl2mp players only have a 'hop' - - if ( m_bFirstJumpFrame ) - { - m_bFirstJumpFrame = false; - RestartMainSequence(); // Reset the animation. - } - - // Reset if we hit water and start swimming. - if ( m_pHL2MPPlayer->GetWaterLevel() >= WL_Waist ) - { - m_bJumping = false; - RestartMainSequence(); - } - // Don't check if he's on the ground for a sec.. sometimes the client still has the - // on-ground flag set right when the message comes in. - else if ( gpGlobals->curtime - m_flJumpStartTime > 0.2f ) - { - if ( m_pHL2MPPlayer->GetFlags() & FL_ONGROUND ) - { - m_bJumping = false; - RestartMainSequence(); - - if ( bNewJump ) - { -#ifdef NEO - RestartGesture(GESTURE_SLOT_JUMP, ACT_HOP); -#else - RestartGesture( GESTURE_SLOT_JUMP, ACT_MP_JUMP_LAND ); -#endif - } - } - } - - // if we're still jumping - if ( m_bJumping ) - { -#ifdef NEO - idealActivity = ACT_HOP; -#else - if ( bNewJump ) - { - if ( gpGlobals->curtime - m_flJumpStartTime > 0.5 ) - { - idealActivity = ACT_MP_JUMP_FLOAT; - } - else - { - idealActivity = ACT_MP_JUMP_START; - } - } - else - { - idealActivity = ACT_MP_JUMP; - } -#endif - } - } - - if ( m_bJumping ) - return true; - - return false; -} - -bool CHL2MPPlayerAnimState::SetupPoseParameters( CStudioHdr *pStudioHdr ) -{ -#ifdef NEO - BaseClass::SetupPoseParameters(pStudioHdr); -#endif - // Check to see if this has already been done. - if ( m_bPoseParameterInit ) - return true; - - // Save off the pose parameter indices. - if ( !pStudioHdr ) - return false; - - // Tony; just set them both to the same for now. - m_PoseParameterData.m_iMoveX = GetBasePlayer()->LookupPoseParameter( pStudioHdr, "move_yaw" ); - m_PoseParameterData.m_iMoveY = GetBasePlayer()->LookupPoseParameter( pStudioHdr, "move_yaw" ); - if ( ( m_PoseParameterData.m_iMoveX < 0 ) || ( m_PoseParameterData.m_iMoveY < 0 ) ) - return false; - - // Look for the aim pitch blender. - m_PoseParameterData.m_iAimPitch = GetBasePlayer()->LookupPoseParameter( pStudioHdr, "body_pitch" ); - if ( m_PoseParameterData.m_iAimPitch < 0 ) - return false; - - // Look for aim yaw blender. - m_PoseParameterData.m_iAimYaw = GetBasePlayer()->LookupPoseParameter( pStudioHdr, "body_yaw" ); - if ( m_PoseParameterData.m_iAimYaw < 0 ) - return false; - - m_bPoseParameterInit = true; - - return true; -} -float SnapYawTo( float flValue ); -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHL2MPPlayerAnimState::EstimateYaw( void ) -{ - // Get the frame time. - float flDeltaTime = gpGlobals->frametime; - if ( flDeltaTime == 0.0f ) - return; - -#if 0 // 9way - // Get the player's velocity and angles. - Vector vecEstVelocity; - GetOuterAbsVelocity( vecEstVelocity ); - QAngle angles = GetBasePlayer()->GetLocalAngles(); - - // If we are not moving, sync up the feet and eyes slowly. - if ( vecEstVelocity.x == 0.0f && vecEstVelocity.y == 0.0f ) - { - float flYawDelta = angles[YAW] - m_PoseParameterData.m_flEstimateYaw; - flYawDelta = AngleNormalize( flYawDelta ); - - if ( flDeltaTime < 0.25f ) - { - flYawDelta *= ( flDeltaTime * 4.0f ); - } - else - { - flYawDelta *= flDeltaTime; - } - - m_PoseParameterData.m_flEstimateYaw += flYawDelta; - AngleNormalize( m_PoseParameterData.m_flEstimateYaw ); - } - else - { - m_PoseParameterData.m_flEstimateYaw = ( atan2( vecEstVelocity.y, vecEstVelocity.x ) * 180.0f / M_PI ); - m_PoseParameterData.m_flEstimateYaw = clamp( m_PoseParameterData.m_flEstimateYaw, -180.0f, 180.0f ); - } -#else - float dt = gpGlobals->frametime; - - // Get the player's velocity and angles. - Vector vecEstVelocity; - GetOuterAbsVelocity( vecEstVelocity ); - QAngle angles = GetBasePlayer()->GetLocalAngles(); - - if ( vecEstVelocity.y == 0 && vecEstVelocity.x == 0 ) - { - float flYawDiff = angles[YAW] - m_PoseParameterData.m_flEstimateYaw; - flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; - if (flYawDiff > 180) - flYawDiff -= 360; - if (flYawDiff < -180) - flYawDiff += 360; - - if (dt < 0.25) - flYawDiff *= dt * 4; - else - flYawDiff *= dt; - - m_PoseParameterData.m_flEstimateYaw += flYawDiff; - m_PoseParameterData.m_flEstimateYaw = m_PoseParameterData.m_flEstimateYaw - (int)(m_PoseParameterData.m_flEstimateYaw / 360) * 360; - } - else - { - m_PoseParameterData.m_flEstimateYaw = (atan2(vecEstVelocity.y, vecEstVelocity.x) * 180 / M_PI); - - if (m_PoseParameterData.m_flEstimateYaw > 180) - m_PoseParameterData.m_flEstimateYaw = 180; - else if (m_PoseParameterData.m_flEstimateYaw < -180) - m_PoseParameterData.m_flEstimateYaw = -180; - } -#endif -} -//----------------------------------------------------------------------------- -// Purpose: Override for backpeddling -// Input : dt - -//----------------------------------------------------------------------------- -float SnapYawTo( float flValue ); -void CHL2MPPlayerAnimState::ComputePoseParam_MoveYaw( CStudioHdr *pStudioHdr ) -{ -#ifdef NEO - BaseClass::ComputePoseParam_MoveYaw(pStudioHdr); -#endif - // Get the estimated movement yaw. - EstimateYaw(); - -#if 1 // 9way - ConVarRef mp_slammoveyaw("mp_slammoveyaw"); - - // Get the view yaw. - float flAngle = AngleNormalize( m_flEyeYaw ); - - // Calc side to side turning - the view vs. movement yaw. - float flYaw = flAngle - m_PoseParameterData.m_flEstimateYaw; - flYaw = AngleNormalize( -flYaw ); - - // Get the current speed the character is running. - bool bIsMoving; - float flPlaybackRate = CalcMovementPlaybackRate( &bIsMoving ); - - // Setup the 9-way blend parameters based on our speed and direction. - Vector2D vecCurrentMoveYaw( 0.0f, 0.0f ); - if ( bIsMoving ) - { - if ( mp_slammoveyaw.GetBool() ) - flYaw = SnapYawTo( flYaw ); - - vecCurrentMoveYaw.x = cos( DEG2RAD( flYaw ) ) * flPlaybackRate; - vecCurrentMoveYaw.y = -sin( DEG2RAD( flYaw ) ) * flPlaybackRate; - } - - GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iMoveX, vecCurrentMoveYaw.x ); - GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iMoveY, vecCurrentMoveYaw.y ); - - m_DebugAnimData.m_vecMoveYaw = vecCurrentMoveYaw; -#else - // view direction relative to movement - float flYaw; - - QAngle angles = GetBasePlayer()->GetLocalAngles(); - float ang = angles[ YAW ]; - if ( ang > 180.0f ) - { - ang -= 360.0f; - } - else if ( ang < -180.0f ) - { - ang += 360.0f; - } - - // calc side to side turning - flYaw = ang - m_PoseParameterData.m_flEstimateYaw; - // Invert for mapping into 8way blend - flYaw = -flYaw; - flYaw = flYaw - (int)(flYaw / 360) * 360; - - if (flYaw < -180) - { - flYaw = flYaw + 360; - } - else if (flYaw > 180) - { - flYaw = flYaw - 360; - } - - //Tony; oops, i inverted this previously above. - GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iMoveY, flYaw ); - -#endif - -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHL2MPPlayerAnimState::ComputePoseParam_AimPitch( CStudioHdr *pStudioHdr ) -{ - // Get the view pitch. - float flAimPitch = m_flEyePitch; - - // Set the aim pitch pose parameter and save. - GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iAimPitch, flAimPitch ); - m_DebugAnimData.m_flAimPitch = flAimPitch; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHL2MPPlayerAnimState::ComputePoseParam_AimYaw( CStudioHdr *pStudioHdr ) -{ - // Get the movement velocity. - Vector vecVelocity; - GetOuterAbsVelocity( vecVelocity ); - - // Check to see if we are moving. - bool bMoving = ( vecVelocity.Length() > 1.0f ) ? true : false; - -#ifdef NEO - // If we are moving or leaning or are prone and undeployed. - if ( bMoving || m_bForceAimYaw || IsLeaning(pStudioHdr) ) -#else - // If we are moving or are prone and undeployed. - if ( bMoving || m_bForceAimYaw ) -#endif // NEO - { - // The feet match the eye direction when moving - the move yaw takes care of the rest. - m_flGoalFeetYaw = m_flEyeYaw; - } - // Else if we are not moving. - else - { - // Initialize the feet. - if ( m_PoseParameterData.m_flLastAimTurnTime <= 0.0f ) - { - m_flGoalFeetYaw = m_flEyeYaw; - m_flCurrentFeetYaw = m_flEyeYaw; - m_PoseParameterData.m_flLastAimTurnTime = gpGlobals->curtime; - } - // Make sure the feet yaw isn't too far out of sync with the eye yaw. - // TODO: Do something better here! - else - { - float flYawDelta = AngleNormalize( m_flGoalFeetYaw - m_flEyeYaw ); - -#ifdef NEO - if (fabs(flYawDelta) > 90.f /*NEO_ANIMSTATE_MAX_BODY_YAW_DEGREES*/) -#else - if ( fabs( flYawDelta ) > 45.0f ) -#endif - { - float flSide = ( flYawDelta > 0.0f ) ? -1.0f : 1.0f; - -#ifdef NEO - m_flGoalFeetYaw += (90.f/*NEO_ANIMSTATE_MAX_BODY_YAW_DEGREES*/ * flSide); -#else - m_flGoalFeetYaw += ( 45.0f * flSide ); -#endif - } - } - } - - // Fix up the feet yaw. - m_flGoalFeetYaw = AngleNormalize( m_flGoalFeetYaw ); - if ( m_flGoalFeetYaw != m_flCurrentFeetYaw ) - { - if ( m_bForceAimYaw ) - { - m_flCurrentFeetYaw = m_flGoalFeetYaw; - } - else - { - ConvergeYawAngles( m_flGoalFeetYaw, 720.0f, gpGlobals->frametime, m_flCurrentFeetYaw ); - m_flLastAimTurnTime = gpGlobals->curtime; - } - } - - // Rotate the body into position. - m_angRender[YAW] = m_flCurrentFeetYaw; - - // Find the aim(torso) yaw base on the eye and feet yaws. - float flAimYaw = m_flEyeYaw - m_flCurrentFeetYaw; - flAimYaw = AngleNormalize( flAimYaw ); - - // Set the aim yaw and save. - GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iAimYaw, flAimYaw ); - m_DebugAnimData.m_flAimYaw = flAimYaw; - - // Turn off a force aim yaw - either we have already updated or we don't need to. - m_bForceAimYaw = false; - -#ifndef CLIENT_DLL - QAngle angle = GetBasePlayer()->GetAbsAngles(); - angle[YAW] = m_flCurrentFeetYaw; - - GetBasePlayer()->SetAbsAngles( angle ); -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: Override the default, because hl2mp models don't use moveX -// Input : - -// Output : float -//----------------------------------------------------------------------------- -float CHL2MPPlayerAnimState::GetCurrentMaxGroundSpeed() -{ -#ifdef NEO - return BaseClass::GetCurrentMaxGroundSpeed(); -#endif - CStudioHdr *pStudioHdr = GetBasePlayer()->GetModelPtr(); - - if ( pStudioHdr == NULL ) - return 1.0f; - -// float prevX = GetBasePlayer()->GetPoseParameter( m_PoseParameterData.m_iMoveX ); - float prevY = GetBasePlayer()->GetPoseParameter( m_PoseParameterData.m_iMoveY ); - - float d = sqrt( /*prevX * prevX + */prevY * prevY ); - float newY;//, newX; - if ( d == 0.0 ) - { -// newX = 1.0; - newY = 0.0; - } - else - { -// newX = prevX / d; - newY = prevY / d; - } - -// GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iMoveX, newX ); - GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iMoveY, newY ); - - float speed = GetBasePlayer()->GetSequenceGroundSpeed( GetBasePlayer()->GetSequence() ); - -// GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iMoveX, prevX ); - GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iMoveY, prevY ); - - return speed; -} - -#ifdef NEO -bool CHL2MPPlayerAnimState::IsLeaning(CStudioHdr *pStudioHdr) -{ - if (CBaseAnimating* pBaseAnimating = GetBasePlayer()->GetBaseAnimating()) - { - constexpr int LEAN_BONE_CONTROLLER = 0; -#ifdef CLIENT_DLL - constexpr float BONE_CONTROLLER_VALUE_NOT_LEANING = 0.5f; -#else - constexpr int BONE_CONTROLLER_VALUE_NOT_LEANING = 0; -#endif // CLIENT_DLL - return pBaseAnimating->GetBoneController(LEAN_BONE_CONTROLLER) != BONE_CONTROLLER_VALUE_NOT_LEANING; - } - return false; -} -#endif // NEO - -// ----------------------------------------------------------------------------- -void CHL2MPPlayerAnimState::AnimStateLog(const char* pMsg, ...) -{ - // Format the string. - char str[4096]; - va_list marker; - va_start(marker, pMsg); - Q_vsnprintf(str, sizeof(str), pMsg, marker); - va_end(marker); - - // Log it? - if (showanimstate_log.GetInt() == 1 || showanimstate_log.GetInt() == 3) - { - Msg("%s", str); - } - - if (showanimstate_log.GetInt() > 1) - { -#ifdef CLIENT_DLL - const char* fname = "AnimStateClient.log"; -#else - const char* fname = "AnimStateServer.log"; -#endif - static FileHandle_t hFile = filesystem->Open(fname, "wt"); - filesystem->FPrintf(hFile, "%s", str); - filesystem->Flush(hFile); - } -} - -// ----------------------------------------------------------------------------- -void CHL2MPPlayerAnimState::AnimStatePrintf(int iLine, const char* pMsg, ...) -{ - // Format the string. - char str[4096]; - va_list marker; - va_start(marker, pMsg); - Q_vsnprintf(str, sizeof(str), pMsg, marker); - va_end(marker); - - // Show it with Con_NPrintf. - engine->Con_NPrintf(iLine, "%s", str); - - // Log it. - AnimStateLog("%s\n", str); -} - - -// ----------------------------------------------------------------------------- -void CHL2MPPlayerAnimState::DebugShowAnimState(int iStartLine) -{ - Vector vOuterVel; - GetOuterAbsVelocity(vOuterVel); - - int iLine = iStartLine; - AnimStatePrintf(iLine++, "main: %s(%d), cycle: %.2f cyclerate: %.2f playbackrate: %.2f\n", - GetSequenceName(m_pHL2MPPlayer->GetModelPtr(), m_pHL2MPPlayer->GetSequence()), - m_pHL2MPPlayer->GetSequence(), - m_pHL2MPPlayer->GetCycle(), - m_pHL2MPPlayer->GetSequenceCycleRate(m_pHL2MPPlayer->GetModelPtr(), m_pHL2MPPlayer->GetSequence()), - m_pHL2MPPlayer->GetPlaybackRate() - ); - - for (int i = 0; i < m_pHL2MPPlayer->GetNumAnimOverlays() - 1; i++) - { - CAnimationLayer* pLayer = m_pHL2MPPlayer->GetAnimOverlay(AIMSEQUENCE_LAYER + i); -#ifdef CLIENT_DLL - AnimStatePrintf(iLine++, "%s(%d), weight: %.2f, cycle: %.2f, order (%d), aim (%d)", - !pLayer->IsActive() ? "-- " : (pLayer->m_nSequence == 0 ? "-- " : GetSequenceName(m_pHL2MPPlayer->GetModelPtr(), pLayer->m_nSequence)), - !pLayer->IsActive() ? 0 : (int)pLayer->m_nSequence, - !pLayer->IsActive() ? 0 : (float)pLayer->m_flWeight, - !pLayer->IsActive() ? 0 : (float)pLayer->m_flCycle, - !pLayer->IsActive() ? 0 : (int)pLayer->m_nOrder, - i - ); -#else - AnimStatePrintf(iLine++, "%s(%d), flags (%d), weight: %.2f, cycle: %.2f, order (%d), aim (%d)", - !pLayer->IsActive() ? "-- " : (pLayer->m_nSequence == 0 ? "-- " : GetSequenceName(m_pHL2MPPlayer->GetModelPtr(), pLayer->m_nSequence)), - !pLayer->IsActive() ? 0 : (int)pLayer->m_nSequence, - !pLayer->IsActive() ? 0 : (int)pLayer->m_fFlags,// Doesn't exist on client - !pLayer->IsActive() ? 0 : (float)pLayer->m_flWeight, - !pLayer->IsActive() ? 0 : (float)pLayer->m_flCycle, - !pLayer->IsActive() ? 0 : (int)pLayer->m_nOrder, - i - ); -#endif - } - - AnimStatePrintf(iLine++, "vel: %.2f, time: %.2f, max: %.2f, animspeed: %.2f", - vOuterVel.Length2D(), gpGlobals->curtime, GetInterpolatedGroundSpeed(), m_pHL2MPPlayer->GetSequenceGroundSpeed(m_pHL2MPPlayer->GetSequence())); - - { - AnimStatePrintf(iLine++, "ent yaw: %.2f, body_yaw: %.2f, body_pitch: %.2f, move_x: %.2f, move_y: %.2f", - m_angRender[YAW], g_flLastBodyYaw, g_flLastBodyPitch, m_DebugAnimData.m_vecMoveYaw.x, m_DebugAnimData.m_vecMoveYaw.y); - } - - // Draw a red triangle on the ground for the eye yaw. - float flBaseSize = 10; - float flHeight = 80; - Vector vBasePos = m_pHL2MPPlayer->GetAbsOrigin() + Vector(0, 0, 3); - QAngle angles(0, 0, 0); - angles[YAW] = m_flEyeYaw; - Vector vForward, vRight, vUp; - AngleVectors(angles, &vForward, &vRight, &vUp); - debugoverlay->AddTriangleOverlay(vBasePos + vRight * flBaseSize / 2, vBasePos - vRight * flBaseSize / 2, vBasePos + vForward * flHeight, 255, 0, 0, 255, false, 0.01); - - // Draw a blue triangle on the ground for the body yaw. - angles[YAW] = m_angRender[YAW]; - AngleVectors(angles, &vForward, &vRight, &vUp); - debugoverlay->AddTriangleOverlay(vBasePos + vRight * flBaseSize / 2, vBasePos - vRight * flBaseSize / 2, vBasePos + vForward * flHeight, 0, 0, 255, 255, false, 0.01); - -} - -// ----------------------------------------------------------------------------- -void CHL2MPPlayerAnimState::DebugShowAnimStateFull(int iStartLine) -{ - AnimStateLog("----------------- frame %d -----------------\n", gpGlobals->framecount); - - DebugShowAnimState(iStartLine); - - AnimStateLog("--------------------------------------------\n\n"); -} diff --git a/src/game/shared/hl2mp/hl2mp_playeranimstate.h b/src/game/shared/hl2mp/hl2mp_playeranimstate.h deleted file mode 100644 index 9a05125539..0000000000 --- a/src/game/shared/hl2mp/hl2mp_playeranimstate.h +++ /dev/null @@ -1,80 +0,0 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -#ifndef HL2MP_PLAYERANIMSTATE_H -#define HL2MP_PLAYERANIMSTATE_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "convar.h" -#include "multiplayer_animstate.h" - -#if defined( CLIENT_DLL ) -class C_HL2MP_Player; -#define CHL2MP_Player C_HL2MP_Player -#else -class CHL2MP_Player; -#endif - -// ------------------------------------------------------------------------------------------------ // -// CPlayerAnimState declaration. -// ------------------------------------------------------------------------------------------------ // -class CHL2MPPlayerAnimState : public CMultiPlayerAnimState -{ -public: - - DECLARE_CLASS( CHL2MPPlayerAnimState, CMultiPlayerAnimState ); - - CHL2MPPlayerAnimState(); - CHL2MPPlayerAnimState( CBasePlayer *pPlayer, MultiPlayerMovementData_t &movementData ); - ~CHL2MPPlayerAnimState(); - - void InitHL2MPAnimState( CHL2MP_Player *pPlayer ); - CHL2MP_Player *GetHL2MPPlayer( void ) { return m_pHL2MPPlayer; } - - virtual void ClearAnimationState(); - int CalcSequenceIndex(const char* pBaseName, ...); - virtual Activity TranslateActivity( Activity actDesired ); - virtual void Update( float eyeYaw, float eyePitch ); - - void DoAnimationEvent( PlayerAnimEvent_t event, int nData = 0 ); - - bool HandleMoving( Activity &idealActivity ); - bool HandleJumping( Activity &idealActivity ); - bool HandleDucking( Activity &idealActivity ); - bool HandleSwimming( Activity &idealActivity ); - - virtual float GetCurrentMaxGroundSpeed(); -#ifdef NEO - virtual void ComputeSequences(CStudioHdr* pStudioHdr); -#endif - - void DebugShowAnimStateFull(int iStartLine); - virtual void DebugShowAnimState(int iStartLine); - void AnimStatePrintf(int iLine, PRINTF_FORMAT_STRING const char* pMsg, ...); - void AnimStateLog(PRINTF_FORMAT_STRING const char* pMsg, ...); - -private: - - bool SetupPoseParameters( CStudioHdr *pStudioHdr ); - virtual void EstimateYaw( void ); - virtual void ComputePoseParam_MoveYaw( CStudioHdr *pStudioHdr ); - virtual void ComputePoseParam_AimPitch( CStudioHdr *pStudioHdr ); - virtual void ComputePoseParam_AimYaw( CStudioHdr *pStudioHdr ); - -#ifdef NEO - bool IsLeaning(CStudioHdr *pStudioHdr); -#endif // NEO - - CHL2MP_Player *m_pHL2MPPlayer; -#ifndef NEO - bool m_bInAirWalk; - float m_flHoldDeployedPoseUntilTime; -#endif -}; - -CHL2MPPlayerAnimState *CreateHL2MPPlayerAnimState( CHL2MP_Player *pPlayer ); - - - -#endif // HL2MP_PLAYERANIMSTATE_H diff --git a/src/game/shared/neo/neo_gamerules.cpp b/src/game/shared/neo/neo_gamerules.cpp index 85605812cf..38e7155f74 100644 --- a/src/game/shared/neo/neo_gamerules.cpp +++ b/src/game/shared/neo/neo_gamerules.cpp @@ -569,6 +569,7 @@ static const char *s_NeoPreserveEnts[] = "info_player_defender", "info_player_start", "neo_predicted_viewmodel", + "neo_predicted_viewmodel_muzzleflash", "neo_ghost_retrieval_point", "neo_ghostspawnpoint", "neo_juggernautspawnpoint", diff --git a/src/game/shared/neo/neo_playeranimstate.cpp b/src/game/shared/neo/neo_playeranimstate.cpp new file mode 100644 index 0000000000..a5905ff0e3 --- /dev/null +++ b/src/game/shared/neo/neo_playeranimstate.cpp @@ -0,0 +1,495 @@ +#include "cbase.h" +#include "neo_playeranimstate.h" +#include "datacache/imdlcache.h" +#include "weapon_detpack.h" + +#ifdef CLIENT_DLL +#include "c_neo_player.h" +#else +#include "neo_player.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Upper body sequence names, each followed by the weapon's animation suffix +static constexpr const char *AIM_IDLE_NAME = "idle_upper_"; +static constexpr const char *AIM_CROUCH_IDLE_NAME = "crouch_idle_upper_"; +static constexpr const char *AIM_CROUCH_WALK_NAME = "crouch_walk_upper_"; +static constexpr const char *AIM_WALK_NAME = "walk_upper_"; +static constexpr const char *AIM_RUN_NAME = "run_upper_"; + +static constexpr const char *FIRE_IDLE_NAME = "idle_shoot_"; +static constexpr const char *FIRE_CROUCH_NAME = "crouch_idle_shoot_"; +static constexpr const char *FIRE_CROUCH_WALK_NAME = "crouch_walk_shoot_"; +static constexpr const char *FIRE_WALK_NAME = "walk_shoot_"; +static constexpr const char *FIRE_RUN_NAME = "run_shoot_"; + +// The fire, reload and throw sequences play on the layer above the base's aim layers +static constexpr int GESTURE_LAYER = AIMSEQUENCE_LAYER + NUM_AIMSEQUENCE_LAYERS; +static constexpr int NUM_LAYERS = GESTURE_LAYER + 1; + +// A reload cut short fades its gesture out over this long instead of snapping to the aim pose, +// as the gesture slots of the HL2MP anim state did +static constexpr float GESTURE_FADE_OUT_TIME = 0.2f; + +// The legs play at the rate of the player's speed over the speed the walk, run and crouch walk sequences cover, +// which is constant as in classic NEOTOKYO: two of the original models' forward walk and run animations carry no movement +static constexpr float ANIM_TOPSPEED_WALK = 100.0f; +static constexpr float ANIM_TOPSPEED_RUN = 250.0f; +static constexpr float ANIM_TOPSPEED_RUN_CROUCH = 85.0f; + +// A sprinting player plays the run legs only above this speed: neo's slowest sprint is 185, +// and bots sprint through turns at 100-180 +static constexpr float SPRINT_LEGS_MIN_SPEED = 100.0f; + +// The feet face the eyes when they are this close (degrees) +static constexpr float FEET_FACE_EYES_TOLERANCE = 1.0f; + +// Don't check if a jumping player is on the ground for this long: +// sometimes the client still has the on-ground flag set right when the jump event comes in +static constexpr float JUMP_MIN_TIME = 0.2f; + +// The remote of a thrown detpack fires the remote's sequence only this long after the detpack is ready to trigger +static constexpr float DET_REMOTE_FIRE_DELAY = 0.005f; + +#ifdef CLIENT_DLL +// A jump has landed when the drawn body is this close above the ground (the ground check of player movement), +static constexpr float JUMP_LAND_GROUND_DIST = 2.0f; +// or when the ground flag has been set this long and the trace still finds no ground under the drawn body +static constexpr float JUMP_LAND_MAX_FLAG_LEAD = 0.5f; +#endif + +CNEOPlayerAnimState *CreateNEOPlayerAnimState( CHL2MP_Player *pPlayer ) +{ + MDLCACHE_CRITICAL_SECTION(); + return new CNEOPlayerAnimState( pPlayer ); +} + +CNEOPlayerAnimState::CNEOPlayerAnimState( CHL2MP_Player *pPlayer ) +{ + m_pPlayer = pPlayer; + m_bJumping = false; + m_bFirstJumpFrame = false; + m_flJumpStartTime = 0.0f; +#ifdef CLIENT_DLL + m_flJumpGroundFlagTime = 0.0f; +#endif + m_iGestureSequence = 0; + m_flGestureCycle = 0.0f; + m_flGestureFadeStart = 0.0f; + m_eGestureKind = GESTURE_FIRE; + + CModAnimConfig config; + config.m_flMaxBodyYawDegrees = NEO_ANIMSTATE_MAX_BODY_YAW_DEGREES; + config.m_LegAnimType = LEGANIM_9WAY; + config.m_bUseAimSequences = true; + Init( pPlayer, config ); +} + +void CNEOPlayerAnimState::ClearAnimationState() +{ + m_bJumping = false; + m_iGestureSequence = 0; + BaseClass::ClearAnimationState(); +} + +void CNEOPlayerAnimState::ClearAnimationLayers() +{ + if ( !m_pOuter ) + { + return; + } + + m_pOuter->SetNumAnimOverlays( NUM_LAYERS ); + for ( int i = 0; i < m_pOuter->GetNumAnimOverlays(); i++ ) + { + m_pOuter->GetAnimOverlay( i )->SetOrder( CBaseAnimatingOverlay::MAX_OVERLAYS ); +#ifndef CLIENT_DLL + m_pOuter->GetAnimOverlay( i )->m_fFlags = 0; +#endif + } +} + +void CNEOPlayerAnimState::DoAnimationEvent( PlayerAnimEvent_t event, int nData ) +{ + switch ( event ) + { + case PLAYERANIMEVENT_ATTACK_PRIMARY: + StartGesture( CalcFireSequence(), GESTURE_FIRE ); + break; + + case PLAYERANIMEVENT_RELOAD: + StartGesture( CalcReloadSequence(), GESTURE_RELOAD ); + + // a resumed reload starts the gesture where the reload resumes: nData is that cycle x 1000 + if ( nData > 0 && m_iGestureSequence > 0 ) + { + m_flGestureCycle = nData / 1000.0f; + } + break; + + case PLAYERANIMEVENT_RELOAD_END: + // the reload ended or was cut short: fade its gesture out, if the layer still plays it + if ( m_iGestureSequence > 0 && m_iGestureSequence == CalcReloadSequence() && m_flGestureFadeStart <= 0.0f ) + { + m_flGestureFadeStart = gpGlobals->curtime; + } + break; + + case PLAYERANIMEVENT_JUMP: + m_bJumping = true; + m_bFirstJumpFrame = true; + m_flJumpStartTime = gpGlobals->curtime; + break; + + case PLAYERANIMEVENT_SPAWN: + ClearAnimationState(); + break; + + default: + break; + } +} + +void CNEOPlayerAnimState::ComputeSequences( CStudioHdr *pStudioHdr ) +{ + BaseClass::ComputeSequences( pStudioHdr ); + ComputeGestureSequence( pStudioHdr ); +} + +void CNEOPlayerAnimState::StartGesture( int iSequence, GestureKind_t eKind ) +{ + if ( iSequence <= 0 ) + { + return; + } + + m_iGestureSequence = iSequence; + m_flGestureCycle = 0.0f; + m_flGestureFadeStart = 0.0f; + m_eGestureKind = eKind; + m_hGestureWeapon = m_pPlayer->GetActiveWeapon(); +} + +void CNEOPlayerAnimState::ComputeGestureSequence( CStudioHdr *pStudioHdr ) +{ + if ( m_iGestureSequence <= 0 ) + { + return; + } + + // a weapon switch ends the gesture of the previous weapon: + // a reload fades out as when it ends early, any other gesture stops at once + if ( m_pPlayer->GetActiveWeapon() != m_hGestureWeapon.Get() ) + { + if ( m_eGestureKind != GESTURE_RELOAD ) + { + m_iGestureSequence = 0; + return; + } + + if ( m_flGestureFadeStart <= 0.0f ) + { + m_flGestureFadeStart = gpGlobals->curtime; + } + } + + m_flGestureCycle += m_pOuter->GetSequenceCycleRate( pStudioHdr, m_iGestureSequence ) * gpGlobals->frametime; + if ( m_flGestureCycle >= 1.0f ) + { + m_iGestureSequence = 0; + return; + } + + float flWeight = 1.0f; + if ( m_flGestureFadeStart > 0.0f ) + { + flWeight = 1.0f - ( gpGlobals->curtime - m_flGestureFadeStart ) / GESTURE_FADE_OUT_TIME; + if ( flWeight <= 0.0f ) + { + m_iGestureSequence = 0; + return; + } + } + + CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( GESTURE_LAYER ); + pLayer->m_nSequence = m_iGestureSequence; + pLayer->m_flCycle = m_flGestureCycle; + pLayer->m_flPlaybackRate = 1.0f; + pLayer->m_flWeight = flWeight; + pLayer->m_nOrder = GESTURE_LAYER; +#ifndef CLIENT_DLL + pLayer->m_fFlags |= ANIM_LAYER_ACTIVE; +#endif +} + +Activity CNEOPlayerAnimState::CalcMainActivity() +{ + if ( HandleJumping() ) + { + return ACT_HOP; + } + + if ( m_pPlayer->GetWaterLevel() >= WL_Waist ) + { + return ACT_SWIM; + } + + const float flSpeed = GetOuterXYSpeed(); + if ( m_pPlayer->GetFlags() & FL_DUCKING ) + { + return ( flSpeed > MOVING_MINIMUM_SPEED ) ? ACT_RUN_CROUCH : ACT_CROUCHIDLE; + } + + if ( flSpeed <= MOVING_MINIMUM_SPEED ) + { + return ACT_IDLE; + } + + return ( m_pPlayer->IsSprinting() && flSpeed > SPRINT_LEGS_MIN_SPEED ) ? ACT_RUN : ACT_WALK; +} + +bool CNEOPlayerAnimState::HandleJumping() +{ + if ( !m_bJumping ) + { + return false; + } + + if ( m_bFirstJumpFrame ) + { + m_bFirstJumpFrame = false; + RestartMainSequence(); +#ifdef CLIENT_DLL + m_flJumpGroundFlagTime = 0.0f; +#endif + } + + const bool bInWater = m_pPlayer->GetWaterLevel() >= WL_Waist; + if ( bInWater || ( gpGlobals->curtime - m_flJumpStartTime > JUMP_MIN_TIME && HasLandedFromJump() ) ) + { + m_bJumping = false; + RestartMainSequence(); + } + + return m_bJumping; +} + +//----------------------------------------------------------------------------- +// Purpose: Whether a jumping player has landed. The client draws other players a few tenths of a second behind +// their networked ground flag, so there the jump lasts until the drawn body reaches the ground. +//----------------------------------------------------------------------------- +bool CNEOPlayerAnimState::HasLandedFromJump() +{ + if ( !( m_pPlayer->GetFlags() & FL_ONGROUND ) ) + { +#ifdef CLIENT_DLL + m_flJumpGroundFlagTime = 0.0f; +#endif + return false; + } + +#ifdef CLIENT_DLL + if ( m_flJumpGroundFlagTime <= 0.0f ) + { + m_flJumpGroundFlagTime = gpGlobals->curtime; + } + + if ( gpGlobals->curtime - m_flJumpGroundFlagTime > JUMP_LAND_MAX_FLAG_LEAD ) + { + return true; + } + + const Vector &vecOrigin = m_pPlayer->GetAbsOrigin(); + trace_t tr; + UTIL_TraceHull( vecOrigin, vecOrigin - Vector( 0, 0, JUMP_LAND_GROUND_DIST ), + m_pPlayer->GetPlayerMins(), m_pPlayer->GetPlayerMaxs(), + MASK_PLAYERSOLID, m_pPlayer, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); + + return tr.fraction < 1.0f || tr.startsolid; +#else + return true; +#endif // CLIENT_DLL +} + +float CNEOPlayerAnimState::GetCurrentMaxGroundSpeed() +{ + switch ( m_pOuter->GetSequenceActivity( m_pOuter->GetSequence() ) ) + { + case ACT_IDLE: + case ACT_WALK: + return ANIM_TOPSPEED_WALK; + case ACT_RUN: + return ANIM_TOPSPEED_RUN; + case ACT_RUN_CROUCH: + return ANIM_TOPSPEED_RUN_CROUCH; + default: + return 0.0f; + } +} + +void CNEOPlayerAnimState::ComputePoseParam_BodyYaw() +{ + // a leaning player's feet turn to the eyes, as the base turns a new player's (no turn yet), + const bool bLeaning = IsLeaning(); + if ( bLeaning ) + { + m_flLastTurnTime = 0.0f; + } + + BaseClass::ComputePoseParam_BodyYaw(); + + // and the face-front time counts from the lean or the last frame the feet faced the eyes: + // the base counts from the feet's last turn, so after standing still it squared the feet up on the next turn at once + if ( bLeaning || fabsf( AngleDiff( m_flEyeYaw, m_flCurrentFeetYaw ) ) < FEET_FACE_EYES_TOLERANCE ) + { + m_flLastTurnTime = gpGlobals->curtime; + } +} + +bool CNEOPlayerAnimState::IsLeaning() const +{ + constexpr int LEAN_BONE_CONTROLLER = 0; + // the client reads the lean controller encoded 0-1 (0.5 is upright), the server reads it in degrees +#ifdef CLIENT_DLL + constexpr float BONE_CONTROLLER_VALUE_NOT_LEANING = 0.5f; +#else + constexpr float BONE_CONTROLLER_VALUE_NOT_LEANING = 0.0f; +#endif // CLIENT_DLL + return m_pPlayer->GetBoneController( LEAN_BONE_CONTROLLER ) != BONE_CONTROLLER_VALUE_NOT_LEANING; +} + +//----------------------------------------------------------------------------- +// Purpose: Whether a remote detpack shows its remote: triggered, or thrown and ready to trigger +//----------------------------------------------------------------------------- +static bool IsDetRemoteOut( CBaseCombatWeapon *pWeapon, float flReadyDelay ) +{ + auto pDetpack = static_cast( pWeapon ); + return pDetpack->m_bRemoteHasBeenTriggered || + ( pDetpack->m_bThisDetpackHasBeenThrown && gpGlobals->curtime > pDetpack->m_flNextPrimaryAttack + flReadyDelay ); +} + +static bool IsRemoteDet( CBaseCombatWeapon *pWeapon ) +{ + return !Q_strcmp( pWeapon->GetClassname(), "weapon_remotedet" ); +} + +//----------------------------------------------------------------------------- +// Purpose: The weapon's animation name suffix, with the substitutions for the classes that lack the weapon's animations +//----------------------------------------------------------------------------- +const char *CNEOPlayerAnimState::GetAimSuffix() const +{ + auto pWeapon = m_pPlayer->GetActiveWeapon(); + if ( !pWeapon ) + { + return nullptr; + } + + const int iClass = ToNEOPlayer( m_pPlayer )->m_iNeoClass; + if ( IsRemoteDet( pWeapon ) ) + { + // only recon has the detpack animations, the other classes hold it like a grenade + if ( iClass != NEO_CLASS_RECON ) + { + return "Grenade"; + } + + return IsDetRemoteOut( pWeapon, 0.0f ) ? "detremote" : "detpack"; + } + + const char *pSuffix = pWeapon->GetWpnData().szAnimationPrefix; + if ( iClass != NEO_CLASS_VIP && !Q_strcmp( pSuffix, "smac" ) ) + { + return "srm"; + } + + if ( iClass == NEO_CLASS_VIP && !Q_strcmp( pSuffix, "ZR68C" ) ) + { + return "m4"; + } + + return pSuffix; +} + +int CNEOPlayerAnimState::CalcAimLayerSequence( float *flCycle, float *flAimSequenceWeight, bool bForceIdle ) +{ + const char *pSuffix = GetAimSuffix(); + if ( !pSuffix ) + { + return 0; + } + + const Activity act = GetCurrentMainSequenceActivity(); + if ( bForceIdle ) + { + return CalcSequenceIndex( "%s%s", ( act == ACT_CROUCHIDLE ) ? AIM_CROUCH_IDLE_NAME : AIM_IDLE_NAME, pSuffix ); + } + + switch ( act ) + { + case ACT_RUN: + return CalcSequenceIndex( "%s%s", AIM_RUN_NAME, pSuffix ); + case ACT_WALK: + return CalcSequenceIndex( "%s%s", AIM_WALK_NAME, pSuffix ); + case ACT_CROUCHIDLE: + return CalcSequenceIndex( "%s%s", AIM_CROUCH_IDLE_NAME, pSuffix ); + case ACT_RUN_CROUCH: + return CalcSequenceIndex( "%s%s", AIM_CROUCH_WALK_NAME, pSuffix ); + default: + return CalcSequenceIndex( "%s%s", AIM_IDLE_NAME, pSuffix ); + } +} + +int CNEOPlayerAnimState::CalcFireSequence() +{ + const char *pSuffix = GetAimSuffix(); + if ( !pSuffix ) + { + return 0; + } + + auto pWeapon = m_pPlayer->GetActiveWeapon(); + if ( IsRemoteDet( pWeapon ) ) + { + // a class without the detpack animations throws it like a grenade and does not swing the remote + const bool bRemote = IsDetRemoteOut( pWeapon, DET_REMOTE_FIRE_DELAY ); + if ( ToNEOPlayer( m_pPlayer )->m_iNeoClass == NEO_CLASS_RECON ) + { + pSuffix = bRemote ? "detremote" : "detpack"; + } + else + { + pSuffix = bRemote ? "SRM" : "Gren1"; + } + } + else if ( !Q_strcmp( pSuffix, "Grenade" ) ) + { + pSuffix = "Gren1"; + } + + switch ( GetCurrentMainSequenceActivity() ) + { + case ACT_RUN: + return CalcSequenceIndex( "%s%s", FIRE_RUN_NAME, pSuffix ); + case ACT_WALK: + return CalcSequenceIndex( "%s%s", FIRE_WALK_NAME, pSuffix ); + case ACT_CROUCHIDLE: + return CalcSequenceIndex( "%s%s", FIRE_CROUCH_NAME, pSuffix ); + case ACT_RUN_CROUCH: + return CalcSequenceIndex( "%s%s", FIRE_CROUCH_WALK_NAME, pSuffix ); + default: + return CalcSequenceIndex( "%s%s", FIRE_IDLE_NAME, pSuffix ); + } +} + +int CNEOPlayerAnimState::CalcReloadSequence() +{ + auto pWeapon = m_pPlayer->GetActiveWeapon(); + if ( !pWeapon ) + { + return 0; + } + + return CalcSequenceIndex( "Reload_%s", pWeapon->GetWpnData().szAnimationPrefix ); +} diff --git a/src/game/shared/neo/neo_playeranimstate.h b/src/game/shared/neo/neo_playeranimstate.h new file mode 100644 index 0000000000..978950197e --- /dev/null +++ b/src/game/shared/neo/neo_playeranimstate.h @@ -0,0 +1,85 @@ +#ifndef NEO_PLAYERANIMSTATE_H +#define NEO_PLAYERANIMSTATE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "base_playeranimstate.h" + +#ifdef CLIENT_DLL +class C_HL2MP_Player; +#define CHL2MP_Player C_HL2MP_Player +#else +class CHL2MP_Player; +#endif + +enum PlayerAnimEvent_t +{ + PLAYERANIMEVENT_ATTACK_PRIMARY, + PLAYERANIMEVENT_RELOAD, + PLAYERANIMEVENT_RELOAD_END, + PLAYERANIMEVENT_JUMP, + PLAYERANIMEVENT_SPAWN, + + PLAYERANIMEVENT_COUNT +}; + +// The player animation of classic NEOTOKYO: the 2006 SDK base (9-way legs, upper body aim layers blended by speed) +// plus one gesture layer for firing, reloading and throwing. +class CNEOPlayerAnimState : public CBasePlayerAnimState +{ +public: + DECLARE_CLASS( CNEOPlayerAnimState, CBasePlayerAnimState ); + + CNEOPlayerAnimState( CHL2MP_Player *pPlayer ); + + void DoAnimationEvent( PlayerAnimEvent_t event, int nData = 0 ); + + virtual void ClearAnimationState() override; + virtual void ClearAnimationLayers() override; + virtual void ComputeSequences( CStudioHdr *pStudioHdr ) override; + virtual Activity CalcMainActivity() override; + virtual int CalcAimLayerSequence( float *flCycle, float *flAimSequenceWeight, bool bForceIdle ) override; + virtual float GetCurrentMaxGroundSpeed() override; + +protected: + virtual void ComputePoseParam_BodyYaw() override; + +private: + const char *GetAimSuffix() const; + int CalcFireSequence(); + int CalcReloadSequence(); + enum GestureKind_t + { + GESTURE_FIRE, + GESTURE_RELOAD, + }; + void StartGesture( int iSequence, GestureKind_t eKind ); + void ComputeGestureSequence( CStudioHdr *pStudioHdr ); + + bool HandleJumping(); + bool HasLandedFromJump(); + bool IsLeaning() const; + + CHL2MP_Player *m_pPlayer; + + // the jump pose plays from the jump event until the player lands + bool m_bJumping; + bool m_bFirstJumpFrame; + float m_flJumpStartTime; +#ifdef CLIENT_DLL + float m_flJumpGroundFlagTime; // when the client first saw the ground flag during a jump, 0 while in the air +#endif + + // fire, reload and throw sequences share one layer above the aim layers, and a new one restarts it + int m_iGestureSequence; + float m_flGestureCycle; + EHANDLE m_hGestureWeapon; + // a reload cut short or switched away fades its gesture out from this time, 0 while it plays in full + float m_flGestureFadeStart; + GestureKind_t m_eGestureKind; +}; + +CNEOPlayerAnimState *CreateNEOPlayerAnimState( CHL2MP_Player *pPlayer ); + +#endif // NEO_PLAYERANIMSTATE_H diff --git a/src/game/shared/neo/weapons/neo_weapon_parse.cpp b/src/game/shared/neo/weapons/neo_weapon_parse.cpp index 5a7dbd3214..b9073bab3a 100644 --- a/src/game/shared/neo/weapons/neo_weapon_parse.cpp +++ b/src/game/shared/neo/weapons/neo_weapon_parse.cpp @@ -24,6 +24,11 @@ CNEOWeaponInfo::CNEOWeaponInfo() m_flPenetration = 0.f; m_bDropOnDeath = true; iAimFOV = 0; + m_flReloadReadyBeforeEnd = 0.0f; + m_flChamberAfterShot = 0.0f; + m_flChamberAfterSlugShot = 0.0f; + m_bChamberedRound = false; + m_iNumReloadTables = 0; m_flVMFov = m_flVMAimFov = 0.f; m_vecVMPosOffset = m_vecVMAimPosOffset = vec3_origin; @@ -31,6 +36,70 @@ CNEOWeaponInfo::CNEOWeaponInfo() } +static ReloadPhase_t ReloadPhaseFromName(const char *pszName) +{ + static constexpr const char *PHASE_NAMES[RELOAD_PHASE_COUNT] = { "", "drop", "seat", "slap", "bolt" }; + for (int i = RELOAD_PHASE_DROPPED; i < RELOAD_PHASE_COUNT; ++i) + { + if (V_stricmp(pszName, PHASE_NAMES[i]) == 0) + { + return static_cast(i); + } + } + return RELOAD_PHASE_NONE; +} + +// "ReloadPhases" { "ChamberedRound" "1" "" { "" { "credit" "s" "sound" "name" "at" "s" } ... } ... } +void CNEOWeaponInfo::ParseReloadPhases(KeyValues *pBlock) +{ + m_iNumReloadTables = 0; + if (!pBlock) + { + return; + } + + m_bChamberedRound = pBlock->GetBool("ChamberedRound", true); + for (KeyValues *pSeq = pBlock->GetFirstTrueSubKey(); pSeq && m_iNumReloadTables < RELOAD_PHASE_TABLES_MAX; pSeq = pSeq->GetNextTrueSubKey()) + { + ReloadPhaseTable_t &table = m_reloadTables[m_iNumReloadTables++]; + V_strncpy(table.szSequence, pSeq->GetName(), sizeof(table.szSequence)); + table.flFireAt = pSeq->GetFloat("fire_at"); + table.iNumRows = 0; + + for (KeyValues *pRow = pSeq->GetFirstTrueSubKey(); pRow && table.iNumRows < RELOAD_PHASE_ROWS_MAX; pRow = pRow->GetNextTrueSubKey()) + { + const ReloadPhase_t phase = ReloadPhaseFromName(pRow->GetName()); + if (phase == RELOAD_PHASE_NONE) + { + Warning("%s: unknown reload phase \"%s\" in \"%s\"\n", szClassName, pRow->GetName(), table.szSequence); + continue; + } + + ReloadPhaseRow_t &row = table.rows[table.iNumRows++]; + row.phase = phase; + row.flCredit = pRow->GetFloat("credit"); + static constexpr const char *SOUND_KEYS[RELOAD_SOUNDS_PER_ROW][2] = { { "sound", "at" }, { "sound2", "at2" } }; + for (int j = 0; j < RELOAD_SOUNDS_PER_ROW; ++j) + { + row.sounds[j].flAt = pRow->GetFloat(SOUND_KEYS[j][1]); + V_strncpy(row.sounds[j].szName, pRow->GetString(SOUND_KEYS[j][0]), sizeof(row.sounds[j].szName)); + } + } + } +} + +int CNEOWeaponInfo::FindReloadTable(const char *pszSequence) const +{ + for (int i = 0; i < m_iNumReloadTables; ++i) + { + if (V_stricmp(m_reloadTables[i].szSequence, pszSequence) == 0) + { + return i; + } + } + return -1; +} + void CNEOWeaponInfo::Parse( KeyValues *pKeyValuesData, const char *szWeaponName ) { BaseClass::Parse( pKeyValuesData, szWeaponName ); @@ -38,6 +107,10 @@ void CNEOWeaponInfo::Parse( KeyValues *pKeyValuesData, const char *szWeaponName m_iPlayerDamage = pKeyValuesData->GetInt( "Damage", 42 ); // Douglas Adams 1952 - 2001 m_iBullets = pKeyValuesData->GetInt( "Bullets", 1 ); m_flCycleTime = pKeyValuesData->GetFloat( "CycleTime", 0.15 ); + m_flReloadReadyBeforeEnd = pKeyValuesData->GetFloat( "ReloadReadyBeforeEnd", 0.0f ); + m_flChamberAfterShot = pKeyValuesData->GetFloat("ChamberAfterShot", 0.0f); + m_flChamberAfterSlugShot = pKeyValuesData->GetFloat("ChamberAfterSlugShot", 0.0f); + ParseReloadPhases(pKeyValuesData->FindKey("ReloadPhases")); const char *notFoundStr = "notfound"; Q_strncpy(szViewModel2, pKeyValuesData->GetString("team2viewmodel", notFoundStr), MAX_WEAPON_STRING); diff --git a/src/game/shared/neo/weapons/neo_weapon_parse.h b/src/game/shared/neo/weapons/neo_weapon_parse.h index c36e610af5..a26e8142cc 100644 --- a/src/game/shared/neo/weapons/neo_weapon_parse.h +++ b/src/game/shared/neo/weapons/neo_weapon_parse.h @@ -12,6 +12,52 @@ #include "hl2mp_weapon_parse.h" +// A reload is a linear sequence of phases; a reload sequence credits each phase at a point of its animation +enum ReloadPhase_t +{ + RELOAD_PHASE_NONE = 0, // no reload in progress, or none pending + RELOAD_PHASE_DROPPED, // the old magazine is out + RELOAD_PHASE_SEATED, // the fresh magazine is in + RELOAD_PHASE_SLAPPED, // the magazine was tapped or slapped home + RELOAD_PHASE_BOLTED, // the bolt or charging handle chambered a round + + RELOAD_PHASE_COUNT +}; + +static constexpr int RELOAD_PHASE_TABLES_MAX = 12; +// bits to network a table index, signed so that -1 (no table) fits +static constexpr int RELOAD_PHASE_TABLE_BITS = 5; +static_assert((1 << (RELOAD_PHASE_TABLE_BITS - 1)) >= RELOAD_PHASE_TABLES_MAX, "RELOAD_PHASE_TABLE_BITS cannot hold every table index"); +static constexpr int RELOAD_PHASE_ROWS_MAX = RELOAD_PHASE_COUNT - 1; +static constexpr int RELOAD_SOUND_NAME_MAX = 64; +static constexpr int RELOAD_SEQUENCE_NAME_MAX = 32; +// a phase may need two sound pieces timed apart, such as the M41's tap rattle and tap clicks +static constexpr int RELOAD_SOUNDS_PER_ROW = 2; + +// A sound piece of a phase +struct ReloadSoundPiece_t +{ + float flAt; // when the piece's first transient plays, seconds from the sequence start + char szName[RELOAD_SOUND_NAME_MAX]; +}; + +// One phase of a reload sequence: when it is credited, and the sound pieces that go with it +struct ReloadPhaseRow_t +{ + ReloadPhase_t phase; + float flCredit; // seconds from the sequence start + ReloadSoundPiece_t sounds[RELOAD_SOUNDS_PER_ROW]; +}; + +// The phases one reload sequence carries, in order +struct ReloadPhaseTable_t +{ + char szSequence[RELOAD_SEQUENCE_NAME_MAX]; + float flFireAt; // a chambered-round shot's sequence: when the bullet fires, seconds from its start; 0 otherwise + int iNumRows; + ReloadPhaseRow_t rows[RELOAD_PHASE_ROWS_MAX]; +}; + //-------------------------------------------------------------------------------------------------------- class CNEOWeaponInfo : public CHL2MPSWeaponInfo { @@ -24,6 +70,15 @@ class CNEOWeaponInfo : public CHL2MPSWeaponInfo int m_iBullets; float m_flCycleTime; + float m_flReloadReadyBeforeEnd; + // a pump gun: when the fire animation's pump is home with the next round, seconds after a shell / slug shot + float m_flChamberAfterShot; + float m_flChamberAfterSlugShot; + bool m_bChamberedRound; // a partial reload keeps the round in the chamber + int m_iNumReloadTables; // 0: the weapon reloads without phases + ReloadPhaseTable_t m_reloadTables[RELOAD_PHASE_TABLES_MAX]; + + int FindReloadTable(const char *pszSequence) const; char szViewModel2[MAX_WEAPON_STRING]; // Team 2 (NSF) wep vm char szBulletCharacter[MAX_BULLET_CHARACTER];// character used to display ammunition in current clip char szDeathIcon[MAX_BULLET_CHARACTER]; @@ -38,6 +93,9 @@ class CNEOWeaponInfo : public CHL2MPSWeaponInfo float m_flVMAimFov; Vector m_vecVMAimPosOffset; QAngle m_angVMAimAngOffset; + +private: + void ParseReloadPhases(::KeyValues *pBlock); }; diff --git a/src/game/shared/neo/weapons/weapon_aa13.cpp b/src/game/shared/neo/weapons/weapon_aa13.cpp index f1e8e1cc72..d223efc5df 100644 --- a/src/game/shared/neo/weapons/weapon_aa13.cpp +++ b/src/game/shared/neo/weapons/weapon_aa13.cpp @@ -93,12 +93,19 @@ void CWeaponAA13::PrimaryAttack(void) #endif pPlayer->DoMuzzleFlash(); - SendWeaponAnim(ACT_VM_PRIMARYATTACK); + // a chambered-round shot plays inside its reload sequence + if (!m_bInReload) + { + SendWeaponAnim(ACT_VM_PRIMARYATTACK); + } m_iClip1 -= 1; - // player "shoot" animation - pPlayer->DoAnimationEvent(PLAYERANIMEVENT_ATTACK_PRIMARY); + // player "shoot" animation (a chambered-round shot keeps the reload gesture) + if (!m_bInReload) + { + pPlayer->DoAnimationEvent(PLAYERANIMEVENT_ATTACK_PRIMARY); + } Vector vecSrc = pPlayer->Weapon_ShootPosition(); Vector vecAiming = pPlayer->GetAutoaimVector(AUTOAIM_10DEGREES); diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp index 4d75d319dc..cfd51ecb0b 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp @@ -41,6 +41,14 @@ BEGIN_NETWORK_TABLE( CNEOBaseCombatWeapon, DT_NEOBaseCombatWeapon ) RecvPropFloat(RECVINFO(m_flAccuracyPenalty)), RecvPropInt(RECVINFO(m_nNumShotsFired)), RecvPropBool(RECVINFO(m_bTriggerReset)), + RecvPropTime(RECVINFO(m_flReloadReadyTime)), + RecvPropInt(RECVINFO(m_nReloadPhase)), + RecvPropBool(RECVINFO(m_bChamberEmpty)), + RecvPropBool(RECVINFO(m_bReloadDry)), + RecvPropInt(RECVINFO(m_nReloadTable)), + RecvPropTime(RECVINFO(m_flReloadStart)), + RecvPropInt(RECVINFO(m_nReloadSoundsDone)), + RecvPropBool(RECVINFO(m_bChamberShotPending)), RecvPropInt(RECVINFO(m_spawnflags)), #else SendPropTime(SENDINFO(m_flSoonestAttack)), @@ -48,6 +56,14 @@ BEGIN_NETWORK_TABLE( CNEOBaseCombatWeapon, DT_NEOBaseCombatWeapon ) SendPropFloat(SENDINFO(m_flAccuracyPenalty)), SendPropInt(SENDINFO(m_nNumShotsFired)), SendPropBool(SENDINFO(m_bTriggerReset)), + SendPropTime(SENDINFO(m_flReloadReadyTime)), + SendPropInt(SENDINFO(m_nReloadPhase), 3, SPROP_UNSIGNED), + SendPropBool(SENDINFO(m_bChamberEmpty)), + SendPropBool(SENDINFO(m_bReloadDry)), + SendPropInt(SENDINFO(m_nReloadTable), RELOAD_PHASE_TABLE_BITS), + SendPropTime(SENDINFO(m_flReloadStart)), + SendPropInt(SENDINFO(m_nReloadSoundsDone), RELOAD_PHASE_ROWS_MAX * RELOAD_SOUNDS_PER_ROW, SPROP_UNSIGNED), + SendPropBool(SENDINFO(m_bChamberShotPending)), SendPropInt(SENDINFO(m_spawnflags)), SendPropExclude("DT_BaseAnimating", "m_nSequence"), #endif @@ -60,6 +76,14 @@ BEGIN_PREDICTION_DATA(CNEOBaseCombatWeapon) DEFINE_PRED_FIELD(m_flAccuracyPenalty, FIELD_FLOAT, FTYPEDESC_INSENDTABLE), DEFINE_PRED_FIELD(m_nNumShotsFired, FIELD_INTEGER, FTYPEDESC_INSENDTABLE), DEFINE_PRED_FIELD(m_bTriggerReset, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_flReloadReadyTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_nReloadPhase, FIELD_INTEGER, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_bChamberEmpty, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_bReloadDry, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_nReloadTable, FIELD_INTEGER, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_flReloadStart, FIELD_FLOAT, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_nReloadSoundsDone, FIELD_INTEGER, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_bChamberShotPending, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE), END_PREDICTION_DATA() #endif @@ -72,6 +96,14 @@ BEGIN_DATADESC( CNEOBaseCombatWeapon ) DEFINE_FIELD(m_flAccuracyPenalty, FIELD_FLOAT), DEFINE_FIELD(m_nNumShotsFired, FIELD_INTEGER), DEFINE_FIELD(m_bTriggerReset, FIELD_BOOLEAN), + DEFINE_FIELD(m_flReloadReadyTime, FIELD_TIME), + DEFINE_FIELD(m_nReloadPhase, FIELD_INTEGER), + DEFINE_FIELD(m_bChamberEmpty, FIELD_BOOLEAN), + DEFINE_FIELD(m_bReloadDry, FIELD_BOOLEAN), + DEFINE_FIELD(m_nReloadTable, FIELD_INTEGER), + DEFINE_FIELD(m_flReloadStart, FIELD_TIME), + DEFINE_FIELD(m_nReloadSoundsDone, FIELD_INTEGER), + DEFINE_FIELD(m_bChamberShotPending, FIELD_BOOLEAN), END_DATADESC() #endif @@ -229,6 +261,13 @@ static const WeaponHandlingInfo_t handlingTable[] = { CNEOBaseCombatWeapon::CNEOBaseCombatWeapon( void ) { m_bTriggerReset = true; + m_nReloadPhase = RELOAD_PHASE_NONE; + m_bChamberEmpty = false; + m_bReloadDry = false; + m_nReloadTable = -1; + m_flReloadStart = 0.0f; + m_nReloadSoundsDone = 0; + m_bChamberShotPending = false; } const CNEOWeaponInfo &CNEOBaseCombatWeapon::GetNEOWpnData() const @@ -414,9 +453,42 @@ void CNEOBaseCombatWeapon::Equip(CBaseCombatCharacter* pOwner) } +// The pistols whose slide locks back on the last shot, until a reload releases it +static constexpr NEO_WEP_BITS_UNDERLYING_TYPE NEO_WEP_SLIDE_LOCK = NEO_WEP_MILSO | NEO_WEP_TACHI; + bool CNEOBaseCombatWeapon::Reload( void ) { - return BaseClass::Reload(); + if (HasReloadPhases()) + { + return ReloadWithPhases(); + } + + // an empty weapon reloads with its slide held back until the release, where the model has that reload + bool bReloading; + if (UsesClipsForAmmo1() && m_iClip1 <= 0 && SelectWeightedSequence(ACT_VM_RELOAD_EMPTY) != ACTIVITY_NOT_AVAILABLE) + { + bReloading = DefaultReload(GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD_EMPTY); + // others see the reload, as for a reload with rounds left + if (bReloading) + { + ToHL2MPPlayer(GetOwner())->DoAnimationEvent(PLAYERANIMEVENT_RELOAD); + } + } + else + { + bReloading = BaseClass::Reload(); + } + + if (!bReloading) + { + return false; + } + + // The magazine loads at the gun's last reload click, which the script gives as a time before the reload animation ends; + // the animation and the wait to fire still run to the end + m_flReloadReadyTime = Max(gpGlobals->curtime, m_flNextPrimaryAttack - GetNEOWpnData().m_flReloadReadyBeforeEnd); + + return true; #if(0) CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); @@ -446,12 +518,666 @@ bool CNEOBaseCombatWeapon::Reload( void ) #endif } +// Each reload sound piece's file starts this long before its first transient +static constexpr float RELOAD_SOUND_LEAD = 0.02f; + +// The shipped reload sequence, the dry reload with every phase +static constexpr const char *VM_SEQ_RELOAD = "reload"; +// A pistol's dry reload (the Milso ships it, the Tachi's is built) +static constexpr const char *VM_SEQ_RELOAD_EMPTY = "reloadempty"; + +// The reload sequence for the phase credited last, dry or partial; nullptr: nothing left to play +// (sequences other than the shipped reload carry no activity, so they are picked by name) +static const char *ReloadSequenceName(int iPhaseDone, bool bDry, bool bFromDraw) +{ + switch (iPhaseDone) + { + case RELOAD_PHASE_NONE: + // a pistol's dry reload ends with the slide release; magazine guns have no reloadempty and play the reload + return bDry ? VM_SEQ_RELOAD_EMPTY : "reload_partial"; + case RELOAD_PHASE_DROPPED: + // after a switch the magazine is out: the draw itself brings the fresh one up out of view + if (bFromDraw) + { + return bDry ? "draw_resume_drop_dry" : "draw_resume_drop_partial"; + } + return bDry ? "reload_from_drop_dry" : "reload_from_drop_partial"; + case RELOAD_PHASE_SEATED: + return bDry ? "reload_from_seat_dry" : "reload_from_seat_partial"; + case RELOAD_PHASE_SLAPPED: + return bDry ? "reload_from_slap_dry" : nullptr; + default: + return nullptr; + } +} + +bool CNEOBaseCombatWeapon::HasReloadPhases() const +{ + return GetNEOWpnData().m_iNumReloadTables > 0; +} + +// A reload is dry when it has to chamber a round with the bolt +bool CNEOBaseCombatWeapon::IsReloadDry() const +{ + return !GetNEOWpnData().m_bChamberedRound || m_bChamberEmpty; +} + +// The phase that completes a reload: the bolt on a dry one, the last magazine phase otherwise +ReloadPhase_t CNEOBaseCombatWeapon::LastReloadPhase(bool bDry) const +{ + // the full reload of that kind lists every phase it needs (a pistol's slide release is only in reloadempty) + const CNEOWeaponInfo &info = GetNEOWpnData(); + int iTable = bDry ? info.FindReloadTable(VM_SEQ_RELOAD_EMPTY) : -1; + if (iTable < 0) + { + iTable = info.FindReloadTable(VM_SEQ_RELOAD); + } + if (iTable < 0) + { + return RELOAD_PHASE_NONE; + } + + ReloadPhase_t last = RELOAD_PHASE_NONE; + const ReloadPhaseTable_t &table = info.m_reloadTables[iTable]; + for (int i = 0; i < table.iNumRows; ++i) + { + if (table.rows[i].phase == RELOAD_PHASE_BOLTED && !bDry) + { + continue; + } + last = Max(last, table.rows[i].phase); + } + return last; +} + +// An interrupted reload has phases credited and phases left to do +bool CNEOBaseCombatWeapon::IsReloadPending() const +{ + return m_nReloadPhase != RELOAD_PHASE_NONE && m_nReloadPhase < LastReloadPhase(IsReloadDry()); +} + +// Start, or resume after a switch, a reload whose phases the weapon script lists +bool CNEOBaseCombatWeapon::ReloadWithPhases() +{ + CBaseCombatCharacter *pOwner = GetOwner(); + if (!pOwner || m_bInReload || pOwner->GetNextAttack() > gpGlobals->curtime) + { + return false; + } + + const CNEOWeaponInfo &info = GetNEOWpnData(); + if (!IsReloadPending()) + { + m_nReloadPhase = RELOAD_PHASE_NONE; + if (m_iPrimaryAmmoCount <= 0) + { + return false; + } + + if (m_iClip1 == 0) + { + CBasePlayer *pPlayer = ToBasePlayer(pOwner); + if (pPlayer && !(pPlayer->m_nButtons & IN_RELOAD) && !ClientWantsAutoReload(pOwner)) + { + return false; + } + m_bChamberEmpty = info.m_bChamberedRound; + } + + // a full magazine with a round in the chamber has nothing to gain + const int iFull = GetMaxClip1() + (IsReloadDry() ? 0 : 1); + if (m_iClip1 >= iFull) + { + return false; + } + } + + m_bReloadDry = IsReloadDry(); + int iSequence = -1; + const int iTable = SelectReloadTable(false, &iSequence); + if (iTable < 0) + { + Warning("%s: ReloadPhases has no \"%s\" sequence\n", GetClassname(), VM_SEQ_RELOAD); + return false; + } + + StartReloadSequence(iTable, iSequence, ResumeGestureCycle()); + return true; +} + +// A trigger press can end a reload; turn this off and the trigger waits for the reload to end, as before +static constexpr bool RELOAD_FIRE_INTERRUPTS = true; +// A trigger press while the magazine is out fires the chambered round with the one-handed fire_chamber_onehand +// sequence (a model without it refuses the shot) +ConVar sv_neo_reload_chamber_shot("sv_neo_reload_chamber_shot", "1", FCVAR_REPLICATED, + "A trigger press while the magazine is out of a reloading gun fires the chambered round"); +static constexpr const char *VM_SEQ_FIRE_CHAMBER_ONEHAND = "fire_chamber_onehand"; +// The first shot after a cancelled reload, the shipped fire blended in from the reload pose instead of snapped +static constexpr const char *VM_SEQ_FIRE_BLEND = "fire_blend"; + +// The one way a trigger press acts on a reload in progress (the owner is busy, so it reaches only ItemBusyFrame): +// before the magazine drops, or once the fresh one is seated (and slapped, on a gun with a slap), the reload ends +// and the gun fires; +// with the magazine out, the chambered round fires one-handed and the reload goes on dry; a dry reload ignores it +bool CNEOBaseCombatWeapon::TryFireDuringReload() +{ + if (!RELOAD_FIRE_INTERRUPTS || !m_bInReload || m_nReloadTable < 0) + { + return false; + } + + CBasePlayer *pPlayer = ToBasePlayer(GetOwner()); + if (!pPlayer || !(pPlayer->m_afButtonPressed & IN_ATTACK) || ShootingIsPrevented()) + { + return false; + } + + if (m_nReloadPhase == RELOAD_PHASE_DROPPED) + { + return StartChamberShot(); + } + + // once the magazine is out, the reload ends only when its last phase is done (a slap gun's slap, else the seat), + // so a cancel skips only what is already complete + if (m_nReloadPhase != RELOAD_PHASE_NONE && m_nReloadPhase < LastReloadPhase(m_bReloadDry)) + { + return false; + } + + if (!HasRoundToFire()) + { + return false; + } + + CancelReloadAndFire(); + return true; +} + +// With the magazine out, the shot's sequence flicks the gun to the hold one-handed and goes on with a dry reload +// from the insert; the bullet fires when the gun reaches the hold (the table's fire_at) +bool CNEOBaseCombatWeapon::StartChamberShot() +{ + if (!sv_neo_reload_chamber_shot.GetBool() || m_bChamberShotPending || !IsRoundChambered()) + { + return false; + } + + // a bolt action works its bolt from its own attack code + if (GetNeoWepBits() & NEO_WEP_SRS) + { + return false; + } + + const int iTable = GetNEOWpnData().FindReloadTable(VM_SEQ_FIRE_CHAMBER_ONEHAND); + const int iSequence = (iTable >= 0) ? LookupReloadSequence(VM_SEQ_FIRE_CHAMBER_ONEHAND) : -1; + if (iSequence < 0 || GetNEOWpnData().m_reloadTables[iTable].flFireAt <= 0.0f) + { + return false; + } + + StopPendingReloadSoundPieces(); + // the round about to fire leaves the chamber empty: the rest of the reload works the bolt + m_bReloadDry = true; + m_bChamberShotPending = true; + StartReloadSequence(iTable, iSequence, ResumeGestureCycle()); + return true; +} + +// The chambered round's bullet, fired from the reload: no fire animation or gesture, the reload's timers kept +void CNEOBaseCombatWeapon::FireChamberShot() +{ + m_bChamberShotPending = false; + + const float flNextPrimary = m_flNextPrimaryAttack; + const float flNextSecondary = m_flNextSecondaryAttack; + const float flIdle = m_flTimeWeaponIdle; + m_flNextPrimaryAttack = gpGlobals->curtime; + m_bTriggerReset = true; + PrimaryAttack(); + m_flNextPrimaryAttack = flNextPrimary; + m_flNextSecondaryAttack = flNextSecondary; + m_flTimeWeaponIdle = flIdle; + + // the chamber is empty once its round is gone (the AA13 fires through its own attack code) + if (m_iClip1 <= 0) + { + m_bChamberEmpty = true; + } +} + +// A round the trigger can fire now: a chambered one, or a revolver's with its cylinder closed +bool CNEOBaseCombatWeapon::HasRoundToFire() const +{ + if (HasChamberSlot()) + { + return IsRoundChambered(); + } + return m_iClip1 > 0 && m_nReloadPhase != RELOAD_PHASE_DROPPED; +} + +// End the reload where it is, with the phases credited so far, and fire on this tick +void CNEOBaseCombatWeapon::CancelReloadAndFire() +{ + StopPendingReloadSoundPieces(); + EndReloadGesture(); + m_bInReload = false; + m_nReloadTable = -1; + m_nReloadPhase = RELOAD_PHASE_NONE; + + CBaseCombatCharacter *pOwner = GetOwner(); + if (pOwner) + { + pOwner->SetNextAttack(gpGlobals->curtime); + } + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->curtime; + SetWeaponIdleTime(gpGlobals->curtime); + m_bTriggerReset = true; + + const int iClipBefore = m_iClip1; + PrimaryAttack(); + + // the hand comes back onto the gun with the recoil, where the model has that blend + const int iFireBlend = LookupReloadSequence(VM_SEQ_FIRE_BLEND); + if (m_iClip1 < iClipBefore && m_iClip1 > 0 && iFireBlend >= 0) + { + SetSequence(iFireBlend); + SendViewModelAnim(iFireBlend); + } +} + +// Stop the sound pieces of the phases the reload has not reached (a piece can start before its phase's credit); +// the pieces of credited phases play out +void CNEOBaseCombatWeapon::StopPendingReloadSoundPieces() +{ + CBaseCombatCharacter *pOwner = GetOwner(); + if (!pOwner || m_nReloadTable < 0) + { + return; + } + + const ReloadPhaseTable_t &table = GetNEOWpnData().m_reloadTables[m_nReloadTable]; + for (int i = 0; i < table.iNumRows; ++i) + { + if (table.rows[i].phase <= m_nReloadPhase) + { + continue; + } + for (int j = 0; j < RELOAD_SOUNDS_PER_ROW; ++j) + { + if (table.rows[i].sounds[j].szName[0]) + { + StopSound(pOwner->entindex(), table.rows[i].sounds[j].szName); + } + } + } +} + +// Credit every phase of the playing reload sequence whose time has come +void CNEOBaseCombatWeapon::RunReloadPhases() +{ + if (!m_bInReload || m_nReloadTable < 0) + { + return; + } + + const ReloadPhaseTable_t &table = GetNEOWpnData().m_reloadTables[m_nReloadTable]; + const float flElapsed = gpGlobals->curtime - m_flReloadStart; + + if (m_bChamberShotPending && flElapsed >= table.flFireAt) + { + FireChamberShot(); + } + + // Sound pieces play on their own times, before or after their phase's credit + // (the pieces of phases credited before this sequence started are marked done when it starts) + for (int i = 0; i < table.iNumRows; ++i) + { + for (int j = 0; j < RELOAD_SOUNDS_PER_ROW; ++j) + { + const ReloadSoundPiece_t &piece = table.rows[i].sounds[j]; + const int iBit = 1 << (i * RELOAD_SOUNDS_PER_ROW + j); + if (piece.szName[0] && !(m_nReloadSoundsDone & iBit) && flElapsed >= piece.flAt - RELOAD_SOUND_LEAD) + { + m_nReloadSoundsDone |= iBit; + EmitReloadSoundPiece(piece.szName); + } + } + } + + for (int i = 0; i < table.iNumRows; ++i) + { + const ReloadPhaseRow_t &row = table.rows[i]; + if (row.phase <= m_nReloadPhase || (row.phase == RELOAD_PHASE_BOLTED && !m_bReloadDry)) + { + continue; + } + if (flElapsed < row.flCredit) + { + break; + } + CreditReloadPhase(row.phase); + } +} + +// The phase table, and the viewmodel sequence (-1: the reload activity), for the reload to play now. +// A model without the sequence a phase calls for plays the shipped reload, crediting only the phases left +int CNEOBaseCombatWeapon::SelectReloadTable(bool bFromDraw, int *pSequence) const +{ + const CNEOWeaponInfo &info = GetNEOWpnData(); + *pSequence = -1; + + const char *pszName = ReloadSequenceName(m_nReloadPhase, m_bReloadDry, bFromDraw); + if (pszName && V_strcmp(pszName, VM_SEQ_RELOAD) != 0) + { + const int iTable = info.FindReloadTable(pszName); + const int iSequence = (iTable >= 0) ? LookupReloadSequence(pszName) : -1; + if (iSequence >= 0) + { + *pSequence = iSequence; + return iTable; + } + } + + if (bFromDraw) + { + return -1; + } + return info.FindReloadTable(VM_SEQ_RELOAD); +} + +// Play a reload sequence from its start; its phase table credits the phases left from now on +// Where the full reload's gesture should start for a reload resumed after a switch: the time the last completed phase +// counts in the full reload, as a fraction of it (0 for a fresh reload) +float CNEOBaseCombatWeapon::ResumeGestureCycle() const +{ + if (m_nReloadPhase == RELOAD_PHASE_NONE) + { + return 0.0f; + } + + const CNEOWeaponInfo &info = GetNEOWpnData(); + const int iTable = info.FindReloadTable(VM_SEQ_RELOAD); + const int iSequence = LookupReloadSequence(VM_SEQ_RELOAD); + if (iTable < 0 || iSequence < 0) + { + return 0.0f; + } + + const float flDuration = ReloadSequenceDuration(iSequence); + const ReloadPhaseTable_t &table = info.m_reloadTables[iTable]; + for (int i = 0; i < table.iNumRows; ++i) + { + if (table.rows[i].phase == m_nReloadPhase && flDuration > 0.0f) + { + return Clamp(table.rows[i].flCredit / flDuration, 0.0f, 1.0f); + } + } + return 0.0f; +} + +// Reload sequences are looked up on the owner's viewmodel: on the client the weapon entity carries the world model, +// so a lookup there would fail and the client would predict a different sequence than the server plays +CBaseViewModel *CNEOBaseCombatWeapon::ReloadViewModel() const +{ + CBasePlayer *pOwner = ToBasePlayer(GetOwner()); + return pOwner ? pOwner->GetViewModel(m_nViewModelIndex) : nullptr; +} + +int CNEOBaseCombatWeapon::LookupReloadSequence(const char *pszName) const +{ + CBaseViewModel *pVM = ReloadViewModel(); + return pVM ? pVM->LookupSequence(pszName) : -1; +} + +float CNEOBaseCombatWeapon::ReloadSequenceDuration(int iSequence) const +{ + CBaseViewModel *pVM = ReloadViewModel(); + return pVM ? pVM->SequenceDuration(iSequence) : 0.0f; +} + +// Stop the reload gesture others see, for a reload that ended or was cut short +void CNEOBaseCombatWeapon::EndReloadGesture() +{ + CBaseCombatCharacter *pOwner = GetOwner(); + if (pOwner && pOwner->IsPlayer()) + { + static_cast(pOwner)->DoAnimationEvent(PLAYERANIMEVENT_RELOAD_END); + } +} + +void CNEOBaseCombatWeapon::StartReloadSequence(int iTable, int iSequence, float flGestureStartCycle) +{ + CBaseCombatCharacter *pOwner = GetOwner(); + m_nReloadTable = iTable; + + // the phases already credited, and a partial reload's bolt, play no sound + const ReloadPhaseTable_t &table = GetNEOWpnData().m_reloadTables[iTable]; + int iSoundsDone = 0; + for (int i = 0; i < table.iNumRows; ++i) + { + const ReloadPhase_t phase = table.rows[i].phase; + if (phase <= m_nReloadPhase || (phase == RELOAD_PHASE_BOLTED && !m_bReloadDry)) + { + iSoundsDone |= ((1 << RELOAD_SOUNDS_PER_ROW) - 1) << (i * RELOAD_SOUNDS_PER_ROW); + } + } + m_nReloadSoundsDone = iSoundsDone; + if (!HasReloadSoundPieces()) + { + WeaponSound(RELOAD_NPC); + } + + if (iSequence >= 0) + { + SetSequence(iSequence); + SendViewModelAnim(iSequence); + } + else + { + SendWeaponAnim(ACT_VM_RELOAD); + } + // the reload gesture other players see (SetAnimation(PLAYER_RELOAD) does nothing under NEO) + if (pOwner && pOwner->IsPlayer()) + { + // the gesture's start cycle travels in the event's spare data, in thousandths + static_cast(pOwner)->DoAnimationEvent(PLAYERANIMEVENT_RELOAD, static_cast(flGestureStartCycle * 1000.0f)); + } + + // the wait to fire is never shorter than the animation that plays + const float flEnd = gpGlobals->curtime + ((iSequence >= 0) ? ReloadSequenceDuration(iSequence) : SequenceDuration()); + if (pOwner) + { + pOwner->SetNextAttack(flEnd); + } + m_flNextPrimaryAttack = m_flNextSecondaryAttack = flEnd; + SetWeaponIdleTime(flEnd); + m_flReloadStart = gpGlobals->curtime; + m_bInReload = true; +} + +bool CNEOBaseCombatWeapon::HasReloadSoundPieces() const +{ + const CNEOWeaponInfo &info = GetNEOWpnData(); + for (int t = 0; t < info.m_iNumReloadTables; ++t) + { + for (int i = 0; i < info.m_reloadTables[t].iNumRows; ++i) + { + if (info.m_reloadTables[t].rows[i].sounds[0].szName[0]) + { + return true; + } + } + } + return false; +} + +// Play a reload sound piece from the owner, as WeaponSound does: predicted for the owner, from the server for the rest +void CNEOBaseCombatWeapon::EmitReloadSoundPiece(const char *pszSound) +{ + CBaseCombatCharacter *pOwner = GetOwner(); + if (!pOwner) + { + return; + } + + CSoundParameters params; + if (!GetParametersForSound(pszSound, params, NULL)) + { + return; + } + + CPASAttenuationFilter filter(pOwner, params.soundlevel); + if (IsPredicted() && CBaseEntity::GetPredictionPlayer()) + { + filter.UsePredictionRules(); + } + EmitSound(filter, pOwner->entindex(), pszSound); +} + +// A dropped weapon (a throw, or its owner's death) cuts the reload short: its sound pieces stop +void CNEOBaseCombatWeapon::StopReloadOnDrop() +{ + if (HasReloadPhases()) + { + StopReloadSoundPieces(); + m_bChamberShotPending = false; + if (m_bInReload) + { + EndReloadGesture(); + } + } +} + +// A switch cuts the reload sound pieces off +void CNEOBaseCombatWeapon::StopReloadSoundPieces() +{ + CBaseCombatCharacter *pOwner = GetOwner(); + if (!pOwner) + { + return; + } + + const CNEOWeaponInfo &info = GetNEOWpnData(); + for (int t = 0; t < info.m_iNumReloadTables; ++t) + { + for (int i = 0; i < info.m_reloadTables[t].iNumRows; ++i) + { + for (int j = 0; j < RELOAD_SOUNDS_PER_ROW; ++j) + { + const char *pszName = info.m_reloadTables[t].rows[i].sounds[j].szName; + if (pszName[0]) + { + StopSound(pOwner->entindex(), pszName); + } + } + } + } +} + +int CNEOBaseCombatWeapon::GetMaxRoundsInGun() const +{ + return GetMaxClip1() + ((HasReloadPhases() && GetNEOWpnData().m_bChamberedRound) ? 1 : 0); +} + +bool CNEOBaseCombatWeapon::HasChamberSlot() const +{ + return HasReloadPhases() && GetNEOWpnData().m_bChamberedRound; +} + +// The clip counts the chambered round, and the chamber is empty after the last shot until a dry reload's bolt +bool CNEOBaseCombatWeapon::IsRoundChambered() const +{ + return HasChamberSlot() && m_iClip1 > 0 && !m_bChamberEmpty; +} + +int CNEOBaseCombatWeapon::RoundsInMagazine() const +{ + return m_iClip1 - (IsRoundChambered() ? 1 : 0); +} + +int CNEOBaseCombatWeapon::MagazineCapacity() const +{ + return GetMaxClip1(); +} + +void CNEOBaseCombatWeapon::CreditReloadPhase(ReloadPhase_t phase) +{ + switch (phase) + { + case RELOAD_PHASE_DROPPED: + // the old magazine and its rounds are gone, a chambered round stays; + // a revolver's cylinder (no chamber) keeps its live rounds and drops only the empty cases + if (GetNEOWpnData().m_bChamberedRound) + { + m_iClip1 = (m_iClip1 > 0 && !IsReloadDry()) ? 1 : 0; + } + break; + case RELOAD_PHASE_SEATED: + { + // a fresh magazine, or the rounds that fill a cylinder's empty slots + const int iRoom = GetNEOWpnData().m_bChamberedRound ? GetMaxClip1() : GetMaxClip1() - m_iClip1; + const int iLoad = Min(iRoom, m_iPrimaryAmmoCount.Get()); + m_iClip1 += iLoad; + m_iPrimaryAmmoCount -= iLoad; + break; + } + case RELOAD_PHASE_BOLTED: + m_bChamberEmpty = false; + break; + default: + break; + } + + m_nReloadPhase = phase; +} + +void CNEOBaseCombatWeapon::CheckReloadWithPhases() +{ + if (m_bInReload) + { + RunReloadPhases(); + if (m_flNextPrimaryAttack > gpGlobals->curtime) + { + return; + } + + // the sequence has ended + m_bInReload = false; + EndReloadGesture(); + m_nReloadTable = -1; + if (!IsReloadPending()) + { + m_nReloadPhase = RELOAD_PHASE_NONE; + } + return; + } + + // an interrupted reload resumes once the weapon is drawn again + if (IsReloadPending()) + { + Reload(); + } +} + //----------------------------------------------------------------------------- // Purpose: Reload has finished. //----------------------------------------------------------------------------- void CNEOBaseCombatWeapon::FinishReload(void) { + // A reload with phases loads at its seat credit + if (HasReloadPhases()) + { + return; + } + + // The magazine was already loaded at the reload's ready time + if (m_flReloadReadyTime == 0.0f) + { + return; + } + m_flReloadReadyTime = 0.0f; + CBaseCombatCharacter* pOwner = GetOwner(); if (pOwner) @@ -498,6 +1224,29 @@ bool CNEOBaseCombatWeapon::Deploy(void) AddEffects(EF_BONEMERGE); m_bTriggerReset = true; + // An empty weapon draws with its slide held back where the model has that draw, + // which carries no activity, so it is picked by name + static constexpr const char *VM_SEQ_DRAW_EMPTY = "drawempty"; + const int iDrawEmpty = IsSlideLockedBack() ? LookupSequence(VM_SEQ_DRAW_EMPTY) : ACTIVITY_NOT_AVAILABLE; + if (iDrawEmpty != ACTIVITY_NOT_AVAILABLE) + { + SetSequence(iDrawEmpty); + SendViewModelAnim(iDrawEmpty); + } + + // A reload interrupted after the magazine dropped resumes in the draw, where the model has that draw + if (HasReloadPhases() && m_nReloadPhase == RELOAD_PHASE_DROPPED && IsReloadPending()) + { + m_bReloadDry = IsReloadDry(); + int iSequence = -1; + const int iTable = SelectReloadTable(true, &iSequence); + if (iTable >= 0) + { + // the draw is part of the motion: the full gesture fits the draw-resume + StartReloadSequence(iTable, iSequence, 0.0f); + } + } + #ifdef DEBUG CNEO_Player* pOwner = NULL; if (GetOwner()) @@ -558,11 +1307,38 @@ float CNEOBaseCombatWeapon::GetPenetration() const bool CNEOBaseCombatWeapon::Holster(CBaseCombatWeapon* pSwitchingTo) { + // The phases credited so far stay; a reload with nothing left to do is over + if (HasReloadPhases()) + { + if (m_bInReload) + { + EndReloadGesture(); + } + // a switch during the flick cancels the chambered-round shot: the round stays, the reload is partial again + if (m_bChamberShotPending) + { + m_bChamberShotPending = false; + m_bReloadDry = IsReloadDry(); + } + if (!IsReloadPending()) + { + m_nReloadPhase = RELOAD_PHASE_NONE; + } + m_nReloadTable = -1; + StopReloadSoundPieces(); + } + return BaseClass::Holster(pSwitchingTo); } void CNEOBaseCombatWeapon::CheckReload(void) { + if (HasReloadPhases()) + { + CheckReloadWithPhases(); + return; + } + if (!m_bInReload && UsesClipsForAmmo1() && m_iClip1 == 0 && GetOwner() && !ClientWantsAutoReload(GetOwner())) { return; @@ -612,6 +1388,19 @@ void CNEOBaseCombatWeapon::ItemBusyFrame(void) { BaseClass::ItemBusyFrame(); UpdateInaccuracy(); + + if (HasReloadPhases()) + { + RunReloadPhases(); + TryFireDuringReload(); + return; + } + + // The owner stays busy until the reload animation ends, but the magazine loads at the reload's ready time + if (m_bInReload && m_flReloadReadyTime > 0.0f && gpGlobals->curtime >= m_flReloadReadyTime) + { + FinishReload(); + } } void CNEOBaseCombatWeapon::ItemHolsterFrame(void) @@ -941,6 +1730,39 @@ void CNEOBaseCombatWeapon::DryFire() m_flNextPrimaryAttack = gpGlobals->curtime + GetFastestDryRefireTime(); // SequenceDuration(); } +// An empty weapon idles on its empty sequence where the model has one, such as a pistol with its slide held back +bool CNEOBaseCombatWeapon::SendWeaponAnim(int iActivity) +{ + if (iActivity == ACT_VM_IDLE && IsSlideLockedBack() && HasEmptyIdle()) + { + iActivity = ACT_VM_IDLE_EMPTY; + } + + return BaseClass::SendWeaponAnim(iActivity); +} + +// An empty weapon shows its empty sequences, and so does a pistol whose dry reload seated a magazine +// but has not released the slide yet +bool CNEOBaseCombatWeapon::IsSlideLockedBack() const +{ + if (!UsesClipsForAmmo1()) + { + return false; + } + + if (m_iClip1 <= 0) + { + return true; + } + + return HasReloadPhases() && m_bChamberEmpty && (GetNeoWepBits() & NEO_WEP_SLIDE_LOCK); +} + +bool CNEOBaseCombatWeapon::HasEmptyIdle() +{ + return SelectWeightedSequence(ACT_VM_IDLE_EMPTY) != ACTIVITY_NOT_AVAILABLE; +} + void CNEOBaseCombatWeapon::PrimaryAttack(void) { if (ShootingIsPrevented()) @@ -953,6 +1775,13 @@ void CNEOBaseCombatWeapon::PrimaryAttack(void) return; } + // a dry reload seated the magazine but has not chambered a round yet + else if (HasReloadPhases() && m_bChamberEmpty && m_iClip1 > 0) + { + CheckReload(); + return; + } + else if (m_iClip1 == 0) { if (!m_bFireOnEmpty) @@ -1019,15 +1848,16 @@ void CNEOBaseCombatWeapon::PrimaryAttack(void) #endif // GAME_DLL } - SendWeaponAnim(GetPrimaryAttackActivity()); - SetWeaponIdleTime(gpGlobals->curtime + 2.0); if (IsSemiAuto()) { m_bTriggerReset = false; } - // player "shoot" animation - pPlayer->DoAnimationEvent(PLAYERANIMEVENT_ATTACK_PRIMARY); + // player "shoot" animation (a chambered-round shot keeps the reload gesture) + if (!m_bInReload) + { + pPlayer->DoAnimationEvent(PLAYERANIMEVENT_ATTACK_PRIMARY); + } FireBulletsInfo_t info; info.m_vecSrc = pPlayer->Weapon_ShootPosition(); @@ -1064,6 +1894,38 @@ void CNEOBaseCombatWeapon::PrimaryAttack(void) m_iPrimaryAmmoCount -= info.m_iShots; } + if (HasReloadPhases()) + { + // the last round leaves the chamber empty, and a shot shows the seated magazine feeds without its slap + if (GetNEOWpnData().m_bChamberedRound && m_iClip1 <= 0) + { + m_bChamberEmpty = true; + } + if (m_nReloadPhase == RELOAD_PHASE_SEATED) + { + m_nReloadPhase = RELOAD_PHASE_NONE; + } + } + + // a chambered-round shot plays inside its reload sequence + if (!m_bInReload) + { + // the last round's shot locks a pistol's slide back: + // the pistols' dry fire sequence is a shot's kick with the slide held back, + if (UsesClipsForAmmo1() && m_iClip1 <= 0 && (GetNeoWepBits() & NEO_WEP_SLIDE_LOCK)) + { + SendWeaponAnim(ACT_VM_DRYFIRE); + } + else + { + SendWeaponAnim(GetPrimaryAttackActivity()); + } + + // and goes straight on to the empty idle when it ends + const bool bEmptyIdleNext = UsesClipsForAmmo1() && m_iClip1 <= 0 && HasEmptyIdle(); + SetWeaponIdleTime(gpGlobals->curtime + (bEmptyIdleNext ? SequenceDuration() : 2.0)); + } + info.m_flDistance = MAX_TRACE_LENGTH; info.m_iAmmoType = m_iPrimaryAmmoType; info.m_iTracerFreq = UsesTracers() ? 1 : 0; diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h index b8a0afbefa..931e3a9e97 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h @@ -191,6 +191,7 @@ class CNEOBaseCombatWeapon : public CBaseHL2MPCombatWeapon #endif virtual void DryFire(void); + virtual bool SendWeaponAnim(int iActivity) override; virtual Activity GetPrimaryAttackActivity(void) override; #ifdef CLIENT_DLL @@ -263,6 +264,17 @@ class CNEOBaseCombatWeapon : public CBaseHL2MPCombatWeapon virtual float GetFireRate() override final; float GetPenetration() const; + // the magazine plus the round a partial reload keeps in the chamber + virtual int GetMaxRoundsInGun() const; + // the gun has a chamber that holds one round apart from the magazine (every gun with reload phases but the Kyla) + virtual bool HasChamberSlot() const; + // a live round is in the chamber + virtual bool IsRoundChambered() const; + // the rounds in the magazine (or tube), the chambered round not counted + virtual int RoundsInMagazine() const; + // how many rounds the magazine (or tube) holds + virtual int MagazineCapacity() const; + void StopReloadOnDrop(); #ifdef CLIENT_DLL float m_flTemperature; int m_spawnflags; @@ -290,8 +302,53 @@ class CNEOBaseCombatWeapon : public CBaseHL2MPCombatWeapon CNetworkVar(int, m_nNumShotsFired); CNetworkVar(bool, m_bTriggerReset); + // When the reload in progress loads the magazine, 0 once it is loaded + CNetworkVar(float, m_flReloadReadyTime); + + // Reload phases (weapons whose script has a ReloadPhases block): the last phase credited, kept across a switch + CNetworkVar(int, m_nReloadPhase); + // no round in the chamber: the gun cannot fire, and the next reload needs the bolt + CNetworkVar(bool, m_bChamberEmpty); + // the reload in progress needs the bolt + CNetworkVar(bool, m_bReloadDry); + // the phase table of the sequence playing, -1 none + CNetworkVar(int, m_nReloadTable); + // when the reload sequence started; phase credits are offsets from it + CNetworkVar(float, m_flReloadStart); + // the sound pieces of the playing sequence already started, one bit per phase row + CNetworkVar(int, m_nReloadSoundsDone); + // a chambered-round shot is under way and its bullet has not fired yet + CNetworkVar(bool, m_bChamberShotPending); + private: CNEOBaseCombatWeapon(const CNEOBaseCombatWeapon &other); + bool HasEmptyIdle(); + bool IsSlideLockedBack() const; + + bool HasReloadPhases() const; + bool IsReloadDry() const; + ReloadPhase_t LastReloadPhase(bool bDry) const; + bool IsReloadPending() const; + bool ReloadWithPhases(); + void RunReloadPhases(); + void CreditReloadPhase(ReloadPhase_t phase); + void CheckReloadWithPhases(); + bool HasReloadSoundPieces() const; + void StopReloadSoundPieces(); + void EmitReloadSoundPiece(const char *pszSound); + int SelectReloadTable(bool bFromDraw, int *pSequence) const; + void StartReloadSequence(int iTable, int iSequence, float flGestureStartCycle); + float ResumeGestureCycle() const; + CBaseViewModel *ReloadViewModel() const; + int LookupReloadSequence(const char *pszName) const; + float ReloadSequenceDuration(int iSequence) const; + void EndReloadGesture(); + bool TryFireDuringReload(); + bool HasRoundToFire() const; + void CancelReloadAndFire(); + void StopPendingReloadSoundPieces(); + bool StartChamberShot(); + void FireChamberShot(); }; diff --git a/src/game/shared/neo/weapons/weapon_srs.cpp b/src/game/shared/neo/weapons/weapon_srs.cpp index ae21ebf7ee..ec5f171da8 100644 --- a/src/game/shared/neo/weapons/weapon_srs.cpp +++ b/src/game/shared/neo/weapons/weapon_srs.cpp @@ -10,8 +10,10 @@ BEGIN_NETWORK_TABLE(CWeaponSRS, DT_WeaponSRS) DEFINE_NEO_BASE_WEP_NETWORK_TABLE #ifdef CLIENT_DLL RecvPropBool(RECVINFO(m_bNeedsBolting)), + RecvPropTime(RECVINFO(m_flBoltHomeTime)), #else SendPropBool(SENDINFO(m_bNeedsBolting)), + SendPropTime(SENDINFO(m_flBoltHomeTime)), #endif END_NETWORK_TABLE() @@ -19,6 +21,7 @@ END_NETWORK_TABLE() BEGIN_PREDICTION_DATA(CWeaponSRS) DEFINE_NEO_BASE_WEP_PREDICTION DEFINE_PRED_FIELD(m_bNeedsBolting, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_flBoltHomeTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE), END_PREDICTION_DATA() #endif @@ -28,6 +31,9 @@ LINK_ENTITY_TO_CLASS(weapon_srs, CWeaponSRS); PRECACHE_WEAPON_REGISTER(weapon_srs); +// The bolt cycle after a shot; its phase table in the weapon script holds the moment the bolt is home +static constexpr const char *VM_SEQ_CHARGE = "charge"; + CWeaponSRS::CWeaponSRS() { m_flSoonestAttack = gpGlobals->curtime; @@ -35,6 +41,7 @@ CWeaponSRS::CWeaponSRS() m_nNumShotsFired = 0; m_bNeedsBolting = false; + m_flBoltHomeTime = 0.0f; m_weaponSeeds = { "srspx", @@ -83,6 +90,12 @@ void CWeaponSRS::ItemPreFrame() SendWeaponAnim(ACT_VM_PULLBACK); WeaponSound(SPECIAL1); m_bNeedsBolting = false; + + // the script's "charge" table gives when the bolt is home with the next round + const CNEOWeaponInfo &info = GetNEOWpnData(); + const int iTable = info.FindReloadTable(VM_SEQ_CHARGE); + const bool bHasCredit = iTable >= 0 && info.m_reloadTables[iTable].iNumRows > 0; + m_flBoltHomeTime = gpGlobals->curtime + (bHasCredit ? info.m_reloadTables[iTable].rows[0].flCredit : 0.0f); } BaseClass::ItemPreFrame(); @@ -93,6 +106,7 @@ bool CWeaponSRS::Deploy() // This is on purpose; the player is allowed to cancel the bolting animation // with a quick-switch input. m_bNeedsBolting = false; + m_flBoltHomeTime = 0.0f; return BaseClass::Deploy(); } @@ -102,10 +116,20 @@ bool CWeaponSRS::Reload() // The reloading animation already contains the bolting motion; // we don't want to bolt twice. m_bNeedsBolting = false; + m_flBoltHomeTime = 0.0f; return BaseClass::Reload(); } +bool CWeaponSRS::IsRoundChambered() const +{ + if (m_bNeedsBolting || gpGlobals->curtime < m_flBoltHomeTime) + { + return false; + } + return BaseClass::IsRoundChambered(); +} + bool CWeaponSRS::CanBePickedUpByClass(int classId) { return classId != NEO_CLASS_JUGGERNAUT; diff --git a/src/game/shared/neo/weapons/weapon_srs.h b/src/game/shared/neo/weapons/weapon_srs.h index b6c632f153..285d488799 100644 --- a/src/game/shared/neo/weapons/weapon_srs.h +++ b/src/game/shared/neo/weapons/weapon_srs.h @@ -46,6 +46,8 @@ class CWeaponSRS : public CNEOBaseCombatWeapon bool CanBePickedUpByClass(int classId) OVERRIDE; inline bool GetNeedsBolting() const { return m_bNeedsBolting; } + // after a shot the chamber holds a spent case until the bolt brings the next round home + bool IsRoundChambered() const override; protected: virtual float GetFastestDryRefireTime() const OVERRIDE { return 0.2f; } @@ -54,6 +56,8 @@ class CWeaponSRS : public CNEOBaseCombatWeapon CWeaponSRS(const CWeaponSRS &other); CNetworkVar(bool, m_bNeedsBolting); + // when the bolt cycle in progress chambers the next round, 0 when none is in progress + CNetworkVar(float, m_flBoltHomeTime); }; #endif // NEO_WEAPON_SRS_H diff --git a/src/game/shared/neo/weapons/weapon_supa7.cpp b/src/game/shared/neo/weapons/weapon_supa7.cpp index d3ca85d258..0e3a0e8507 100644 --- a/src/game/shared/neo/weapons/weapon_supa7.cpp +++ b/src/game/shared/neo/weapons/weapon_supa7.cpp @@ -12,12 +12,26 @@ BEGIN_NETWORK_TABLE(CWeaponSupa7, DT_WeaponSupa7) RecvPropBool(RECVINFO(m_bWeaponRaised)), RecvPropBool(RECVINFO(m_bShellInChamber)), RecvPropFloat(RECVINFO(m_flNextReload)), + RecvPropInt(RECVINFO(m_nTubeCount)), + RecvPropInt(RECVINFO(m_nTubeSlugs)), + RecvPropInt(RECVINFO(m_nChamber)), + RecvPropBool(RECVINFO(m_bRackPending)), + RecvPropTime(RECVINFO(m_flRackTime)), + RecvPropTime(RECVINFO(m_flRackSoundTime)), + RecvPropTime(RECVINFO(m_flPumpHomeTime)), #else SendPropBool(SENDINFO(m_bSlugDelayed)), SendPropBool(SENDINFO(m_bSlugLoaded)), SendPropBool(SENDINFO(m_bWeaponRaised)), SendPropBool(SENDINFO(m_bShellInChamber)), SendPropFloat(SENDINFO(m_flNextReload)), + SendPropInt(SENDINFO(m_nTubeCount), 4, SPROP_UNSIGNED), + SendPropInt(SENDINFO(m_nTubeSlugs), 8, SPROP_UNSIGNED), + SendPropInt(SENDINFO(m_nChamber), 2, SPROP_UNSIGNED), + SendPropBool(SENDINFO(m_bRackPending)), + SendPropTime(SENDINFO(m_flRackTime)), + SendPropTime(SENDINFO(m_flRackSoundTime)), + SendPropTime(SENDINFO(m_flPumpHomeTime)), #endif END_NETWORK_TABLE() @@ -28,6 +42,13 @@ BEGIN_PREDICTION_DATA(CWeaponSupa7) DEFINE_PRED_FIELD(m_bWeaponRaised, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE), DEFINE_PRED_FIELD(m_bShellInChamber, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE), DEFINE_PRED_FIELD(m_flNextReload, FIELD_FLOAT, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_nTubeCount, FIELD_INTEGER, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_nTubeSlugs, FIELD_INTEGER, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_nChamber, FIELD_INTEGER, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_bRackPending, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_flRackTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_flRackSoundTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE), + DEFINE_PRED_FIELD(m_flPumpHomeTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE), END_PREDICTION_DATA() #endif @@ -42,6 +63,13 @@ BEGIN_DATADESC(CWeaponSupa7) DEFINE_FIELD(m_bWeaponRaised, FIELD_BOOLEAN), DEFINE_FIELD(m_bShellInChamber, FIELD_BOOLEAN), DEFINE_FIELD(m_flNextReload, FIELD_FLOAT), + DEFINE_FIELD(m_nTubeCount, FIELD_INTEGER), + DEFINE_FIELD(m_nTubeSlugs, FIELD_INTEGER), + DEFINE_FIELD(m_nChamber, FIELD_INTEGER), + DEFINE_FIELD(m_bRackPending, FIELD_BOOLEAN), + DEFINE_FIELD(m_flRackTime, FIELD_TIME), + DEFINE_FIELD(m_flRackSoundTime, FIELD_TIME), + DEFINE_FIELD(m_flPumpHomeTime, FIELD_TIME), END_DATADESC() #endif @@ -65,6 +93,9 @@ IMPLEMENT_ACTTABLE(CWeaponSupa7); #endif #endif +// One shell or slug goes into the tube every this many seconds +static constexpr float TIME_BETWEEN_SHELLS_LOADED = 0.5f; + CWeaponSupa7::CWeaponSupa7(void) { m_bReloadsSingly = true; @@ -73,6 +104,13 @@ CWeaponSupa7::CWeaponSupa7(void) m_bWeaponRaised = false; m_bShellInChamber = true; m_flNextReload = 0.0f; + m_nTubeCount = 0; + m_nTubeSlugs = 0; + m_nChamber = SUPA7_CHAMBER_EMPTY; + m_bRackPending = false; + m_flRackTime = 0.0f; + m_flPumpHomeTime = 0.0f; + m_flRackSoundTime = 0.0f; m_fMinRange1 = 0.0; m_fMaxRange1 = 500; @@ -88,11 +126,9 @@ CWeaponSupa7::CWeaponSupa7(void) } // Purpose: Override so only reload one shell at a time +// (a chambered slug stays where it is while shells go into the tube behind it) bool CWeaponSupa7::StartReload(void) { - if (m_bSlugLoaded) - return false; - CBaseCombatCharacter* pOwner = GetOwner(); if (pOwner == NULL) @@ -113,7 +149,8 @@ bool CWeaponSupa7::StartReload(void) if (m_iPrimaryAmmoCount <= 0) return false; - if (m_iClip1 >= GetMaxClip1()) + SyncTube(); + if (m_nTubeCount >= TubeCapacity()) return false; SendWeaponAnim(ACT_SHOTGUN_RELOAD_START); @@ -144,7 +181,8 @@ bool CWeaponSupa7::StartReloadSlug(void) return false; } - if (m_iClip1 >= GetMaxClip1()) + SyncTube(); + if (m_nTubeCount >= TubeCapacity()) return false; if (!m_bInReload) @@ -182,7 +220,7 @@ bool CWeaponSupa7::Reload(void) if (m_iPrimaryAmmoCount <= 0) return false; - if (m_iClip1 >= GetMaxClip1()) + if (m_nTubeCount >= TubeCapacity()) return false; // Play reload on different channel as otherwise steals channel away from fire sound @@ -190,8 +228,9 @@ bool CWeaponSupa7::Reload(void) SendWeaponAnim(ACT_VM_RELOAD); pOwner->DoAnimationEvent(PLAYERANIMEVENT_RELOAD); + PlayInsertAtShellPace(); + pOwner->m_flNextAttack = gpGlobals->curtime; - constexpr float TIME_BETWEEN_SHELLS_LOADED = 0.5f; // ProposeNextAttack(gpGlobals->curtime + TIME_BETWEEN_SHELLS_LOADED); m_flNextReload = gpGlobals->curtime + TIME_BETWEEN_SHELLS_LOADED; @@ -219,17 +258,19 @@ bool CWeaponSupa7::ReloadSlug(void) if (m_iSecondaryAmmoCount <= 0) return false; - if (m_iClip1 >= GetMaxClip1()) + if (m_nTubeCount >= TubeCapacity()) return false; + // the slug goes on top of the tube and is racked into the chamber when the reload ends FillClipSlug(); - m_bShellInChamber = false; + m_bRackPending = true; // Play reload on different channel as otherwise steals channel away from fire sound WeaponSound(SPECIAL1); SendWeaponAnim(ACT_VM_RELOAD); pOwner->DoAnimationEvent(PLAYERANIMEVENT_RELOAD); + // a slug insert waits for its whole animation, so it plays at its own pace pOwner->m_flNextAttack = gpGlobals->curtime; // ProposeNextAttack(gpGlobals->curtime + SequenceDuration()); m_flNextReload = gpGlobals->curtime + SequenceDuration(); @@ -249,12 +290,13 @@ void CWeaponSupa7::FinishReload(void) m_bInReload = false; - // Finish reload animation - if (!m_bShellInChamber) + // Finish reload animation: rack when the chamber holds no live round, or to bring a loaded slug up + SyncTube(); + if (!IsChamberLive() || m_bRackPending) { SendWeaponAnim(ACT_SHOTGUN_PUMP); WeaponSound(SPECIAL2); - m_bShellInChamber = true; + Rack(); } else { @@ -273,14 +315,12 @@ void CWeaponSupa7::FillClip(void) if (pOwner == NULL) return; - // Add them to the clip - if (m_iPrimaryAmmoCount > 0) + // Push a shell into the tube + SyncTube(); + if (m_iPrimaryAmmoCount > 0 && m_nTubeCount < TubeCapacity()) { - if (Clip1() < GetMaxClip1()) - { - m_iClip1++; - m_iPrimaryAmmoCount -= 1; - } + TubePush(false); + m_iPrimaryAmmoCount -= 1; } } @@ -293,11 +333,11 @@ void CWeaponSupa7::FillClipSlug(void) return; // Add them to the clip - if (m_iSecondaryAmmoCount > 0 && !m_bSlugLoaded) + SyncTube(); + if (m_iSecondaryAmmoCount > 0 && !m_bSlugLoaded && m_nTubeCount < TubeCapacity()) { - m_iClip1++; + TubePush(true); m_iSecondaryAmmoCount -= 1; - m_bSlugLoaded = true; m_bSlugDelayed = false; } } @@ -323,6 +363,8 @@ void CWeaponSupa7::PrimaryAttack(void) Vector vecSrc = pPlayer->Weapon_ShootPosition(); Vector vecAiming = pPlayer->GetAutoaimVector(AUTOAIM_10DEGREES); + SyncTube(); + // Change the firing characteristics and sound if a slug was loaded if (m_bSlugLoaded) { @@ -352,15 +394,16 @@ void CWeaponSupa7::PrimaryAttack(void) pPlayer->DoMuzzleFlash(); SendWeaponAnim(m_bSlugLoaded ? ACT_VM_PRIMARYATTACK : ACT_VM_SECONDARYATTACK); - m_bSlugLoaded = false; // Don't fire again until fire animation has completed ProposeNextAttack(gpGlobals->curtime + GetFireRate()); - m_iClip1 -= 1; - if (m_iClip1 == 0) - { - m_bShellInChamber = false; - } + + // the shot leaves a hull, and the fire animation's pump ejects it and feeds the tube's top item + // (the HUD shows the fed round in the tube until the pump is home, never after the next shot is allowed) + const float flPumpHome = m_bSlugLoaded ? GetNEOWpnData().m_flChamberAfterSlugShot : GetNEOWpnData().m_flChamberAfterShot; + m_flPumpHomeTime = Min(gpGlobals->curtime + flPumpHome, m_flNextPrimaryAttack.Get()); + m_nChamber = SUPA7_CHAMBER_HULL; + Rack(); // player "shoot" animation pPlayer->DoAnimationEvent(PLAYERANIMEVENT_ATTACK_PRIMARY); @@ -413,6 +456,8 @@ void CWeaponSupa7::ItemPostFrame(void) return; } ProcessAnimationEvents(); + SyncTube(); + RunRack(); if (m_bInReload) { @@ -435,8 +480,9 @@ void CWeaponSupa7::ItemPostFrame(void) } else { - // If out of ammo end reload - if ((m_iPrimaryAmmoCount <= 0 || m_bSlugLoaded || m_iClip1 >= GetMaxClip1())) + // If out of ammo or the tube is full end reload + // (a slug loaded to be fired next is racked up before anything goes on top of it) + if ((m_iPrimaryAmmoCount <= 0 || m_bRackPending || m_nTubeCount >= TubeCapacity())) { FinishReload(); } @@ -475,6 +521,12 @@ void CWeaponSupa7::ItemPostFrame(void) { DryFire(); } + // a reload cut off before its rack leaves rounds in the tube and none in the chamber, + // so the trigger racks one up instead of firing a round the chamber does not hold + else if (!IsChamberLive()) + { + StartRack(); + } // Fire underwater? else if (pOwner->GetWaterLevel() == WL_Eyes && !m_bFiresUnderwater) { @@ -496,8 +548,12 @@ void CWeaponSupa7::ItemPostFrame(void) if (!(pOwner->m_nButtons & IN_ATTACK) && m_flNextPrimaryAttack <= gpGlobals->curtime && pOwner->m_nButtons & IN_RELOAD && UsesClipsForAmmo1() && !m_bInReload) { - // reload when reload is pressed, or if no buttons are down and weapon is empty. - StartReload(); + // reload when reload is pressed, or if no buttons are down and weapon is empty; + // a press with the tube full racks the pump once instead + if (!StartReload() && (pOwner->m_afButtonPressed & IN_RELOAD) && m_nTubeCount > 0 && m_nTubeCount >= TubeCapacity()) + { + StartRack(); + } } else if (!(pOwner->m_nButtons & IN_ATTACK) && m_flNextPrimaryAttack <= gpGlobals->curtime && pOwner->m_nButtons & IN_ATTACK2 && UsesClipsForAmmo1() && !m_bInReload) { @@ -525,6 +581,151 @@ void CWeaponSupa7::ItemPostFrame(void) } } +// The insert animation is longer than the interval between shells, so it was cut and restarted +// with the hand snapping back to the pouch; play it fast enough to finish each shell within the interval +void CWeaponSupa7::PlayInsertAtShellPace() +{ + CBasePlayer *pOwner = ToBasePlayer(GetOwner()); + CBaseViewModel *pVM = pOwner ? pOwner->GetViewModel(m_nViewModelIndex) : nullptr; + if (!pVM) + { + return; + } + + const float flDuration = pVM->SequenceDuration(); + if (flDuration > TIME_BETWEEN_SHELLS_LOADED) + { + pVM->SetPlaybackRate(flDuration / TIME_BETWEEN_SHELLS_LOADED); + } +} + +// The standalone rack: the support hand reaches for the pump and racks it once (the "rack" sequence where the model +// has it, else the reload's finishing rack); the chamber empties at the pump's back stroke, the pump wav timed to it +static constexpr const char *VM_SEQ_RACK = "rack"; +// the pump reaches the back of its stroke this long before either sequence ends (reloadend f12 of f30) +static constexpr float RACK_BACK_BEFORE_END = 0.6f; +// murata_pump.wav's back click comes this far into the file +static constexpr float PUMP_WAV_BACK_CLICK = 0.419f; + +void CWeaponSupa7::StartRack() +{ + CBasePlayer *pOwner = ToBasePlayer(GetOwner()); + if (!pOwner) + { + return; + } + + const int iSeq = LookupSequence(VM_SEQ_RACK); + if (iSeq >= 0) + { + SetSequence(iSeq); + SendViewModelAnim(iSeq); + } + else + { + SendWeaponAnim(ACT_SHOTGUN_PUMP); + } + + const float flDuration = SequenceDuration(); + m_flRackTime = gpGlobals->curtime + Max(0.0f, flDuration - RACK_BACK_BEFORE_END); + m_flRackSoundTime = Max(gpGlobals->curtime, m_flRackTime - PUMP_WAV_BACK_CLICK); + pOwner->m_flNextAttack = gpGlobals->curtime; + ProposeNextAttack(gpGlobals->curtime + flDuration); + SetWeaponIdleTime(gpGlobals->curtime + flDuration); +} + +void CWeaponSupa7::RunRack() +{ + if (m_flRackSoundTime > 0.0f && gpGlobals->curtime >= m_flRackSoundTime) + { + m_flRackSoundTime = 0.0f; + WeaponSound(SPECIAL2); + } + if (m_flRackTime > 0.0f && gpGlobals->curtime >= m_flRackTime) + { + m_flRackTime = 0.0f; + Rack(); + } +} + +bool CWeaponSupa7::Holster(CBaseCombatWeapon *pSwitchingTo) +{ + // a rack cut off before the back stroke does nothing + m_flRackTime = 0.0f; + m_flRackSoundTime = 0.0f; + m_flPumpHomeTime = 0.0f; + return BaseClass::Holster(pSwitchingTo); +} + +// The script's clip size is the tube, and the chamber holds one more round beside it: +// a dry reload fills the tube and racks one up, leaving room in the tube for a top-off +int CWeaponSupa7::TubeCapacity() const +{ + return GetMaxClip1(); +} + +int CWeaponSupa7::GetMaxRoundsInGun() const +{ + return TubeCapacity() + SUPA7_CHAMBER_ROUNDS; +} + +// Rebuild the tube from the clip count when something set the count directly (a new or picked-up gun, a harness give): +// a live shell (or the loaded slug) in the chamber, shells in the tube +void CWeaponSupa7::SyncTube() +{ + const int iLive = m_nTubeCount + (IsChamberLive() ? 1 : 0); + if (iLive == m_iClip1) + { + return; + } + + const int iClip = Clamp(m_iClip1.Get(), 0, GetMaxRoundsInGun()); + m_nChamber = (iClip > 0) ? (m_bSlugLoaded ? SUPA7_CHAMBER_SLUG : SUPA7_CHAMBER_SHELL) : SUPA7_CHAMBER_EMPTY; + m_nTubeCount = Max(0, iClip - SUPA7_CHAMBER_ROUNDS); + m_nTubeSlugs = 0; + UpdateClipFromTube(); +} + +void CWeaponSupa7::TubePush(bool bSlug) +{ + if (bSlug) + { + m_nTubeSlugs |= (1 << m_nTubeCount); + } + else + { + m_nTubeSlugs &= ~(1 << m_nTubeCount); + } + m_nTubeCount += 1; + UpdateClipFromTube(); +} + +// Pump: the chamber's hull or live round is ejected (a live round is lost), and the tube's top item is fed +void CWeaponSupa7::Rack() +{ + m_bRackPending = false; + if (m_nTubeCount <= 0) + { + m_nChamber = SUPA7_CHAMBER_EMPTY; + UpdateClipFromTube(); + return; + } + + m_nTubeCount -= 1; + const bool bSlug = IsTubeSlug(m_nTubeCount); + m_nTubeSlugs &= ~(1 << m_nTubeCount); + m_nChamber = bSlug ? SUPA7_CHAMBER_SLUG : SUPA7_CHAMBER_SHELL; + UpdateClipFromTube(); +} + +// The counts the rest of the game reads: live rounds in the gun, the slug flag for the chambered round +void CWeaponSupa7::UpdateClipFromTube() +{ + m_iClip1 = m_nTubeCount + (IsChamberLive() ? 1 : 0); + m_bSlugLoaded = (m_nChamber == SUPA7_CHAMBER_SLUG); + m_bShellInChamber = IsChamberLive(); +} + const WeaponSpreadInfo_t &CWeaponSupa7::GetSpreadInfo() { Assert(m_weaponHandling.weaponID & GetNeoWepBits()); diff --git a/src/game/shared/neo/weapons/weapon_supa7.h b/src/game/shared/neo/weapons/weapon_supa7.h index f585f15803..6d8d7e868e 100644 --- a/src/game/shared/neo/weapons/weapon_supa7.h +++ b/src/game/shared/neo/weapons/weapon_supa7.h @@ -63,6 +63,28 @@ class CWeaponSupa7 : public CNEOBaseCombatWeapon void ClearDelayedInputs(void); bool CanBePickedUpByClass(int classId) OVERRIDE; + // the tube plus the round in the chamber + int GetMaxRoundsInGun() const OVERRIDE; + bool HasChamberSlot() const OVERRIDE { return true; } + bool IsRoundChambered() const OVERRIDE { return IsChamberLive() && !IsPumpCycling(); } + // while the shot's pump cycles, the round it feeds still shows in the tube + int RoundsInMagazine() const OVERRIDE { return m_nTubeCount + ((IsChamberLive() && IsPumpCycling()) ? 1 : 0); } + int MagazineCapacity() const OVERRIDE { return TubeCapacity(); } + bool Holster(CBaseCombatWeapon *pSwitchingTo) OVERRIDE; + + // What sits in the chamber: a live shell or slug, the hull of a fired one, or nothing + enum Supa7Chamber_t + { + SUPA7_CHAMBER_EMPTY = 0, + SUPA7_CHAMBER_SHELL, + SUPA7_CHAMBER_SLUG, + SUPA7_CHAMBER_HULL, + }; + // the chamber holds one round, outside the tube + static constexpr int SUPA7_CHAMBER_ROUNDS = 1; + int TubeCount() const { return m_nTubeCount; } + bool IsTubeSlug(int iPosition) const { return (m_nTubeSlugs & (1 << iPosition)) != 0; } + Supa7Chamber_t Chamber() const { return static_cast(m_nChamber.Get()); } protected: virtual float GetFastestDryRefireTime() const OVERRIDE { return 0.2f; } @@ -77,6 +99,17 @@ class CWeaponSupa7 : public CNEOBaseCombatWeapon } } + bool IsChamberLive() const { return m_nChamber == SUPA7_CHAMBER_SHELL || m_nChamber == SUPA7_CHAMBER_SLUG; } + int TubeCapacity() const; + bool IsPumpCycling() const { return gpGlobals->curtime < m_flPumpHomeTime; } + void SyncTube(); + void TubePush(bool bSlug); + void Rack(); + void UpdateClipFromTube(); + void PlayInsertAtShellPace(); + void StartRack(); + void RunRack(); + void SetShotgunShellVisible(bool is_visible) { const int bodygroup_shell = 1; @@ -91,6 +124,15 @@ class CWeaponSupa7 : public CNEOBaseCombatWeapon CNetworkVar(bool, m_bShellInChamber); // Slug currently loaded in chamber CNetworkVar(float, m_flNextReload); // Time to load the next shell + // The tube is a stack, the last item loaded is the first fed; m_iClip1 stays the live rounds in the gun + CNetworkVar(int, m_nTubeCount); // items in the tube + CNetworkVar(int, m_nTubeSlugs); // bit i: the item at tube position i (0 = loaded first) is a slug + CNetworkVar(int, m_nChamber); // Supa7Chamber_t + CNetworkVar(bool, m_bRackPending); // a slug was loaded to be fired next: the reload ends with a rack + CNetworkVar(float, m_flRackTime); // a standalone rack's back stroke: the chamber is ejected and fed then + CNetworkVar(float, m_flRackSoundTime); // when its pump sound starts + CNetworkVar(float, m_flPumpHomeTime); // when the shot's pump is home with the next round, for the HUD + private: CWeaponSupa7(const CWeaponSupa7 &other); };