set g_balance_hagar_reload_ammo 0 //default: 25
set g_balance_hagar_reload_time 2
// }}}
-// {{{ rocketlauncher
-set g_balance_rocketlauncher_damage 70
-set g_balance_rocketlauncher_edgedamage 35
-set g_balance_rocketlauncher_force 450
-set g_balance_rocketlauncher_radius 110
-set g_balance_rocketlauncher_speed 1300
-set g_balance_rocketlauncher_speedaccel 1300
-set g_balance_rocketlauncher_speedstart 1000
-set g_balance_rocketlauncher_lifetime 10
-set g_balance_rocketlauncher_refire 1.2
-set g_balance_rocketlauncher_animtime 0.4
-set g_balance_rocketlauncher_ammo 4
-set g_balance_rocketlauncher_health 30 // 30 // 5 hitpoints above maximum laser value -- this way lasers can't blow it up, but grenadelauncher still can most the time.
-set g_balance_rocketlauncher_damageforcescale 1 // low damage force scale so that it can still be affected by other hits, but not so much that it does a 90 degree turn
-set g_balance_rocketlauncher_detonatedelay 0.02 // positive: timer till detonation is allowed, negative: "security device" that prevents ANY remote detonation if it could hurt its owner, zero: detonatable at any time
-set g_balance_rocketlauncher_guiderate 70 // max degrees per second
-set g_balance_rocketlauncher_guideratedelay 0.01 // immediate
-set g_balance_rocketlauncher_guidegoal 512 // goal distance for (non-laser) guiding (higher = less control, lower = erratic)
-set g_balance_rocketlauncher_guidedelay 0.2 // delay before guiding kicks in
-set g_balance_rocketlauncher_guidestop 0 // stop guiding when firing again
-set g_balance_rocketlauncher_remote_damage 70
-set g_balance_rocketlauncher_remote_edgedamage 35
-set g_balance_rocketlauncher_remote_radius 110
-set g_balance_rocketlauncher_remote_force 400
-set g_balance_rocketlauncher_switchdelay_drop 0.3
-set g_balance_rocketlauncher_switchdelay_raise 0.2
-set g_balance_rocketlauncher_reload_ammo 0 //default: 25
-set g_balance_rocketlauncher_reload_time 2
+// {{{ devastator
- set g_balance_devastator_damage 80
- set g_balance_devastator_edgedamage 40
++set g_balance_devastator_damage 70
++set g_balance_devastator_edgedamage 35
+set g_balance_devastator_force 450
+set g_balance_devastator_radius 110
+set g_balance_devastator_speed 1300
+set g_balance_devastator_speedaccel 1300
+set g_balance_devastator_speedstart 1000
+set g_balance_devastator_lifetime 10
- set g_balance_devastator_refire 1.1
++set g_balance_devastator_refire 1.2
+set g_balance_devastator_animtime 0.4
+set g_balance_devastator_ammo 4
+set g_balance_devastator_health 30 // 30 // 5 hitpoints above maximum laser value -- this way lasers can't blow it up, but mortar still can most the time.
+set g_balance_devastator_damageforcescale 1 // low damage force scale so that it can still be affected by other hits, but not so much that it does a 90 degree turn
+set g_balance_devastator_detonatedelay 0.02 // positive: timer till detonation is allowed, negative: "security device" that prevents ANY remote detonation if it could hurt its owner, zero: detonatable at any time
- set g_balance_devastator_guiderate 90 // max degrees per second
++set g_balance_devastator_guiderate 70 // max degrees per second
+set g_balance_devastator_guideratedelay 0.01 // immediate
+set g_balance_devastator_guidegoal 512 // goal distance for (non-laser) guiding (higher = less control, lower = erratic)
+set g_balance_devastator_guidedelay 0.2 // delay before guiding kicks in
+set g_balance_devastator_guidestop 0 // stop guiding when firing again
+set g_balance_devastator_remote_damage 70
+set g_balance_devastator_remote_edgedamage 35
+set g_balance_devastator_remote_radius 110
+set g_balance_devastator_remote_force 400
- set g_balance_devastator_switchdelay_drop 0.2
++set g_balance_devastator_switchdelay_drop 0.3
+set g_balance_devastator_switchdelay_raise 0.2
+set g_balance_devastator_reload_ammo 0 //default: 25
+set g_balance_devastator_reload_time 2
// }}}
// {{{ porto
set g_balance_porto_primary_refire 1.5
--- /dev/null
- set g_balance_rocketlauncher_damage 80
- set g_balance_rocketlauncher_edgedamage 40
+g_mod_balance XPM
+
+// {{{ starting gear
+set g_start_weapon_laser -1 "0 = never provide the weapon, 1 = always provide the weapon, -1 = game mode default, -2 = provide the weapon in ca and lms"
+set g_start_weapon_shotgun -1 "0 = never provide the weapon, 1 = always provide the weapon, -1 = game mode default, -2 = provide the weapon in ca and lms"
+set g_start_weapon_uzi -1 "0 = never provide the weapon, 1 = always provide the weapon, -1 = game mode default, -2 = provide the weapon in ca and lms"
+set g_start_weapon_grenadelauncher -1 "0 = never provide the weapon, 1 = always provide the weapon, -1 = game mode default, -2 = provide the weapon in ca and lms"
+set g_start_weapon_electro -1 "0 = never provide the weapon, 1 = always provide the weapon, -1 = game mode default, -2 = provide the weapon in ca and lms"
+set g_start_weapon_crylink -1 "0 = never provide the weapon, 1 = always provide the weapon, -1 = game mode default, -2 = provide the weapon in ca and lms"
+set g_start_weapon_nex -1 "0 = never provide the weapon, 1 = always provide the weapon, -1 = game mode default, -2 = provide the weapon in ca and lms"
+set g_start_weapon_hagar -1 "0 = never provide the weapon, 1 = always provide the weapon, -1 = game mode default, -2 = provide the weapon in ca and lms" // UNTIL IT CAN BE REMOVED FROM CODE
+set g_start_weapon_rocketlauncher -1 "0 = never provide the weapon, 1 = always provide the weapon, -1 = game mode default, -2 = provide the weapon in ca and lms"
+set g_start_weapon_minstanex -1 "0 = never provide the weapon, 1 = always provide the weapon, -1 = game mode default, -2 = provide the weapon in ca and lms"
+set g_start_weapon_porto -1 "0 = never provide the weapon, 1 = always provide the weapon, -1 = game mode default, -2 = provide the weapon in ca and lms"
+set g_start_weapon_hook -1 "0 = never provide the weapon, 1 = always provide the weapon, -1 = game mode default, -2 = provide the weapon in ca and lms"
+set g_start_weapon_tuba -1 "0 = never provide the weapon, 1 = always provide the weapon, -1 = game mode default, -2 = provide the weapon in ca and lms"
+set g_start_weapon_fireball -1 "0 = never provide the weapon, 1 = always provide the weapon, -1 = game mode default, -2 = provide the weapon in ca and lms"
+set g_balance_health_start 100
+set g_balance_armor_start 0
+set g_start_ammo_shells 15
+set g_start_ammo_nails 0
+set g_start_ammo_rockets 0
+set g_start_ammo_cells 0
+set g_start_ammo_fuel 0
+set g_warmup_start_health 100 "starting values when being in warmup-stage"
+set g_warmup_start_armor 100 "starting values when being in warmup-stage"
+set g_warmup_start_ammo_shells 30 "starting values when being in warmup-stage"
+set g_warmup_start_ammo_nails 160 "starting values when being in warmup-stage"
+set g_warmup_start_ammo_rockets 80 "starting values when being in warmup-stage"
+set g_warmup_start_ammo_cells 90 "starting values when being in warmup-stage"
+set g_warmup_start_ammo_fuel 0 "starting values when being in warmup-stage"
+set g_lms_start_health 200
+set g_lms_start_armor 200
+set g_lms_start_ammo_shells 60
+set g_lms_start_ammo_nails 320
+set g_lms_start_ammo_rockets 160
+set g_lms_start_ammo_cells 180
+set g_lms_start_ammo_fuel 0
+set g_balance_nix_roundtime 25
+set g_balance_nix_incrtime 1.6
+set g_balance_nix_ammo_shells 60
+set g_balance_nix_ammo_nails 320
+set g_balance_nix_ammo_rockets 160
+set g_balance_nix_ammo_cells 180
+set g_balance_nix_ammo_fuel 0
+set g_balance_nix_ammoincr_shells 2 // eh this will need figured out later I assume
+set g_balance_nix_ammoincr_nails 6
+set g_balance_nix_ammoincr_rockets 2
+set g_balance_nix_ammoincr_cells 2
+set g_balance_nix_ammoincr_fuel 2
+// }}}
+
+// {{{ pickup items
+set g_pickup_ammo_anyway 1
+set g_pickup_weapons_anyway 1
+set g_pickup_shells 15
+set g_pickup_shells_weapon 15
+set g_pickup_shells_max 60
+set g_pickup_nails 80
+set g_pickup_nails_weapon 80
+set g_pickup_nails_max 320
+set g_pickup_rockets 40
+set g_pickup_rockets_weapon 40
+set g_pickup_rockets_max 160
+set g_pickup_cells 30
+set g_pickup_cells_weapon 30
+set g_pickup_cells_max 180
+set g_pickup_fuel 50
+set g_pickup_fuel_weapon 50
+set g_pickup_fuel_jetpack 100
+set g_pickup_fuel_max 100
+set g_pickup_armorsmall 5
+set g_pickup_armorsmall_max 200
+set g_pickup_armorsmall_anyway 0
+set g_pickup_armormedium 25
+set g_pickup_armormedium_max 100
+set g_pickup_armormedium_anyway 0
+set g_pickup_armorbig 50
+set g_pickup_armorbig_max 100
+set g_pickup_armorbig_anyway 0
+set g_pickup_armorlarge 100
+set g_pickup_armorlarge_max 200
+set g_pickup_armorlarge_anyway 0
+set g_pickup_healthsmall 5
+set g_pickup_healthsmall_max 200
+set g_pickup_healthsmall_anyway 0
+set g_pickup_healthmedium 25
+set g_pickup_healthmedium_max 100
+set g_pickup_healthmedium_anyway 0
+set g_pickup_healthlarge 50
+set g_pickup_healthlarge_max 100
+set g_pickup_healthlarge_anyway 0
+set g_pickup_healthmega 100
+set g_pickup_healthmega_max 200
+set g_pickup_healthmega_anyway 0
+set g_pickup_respawntime_short 15
+set g_pickup_respawntime_medium 20
+set g_pickup_respawntime_long 30
+set g_pickup_respawntime_powerup 120
+set g_pickup_respawntime_weapon 10
+set g_pickup_respawntime_superweapon 120
+set g_pickup_respawntime_ammo 15
+set g_pickup_respawntimejitter_short 0
+set g_pickup_respawntimejitter_medium 0
+set g_pickup_respawntimejitter_long 0
+set g_pickup_respawntimejitter_powerup 30
+set g_pickup_respawntimejitter_weapon 0
+set g_pickup_respawntimejitter_superweapon 10
+set g_pickup_respawntimejitter_ammo 0
+// }}}
+
+// {{{ regen/rot
+set g_balance_health_regen 0.08
+set g_balance_health_regenlinear 0.5
+set g_balance_pause_health_regen 5
+set g_balance_pause_health_regen_spawn 0
+set g_balance_health_rot 0.03
+set g_balance_health_rotlinear 0.75
+set g_balance_pause_health_rot 1
+set g_balance_pause_health_rot_spawn 5
+set g_balance_health_regenstable 100
+set g_balance_health_rotstable 100
+set g_balance_health_limit 999
+set g_balance_armor_regen 0
+set g_balance_armor_regenlinear 0
+set g_balance_armor_rot 0.03
+set g_balance_armor_rotlinear 0.75
+set g_balance_pause_armor_rot 1
+set g_balance_pause_armor_rot_spawn 5
+set g_balance_armor_regenstable 100
+set g_balance_armor_rotstable 100
+set g_balance_armor_limit 999
+set g_balance_armor_blockpercent 0.6
+set g_balance_fuel_regen 0.1 "fuel regeneration (only applies if the player owns IT_FUEL_REGEN)"
+set g_balance_fuel_regenlinear 0
+set g_balance_pause_fuel_regen 2 // other than this, fuel uses the health regen counter
+set g_balance_fuel_rot 0.05
+set g_balance_fuel_rotlinear 0
+set g_balance_pause_fuel_rot 5
+set g_balance_pause_fuel_rot_spawn 10
+set g_balance_fuel_regenstable 50
+set g_balance_fuel_rotstable 100
+set g_balance_fuel_limit 999
+// }}}
+
+// {{{ misc
+set g_balance_selfdamagepercent 0.65
+set g_weaponspeedfactor 1 "weapon projectile speed multiplier"
+set g_weaponratefactor 1 "weapon fire rate multiplier"
+set g_weapondamagefactor 1 "weapon damage multiplier"
+set g_weaponforcefactor 1 "weapon force multiplier"
+set g_weaponspreadfactor 1 "weapon spread multiplier"
+set g_balance_firetransfer_time 0.9
+set g_balance_firetransfer_damage 0.8
+set g_throughfloor_damage 0.75
+set g_throughfloor_force 0.75
+set g_projectiles_damage 1
+// possible values:
+// -2: absolutely no damage to projectiles (no exceptions)
+// -1: no damage other than the exceptions (electro combo, hagar join explode, ML mines)
+// 0: only damage from contents (lava/slime) or exceptions
+// 1: only self damage or damage from contents or exceptions
+// 2: allow all damage to projectiles normally
+set g_projectiles_keep_owner 0
+set g_projectiles_newton_style 0
+// possible values:
+// 0: absolute velocity projectiles (like Quake)
+// 1: relative velocity projectiles, "Newtonian" (like Tribes 2)
+// 2: relative velocity projectiles, but aim is precorrected so projectiles hit the crosshair (note: strafe rockets then are SLOWER than ones shot while standing, happens in 1 too when aiming correctly which is hard)
+set g_projectiles_newton_style_2_minfactor 0.8
+set g_projectiles_newton_style_2_maxfactor 1.5
+set g_projectiles_spread_style 7
+// possible values:
+// 0: forward + solid sphere (like Quake) - varies velocity
+// 1: forward + flattened solid sphere
+// 2: forward + solid circle
+// 3: forward + normal distribution 3D - varies velocity
+// 4: forward + normal distribution on a plane
+// 5: forward + circle with 1-r falloff
+// 6: forward + circle with 1-r^2 falloff
+// 7: forward + circle with (1-r)(2-r) falloff
+set g_balance_falldamage_deadminspeed 250
+set g_balance_falldamage_minspeed 900
+set g_balance_falldamage_factor 0.20
+set g_balance_falldamage_maxdamage 40
+set g_balance_damagepush_speedfactor 2.5
+set g_balance_contents_damagerate 0.2 // ticrate interval for applying damage with playerdamage/projectiledamage
+set g_balance_contents_drowndelay 10 // time under water before a player begins drowning
+set g_balance_contents_playerdamage_drowning 20 // damage per second for while player is drowning
+set g_balance_contents_playerdamage_lava 50 // damage per second for while player is inside lava
+set g_balance_contents_playerdamage_slime 30 // damage per second for while player is inside slime
+set g_balance_contents_projectiledamage 10000 // instantly kill projectiles upon touching lava/slime
+set g_maxpushtime 8.0 "timeout for kill credit when your damage knocks someone into a death trap"
+// }}}
+
+// {{{ powerups
+set g_balance_powerup_invincible_takedamage 0.25 // only 1/4th damage is taken
+set g_balance_powerup_invincible_time 30
+set g_balance_powerup_strength_damage 3
+set g_balance_powerup_strength_force 3
+set g_balance_powerup_strength_time 30
+set g_balance_powerup_strength_selfdamage 1.5
+set g_balance_powerup_strength_selfforce 1.5
+set g_balance_superweapons_time 30
+// }}}
+
+// {{{ jetpack/hook
+set g_jetpack_antigravity 0.8 "factor of gravity compensation of the jetpack"
+set g_jetpack_acceleration_side 1200 "acceleration of the jetpack in xy direction"
+set g_jetpack_acceleration_up 600 "acceleration of the jetpack in z direction (note: you have to factor in gravity here, if antigravity is not 1)"
+set g_jetpack_maxspeed_side 1200 "max speed of the jetpack in xy direction"
+set g_jetpack_maxspeed_up 600 "max speed of the jetpack in z direction"
+set g_jetpack_fuel 8 "fuel per second for jetpack"
+set g_jetpack_attenuation 2 "jetpack sound attenuation"
+
+set g_grappling_hook_tarzan 2 // 2: can also pull players
+set g_balance_grapplehook_speed_fly 1800
+set g_balance_grapplehook_speed_pull 2000
+set g_balance_grapplehook_force_rubber 2000
+set g_balance_grapplehook_force_rubber_overstretch 1000
+set g_balance_grapplehook_length_min 50
+set g_balance_grapplehook_stretch 50
+set g_balance_grapplehook_airfriction 0.2
+set g_balance_grapplehook_health 50
+set g_balance_grapplehook_damagedbycontents 1
+// }}}
+
+// {{{ weapon properties
+// {{{ laser
+set g_balance_laser_melee_animtime 0.3
+set g_balance_laser_melee_damage 80
+set g_balance_laser_melee_delay 0.25
+set g_balance_laser_melee_force 200
+set g_balance_laser_melee_multihit 1
+set g_balance_laser_melee_no_doubleslap 1
+set g_balance_laser_melee_nonplayerdamage 40
+set g_balance_laser_melee_range 120
+set g_balance_laser_melee_refire 1.25
+set g_balance_laser_melee_swing_side 120
+set g_balance_laser_melee_swing_up 30
+set g_balance_laser_melee_time 0.15
+set g_balance_laser_melee_traces 10
+
+set g_balance_laser_primary 0 // 0 = shockwave attack, 1 = projectile primary
+set g_balance_laser_primary_damage 25
+set g_balance_laser_primary_edgedamage 12.5
+set g_balance_laser_primary_force 300
+set g_balance_laser_primary_radius 70
+set g_balance_laser_primary_speed 6000
+set g_balance_laser_primary_spread 0
+set g_balance_laser_primary_refire 0.7
+set g_balance_laser_primary_animtime 0.2
+set g_balance_laser_primary_lifetime 5
+set g_balance_laser_primary_shotangle 0
+set g_balance_laser_primary_delay 0
+set g_balance_laser_primary_force_zscale 1.2
+set g_balance_laser_primary_force_velocitybias 0
+set g_balance_laser_primary_force_other_scale 1
+
+set g_balance_laser_secondary 2 // 0 = switch away to last used weapon, 1 = projectile secondary, 2 = melee secondary
+set g_balance_laser_secondary_damage 25
+set g_balance_laser_secondary_edgedamage 12.5
+set g_balance_laser_secondary_force 400
+set g_balance_laser_secondary_radius 70
+set g_balance_laser_secondary_speed 12000
+set g_balance_laser_secondary_spread 0
+set g_balance_laser_secondary_refire 0.7
+set g_balance_laser_secondary_animtime 0.2
+set g_balance_laser_secondary_lifetime 5
+set g_balance_laser_secondary_shotangle -90
+set g_balance_laser_secondary_delay 0
+set g_balance_laser_secondary_force_zscale 1.25
+set g_balance_laser_secondary_force_velocitybias 0
+set g_balance_laser_secondary_force_other_scale 1
+
+set g_balance_laser_shockwave_damage 20
+set g_balance_laser_shockwave_distance 2000
+set g_balance_laser_shockwave_edgedamage 0
+set g_balance_laser_shockwave_force 200
+set g_balance_laser_shockwave_force_forwardbias 50
+set g_balance_laser_shockwave_force_zscale 2
+set g_balance_laser_shockwave_jump_damage 20
+set g_balance_laser_shockwave_jump_edgedamage 0
+set g_balance_laser_shockwave_jump_force 300
+set g_balance_laser_shockwave_jump_force_velocitybias 0
+set g_balance_laser_shockwave_jump_force_zscale 1.25
+set g_balance_laser_shockwave_jump_multiplier_accuracy 0.5
+set g_balance_laser_shockwave_jump_multiplier_distance 0.5
+set g_balance_laser_shockwave_jump_multiplier_min 0
+set g_balance_laser_shockwave_jump_radius 150
+set g_balance_laser_shockwave_multiplier_accuracy 0.5
+set g_balance_laser_shockwave_multiplier_distance 0.5
+set g_balance_laser_shockwave_multiplier_min 0
+set g_balance_laser_shockwave_splash_damage 15
+set g_balance_laser_shockwave_splash_edgedamage 0
+set g_balance_laser_shockwave_splash_force 100
+set g_balance_laser_shockwave_splash_force_forwardbias 50
+set g_balance_laser_shockwave_splash_multiplier_accuracy 0.5
+set g_balance_laser_shockwave_splash_multiplier_distance 0.5
+set g_balance_laser_shockwave_splash_multiplier_min 0
+set g_balance_laser_shockwave_splash_radius 70
+set g_balance_laser_shockwave_spread_max 120
+set g_balance_laser_shockwave_spread_min 25
+
+set g_balance_laser_switchdelay_drop 0.15
+set g_balance_laser_switchdelay_raise 0.15
+set g_balance_laser_reload_ammo 0 //default: 6
+set g_balance_laser_reload_time 2
+// }}}
+// {{{ shotgun
+set g_balance_shotgun_primary_bullets 14
+set g_balance_shotgun_primary_damage 4
+set g_balance_shotgun_primary_force 15
+set g_balance_shotgun_primary_spread 0.12
+set g_balance_shotgun_primary_refire 0.75
+set g_balance_shotgun_primary_animtime 0.2
+set g_balance_shotgun_primary_ammo 1
+set g_balance_shotgun_primary_speed 8000
+set g_balance_shotgun_primary_bulletconstant 75 // 3.8qu
+set g_balance_shotgun_secondary 1
+set g_balance_shotgun_secondary_melee_delay 0.25 // 0.35 was too slow
+set g_balance_shotgun_secondary_melee_range 120
+set g_balance_shotgun_secondary_melee_swing_side 120
+set g_balance_shotgun_secondary_melee_swing_up 30
+set g_balance_shotgun_secondary_melee_time 0.15
+set g_balance_shotgun_secondary_melee_traces 10
+set g_balance_shotgun_secondary_melee_no_doubleslap 1
+set g_balance_shotgun_secondary_melee_nonplayerdamage 40
+set g_balance_shotgun_secondary_melee_multihit 1
+set g_balance_shotgun_secondary_damage 80
+set g_balance_shotgun_secondary_force 200
+set g_balance_shotgun_secondary_refire 1.25
+set g_balance_shotgun_secondary_animtime 1
+set g_balance_shotgun_switchdelay_drop 0.2
+set g_balance_shotgun_switchdelay_raise 0.2
+set g_balance_shotgun_reload_ammo 0 //default: 5
+set g_balance_shotgun_reload_time 2
+// }}}
+// {{{ uzi
+set g_balance_uzi_mode 1 // Activates varible spread for sustained & burst mode secondary
+set g_balance_uzi_spread_min 0.02
+set g_balance_uzi_spread_max 0.05
+set g_balance_uzi_spread_add 0.012
+
+set g_balance_uzi_burst 3 // # of bullets in a burst (if set to 2 or more)
+set g_balance_uzi_burst_animtime 0.3
+set g_balance_uzi_burst_refire 0.06 // refire between burst bullets
+set g_balance_uzi_burst_refire2 0.45 // refire after burst
+set g_balance_uzi_burst_spread 0.02
+set g_balance_uzi_burst_damage 25
+set g_balance_uzi_burst_force 20
+set g_balance_uzi_burst_ammo 3
+
+set g_balance_uzi_first 1
+set g_balance_uzi_first_damage 14
+set g_balance_uzi_first_force 5
+set g_balance_uzi_first_spread 0.03
+set g_balance_uzi_first_refire 0.125
+set g_balance_uzi_first_ammo 1
+
+set g_balance_uzi_sustained_damage 10 // 100 dps
+set g_balance_uzi_sustained_force 5
+set g_balance_uzi_sustained_spread 0.03
+set g_balance_uzi_sustained_refire 0.1
+set g_balance_uzi_sustained_ammo 1
+
+set g_balance_uzi_speed 18000
+set g_balance_uzi_bulletconstant 115 // 13.1qu
+
+set g_balance_uzi_switchdelay_drop 0.2
+set g_balance_uzi_switchdelay_raise 0.2
+
+set g_balance_uzi_reload_ammo 60 //default: 30
+set g_balance_uzi_reload_time 2
+// }}}
+// {{{ mortar
+set g_balance_grenadelauncher_primary_type 0
+set g_balance_grenadelauncher_primary_damage 50
+set g_balance_grenadelauncher_primary_edgedamage 25
+set g_balance_grenadelauncher_primary_force 250
+set g_balance_grenadelauncher_primary_radius 120
+set g_balance_grenadelauncher_primary_speed 1900
+set g_balance_grenadelauncher_primary_speed_up 225
+set g_balance_grenadelauncher_primary_speed_z 0
+set g_balance_grenadelauncher_primary_spread 0
+set g_balance_grenadelauncher_primary_lifetime 5
+set g_balance_grenadelauncher_primary_lifetime2 1
+set g_balance_grenadelauncher_primary_refire 0.8
+set g_balance_grenadelauncher_primary_animtime 0.3
+set g_balance_grenadelauncher_primary_ammo 2
+set g_balance_grenadelauncher_primary_health 15
+set g_balance_grenadelauncher_primary_damageforcescale 0
+set g_balance_grenadelauncher_primary_remote_minbouncecnt 0
+
+set g_balance_grenadelauncher_secondary_type 1
+set g_balance_grenadelauncher_secondary_damage 60
+set g_balance_grenadelauncher_secondary_edgedamage 30
+set g_balance_grenadelauncher_secondary_force 250
+set g_balance_grenadelauncher_secondary_radius 120
+set g_balance_grenadelauncher_secondary_speed 1400
+set g_balance_grenadelauncher_secondary_speed_up 150
+set g_balance_grenadelauncher_secondary_speed_z 0
+set g_balance_grenadelauncher_secondary_spread 0
+set g_balance_grenadelauncher_secondary_lifetime 5
+set g_balance_grenadelauncher_secondary_lifetime_bounce 0.5
+set g_balance_grenadelauncher_secondary_lifetime_stick 0
+set g_balance_grenadelauncher_secondary_refire 0.7
+set g_balance_grenadelauncher_secondary_animtime 0.3
+set g_balance_grenadelauncher_secondary_ammo 2
+set g_balance_grenadelauncher_secondary_health 30
+set g_balance_grenadelauncher_secondary_damageforcescale 4
+set g_balance_grenadelauncher_secondary_remote_detonateprimary 0
+
+set g_balance_grenadelauncher_bouncefactor 0.5
+set g_balance_grenadelauncher_bouncestop 0.075
+
+set g_balance_grenadelauncher_switchdelay_drop 0.2
+set g_balance_grenadelauncher_switchdelay_raise 0.2
+
+set g_balance_grenadelauncher_reload_ammo 0 //default: 12
+set g_balance_grenadelauncher_reload_time 2
+// }}}
+// {{{ electro
+set g_balance_electro_lightning 0
+set g_balance_electro_primary_damage 40
+set g_balance_electro_primary_edgedamage 20
+set g_balance_electro_primary_force 200
+set g_balance_electro_primary_force_up 0
+set g_balance_electro_primary_radius 100
+set g_balance_electro_primary_comboradius 300
+set g_balance_electro_primary_speed 2500
+set g_balance_electro_primary_spread 0
+set g_balance_electro_primary_lifetime 5
+set g_balance_electro_primary_refire 0.6
+set g_balance_electro_primary_animtime 0.3
+set g_balance_electro_primary_ammo 4
+set g_balance_electro_primary_range 0
+set g_balance_electro_primary_falloff_mindist 255 // 0.3 * radius
+set g_balance_electro_primary_falloff_maxdist 850
+set g_balance_electro_primary_falloff_halflifedist 425
+set g_balance_electro_secondary_damage 40
+set g_balance_electro_secondary_edgedamage 20
+set g_balance_electro_secondary_force 50
+set g_balance_electro_secondary_radius 150
+set g_balance_electro_secondary_speed 1000
+set g_balance_electro_secondary_speed_up 200
+set g_balance_electro_secondary_speed_z 0
+set g_balance_electro_secondary_spread 0.04
+set g_balance_electro_secondary_lifetime 4
+set g_balance_electro_secondary_refire 0.2
+set g_balance_electro_secondary_refire2 1.6
+set g_balance_electro_secondary_animtime 0.2
+set g_balance_electro_secondary_ammo 2
+set g_balance_electro_secondary_health 5
+set g_balance_electro_secondary_damageforcescale 4
+set g_balance_electro_secondary_damagedbycontents 1
+set g_balance_electro_secondary_count 3
+set g_balance_electro_secondary_bouncefactor 0.3
+set g_balance_electro_secondary_bouncestop 0.05
+set g_balance_electro_combo_damage 50
+set g_balance_electro_combo_edgedamage 25
+set g_balance_electro_combo_force 120
+set g_balance_electro_combo_radius 150
+set g_balance_electro_combo_comboradius 300
+set g_balance_electro_combo_speed 2000
+set g_balance_electro_combo_safeammocheck 1
+set g_balance_electro_switchdelay_drop 0.2
+set g_balance_electro_switchdelay_raise 0.2
+set g_balance_electro_reload_ammo 0 //default: 20
+set g_balance_electro_reload_time 2
+// }}}
+// {{{ lightning
+set g_balance_lightning_primary_ammo 5
+set g_balance_lightning_primary_animtime 0.2
+set g_balance_lightning_primary_damage 100
+set g_balance_lightning_primary_edgedamage 0
+set g_balance_lightning_primary_falloff_mindist 0
+set g_balance_lightning_primary_falloff_maxdist 0
+set g_balance_lightning_primary_falloff_halflifedist 0
+set g_balance_lightning_primary_force 425
+set g_balance_lightning_primary_lifetime 0
+set g_balance_lightning_primary_radius 850
+set g_balance_lightning_primary_range 800
+set g_balance_lightning_primary_refire 0.4
+set g_balance_lightning_primary_speed 0
+set g_balance_lightning_primary_spread 0
+set g_balance_lightning_secondary_ammo 5
+set g_balance_lightning_secondary_animtime 0.5
+set g_balance_lightning_secondary_damage 100
+set g_balance_lightning_secondary_damageforcescale 4
+set g_balance_lightning_secondary_edgedamage 80
+set g_balance_lightning_secondary_flyingdamage 1
+set g_balance_lightning_secondary_flyingforce -80
+set g_balance_lightning_secondary_flyingradius 200
+set g_balance_lightning_secondary_force -200
+set g_balance_lightning_secondary_health 1
+set g_balance_lightning_secondary_lifetime 30
+set g_balance_lightning_secondary_radius 300
+set g_balance_lightning_secondary_refire 5
+set g_balance_lightning_secondary_speed 600
+// }}}
+// {{{ crylink
+set g_balance_crylink_primary_damage 12
+set g_balance_crylink_primary_edgedamage 6
+set g_balance_crylink_primary_force -50
+set g_balance_crylink_primary_radius 80
+set g_balance_crylink_primary_speed 2000
+set g_balance_crylink_primary_spread 0.08
+set g_balance_crylink_primary_shots 6
+set g_balance_crylink_primary_bounces 1
+set g_balance_crylink_primary_refire 0.7
+set g_balance_crylink_primary_animtime 0.3
+set g_balance_crylink_primary_ammo 3
+set g_balance_crylink_primary_bouncedamagefactor 0.5
+set g_balance_crylink_primary_joindelay 0.1
+set g_balance_crylink_primary_joinspread 0.2
+set g_balance_crylink_primary_joinexplode 1
+set g_balance_crylink_primary_joinexplode_damage 0
+set g_balance_crylink_primary_joinexplode_edgedamage 0
+set g_balance_crylink_primary_joinexplode_radius 0
+set g_balance_crylink_primary_joinexplode_force 0
+set g_balance_crylink_primary_linkexplode 1
+
+set g_balance_crylink_primary_middle_lifetime 5 // range: 35000 full, fades to 70000
+set g_balance_crylink_primary_middle_fadetime 5
+set g_balance_crylink_primary_other_lifetime 5
+set g_balance_crylink_primary_other_fadetime 5
+
+set g_balance_crylink_secondary 1
+set g_balance_crylink_secondary_damage 10
+set g_balance_crylink_secondary_edgedamage 5
+set g_balance_crylink_secondary_force -250
+set g_balance_crylink_secondary_radius 100
+set g_balance_crylink_secondary_speed 3000
+set g_balance_crylink_secondary_spread 0.01
+set g_balance_crylink_secondary_spreadtype 1
+set g_balance_crylink_secondary_shots 5
+set g_balance_crylink_secondary_bounces 0
+set g_balance_crylink_secondary_refire 0.7
+set g_balance_crylink_secondary_animtime 0.2
+set g_balance_crylink_secondary_ammo 2
+set g_balance_crylink_secondary_bouncedamagefactor 0.5
+set g_balance_crylink_secondary_joindelay 0
+set g_balance_crylink_secondary_joinspread 0
+set g_balance_crylink_secondary_joinexplode 0
+set g_balance_crylink_secondary_joinexplode_damage 0
+set g_balance_crylink_secondary_joinexplode_edgedamage 0
+set g_balance_crylink_secondary_joinexplode_radius 0
+set g_balance_crylink_secondary_joinexplode_force 0
+set g_balance_crylink_secondary_linkexplode 1
+
+set g_balance_crylink_secondary_middle_lifetime 5 // range: 35000 full, fades to 70000
+set g_balance_crylink_secondary_middle_fadetime 5
+set g_balance_crylink_secondary_line_lifetime 5
+set g_balance_crylink_secondary_line_fadetime 5
+
+set g_balance_crylink_switchdelay_drop 0.2
+set g_balance_crylink_switchdelay_raise 0.2
+
+set g_balance_crylink_reload_ammo 0 //default: 10
+set g_balance_crylink_reload_time 2
+// }}}
+// {{{ nex
+set g_balance_nex_primary_damage 80
+set g_balance_nex_primary_force 400
+set g_balance_nex_primary_refire 1.5
+set g_balance_nex_primary_animtime 0.6
+set g_balance_nex_primary_ammo 6
+set g_balance_nex_primary_damagefalloff_mindist 0 // 1000 For tZork ;3
+set g_balance_nex_primary_damagefalloff_maxdist 0 // 3000
+set g_balance_nex_primary_damagefalloff_halflife 0 // 1500
+set g_balance_nex_primary_damagefalloff_forcehalflife 0 // 1500
+
+set g_balance_nex_secondary 0
+set g_balance_nex_secondary_charge 0
+set g_balance_nex_secondary_charge_rate 0.1
+set g_balance_nex_secondary_chargepool 0
+set g_balance_nex_secondary_chargepool_regen 0.15
+set g_balance_nex_secondary_chargepool_pause_regen 1
+set g_balance_nex_secondary_chargepool_pause_health_regen 1
+set g_balance_nex_secondary_damage 0
+set g_balance_nex_secondary_force 0
+set g_balance_nex_secondary_refire 0
+set g_balance_nex_secondary_animtime 0
+set g_balance_nex_secondary_ammo 2
+set g_balance_nex_secondary_damagefalloff_mindist 0
+set g_balance_nex_secondary_damagefalloff_maxdist 0
+set g_balance_nex_secondary_damagefalloff_halflife 0
+set g_balance_nex_secondary_damagefalloff_forcehalflife 0
+
+set g_balance_nex_charge 1
+set g_balance_nex_charge_mindmg 40
+set g_balance_nex_charge_start 0.5
+set g_balance_nex_charge_rate 0.4
+set g_balance_nex_charge_animlimit 0.5
+set g_balance_nex_charge_limit 1
+set g_balance_nex_charge_rot_rate 0
+set g_balance_nex_charge_rot_pause 0 // Dont rot down until this long after release of charge button
+set g_balance_nex_charge_shot_multiplier 0
+set g_balance_nex_charge_velocity_rate 0
+set g_balance_nex_charge_minspeed 400
+set g_balance_nex_charge_maxspeed 800
+
+set g_balance_nex_switchdelay_drop 0.3
+set g_balance_nex_switchdelay_raise 0.25
+
+set g_balance_nex_reload_ammo 0 //default: 25
+set g_balance_nex_reload_time 2
+// }}}
+// {{{ minstanex
+set g_balance_minstanex_refire 1
+set g_balance_minstanex_animtime 0.3
+set g_balance_minstanex_ammo 10
+set g_balance_minstanex_laser_ammo 0
+set g_balance_minstanex_laser_animtime 0.3
+set g_balance_minstanex_laser_refire 0.7
+set g_balance_minstanex_switchdelay_drop 0.2
+set g_balance_minstanex_switchdelay_raise 0.2
+set g_balance_minstanex_reload_ammo 0 //default: 50
+set g_balance_minstanex_reload_time 2
+// }}}
+// {{{ hagar
+set g_balance_hagar_primary_damage 25
+set g_balance_hagar_primary_edgedamage 12.5
+set g_balance_hagar_primary_force 100
+set g_balance_hagar_primary_health 15
+set g_balance_hagar_primary_damageforcescale 0
+set g_balance_hagar_primary_radius 65
+set g_balance_hagar_primary_spread 0.03
+set g_balance_hagar_primary_speed 2500
+set g_balance_hagar_primary_lifetime 5
+set g_balance_hagar_primary_refire 0.16667 // 6 rockets per second
+set g_balance_hagar_primary_ammo 1
+set g_balance_hagar_secondary 1
+set g_balance_hagar_secondary_load 1
+set g_balance_hagar_secondary_load_speed 0.5
+set g_balance_hagar_secondary_load_spread 0.075
+set g_balance_hagar_secondary_load_spread_bias 0.5
+set g_balance_hagar_secondary_load_max 4
+set g_balance_hagar_secondary_load_hold 4
+set g_balance_hagar_secondary_load_releasedeath 0
+set g_balance_hagar_secondary_load_abort 0
+set g_balance_hagar_secondary_load_linkexplode 0
+set g_balance_hagar_secondary_load_animtime 0.2
+set g_balance_hagar_secondary_damage 40
+set g_balance_hagar_secondary_edgedamage 20
+set g_balance_hagar_secondary_force 75
+set g_balance_hagar_secondary_health 15
+set g_balance_hagar_secondary_damageforcescale 0
+set g_balance_hagar_secondary_radius 80
+set g_balance_hagar_secondary_spread 0.05
+set g_balance_hagar_secondary_speed 2000
+set g_balance_hagar_secondary_lifetime_min 10
+set g_balance_hagar_secondary_lifetime_rand 0
+set g_balance_hagar_secondary_refire 0.5
+set g_balance_hagar_secondary_ammo 1
+set g_balance_hagar_switchdelay_drop 0.2
+set g_balance_hagar_switchdelay_raise 0.2
+set g_balance_hagar_reload_ammo 0 //default: 25
+set g_balance_hagar_reload_time 2
+// }}}
+// {{{ rocketlauncher
- set g_balance_rocketlauncher_refire 1.1
++set g_balance_rocketlauncher_damage 70
++set g_balance_rocketlauncher_edgedamage 35
+set g_balance_rocketlauncher_force 450
+set g_balance_rocketlauncher_radius 110
+set g_balance_rocketlauncher_speed 1300
+set g_balance_rocketlauncher_speedaccel 1300
+set g_balance_rocketlauncher_speedstart 1000
+set g_balance_rocketlauncher_lifetime 10
- set g_balance_rocketlauncher_guiderate 90 // max degrees per second
++set g_balance_rocketlauncher_refire 1.2
+set g_balance_rocketlauncher_animtime 0.4
+set g_balance_rocketlauncher_ammo 4
+set g_balance_rocketlauncher_health 30 // 30 // 5 hitpoints above maximum laser value -- this way lasers can't blow it up, but grenadelauncher still can most the time.
+set g_balance_rocketlauncher_damageforcescale 1 // low damage force scale so that it can still be affected by other hits, but not so much that it does a 90 degree turn
+set g_balance_rocketlauncher_detonatedelay 0.02 // positive: timer till detonation is allowed, negative: "security device" that prevents ANY remote detonation if it could hurt its owner, zero: detonatable at any time
- set g_balance_rocketlauncher_switchdelay_drop 0.2
++set g_balance_rocketlauncher_guiderate 70 // max degrees per second
+set g_balance_rocketlauncher_guideratedelay 0.01 // immediate
+set g_balance_rocketlauncher_guidegoal 512 // goal distance for (non-laser) guiding (higher = less control, lower = erratic)
+set g_balance_rocketlauncher_guidedelay 0.2 // delay before guiding kicks in
+set g_balance_rocketlauncher_guidestop 0 // stop guiding when firing again
+set g_balance_rocketlauncher_remote_damage 70
+set g_balance_rocketlauncher_remote_edgedamage 35
+set g_balance_rocketlauncher_remote_radius 110
+set g_balance_rocketlauncher_remote_force 400
++set g_balance_rocketlauncher_switchdelay_drop 0.3
+set g_balance_rocketlauncher_switchdelay_raise 0.2
+set g_balance_rocketlauncher_reload_ammo 0 //default: 25
+set g_balance_rocketlauncher_reload_time 2
+// }}}
+// {{{ porto
+set g_balance_porto_primary_refire 1.5
+set g_balance_porto_primary_animtime 0.3
+set g_balance_porto_primary_speed 1000
+set g_balance_porto_primary_lifetime 5
+set g_balance_porto_secondary 1
+set g_balance_porto_secondary_refire 1.5
+set g_balance_porto_secondary_animtime 0.3
+set g_balance_porto_secondary_speed 1000
+set g_balance_porto_secondary_lifetime 5
+set g_balance_porto_switchdelay_drop 0.2
+set g_balance_porto_switchdelay_raise 0.2
+set g_balance_portal_health 200 // these get recharged whenever the portal is used
+set g_balance_portal_lifetime 15 // these get recharged whenever the portal is used
+// }}}
+// {{{ hook
+set g_balance_hook_primary_fuel 5 // hook monkeys set 0
+set g_balance_hook_primary_refire 0 // hook monkeys set 0
+set g_balance_hook_primary_animtime 0.3 // good shoot anim
+set g_balance_hook_primary_hooked_time_max 0 // infinite
+set g_balance_hook_primary_hooked_time_free 2 // 2s being hooked are free
+set g_balance_hook_primary_hooked_fuel 5 // fuel per second hooked
+set g_balance_hook_secondary_damage 25 // not much
+set g_balance_hook_secondary_edgedamage 5 // not much
+set g_balance_hook_secondary_radius 500 // LOTS
+set g_balance_hook_secondary_force -2000 // LOTS
+set g_balance_hook_secondary_ammo 30 // a whole pack
+set g_balance_hook_secondary_lifetime 5 // infinite
+set g_balance_hook_secondary_speed 0 // not much throwing
+set g_balance_hook_secondary_gravity 5 // fast falling
+set g_balance_hook_secondary_refire 3 // don't drop too many bombs...
+set g_balance_hook_secondary_animtime 0.3 // good shoot anim
+set g_balance_hook_secondary_power 3 // effect behaves like a square function
+set g_balance_hook_secondary_duration 1.5 // effect runs for three seconds
+set g_balance_hook_secondary_health 15
+set g_balance_hook_secondary_damageforcescale 0
+set g_balance_hook_switchdelay_drop 0.2
+set g_balance_hook_switchdelay_raise 0.2
+// }}}
+// {{{ tuba
+set g_balance_tuba_refire 0.05
+set g_balance_tuba_animtime 0.05
+set g_balance_tuba_attenuation 0.5
+set g_balance_tuba_volume 1
+set g_balance_tuba_fadetime 0.25
+set g_balance_tuba_damage 5
+set g_balance_tuba_edgedamage 0
+set g_balance_tuba_radius 200
+set g_balance_tuba_force 40
+set g_balance_tuba_pitchstep 6
+set g_balance_tuba_switchdelay_drop 0.2
+set g_balance_tuba_switchdelay_raise 0.2
+// }}}
+// {{{ fireball // this is a superweapon -- lets make it behave as one.
+set g_balance_fireball_primary_animtime 0.2
+set g_balance_fireball_primary_bfgdamage 100
+set g_balance_fireball_primary_bfgforce 0
+set g_balance_fireball_primary_bfgradius 1000
+set g_balance_fireball_primary_damage 200
+set g_balance_fireball_primary_damageforcescale 0
+set g_balance_fireball_primary_edgedamage 50
+set g_balance_fireball_primary_force 600
+set g_balance_fireball_primary_health 0
+set g_balance_fireball_primary_laserburntime 0.5
+set g_balance_fireball_primary_laserdamage 80
+set g_balance_fireball_primary_laseredgedamage 20
+set g_balance_fireball_primary_laserradius 256
+set g_balance_fireball_primary_lifetime 15
+set g_balance_fireball_primary_radius 200
+set g_balance_fireball_primary_refire 2
+set g_balance_fireball_primary_refire2 0
+set g_balance_fireball_primary_speed 1200
+set g_balance_fireball_primary_spread 0
+set g_balance_fireball_secondary_animtime 0.3
+set g_balance_fireball_secondary_damage 40
+set g_balance_fireball_secondary_damageforcescale 4
+set g_balance_fireball_secondary_damagetime 5
+set g_balance_fireball_secondary_force 100
+set g_balance_fireball_secondary_laserburntime 0.5
+set g_balance_fireball_secondary_laserdamage 50
+set g_balance_fireball_secondary_laseredgedamage 20
+set g_balance_fireball_secondary_laserradius 110
+set g_balance_fireball_secondary_lifetime 7
+set g_balance_fireball_secondary_refire 1.5
+set g_balance_fireball_secondary_speed 900
+set g_balance_fireball_secondary_speed_up 100
+set g_balance_fireball_secondary_speed_z 0
+set g_balance_fireball_secondary_spread 0
+set g_balance_fireball_switchdelay_drop 0.2
+set g_balance_fireball_switchdelay_raise 0.2
+// }}}
case ENT_CLIENT_WARPZONE_TELEPORTED: WarpZone_Teleported_Read(bIsNewEntity); break;
case ENT_CLIENT_TRIGGER_MUSIC: Ent_ReadTriggerMusic(); break;
case ENT_CLIENT_HOOK: Ent_ReadHook(bIsNewEntity, ENT_CLIENT_HOOK); break;
- case ENT_CLIENT_LGBEAM: Ent_ReadHook(bIsNewEntity, ENT_CLIENT_LGBEAM); break;
- case ENT_CLIENT_GAUNTLET: Ent_ReadHook(bIsNewEntity, ENT_CLIENT_GAUNTLET); break;
+ case ENT_CLIENT_ELECTRO_BEAM: Ent_ReadHook(bIsNewEntity, ENT_CLIENT_ELECTRO_BEAM); break;
+ case ENT_CLIENT_ARC_BEAM: Ent_ReadHook(bIsNewEntity, ENT_CLIENT_ARC_BEAM); break;
case ENT_CLIENT_ACCURACY: Ent_ReadAccuracy(); break;
case ENT_CLIENT_AUXILIARYXHAIR: Net_AuXair2(bIsNewEntity); break;
- case ENT_CLIENT_TURRET: ent_turret(); break;
+ case ENT_CLIENT_TURRET: ent_turret(); break;
case ENT_CLIENT_MODEL: CSQCModel_Read(bIsNewEntity); break;
- case ENT_CLIENT_ITEM: ItemRead(bIsNewEntity); break;
+ case ENT_CLIENT_ITEM: ItemRead(bIsNewEntity); break;
case ENT_CLIENT_BUMBLE_RAYGUN: bumble_raygun_read(bIsNewEntity); break;
case ENT_CLIENT_SPAWNPOINT: Ent_ReadSpawnPoint(bIsNewEntity); break;
case ENT_CLIENT_SPAWNEVENT: Ent_ReadSpawnEvent(bIsNewEntity); break;
--- /dev/null
- rot = self.avelocity;
+.vector iorigin1, iorigin2;
+.float spawntime;
+.vector trail_oldorigin;
+.float trail_oldtime;
+.float fade_time, fade_rate;
+
+void SUB_Stop()
+{
+ self.move_velocity = self.move_avelocity = '0 0 0';
+ self.move_movetype = MOVETYPE_NONE;
+}
+
+.float alphamod;
+.float count; // set if clientside projectile
+.float cnt; // sound index
+.float gravity;
+.float snd_looping;
+.float silent;
+
+void Projectile_ResetTrail(vector to)
+{
+ self.trail_oldorigin = to;
+ self.trail_oldtime = time;
+}
+
+void Projectile_DrawTrail(vector to)
+{
+ vector from;
+ float t0;
+
+ from = self.trail_oldorigin;
+ t0 = self.trail_oldtime;
+ self.trail_oldorigin = to;
+ self.trail_oldtime = time;
+
+ // force the effect even for stationary firemine
+ if(self.cnt == PROJECTILE_FIREMINE)
+ if(from == to)
+ from_z += 1;
+
+ if (self.traileffect)
+ {
+ particles_alphamin = particles_alphamax = particles_fade = sqrt(self.alpha);
+ boxparticles(self.traileffect, self, from, to, self.velocity, self.velocity, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE | PARTICLES_DRAWASTRAIL);
+ }
+}
+
+void Projectile_Draw()
+{
+ vector rot;
+ vector trailorigin;
+ float f;
+ float drawn;
+ float t;
+ float a;
+
+ f = self.move_flags;
+
+ if(self.count & 0x80)
+ {
+ //self.move_flags &= ~FL_ONGROUND;
+ if(self.move_movetype == MOVETYPE_NONE || self.move_movetype == MOVETYPE_FLY)
+ Movetype_Physics_NoMatchServer();
+ // the trivial movetypes do not have to match the
+ // server's ticrate as they are ticrate independent
+ // NOTE: this assumption is only true if MOVETYPE_FLY
+ // projectiles detonate on impact. If they continue
+ // moving, we might still be ticrate dependent.
+ else
+ Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy);
+ if(!(self.move_flags & FL_ONGROUND))
+ if(self.velocity != '0 0 0')
+ self.move_angles = self.angles = vectoangles(self.velocity);
+ }
+ else
+ {
+ InterpolateOrigin_Do();
+ }
+
+ if(self.count & 0x80)
+ {
+ drawn = (time >= self.spawntime - 0.02);
+ t = max(time, self.spawntime);
+ }
+ else
+ {
+ drawn = (self.iflags & IFLAG_VALID);
+ t = time;
+ }
+
+ if(!(f & FL_ONGROUND))
+ {
+ rot = '0 0 0';
+ switch(self.cnt)
+ {
+ /*
+ case PROJECTILE_GRENADE:
+ rot = '-2000 0 0'; // forward
+ break;
+ */
+ case PROJECTILE_GRENADE_BOUNCING:
+ rot = '0 -1000 0'; // sideways
+ break;
+ case PROJECTILE_NADE_RED_BURN:
+ case PROJECTILE_NADE_RED:
+ case PROJECTILE_NADE_BLUE_BURN:
+ case PROJECTILE_NADE_BLUE:
+ case PROJECTILE_NADE_YELLOW_BURN:
+ case PROJECTILE_NADE_YELLOW:
+ case PROJECTILE_NADE_PINK_BURN:
+ case PROJECTILE_NADE_PINK:
+ case PROJECTILE_NADE_BURN:
+ case PROJECTILE_NADE:
- case PROJECTILE_BULLET_GLOWING:
- case PROJECTILE_BULLET_GLOWING_TRACER:
- adddynamiclight(self.origin, 50 * a, '1 1 0');
- break;
++ rot = self.avelocity;
+ break;
+ case PROJECTILE_HOOKBOMB:
+ rot = '1000 0 0'; // forward
+ break;
+ default:
+ break;
+ }
+ self.angles = AnglesTransform_ToAngles(AnglesTransform_Multiply(AnglesTransform_FromAngles(self.angles), rot * (t - self.spawntime)));
+ }
+
+ vector ang;
+ ang = self.angles;
+ ang_x = -ang_x;
+ makevectors(ang);
+
+ a = 1 - (time - self.fade_time) * self.fade_rate;
+ self.alpha = bound(0, self.alphamod * a, 1);
+ if(self.alpha <= 0)
+ drawn = 0;
+ self.renderflags = 0;
+
+ trailorigin = self.origin;
+ switch(self.cnt)
+ {
+ case PROJECTILE_NADE_RED_BURN:
+ case PROJECTILE_NADE_RED:
+ case PROJECTILE_NADE_BLUE_BURN:
+ case PROJECTILE_NADE_BLUE:
+ case PROJECTILE_NADE_YELLOW_BURN:
+ case PROJECTILE_NADE_YELLOW:
+ case PROJECTILE_NADE_PINK_BURN:
+ case PROJECTILE_NADE_PINK:
+ case PROJECTILE_NADE_BURN:
+ case PROJECTILE_NADE:
+ trailorigin += v_up * 4;
+ break;
+ case PROJECTILE_GRENADE:
+ case PROJECTILE_GRENADE_BOUNCING:
+ trailorigin += v_right * 1 + v_forward * -10;
+ break;
+ default:
+ break;
+ }
+ if(drawn)
+ Projectile_DrawTrail(trailorigin);
+ else
+ Projectile_ResetTrail(trailorigin);
+
+ self.drawmask = 0;
+
+ if(!drawn)
+ return;
+
+ switch(self.cnt)
+ {
- case PROJECTILE_BULLET: setmodel(self, "models/tracer.mdl");self.traileffect = particleeffectnum("tr_bullet"); break;
- case PROJECTILE_BULLET_GLOWING: setmodel(self, "models/tracer.mdl");self.traileffect = particleeffectnum("tr_rifle_weak"); break;
- case PROJECTILE_BULLET_GLOWING_TRACER: setmodel(self, "models/tracer.mdl");self.traileffect = particleeffectnum("tr_rifle"); break;
++ // Possibly add dlights here.
+ default:
+ break;
+ }
+
+ self.drawmask = MASK_NORMAL;
+}
+
+void loopsound(entity e, float ch, string samp, float vol, float attn)
+{
+ if(self.silent)
+ return;
+
+ sound(e, ch, samp, vol, attn);
+ e.snd_looping = ch;
+}
+
+void Ent_RemoveProjectile()
+{
+ if(self.count & 0x80)
+ {
+ tracebox(self.origin, self.mins, self.maxs, self.origin + self.velocity * 0.05, MOVE_NORMAL, self);
+ Projectile_DrawTrail(trace_endpos);
+ }
+}
+
+void Ent_Projectile()
+{
+ float f;
+
+ // projectile properties:
+ // kind (interpolated, or clientside)
+ //
+ // modelindex
+ // origin
+ // scale
+ // if clientside:
+ // velocity
+ // gravity
+ // soundindex (hardcoded list)
+ // effects
+ //
+ // projectiles don't send angles, because they always follow the velocity
+
+ f = ReadByte();
+ self.count = (f & 0x80);
+ self.iflags = (self.iflags & IFLAG_INTERNALMASK) | IFLAG_AUTOANGLES | IFLAG_ANGLES | IFLAG_ORIGIN;
+ self.solid = SOLID_TRIGGER;
+ //self.effects = EF_NOMODELFLAGS;
+
+ // this should make collisions with bmodels more exact, but it leads to
+ // projectiles no longer being able to lie on a bmodel
+ self.move_nomonsters = MOVE_WORLDONLY;
+ if(f & 0x40)
+ self.move_flags |= FL_ONGROUND;
+ else
+ self.move_flags &= ~FL_ONGROUND;
+
+ if(!self.move_time)
+ {
+ // for some unknown reason, we don't need to care for
+ // sv_gameplayfix_delayprojectiles here.
+ self.move_time = time;
+ self.spawntime = time;
+ }
+ else
+ self.move_time = max(self.move_time, time);
+
+ if(!(self.count & 0x80))
+ InterpolateOrigin_Undo();
+
+ if(f & 1)
+ {
+ self.origin_x = ReadCoord();
+ self.origin_y = ReadCoord();
+ self.origin_z = ReadCoord();
+ setorigin(self, self.origin);
+ if(self.count & 0x80)
+ {
+ self.velocity_x = ReadCoord();
+ self.velocity_y = ReadCoord();
+ self.velocity_z = ReadCoord();
+ if(f & 0x10)
+ self.gravity = ReadCoord();
+ else
+ self.gravity = 0; // none
+ self.move_origin = self.origin;
+ self.move_velocity = self.velocity;
+ }
+
+ if(time == self.spawntime || (self.count & 0x80) || (f & 0x08))
+ {
+ self.trail_oldorigin = self.origin;
+ if(!(self.count & 0x80))
+ InterpolateOrigin_Reset();
+ }
+
+ if(f & 0x20)
+ {
+ self.fade_time = time + ReadByte() * ticrate;
+ self.fade_rate = 1 / (ReadByte() * ticrate);
+ }
+ else
+ {
+ self.fade_time = 0;
+ self.fade_rate = 0;
+ }
+ }
+
+ if(f & 2)
+ {
+ self.cnt = ReadByte();
+
+ self.silent = (self.cnt & 0x80);
+ self.cnt = (self.cnt & 0x7F);
+
+ self.scale = 1;
+ self.traileffect = 0;
+ switch(self.cnt)
+ {
+ case PROJECTILE_ELECTRO: setmodel(self, "models/ebomb.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break;
+ case PROJECTILE_ROCKET: setmodel(self, "models/rocket.md3");self.traileffect = particleeffectnum("TR_ROCKET"); self.scale = 2; break;
-
+ case PROJECTILE_CRYLINK: setmodel(self, "models/plasmatrail.mdl");self.traileffect = particleeffectnum("TR_CRYLINKPLASMA"); break;
+ case PROJECTILE_CRYLINK_BOUNCING: setmodel(self, "models/plasmatrail.mdl");self.traileffect = particleeffectnum("TR_CRYLINKPLASMA"); break;
+ case PROJECTILE_ELECTRO_BEAM: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break;
+ case PROJECTILE_GRENADE: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_GRENADE"); break;
+ case PROJECTILE_GRENADE_BOUNCING: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_GRENADE"); break;
+ case PROJECTILE_MINE: setmodel(self, "models/mine.md3");self.traileffect = particleeffectnum("TR_GRENADE"); break;
+ case PROJECTILE_LASER: setmodel(self, "models/laser.mdl");self.traileffect = particleeffectnum(""); break;
+ case PROJECTILE_HLAC: setmodel(self, "models/hlac_bullet.md3");self.traileffect = particleeffectnum(""); break;
+ case PROJECTILE_PORTO_RED: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_WIZSPIKE"); self.scale = 4; break;
+ case PROJECTILE_PORTO_BLUE: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_WIZSPIKE"); self.scale = 4; break;
+ case PROJECTILE_HOOKBOMB: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_KNIGHTSPIKE"); break;
+ case PROJECTILE_HAGAR: setmodel(self, "models/hagarmissile.mdl");self.traileffect = particleeffectnum("tr_hagar"); self.scale = 0.75; break;
+ case PROJECTILE_HAGAR_BOUNCING: setmodel(self, "models/hagarmissile.mdl");self.traileffect = particleeffectnum("tr_hagar"); self.scale = 0.75; break;
+ case PROJECTILE_FIREBALL: self.model = ""; self.modelindex = 0; self.traileffect = particleeffectnum("fireball"); break; // particle effect is good enough
+ case PROJECTILE_FIREMINE: self.model = ""; self.modelindex = 0; self.traileffect = particleeffectnum("firemine"); break; // particle effect is good enough
+ case PROJECTILE_TAG: setmodel(self, "models/laser.mdl"); self.traileffect = particleeffectnum("TR_ROCKET"); break;
+ case PROJECTILE_FLAC: setmodel(self, "models/hagarmissile.mdl"); self.scale = 0.4; self.traileffect = particleeffectnum("TR_SEEKER"); break;
+ case PROJECTILE_SEEKER: setmodel(self, "models/tagrocket.md3"); self.traileffect = particleeffectnum("TR_SEEKER"); break;
+
+ case PROJECTILE_RAPTORBOMB: setmodel(self, "models/vehicles/clusterbomb.md3"); self.gravity = 1; self.avelocity = '0 0 180'; self.traileffect = particleeffectnum(""); break;
+ case PROJECTILE_RAPTORBOMBLET: setmodel(self, "models/vehicles/bomblet.md3"); self.gravity = 1; self.avelocity = '0 0 180'; self.traileffect = particleeffectnum(""); break;
+ case PROJECTILE_RAPTORCANNON: setmodel(self, "models/plasmatrail.mdl"); self.traileffect = particleeffectnum("TR_CRYLINKPLASMA"); break;
+
+ case PROJECTILE_SPIDERROCKET: setmodel(self, "models/vehicles/rocket02.md3"); self.traileffect = particleeffectnum("spiderbot_rocket_thrust"); break;
+ case PROJECTILE_WAKIROCKET: setmodel(self, "models/vehicles/rocket01.md3"); self.traileffect = particleeffectnum("wakizashi_rocket_thrust"); break;
+ case PROJECTILE_WAKICANNON: setmodel(self, "models/laser.mdl"); self.traileffect = particleeffectnum(""); break;
+
+ case PROJECTILE_BUMBLE_GUN: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break;
+ case PROJECTILE_BUMBLE_BEAM: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break;
- break;
++
+ case PROJECTILE_NADE_RED: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_red"); break;
+ case PROJECTILE_NADE_RED_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_red_burn"); break;
+ case PROJECTILE_NADE_BLUE: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_blue"); break;
+ case PROJECTILE_NADE_BLUE_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_blue_burn"); break;
+ case PROJECTILE_NADE_YELLOW: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_yellow"); break;
+ case PROJECTILE_NADE_YELLOW_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_yellow_burn"); break;
+ case PROJECTILE_NADE_PINK: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_pink"); break;
+ case PROJECTILE_NADE_PINK_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_pink_burn"); break;
+ case PROJECTILE_NADE: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade"); break;
+ case PROJECTILE_NADE_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_burn"); break;
+
+ default:
+ error("Received invalid CSQC projectile, can't work with this!");
+ break;
+ }
+
+ self.mins = '0 0 0';
+ self.maxs = '0 0 0';
+ self.colormod = '0 0 0';
+ self.move_touch = SUB_Stop;
+ self.move_movetype = MOVETYPE_TOSS;
+ self.alphamod = 1;
+
+ switch(self.cnt)
+ {
+ case PROJECTILE_ELECTRO:
+ // only new engines support sound moving with object
+ loopsound(self, CH_SHOTS_SINGLE, "weapons/electro_fly.wav", VOL_BASE, ATTEN_NORM);
+ self.mins = '0 0 -4';
+ self.maxs = '0 0 -4';
+ self.move_movetype = MOVETYPE_BOUNCE;
+ self.move_touch = func_null;
+ self.move_bounce_factor = g_balance_electro_secondary_bouncefactor;
+ self.move_bounce_stopspeed = g_balance_electro_secondary_bouncestop;
+ break;
+ case PROJECTILE_ROCKET:
+ loopsound(self, CH_SHOTS_SINGLE, "weapons/rocket_fly.wav", VOL_BASE, ATTEN_NORM);
+ self.mins = '-3 -3 -3';
+ self.maxs = '3 3 3';
+ break;
+ case PROJECTILE_GRENADE:
+ self.mins = '-3 -3 -3';
+ self.maxs = '3 3 3';
+ break;
+ case PROJECTILE_NADE_RED_BURN:
+ case PROJECTILE_NADE_RED:
+ case PROJECTILE_NADE_BLUE_BURN:
+ case PROJECTILE_NADE_BLUE:
+ self.mins = '-3 -3 -3';
+ self.maxs = '3 3 3';
+ self.move_movetype = MOVETYPE_BOUNCE;
+ self.move_touch = func_null;
+ self.scale = 1.5;
+ self.avelocity = randomvec() * 720;
+ break;
+ case PROJECTILE_GRENADE_BOUNCING:
+ self.mins = '-3 -3 -3';
+ self.maxs = '3 3 3';
+ self.move_movetype = MOVETYPE_BOUNCE;
+ self.move_touch = func_null;
+ self.move_bounce_factor = g_balance_grenadelauncher_bouncefactor;
+ self.move_bounce_stopspeed = g_balance_grenadelauncher_bouncestop;
+ break;
+ case PROJECTILE_NADE_RED_BURN:
+ case PROJECTILE_NADE_RED:
+ case PROJECTILE_NADE_BLUE_BURN:
+ case PROJECTILE_NADE_BLUE:
+ case PROJECTILE_NADE_YELLOW_BURN:
+ case PROJECTILE_NADE_YELLOW:
+ case PROJECTILE_NADE_PINK_BURN:
+ case PROJECTILE_NADE_PINK:
+ case PROJECTILE_NADE_BURN:
+ case PROJECTILE_NADE:
+ self.mins = '-16 -16 -16';
+ self.maxs = '16 16 16';
+ self.move_movetype = MOVETYPE_BOUNCE;
+ self.move_touch = func_null;
+ self.scale = 1.5;
+ self.avelocity = randomvec() * 720;
+ break;
+ case PROJECTILE_MINE:
+ self.mins = '-4 -4 -4';
+ self.maxs = '4 4 4';
+ break;
+ case PROJECTILE_PORTO_RED:
+ self.colormod = '2 1 1';
+ self.alphamod = 0.5;
+ self.move_movetype = MOVETYPE_BOUNCE;
+ self.move_touch = func_null;
+ break;
+ case PROJECTILE_PORTO_BLUE:
+ self.colormod = '1 1 2';
+ self.alphamod = 0.5;
+ self.move_movetype = MOVETYPE_BOUNCE;
+ self.move_touch = func_null;
+ break;
+ case PROJECTILE_HAGAR_BOUNCING:
+ self.move_movetype = MOVETYPE_BOUNCE;
+ self.move_touch = func_null;
+ break;
+ case PROJECTILE_CRYLINK_BOUNCING:
+ self.move_movetype = MOVETYPE_BOUNCE;
+ self.move_touch = func_null;
+ break;
+ case PROJECTILE_FIREBALL:
+ loopsound(self, CH_SHOTS_SINGLE, "weapons/fireball_fly2.wav", VOL_BASE, ATTEN_NORM);
+ self.mins = '-16 -16 -16';
+ self.maxs = '16 16 16';
+ break;
+ case PROJECTILE_FIREMINE:
+ loopsound(self, CH_SHOTS_SINGLE, "weapons/fireball_fly.wav", VOL_BASE, ATTEN_NORM);
+ self.move_movetype = MOVETYPE_BOUNCE;
+ self.move_touch = func_null;
+ self.mins = '-4 -4 -4';
+ self.maxs = '4 4 4';
+ break;
+ case PROJECTILE_TAG:
+ self.mins = '-2 -2 -2';
+ self.maxs = '2 2 2';
+ break;
+ case PROJECTILE_FLAC:
+ self.mins = '-2 -2 -2';
+ self.maxs = '2 2 2';
+ break;
+ case PROJECTILE_SEEKER:
+ loopsound(self, CH_SHOTS_SINGLE, "weapons/tag_rocket_fly.wav", VOL_BASE, ATTEN_NORM);
+ self.mins = '-4 -4 -4';
+ self.maxs = '4 4 4';
+ break;
+ case PROJECTILE_RAPTORBOMB:
+ self.mins = '-3 -3 -3';
+ self.maxs = '3 3 3';
+ break;
+ case PROJECTILE_RAPTORBOMBLET:
+ break;
+ case PROJECTILE_RAPTORCANNON:
+ break;
+ case PROJECTILE_SPIDERROCKET:
+ loopsound(self, CH_SHOTS_SINGLE, "weapons/tag_rocket_fly.wav", VOL_BASE, ATTEN_NORM);
+ break;
+ case PROJECTILE_WAKIROCKET:
+ loopsound(self, CH_SHOTS_SINGLE, "weapons/tag_rocket_fly.wav", VOL_BASE, ATTEN_NORM);
-
++ break;
+ /*
+ case PROJECTILE_WAKICANNON:
+ break;
+ case PROJECTILE_BUMBLE_GUN:
+ // only new engines support sound moving with object
+ loopsound(self, CH_SHOTS_SINGLE, "weapons/electro_fly.wav", VOL_BASE, ATTEN_NORM);
+ self.mins = '0 0 -4';
+ self.maxs = '0 0 -4';
+ self.move_movetype = MOVETYPE_BOUNCE;
+ self.move_touch = func_null;
+ self.move_bounce_factor = g_balance_electro_secondary_bouncefactor;
+ self.move_bounce_stopspeed = g_balance_electro_secondary_bouncestop;
+ break;
+ */
+ default:
+ break;
+ }
+ setsize(self, self.mins, self.maxs);
+ }
+
+ if(self.gravity)
+ {
+ if(self.move_movetype == MOVETYPE_FLY)
+ self.move_movetype = MOVETYPE_TOSS;
+ if(self.move_movetype == MOVETYPE_BOUNCEMISSILE)
+ self.move_movetype = MOVETYPE_BOUNCE;
+ }
+ else
+ {
+ if(self.move_movetype == MOVETYPE_TOSS)
+ self.move_movetype = MOVETYPE_FLY;
+ if(self.move_movetype == MOVETYPE_BOUNCE)
+ self.move_movetype = MOVETYPE_BOUNCEMISSILE;
+ }
+
+ if(!(self.count & 0x80))
+ InterpolateOrigin_Note();
+
+ self.draw = Projectile_Draw;
+ self.entremove = Ent_RemoveProjectile;
+}
+
+void Projectile_Precache()
+{
+ precache_model("models/ebomb.mdl");
+ precache_model("models/elaser.mdl");
+ precache_model("models/grenademodel.md3");
+ precache_model("models/mine.md3");
+ precache_model("models/hagarmissile.mdl");
+ precache_model("models/hlac_bullet.md3");
+ precache_model("models/laser.mdl");
+ precache_model("models/plasmatrail.mdl");
+ precache_model("models/rocket.md3");
+ precache_model("models/tagrocket.md3");
+ precache_model("models/tracer.mdl");
++
+ precache_model("models/weapons/v_ok_grenade.md3");
+
+ precache_sound("weapons/electro_fly.wav");
+ precache_sound("weapons/rocket_fly.wav");
+ precache_sound("weapons/fireball_fly.wav");
+ precache_sound("weapons/fireball_fly2.wav");
+ precache_sound("weapons/tag_rocket_fly.wav");
+
+}
#ifndef MENUQC
float fh, alsoprint = FALSE;
string filename = argv(1);
-
+
if(filename == "")
{
- filename = "notifications.cfg";
+ filename = "notifications_dump.cfg";
alsoprint = FALSE;
}
else if(filename == "-")
--- /dev/null
-
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id */ ELECTRO,
+/* function */ w_electro,
+/* ammotype */ IT_CELLS,
+/* impulse */ 5,
+/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
+/* rating */ BOT_PICKUP_RATING_MID,
+/* model */ "electro",
+/* netname */ "electro",
+/* fullname */ _("Electro")
+);
+
+#ifdef SVQC
+void ElectroInit();
+vector electro_shotorigin[4];
+var float autocvar_g_balance_electro_combo_comboradius_thruwall = 200;
+#endif
+#else
+#ifdef SVQC
+void spawnfunc_weapon_electro() { weapon_defaultspawnfunc(WEP_ELECTRO); }
+
+.float electro_count;
+.float electro_secondarytime;
+
+void W_Plasma_Explode_Combo (void);
+
+void W_Plasma_TriggerCombo(vector org, float rad, entity own)
+{
+ entity e;
+ e = WarpZone_FindRadius(org, rad, !autocvar_g_balance_electro_combo_comboradius_thruwall);
+ while (e)
+ {
+ if (e.classname == "plasma")
+ {
+ // change owner to whoever caused the combo explosion
+ WarpZone_TraceLine(org, e.origin, MOVE_NOMONSTERS, e);
+
+ if((trace_fraction == 1) || (autocvar_g_balance_electro_combo_comboradius_thruwall > e.WarpZone_findradius_dist))
+ {
+ e.realowner = own;
+ e.takedamage = DAMAGE_NO;
+ e.classname = "plasma_chain";
+ e.think = W_Plasma_Explode_Combo;
+ e.nextthink = time + vlen(e.WarpZone_findradius_dist) / autocvar_g_balance_electro_combo_speed; // delay combo chains, looks cooler
+ }
+ }
+ e = e.chain;
+ }
+}
+
+void W_Plasma_Explode (void)
+{
+ if(other.takedamage == DAMAGE_AIM)
+ if(IS_PLAYER(other))
+ if(DIFF_TEAM(self.realowner, other))
+ if(other.deadflag == DEAD_NO)
+ if(IsFlying(other))
+ Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH);
+
+ self.event_damage = func_null;
+ self.takedamage = DAMAGE_NO;
+ if (self.movetype == MOVETYPE_BOUNCE)
+ {
+ RadiusDamage (self, self.realowner, autocvar_g_balance_electro_secondary_damage, autocvar_g_balance_electro_secondary_edgedamage, autocvar_g_balance_electro_secondary_radius, world, world, autocvar_g_balance_electro_secondary_force, self.projectiledeathtype, other);
+ }
+ else
+ {
+ W_Plasma_TriggerCombo(self.origin, autocvar_g_balance_electro_primary_comboradius, self.realowner);
+ RadiusDamage (self, self.realowner, autocvar_g_balance_electro_primary_damage, autocvar_g_balance_electro_primary_edgedamage, autocvar_g_balance_electro_primary_radius, world, world, autocvar_g_balance_electro_primary_force, self.projectiledeathtype, other);
+ }
+
+ remove (self);
+}
+
+void W_Plasma_Explode_Combo (void)
+{
+ W_Plasma_TriggerCombo(self.origin, autocvar_g_balance_electro_combo_comboradius, self.realowner);
+
+ self.event_damage = func_null;
+ RadiusDamage (self, self.realowner, autocvar_g_balance_electro_combo_damage, autocvar_g_balance_electro_combo_edgedamage, autocvar_g_balance_electro_combo_radius, world, world, autocvar_g_balance_electro_combo_force, WEP_ELECTRO | HITTYPE_BOUNCE, world); // use THIS type for a combo because primary can't bounce
+
+ remove (self);
+}
+
+void W_Plasma_Touch (void)
+{
+ //self.velocity = self.velocity * 0.1;
+
+ PROJECTILE_TOUCH;
+ if (other.takedamage == DAMAGE_AIM) {
+ W_Plasma_Explode ();
+ } else {
+ //UpdateCSQCProjectile(self);
+ spamsound (self, CH_SHOTS, "weapons/electro_bounce.wav", VOL_BASE, ATTEN_NORM);
+ self.projectiledeathtype |= HITTYPE_BOUNCE;
+ }
+}
+
+void W_Plasma_TouchExplode (void)
+{
+ PROJECTILE_TOUCH;
+ W_Plasma_Explode ();
+}
+
+void W_Plasma_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ if(self.health <= 0)
+ return;
+
+ // note: combos are usually triggered by W_Plasma_TriggerCombo, not damage
+ float is_combo = (inflictor.classname == "plasma_chain" || inflictor.classname == "plasma_prim");
- return; // g_projectiles_damage says to halt
-
++
+ if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_combo ? 1 : -1)))
- if not(owner_player.items & IT_UNLIMITED_WEAPON_AMMO)
++ return; // g_projectiles_damage says to halt
++
+ self.health = self.health - damage;
+ if (self.health <= 0)
+ {
+ self.takedamage = DAMAGE_NO;
+ self.nextthink = time;
+ if (is_combo)
+ {
+ // change owner to whoever caused the combo explosion
+ self.realowner = inflictor.realowner;
+ self.classname = "plasma_chain";
+ self.think = W_Plasma_Explode_Combo;
+ self.nextthink = time + min(autocvar_g_balance_electro_combo_radius, vlen(self.origin - inflictor.origin)) / autocvar_g_balance_electro_combo_speed; // delay combo chains, looks cooler
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ bounding the length, because inflictor may be in a galaxy far far away (warpzones)
+ }
+ else
+ {
+ self.use = W_Plasma_Explode;
+ self.think = adaptor_think2use; // not _hittype_splash, as this runs "immediately"
+ }
+ }
+}
+
+void W_Electro_Attack()
+{
+ entity proj;
+
+ W_DecreaseAmmo(ammo_cells, autocvar_g_balance_electro_primary_ammo, autocvar_g_balance_electro_reload_ammo);
+
+ W_SetupShot_ProjectileSize (self, '0 0 -3', '0 0 -3', FALSE, 2, "weapons/electro_fire.wav", CH_WEAPON_A, autocvar_g_balance_electro_primary_damage);
+
+ pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+ proj = spawn ();
+ proj.classname = "plasma_prim";
+ proj.owner = proj.realowner = self;
+ proj.bot_dodge = TRUE;
+ proj.bot_dodgerating = autocvar_g_balance_electro_primary_damage;
+ proj.use = W_Plasma_Explode;
+ proj.think = adaptor_think2use_hittype_splash;
+ proj.nextthink = time + autocvar_g_balance_electro_primary_lifetime;
+ PROJECTILE_MAKETRIGGER(proj);
+ proj.projectiledeathtype = WEP_ELECTRO;
+ setorigin(proj, w_shotorg);
+
+ proj.movetype = MOVETYPE_FLY;
+ W_SETUPPROJECTILEVELOCITY(proj, g_balance_electro_primary);
+ proj.angles = vectoangles(proj.velocity);
+ proj.touch = W_Plasma_TouchExplode;
+ setsize(proj, '0 0 -3', '0 0 -3');
+ proj.flags = FL_PROJECTILE;
+ proj.missile_flags = MIF_SPLASH;
+
+ CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO_BEAM, TRUE);
+
+ other = proj; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+void W_Electro_Attack2()
+{
+ entity proj;
+
+ W_DecreaseAmmo(ammo_cells, autocvar_g_balance_electro_secondary_ammo, autocvar_g_balance_electro_reload_ammo);
+
+ W_SetupShot_ProjectileSize (self, '0 0 -4', '0 0 -4', FALSE, 2, "weapons/electro_fire2.wav", CH_WEAPON_A, autocvar_g_balance_electro_secondary_damage);
+
+ w_shotdir = v_forward; // no TrueAim for grenades please
+
+ pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+ proj = spawn ();
+ proj.classname = "plasma";
+ proj.owner = proj.realowner = self;
+ proj.use = W_Plasma_Explode;
+ proj.think = adaptor_think2use_hittype_splash;
+ proj.bot_dodge = TRUE;
+ proj.bot_dodgerating = autocvar_g_balance_electro_secondary_damage;
+ proj.nextthink = time + autocvar_g_balance_electro_secondary_lifetime;
+ PROJECTILE_MAKETRIGGER(proj);
+ proj.projectiledeathtype = WEP_ELECTRO | HITTYPE_SECONDARY;
+ setorigin(proj, w_shotorg);
+
+ //proj.glow_size = 50;
+ //proj.glow_color = 45;
+ proj.movetype = MOVETYPE_BOUNCE;
+ W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_electro_secondary);
+ proj.touch = W_Plasma_Touch;
+ setsize(proj, '0 0 -4', '0 0 -4');
+ proj.takedamage = DAMAGE_YES;
+ proj.damageforcescale = autocvar_g_balance_electro_secondary_damageforcescale;
+ proj.health = autocvar_g_balance_electro_secondary_health;
+ proj.event_damage = W_Plasma_Damage;
+ proj.flags = FL_PROJECTILE;
+ proj.damagedbycontents = (autocvar_g_balance_electro_secondary_damagedbycontents);
+
+ proj.bouncefactor = autocvar_g_balance_electro_secondary_bouncefactor;
+ proj.bouncestop = autocvar_g_balance_electro_secondary_bouncestop;
+ proj.missile_flags = MIF_SPLASH | MIF_ARC;
+
+#if 0
+ entity p2;
+ p2 = spawn();
+ copyentity(proj, p2);
+ setmodel(p2, "models/ebomb.mdl");
+ setsize(p2, proj.mins, proj.maxs);
+#endif
+
+ CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO, FALSE); // no culling, it has sound
+
+ other = proj; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+.vector hook_start, hook_end;
+float lgbeam_send(entity to, float sf)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_ELECTRO_BEAM);
+ sf = sf & 0x7F;
+ if(sound_allowed(MSG_BROADCAST, self.realowner))
+ sf |= 0x80;
+ WriteByte(MSG_ENTITY, sf);
+ if(sf & 1)
+ {
+ WriteByte(MSG_ENTITY, num_for_edict(self.realowner));
+ WriteCoord(MSG_ENTITY, autocvar_g_balance_electro_primary_range);
+ }
+ if(sf & 2)
+ {
+ WriteCoord(MSG_ENTITY, self.hook_start_x);
+ WriteCoord(MSG_ENTITY, self.hook_start_y);
+ WriteCoord(MSG_ENTITY, self.hook_start_z);
+ }
+ if(sf & 4)
+ {
+ WriteCoord(MSG_ENTITY, self.hook_end_x);
+ WriteCoord(MSG_ENTITY, self.hook_end_y);
+ WriteCoord(MSG_ENTITY, self.hook_end_z);
+ }
+ return TRUE;
+}
+.entity lgbeam;
+.float prevlgfire;
+float lgbeam_checkammo()
+{
+ if(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO)
+ return TRUE;
+ else if(autocvar_g_balance_electro_reload_ammo)
+ return self.realowner.clip_load > 0;
+ else
+ return self.realowner.ammo_cells > 0;
+}
+
+entity lgbeam_owner_ent;
+void lgbeam_think()
+{
+ entity owner_player;
+ owner_player = self.realowner;
+
+ owner_player.prevlgfire = time;
+ if (self != owner_player.lgbeam)
+ {
+ remove(self);
+ return;
+ }
+
+ if (owner_player.weaponentity.state != WS_INUSE || !lgbeam_checkammo() || owner_player.deadflag != DEAD_NO || !owner_player.BUTTON_ATCK || owner_player.freezetag_frozen)
+ {
+ if(self == owner_player.lgbeam)
+ owner_player.lgbeam = world;
+ remove(self);
+ return;
+ }
+
+ self.nextthink = time;
+
+ makevectors(owner_player.v_angle);
+
+ float dt, f;
+ dt = frametime;
+
+ // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
++ if (!(owner_player.items & IT_UNLIMITED_WEAPON_AMMO))
+ {
+ if(autocvar_g_balance_electro_primary_ammo)
+ {
+ if(autocvar_g_balance_electro_reload_ammo)
+ {
+ dt = min(dt, owner_player.clip_load / autocvar_g_balance_electro_primary_ammo);
+ owner_player.clip_load = max(0, owner_player.clip_load - autocvar_g_balance_electro_primary_ammo * frametime);
+ owner_player.(weapon_load[WEP_ELECTRO]) = owner_player.clip_load;
+ }
+ else
+ {
+ dt = min(dt, owner_player.ammo_cells / autocvar_g_balance_electro_primary_ammo);
+ owner_player.ammo_cells = max(0, owner_player.ammo_cells - autocvar_g_balance_electro_primary_ammo * frametime);
+ }
+ }
+ }
+
+ W_SetupShot_Range(owner_player, TRUE, 0, "", 0, autocvar_g_balance_electro_primary_damage * dt, autocvar_g_balance_electro_primary_range);
+ if(!lgbeam_owner_ent)
+ {
+ lgbeam_owner_ent = spawn();
+ lgbeam_owner_ent.classname = "lgbeam_owner_ent";
+ }
+ WarpZone_traceline_antilag(lgbeam_owner_ent, w_shotorg, w_shotend, MOVE_NORMAL, lgbeam_owner_ent, ANTILAG_LATENCY(owner_player));
+
+ // apply the damage
+ if(trace_ent)
+ {
+ vector force;
+ force = w_shotdir * autocvar_g_balance_electro_primary_force + '0 0 1' * autocvar_g_balance_electro_primary_force_up;
+
+ f = ExponentialFalloff(autocvar_g_balance_electro_primary_falloff_mindist, autocvar_g_balance_electro_primary_falloff_maxdist, autocvar_g_balance_electro_primary_falloff_halflifedist, vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - w_shotorg));
+
+ if(accuracy_isgooddamage(owner_player, trace_ent))
+ accuracy_add(owner_player, WEP_ELECTRO, 0, autocvar_g_balance_electro_primary_damage * dt * f);
+ Damage (trace_ent, owner_player, owner_player, autocvar_g_balance_electro_primary_damage * dt * f, WEP_ELECTRO, trace_endpos, force * dt);
+ }
+ W_Plasma_TriggerCombo(trace_endpos, autocvar_g_balance_electro_primary_comboradius, owner_player);
+
+ // draw effect
+ if(w_shotorg != self.hook_start)
+ {
+ self.SendFlags |= 2;
+ self.hook_start = w_shotorg;
+ }
+ if(w_shotend != self.hook_end)
+ {
+ self.SendFlags |= 4;
+ self.hook_end = w_shotend;
+ }
+}
+
+// experimental lightning gun
+void W_Electro_Attack3 (void)
+{
+ // only play fire sound if 0.5 sec has passed since player let go the fire button
+ if(time - self.prevlgfire > 0.5)
+ sound (self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTEN_NORM);
+
+ entity beam, oldself;
+
+ self.lgbeam = beam = spawn();
+ beam.classname = "lgbeam";
+ beam.solid = SOLID_NOT;
+ beam.think = lgbeam_think;
+ beam.owner = beam.realowner = self;
+ beam.movetype = MOVETYPE_NONE;
+ beam.shot_spread = 0;
+ beam.bot_dodge = TRUE;
+ beam.bot_dodgerating = autocvar_g_balance_electro_primary_damage;
+ Net_LinkEntity(beam, FALSE, 0, lgbeam_send);
+
+ oldself = self;
+ self = beam;
+ self.think();
+ self = oldself;
+}
+
+void ElectroInit()
+{
+ WEP_ACTION(WEP_ELECTRO, WR_INIT);
+ electro_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 1);
+ electro_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 2);
+ electro_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 3);
+ electro_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 4);
+}
+
+void w_electro_checkattack()
+{
+ if(self.electro_count > 1)
+ if(self.BUTTON_ATCK2)
+ if(weapon_prepareattack(1, -1))
+ {
+ W_Electro_Attack2();
+ self.electro_count -= 1;
+ weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_electro_secondary_animtime, w_electro_checkattack);
+ return;
+ }
+
+ w_ready();
+}
+
+.float bot_secondary_electromooth;
+.float BUTTON_ATCK_prev;
+float w_electro(float req)
+{
+ float ammo_amount;
+ switch(req)
+ {
+ case WR_AIM:
+ {
+ self.BUTTON_ATCK=FALSE;
+ self.BUTTON_ATCK2=FALSE;
+ if(vlen(self.origin-self.enemy.origin) > 1000)
+ self.bot_secondary_electromooth = 0;
+ if(self.bot_secondary_electromooth == 0)
+ {
+ float shoot;
+
+ if(autocvar_g_balance_electro_primary_speed)
+ shoot = bot_aim(autocvar_g_balance_electro_primary_speed, 0, autocvar_g_balance_electro_primary_lifetime, FALSE);
+ else
+ shoot = bot_aim(1000000, 0, 0.001, FALSE);
+
+ if(shoot)
+ {
+ self.BUTTON_ATCK = TRUE;
+ if(random() < 0.01) self.bot_secondary_electromooth = 1;
+ }
+ }
+ else
+ {
+ if(bot_aim(autocvar_g_balance_electro_secondary_speed, autocvar_g_balance_mortar_secondary_speed_up, autocvar_g_balance_electro_secondary_lifetime, TRUE)) // WHAT THE ACTUAL FUUUUUUUUUCK?!?!? WEAPONTODO
+ {
+ self.BUTTON_ATCK2 = TRUE;
+ if(random() < 0.03) self.bot_secondary_electromooth = 0;
+ }
+ }
+
+ return TRUE;
+ }
+ case WR_THINK:
+ {
+ if(autocvar_g_balance_electro_reload_ammo) // forced reload
+ {
+ ammo_amount = 0;
+ if(autocvar_g_balance_electro_lightning)
+ {
+ if(self.clip_load > 0)
+ ammo_amount = 1;
+ }
+ else if(self.clip_load >= autocvar_g_balance_electro_primary_ammo)
+ ammo_amount = 1;
+ if(self.clip_load >= autocvar_g_balance_electro_secondary_ammo)
+ ammo_amount += 1;
+
+ if(!ammo_amount)
+ {
+ WEP_ACTION(self.weapon, WR_RELOAD);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ if (self.BUTTON_ATCK)
+ {
+ if(autocvar_g_balance_electro_lightning)
+ if(self.BUTTON_ATCK_prev)
+ weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
+
+ if (weapon_prepareattack(0, (autocvar_g_balance_electro_lightning ? 0 : autocvar_g_balance_electro_primary_refire)))
+ {
+ if(autocvar_g_balance_electro_lightning)
+ {
+ if ((!self.lgbeam) || wasfreed(self.lgbeam))
+ {
+ W_Electro_Attack3();
+ }
+ if(!self.BUTTON_ATCK_prev)
+ {
+ weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
+ self.BUTTON_ATCK_prev = 1;
+ }
+ }
+ else
+ {
+ W_Electro_Attack();
+ weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
+ }
+ }
+ } else {
+ if(autocvar_g_balance_electro_lightning)
+ {
+ if (self.BUTTON_ATCK_prev != 0)
+ {
+ weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
+ ATTACK_FINISHED(self) = time + autocvar_g_balance_electro_primary_refire * W_WeaponRateFactor();
+ }
+ self.BUTTON_ATCK_prev = 0;
+ }
+
+ if (self.BUTTON_ATCK2)
+ {
+ if (time >= self.electro_secondarytime)
+ if (weapon_prepareattack(1, autocvar_g_balance_electro_secondary_refire))
+ {
+ W_Electro_Attack2();
+ self.electro_count = autocvar_g_balance_electro_secondary_count;
+ weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_electro_secondary_animtime, w_electro_checkattack);
+ self.electro_secondarytime = time + autocvar_g_balance_electro_secondary_refire2 * W_WeaponRateFactor();
+ }
+ }
+ }
+
+ return TRUE;
+ }
+ case WR_INIT:
+ {
+ precache_model ("models/weapons/g_electro.md3");
+ precache_model ("models/weapons/v_electro.md3");
+ precache_model ("models/weapons/h_electro.iqm");
+ precache_sound ("weapons/electro_bounce.wav");
+ precache_sound ("weapons/electro_fire.wav");
+ precache_sound ("weapons/electro_fire2.wav");
+ precache_sound ("weapons/electro_impact.wav");
+ precache_sound ("weapons/electro_impact_combo.wav");
+
+ if(autocvar_g_balance_electro_lightning)
+ precache_sound ("weapons/lgbeam_fire.wav");
+
+ return TRUE;
+ }
+ case WR_SETUP:
+ {
+ self.current_ammo = ammo_cells;
+ return TRUE;
+ }
+ case WR_CHECKAMMO1:
+ {
+ if(autocvar_g_balance_electro_lightning)
+ {
+ if(!autocvar_g_balance_electro_primary_ammo)
+ ammo_amount = 1;
+ else
+ ammo_amount = self.ammo_cells > 0;
+ ammo_amount += self.(weapon_load[WEP_ELECTRO]) > 0;
+ }
+ else
+ {
+ ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_primary_ammo;
+ ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_primary_ammo;
+ }
+ return ammo_amount;
+ }
+ case WR_CHECKAMMO2:
+ {
+ if(autocvar_g_balance_electro_combo_safeammocheck) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false.
+ {
+ ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_secondary_ammo + autocvar_g_balance_electro_primary_ammo;
+ ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_secondary_ammo + autocvar_g_balance_electro_primary_ammo;
+ }
+ else
+ {
+ ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_secondary_ammo;
+ ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_secondary_ammo;
+ }
+ return ammo_amount;
+ }
+ case WR_RESETPLAYER:
+ {
+ self.electro_secondarytime = time;
+ return TRUE;
+ }
+ case WR_RELOAD:
+ {
+ W_Reload(min(autocvar_g_balance_electro_primary_ammo, autocvar_g_balance_electro_secondary_ammo), "weapons/reload.wav");
+ return TRUE;
+ }
+ case WR_SUICIDEMESSAGE:
+ {
+ if(w_deathtype & HITTYPE_SECONDARY)
+ return WEAPON_ELECTRO_SUICIDE_ORBS;
+ else
+ return WEAPON_ELECTRO_SUICIDE_BOLT;
+ }
+ case WR_KILLMESSAGE:
+ {
+ if(w_deathtype & HITTYPE_SECONDARY)
+ {
+ return WEAPON_ELECTRO_MURDER_ORBS;
+ }
+ else
+ {
+ if(w_deathtype & HITTYPE_BOUNCE)
+ return WEAPON_ELECTRO_MURDER_COMBO;
+ else
+ return WEAPON_ELECTRO_MURDER_BOLT;
+ }
+ }
+ }
+ return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_electro(float req)
+{
+ switch(req)
+ {
+ case WR_IMPACTEFFECT:
+ {
+ vector org2;
+ org2 = w_org + w_backoff * 6;
+ if(w_deathtype & HITTYPE_SECONDARY)
+ {
+ pointparticles(particleeffectnum("electro_ballexplode"), org2, '0 0 0', 1);
+ if(!w_issilent)
+ sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTEN_NORM);
+ }
+ else
+ {
+ if(w_deathtype & HITTYPE_BOUNCE)
+ {
+ // this is sent as "primary (w_deathtype & HITTYPE_BOUNCE)" to distinguish it from (w_deathtype & HITTYPE_SECONDARY) bounced balls
+ pointparticles(particleeffectnum("electro_combo"), org2, '0 0 0', 1);
+ if(!w_issilent)
+ sound(self, CH_SHOTS, "weapons/electro_impact_combo.wav", VOL_BASE, ATTEN_NORM);
+ }
+ else
+ {
+ pointparticles(particleeffectnum("electro_impact"), org2, '0 0 0', 1);
+ if(!w_issilent)
+ sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTEN_NORM);
+ }
+ }
+
+ return TRUE;
+ }
+ case WR_INIT:
+ {
+ precache_sound("weapons/electro_impact.wav");
+ precache_sound("weapons/electro_impact_combo.wav");
+ return TRUE;
+ }
+ }
+ return TRUE;
+}
+#endif
+#endif
--- /dev/null
-
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id */ FIREBALL,
+/* function */ w_fireball,
+/* ammotype */ 0,
+/* impulse */ 9,
+/* flags */ WEP_FLAG_SUPERWEAPON | WEP_TYPE_SPLASH,
+/* rating */ BOT_PICKUP_RATING_MID,
+/* model */ "fireball",
+/* netname */ "fireball",
+/* fullname */ _("Fireball")
+);
+#define FIREBALL_SETTINGS(weapon) \
+ WEP_ADD_CVAR(weapon, MO_BOTH, animtime) \
+ WEP_ADD_CVAR(weapon, MO_BOTH, refire) \
+ WEP_ADD_CVAR(weapon, MO_BOTH, damage) \
+ WEP_ADD_CVAR(weapon, MO_BOTH, damageforcescale) \
+ WEP_ADD_CVAR(weapon, MO_BOTH, speed) \
+ WEP_ADD_CVAR(weapon, MO_BOTH, lifetime) \
+ WEP_ADD_CVAR(weapon, MO_BOTH, laserburntime) \
+ WEP_ADD_CVAR(weapon, MO_BOTH, laserdamage) \
+ WEP_ADD_CVAR(weapon, MO_BOTH, laseredgedamage) \
+ WEP_ADD_CVAR(weapon, MO_BOTH, laserradius) \
+ WEP_ADD_CVAR(weapon, MO_PRI, edgedamage) \
+ WEP_ADD_CVAR(weapon, MO_PRI, force) \
+ WEP_ADD_CVAR(weapon, MO_PRI, radius) \
+ WEP_ADD_CVAR(weapon, MO_PRI, health) \
+ WEP_ADD_CVAR(weapon, MO_PRI, refire2) \
+ WEP_ADD_CVAR(weapon, MO_PRI, bfgdamage) \
+ WEP_ADD_CVAR(weapon, MO_PRI, bfgforce) \
+ WEP_ADD_CVAR(weapon, MO_PRI, bfgradius) \
+ WEP_ADD_CVAR(weapon, MO_SEC, damagetime) \
+ WEP_ADD_CVAR(weapon, MO_SEC, speed_up) \
+ WEP_ADD_PROP(weapon, reloading_ammo, reload_ammo) \
+ WEP_ADD_PROP(weapon, reloading_time, reload_time) \
+ WEP_ADD_PROP(weapon, switchdelay_raise, switchdelay_raise) \
+ WEP_ADD_PROP(weapon, switchdelay_drop, switchdelay_drop)
+
+#ifdef SVQC
+FIREBALL_SETTINGS(fireball)
+.float bot_primary_fireballmooth; // whatever a mooth is
+.vector fireball_impactvec;
+.float fireball_primarytime;
+#endif
+#else
+#ifdef SVQC
+void spawnfunc_weapon_fireball() { weapon_defaultspawnfunc(WEP_FIREBALL); }
+
+void W_Fireball_Explode (void)
+{
+ entity e;
+ float dist;
+ float points;
+ vector dir;
+ float d;
+
+ self.event_damage = func_null;
+ self.takedamage = DAMAGE_NO;
+
+ // 1. dist damage
+ d = (self.realowner.health + self.realowner.armorvalue);
+ RadiusDamage (self, self.realowner, WEP_CVAR_PRI(fireball, damage), WEP_CVAR_PRI(fireball, edgedamage), WEP_CVAR_PRI(fireball, radius), world, world, WEP_CVAR_PRI(fireball, force), self.projectiledeathtype, other);
+ if(self.realowner.health + self.realowner.armorvalue >= d)
+ if(!self.cnt)
+ {
+ modeleffect_spawn("models/sphere/sphere.md3", 0, 0, self.origin, '0 0 0', '0 0 0', '0 0 0', 0, WEP_CVAR_PRI(fireball, bfgradius), 0.2, 0.05, 0.25);
+
+ // 2. bfg effect
+ // NOTE: this cannot be made warpzone aware by design. So, better intentionally ignore warpzones here.
+ for(e = findradius(self.origin, WEP_CVAR_PRI(fireball, bfgradius)); e; e = e.chain)
+ if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self))
+ {
+ // can we see fireball?
+ traceline(e.origin + e.view_ofs, self.origin, MOVE_NORMAL, e);
+ if(/* trace_startsolid || */ trace_fraction != 1) // startsolid should be never happening anyway
+ continue;
+ // can we see player who shot fireball?
+ traceline(e.origin + e.view_ofs, self.realowner.origin + self.realowner.view_ofs, MOVE_NORMAL, e);
+ if(trace_ent != self.realowner)
+ if(/* trace_startsolid || */ trace_fraction != 1)
+ continue;
+ dist = vlen(self.origin - e.origin - e.view_ofs);
+ points = (1 - sqrt(dist / WEP_CVAR_PRI(fireball, bfgradius)));
+ if(points <= 0)
+ continue;
+ dir = normalize(e.origin + e.view_ofs - self.origin);
+
+ if(accuracy_isgooddamage(self.realowner, e))
+ accuracy_add(self.realowner, WEP_FIREBALL, 0, WEP_CVAR_PRI(fireball, bfgdamage) * points);
+
+ Damage(e, self, self.realowner, WEP_CVAR_PRI(fireball, bfgdamage) * points, self.projectiledeathtype | HITTYPE_BOUNCE | HITTYPE_SPLASH, e.origin + e.view_ofs, WEP_CVAR_PRI(fireball, bfgforce) * dir);
+ pointparticles(particleeffectnum("fireball_bfgdamage"), e.origin, -1 * dir, 1);
+ }
+ }
+
+ remove (self);
+}
+
+void W_Fireball_TouchExplode (void)
+{
+ PROJECTILE_TOUCH;
+ W_Fireball_Explode ();
+}
+
+void W_Fireball_LaserPlay(float dt, float dist, float damage, float edgedamage, float burntime)
+{
+ entity e;
+ float d;
+ vector p;
+
+ if(damage <= 0)
+ return;
+
+ RandomSelection_Init();
+ for(e = WarpZone_FindRadius(self.origin, dist, TRUE); e; e = e.chain)
+ if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self))
+ {
+ p = e.origin;
+ p_x += e.mins_x + random() * (e.maxs_x - e.mins_x);
+ p_y += e.mins_y + random() * (e.maxs_y - e.mins_y);
+ p_z += e.mins_z + random() * (e.maxs_z - e.mins_z);
+ d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p);
+ if(d < dist)
+ {
+ e.fireball_impactvec = p;
+ RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e));
+ }
+ }
+ if(RandomSelection_chosen_ent)
+ {
+ d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec);
+ d = damage + (edgedamage - damage) * (d / dist);
+ Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE);
+ //trailparticles(self, particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec);
+ pointparticles(particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1);
+ }
+}
+
+void W_Fireball_Think()
+{
+ if(time > self.pushltime)
+ {
+ self.cnt = 1;
+ self.projectiledeathtype |= HITTYPE_SPLASH;
+ W_Fireball_Explode();
+ return;
+ }
+
+ W_Fireball_LaserPlay(0.1, WEP_CVAR_PRI(fireball, laserradius), WEP_CVAR_PRI(fireball, laserdamage), WEP_CVAR_PRI(fireball, laseredgedamage), WEP_CVAR_PRI(fireball, laserburntime));
+
+ self.nextthink = time + 0.1;
+}
+
+void W_Fireball_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ if(self.health <= 0)
+ return;
-
++
+ if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+ return; // g_projectiles_damage says to halt
-
++
+ self.health = self.health - damage;
+ if (self.health <= 0)
+ {
+ self.cnt = 1;
+ W_PrepareExplosionByDamage(attacker, W_Fireball_Explode);
+ }
+}
+
+void W_Fireball_Attack1()
+{
+ entity proj;
+
+ W_SetupShot_ProjectileSize (self, '-16 -16 -16', '16 16 16', FALSE, 2, "weapons/fireball_fire2.wav", CH_WEAPON_A, WEP_CVAR_PRI(fireball, damage) + WEP_CVAR_PRI(fireball, bfgdamage));
+
+ pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+ proj = spawn ();
+ proj.classname = "plasma_prim";
+ proj.owner = proj.realowner = self;
+ proj.bot_dodge = TRUE;
+ proj.bot_dodgerating = WEP_CVAR_PRI(fireball, damage);
+ proj.pushltime = time + WEP_CVAR_PRI(fireball, lifetime);
+ proj.use = W_Fireball_Explode;
+ proj.think = W_Fireball_Think;
+ proj.nextthink = time;
+ proj.health = WEP_CVAR_PRI(fireball, health);
+ proj.team = self.team;
+ proj.event_damage = W_Fireball_Damage;
+ proj.takedamage = DAMAGE_YES;
+ proj.damageforcescale = WEP_CVAR_PRI(fireball, damageforcescale);
+ PROJECTILE_MAKETRIGGER(proj);
+ proj.projectiledeathtype = WEP_FIREBALL;
+ setorigin(proj, w_shotorg);
+
+ proj.movetype = MOVETYPE_FLY;
+ W_SETUPPROJECTILEVELOCITY(proj, g_balance_fireball_primary);
+ proj.angles = vectoangles(proj.velocity);
+ proj.touch = W_Fireball_TouchExplode;
+ setsize(proj, '-16 -16 -16', '16 16 16');
+ proj.flags = FL_PROJECTILE;
+ proj.missile_flags = MIF_SPLASH | MIF_PROXY;
-
++
+ CSQCProjectile(proj, TRUE, PROJECTILE_FIREBALL, TRUE);
+
+ other = proj; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+void W_Fireball_AttackEffect(float i, vector f_diff)
+{
+ W_SetupShot_ProjectileSize (self, '-16 -16 -16', '16 16 16', FALSE, 0, "", 0, 0);
+ w_shotorg += f_diff_x * v_up + f_diff_y * v_right;
+ pointparticles(particleeffectnum("fireball_preattack_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+}
+
+void W_Fireball_Attack1_Frame4()
+{
+ W_Fireball_Attack1();
+ weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), w_ready);
+}
+
+void W_Fireball_Attack1_Frame3()
+{
+ W_Fireball_AttackEffect(0, '+1.25 +3.75 0');
+ weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame4);
+}
+
+void W_Fireball_Attack1_Frame2()
+{
+ W_Fireball_AttackEffect(0, '-1.25 +3.75 0');
+ weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame3);
+}
+
+void W_Fireball_Attack1_Frame1()
+{
+ W_Fireball_AttackEffect(1, '+1.25 -3.75 0');
+ weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame2);
+}
+
+void W_Fireball_Attack1_Frame0()
+{
+ W_Fireball_AttackEffect(0, '-1.25 -3.75 0');
+ sound (self, CH_WEAPON_SINGLE, "weapons/fireball_prefire2.wav", VOL_BASE, ATTEN_NORM);
+ weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame1);
+}
+
+void W_Firemine_Think()
+{
+ if(time > self.pushltime)
+ {
+ remove(self);
+ return;
+ }
+
+ // make it "hot" once it leaves its owner
+ if(self.owner)
+ {
+ if(vlen(self.origin - self.owner.origin - self.owner.view_ofs) > WEP_CVAR_SEC(fireball, laserradius))
+ {
+ self.cnt += 1;
+ if(self.cnt == 3)
+ self.owner = world;
+ }
+ else
+ self.cnt = 0;
+ }
+
+ W_Fireball_LaserPlay(0.1, WEP_CVAR_SEC(fireball, laserradius), WEP_CVAR_SEC(fireball, laserdamage), WEP_CVAR_SEC(fireball, laseredgedamage), WEP_CVAR_SEC(fireball, laserburntime));
+
+ self.nextthink = time + 0.1;
+}
+
+void W_Firemine_Touch (void)
+{
+ PROJECTILE_TOUCH;
+ if (other.takedamage == DAMAGE_AIM)
+ if(Fire_AddDamage(other, self.realowner, WEP_CVAR_SEC(fireball, damage), WEP_CVAR_SEC(fireball, damagetime), self.projectiledeathtype) >= 0)
+ {
+ remove(self);
+ return;
+ }
+ self.projectiledeathtype |= HITTYPE_BOUNCE;
+}
+
+void W_Fireball_Attack2()
+{
+ entity proj;
+ vector f_diff;
+ float c;
+
+ c = mod(self.bulletcounter, 4);
+ switch(c)
+ {
+ case 0:
+ f_diff = '-1.25 -3.75 0';
+ break;
+ case 1:
+ f_diff = '+1.25 -3.75 0';
+ break;
+ case 2:
+ f_diff = '-1.25 +3.75 0';
+ break;
+ case 3:
+ default:
+ f_diff = '+1.25 +3.75 0';
+ break;
+ }
+ W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', FALSE, 2, "weapons/fireball_fire.wav", CH_WEAPON_A, WEP_CVAR_SEC(fireball, damage));
+ traceline(w_shotorg, w_shotorg + f_diff_x * v_up + f_diff_y * v_right, MOVE_NORMAL, self);
+ w_shotorg = trace_endpos;
+
+ pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+ proj = spawn ();
+ proj.owner = proj.realowner = self;
+ proj.classname = "grenade";
+ proj.bot_dodge = TRUE;
+ proj.bot_dodgerating = WEP_CVAR_SEC(fireball, damage);
+ proj.movetype = MOVETYPE_BOUNCE;
+ proj.projectiledeathtype = WEP_FIREBALL | HITTYPE_SECONDARY;
+ proj.touch = W_Firemine_Touch;
+ PROJECTILE_MAKETRIGGER(proj);
+ setsize(proj, '-4 -4 -4', '4 4 4');
+ setorigin(proj, w_shotorg);
+ proj.think = W_Firemine_Think;
+ proj.nextthink = time;
+ proj.damageforcescale = WEP_CVAR_SEC(fireball, damageforcescale);
+ proj.pushltime = time + WEP_CVAR_SEC(fireball, lifetime);
+ W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_fireball_secondary);
+
+ proj.angles = vectoangles(proj.velocity);
+ proj.flags = FL_PROJECTILE;
+ proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC;
++
+ CSQCProjectile(proj, TRUE, PROJECTILE_FIREMINE, TRUE);
+
+ other = proj; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+float w_fireball(float req)
+{
+ switch(req)
+ {
+ case WR_AIM:
+ {
+ self.BUTTON_ATCK = FALSE;
+ self.BUTTON_ATCK2 = FALSE;
+ if (self.bot_primary_fireballmooth == 0)
+ {
+ if(bot_aim(WEP_CVAR_PRI(fireball, speed), 0, WEP_CVAR_PRI(fireball, lifetime), FALSE))
+ {
+ self.BUTTON_ATCK = TRUE;
+ if(random() < 0.02) self.bot_primary_fireballmooth = 0;
+ }
+ }
+ else
+ {
+ if(bot_aim(WEP_CVAR_SEC(fireball, speed), WEP_CVAR_SEC(fireball, speed_up), WEP_CVAR_SEC(fireball, lifetime), TRUE))
+ {
+ self.BUTTON_ATCK2 = TRUE;
+ if(random() < 0.01) self.bot_primary_fireballmooth = 1;
+ }
+ }
+
+ return TRUE;
+ }
+ case WR_THINK:
+ {
+ if (self.BUTTON_ATCK)
+ {
+ if (time >= self.fireball_primarytime)
+ if (weapon_prepareattack(0, WEP_CVAR_PRI(fireball, refire)))
+ {
+ W_Fireball_Attack1_Frame0();
+ self.fireball_primarytime = time + WEP_CVAR_PRI(fireball, refire2) * W_WeaponRateFactor();
+ }
+ }
+ else if (self.BUTTON_ATCK2)
+ {
+ if (weapon_prepareattack(1, WEP_CVAR_SEC(fireball, refire)))
+ {
+ W_Fireball_Attack2();
+ weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(fireball, animtime), w_ready);
+ }
+ }
+
+ return TRUE;
+ }
+ case WR_INIT:
+ {
+ precache_model ("models/weapons/g_fireball.md3");
+ precache_model ("models/weapons/v_fireball.md3");
+ precache_model ("models/weapons/h_fireball.iqm");
+ precache_model ("models/sphere/sphere.md3");
+ precache_sound ("weapons/fireball_fire.wav");
+ precache_sound ("weapons/fireball_fire2.wav");
+ precache_sound ("weapons/fireball_prefire2.wav");
+ WEP_SET_PROPS(FIREBALL_SETTINGS(fireball), WEP_FIREBALL)
+ return TRUE;
+ }
+ case WR_SETUP:
+ {
+ self.current_ammo = ammo_none;
+ return TRUE;
+ }
+ case WR_CHECKAMMO1:
+ case WR_CHECKAMMO2:
+ {
+ return TRUE; // fireball has infinite ammo
+ }
+ case WR_CONFIG:
+ {
+ WEP_CONFIG_SETTINGS(FIREBALL_SETTINGS(fireball))
+ return TRUE;
+ }
+ case WR_RESETPLAYER:
+ {
+ self.fireball_primarytime = time;
+ return TRUE;
+ }
+ case WR_SUICIDEMESSAGE:
+ {
+ if(w_deathtype & HITTYPE_SECONDARY)
+ return WEAPON_FIREBALL_SUICIDE_FIREMINE;
+ else
+ return WEAPON_FIREBALL_SUICIDE_BLAST;
+ }
+ case WR_KILLMESSAGE:
+ {
+ if(w_deathtype & HITTYPE_SECONDARY)
+ return WEAPON_FIREBALL_MURDER_FIREMINE;
+ else
+ return WEAPON_FIREBALL_MURDER_BLAST;
+ }
+ }
+ return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_fireball(float req)
+{
+ switch(req)
+ {
+ case WR_IMPACTEFFECT:
+ {
+ vector org2;
+ if(w_deathtype & HITTYPE_SECONDARY)
+ {
+ // firemine goes out silently
+ }
+ else
+ {
+ org2 = w_org + w_backoff * 16;
+ pointparticles(particleeffectnum("fireball_explode"), org2, '0 0 0', 1);
+ if(!w_issilent)
+ sound(self, CH_SHOTS, "weapons/fireball_impact2.wav", VOL_BASE, ATTEN_NORM * 0.25); // long range boom
+ }
+
+ return TRUE;
+ }
+ case WR_INIT:
+ {
+ precache_sound("weapons/fireball_impact2.wav");
+ return TRUE;
+ }
+ }
+
+ return TRUE;
+}
+#endif
+#endif
--- /dev/null
-
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id */ SEEKER,
+/* function */ w_seeker,
+/* ammotype */ IT_ROCKETS,
+/* impulse */ 8,
+/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
+/* rating */ BOT_PICKUP_RATING_MID,
+/* model */ "seeker",
+/* netname */ "seeker",
+/* fullname */ _("T.A.G. Seeker")
+);
+
+#define SEEKER_SETTINGS(weapon) \
+ WEP_ADD_CVAR(weapon, MO_NONE, type) \
+ WEP_ADD_CVAR(weapon, MO_NONE, flac_ammo) \
+ WEP_ADD_CVAR(weapon, MO_NONE, flac_animtime) \
+ WEP_ADD_CVAR(weapon, MO_NONE, flac_damage) \
+ WEP_ADD_CVAR(weapon, MO_NONE, flac_edgedamage) \
+ WEP_ADD_CVAR(weapon, MO_NONE, flac_force) \
+ WEP_ADD_CVAR(weapon, MO_NONE, flac_lifetime) \
+ WEP_ADD_CVAR(weapon, MO_NONE, flac_lifetime_rand) \
+ WEP_ADD_CVAR(weapon, MO_NONE, flac_radius) \
+ WEP_ADD_CVAR(weapon, MO_NONE, flac_refire) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_accel) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_ammo) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_animtime) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_count) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_damage) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_damageforcescale) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_decel) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_delay) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_edgedamage) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_force) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_health) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_lifetime) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_proxy) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_proxy_delay) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_proxy_maxrange) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_radius) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_refire) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_smart) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_smart_mindist) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_smart_trace_max) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_smart_trace_min) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_speed_max) \
+ WEP_ADD_CVAR(weapon, MO_NONE, missile_turnrate) \
+ WEP_ADD_CVAR(weapon, MO_NONE, tag_ammo) \
+ WEP_ADD_CVAR(weapon, MO_NONE, tag_animtime) \
+ WEP_ADD_CVAR(weapon, MO_NONE, tag_damageforcescale) \
+ WEP_ADD_CVAR(weapon, MO_NONE, tag_health) \
+ WEP_ADD_CVAR(weapon, MO_NONE, tag_lifetime) \
+ WEP_ADD_CVAR(weapon, MO_NONE, tag_refire) \
+ WEP_ADD_CVAR(weapon, MO_NONE, tag_speed) \
+ WEP_ADD_CVAR(weapon, MO_NONE, tag_tracker_lifetime) \
+ WEP_ADD_PROP(weapon, reloading_ammo, reload_ammo) \
+ WEP_ADD_PROP(weapon, reloading_time, reload_time) \
+ WEP_ADD_PROP(weapon, switchdelay_raise, switchdelay_raise) \
+ WEP_ADD_PROP(weapon, switchdelay_drop, switchdelay_drop)
+
+#ifdef SVQC
+SEEKER_SETTINGS(seeker)
+.entity tag_target, wps_tag_tracker;
+.float tag_time;
+#endif
+#else
+#ifdef SVQC
+void spawnfunc_weapon_seeker (void) { weapon_defaultspawnfunc(WEP_SEEKER); }
+
+// ============================
+// Begin: Missile functions, these are general functions to be manipulated by other code
+// ============================
+void Seeker_Missile_Explode ()
+{
+ self.event_damage = func_null;
+ RadiusDamage (self, self.realowner, WEP_CVAR(seeker, missile_damage), WEP_CVAR(seeker, missile_edgedamage), WEP_CVAR(seeker, missile_radius), world, world, WEP_CVAR(seeker, missile_force), self.projectiledeathtype, other);
+
+ remove (self);
+}
+
+void Seeker_Missile_Touch()
+{
+ PROJECTILE_TOUCH;
+
+ Seeker_Missile_Explode();
+}
+
+void Seeker_Missile_Think()
+{
+ entity e;
+ vector desireddir, olddir, newdir, eorg;
+ float turnrate;
+ float dist;
+ float spd;
+
+ if (time > self.cnt)
+ {
+ self.projectiledeathtype |= HITTYPE_SPLASH;
+ Seeker_Missile_Explode();
+ }
+
+ spd = vlen(self.velocity);
+ spd = bound(
+ spd - WEP_CVAR(seeker, missile_decel) * frametime,
+ WEP_CVAR(seeker, missile_speed_max),
+ spd + WEP_CVAR(seeker, missile_accel) * frametime
+ );
+
+ if (self.enemy != world)
+ if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
+ self.enemy = world;
+
+ if (self.enemy != world)
+ {
+ e = self.enemy;
+ eorg = 0.5 * (e.absmin + e.absmax);
+ turnrate = WEP_CVAR(seeker, missile_turnrate); // how fast to turn
+ desireddir = normalize(eorg - self.origin);
+ olddir = normalize(self.velocity); // get my current direction
+ dist = vlen(eorg - self.origin);
+
+ // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P )
+ if (WEP_CVAR(seeker, missile_smart) && (dist > WEP_CVAR(seeker, missile_smart_mindist)))
+ {
+ // Is it a better idea (shorter distance) to trace to the target itself?
+ if ( vlen(self.origin + olddir * self.wait) < dist)
+ traceline(self.origin, self.origin + olddir * self.wait, FALSE, self);
+ else
+ traceline(self.origin, eorg, FALSE, self);
+
+ // Setup adaptive tracelength
+ self.wait = bound(WEP_CVAR(seeker, missile_smart_trace_min), vlen(self.origin - trace_endpos), self.wait = WEP_CVAR(seeker, missile_smart_trace_max));
+
+ // Calc how important it is that we turn and add this to the desierd (enemy) dir.
+ desireddir = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5);
+ }
-
++
+ newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy
+ self.velocity = newdir * spd; // make me fly in the new direction at my flight speed
+ }
+ else
+ dist = 0;
+
+ // Proxy
+ if (WEP_CVAR(seeker, missile_proxy))
+ {
+ if ( dist <= WEP_CVAR(seeker, missile_proxy_maxrange))
+ {
+ if (self.autoswitch == 0)
+ {
+ self.autoswitch = time + WEP_CVAR(seeker, missile_proxy_delay);
+ }
+ else
+ {
+ if (self.autoswitch <= time)
+ {
+ Seeker_Missile_Explode();
+ self.autoswitch = 0;
+ }
+ }
+ }
+ else
+ {
+ if (self.autoswitch != 0)
+ self.autoswitch = 0;
+ }
+ }
+ ///////////////
+
+ if (self.enemy.deadflag != DEAD_NO)
+ {
+ self.enemy = world;
+ self.cnt = time + 1 + (random() * 4);
+ self.nextthink = self.cnt;
+ return;
+ }
+
+ //self.angles = vectoangles(self.velocity); // turn model in the new flight direction
+ self.nextthink = time;// + 0.05; // csqc projectiles
+ UpdateCSQCProjectile(self);
+}
+
+
+
+void Seeker_Missile_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ if (self.health <= 0)
+ return;
-
++
+ if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+ return; // g_projectiles_damage says to halt
+
+ if (self.realowner == attacker)
+ self.health = self.health - (damage * 0.25);
+ else
+ self.health = self.health - damage;
-
++
+ if (self.health <= 0)
+ W_PrepareExplosionByDamage(attacker, Seeker_Missile_Explode);
+}
+
+/*
+void Seeker_Missile_Animate()
+{
+ self.frame = self.frame +1;
+ self.nextthink = time + 0.05;
+
+ if (self.enemy != world)
+ if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
+ self.enemy = world;
+
+ if(self.frame == 5)
+ {
+ self.think = Seeker_Missile_Think;
+ self.nextthink = time;// + cvar("g_balance_seeker_missile_activate_delay"); // cant dealy with csqc projectiles
+
+ if (autocvar_g_balance_seeker_missile_proxy)
+ self.movetype = MOVETYPE_BOUNCEMISSILE;
+ else
+ self.movetype = MOVETYPE_FLYMISSILE;
+ }
+
+ UpdateCSQCProjectile(self);
+}
+*/
+
+void Seeker_Fire_Missile(vector f_diff, entity m_target)
+{
+ entity missile;
+
+ W_DecreaseAmmo(ammo_rockets, WEP_CVAR(seeker, missile_ammo), autocvar_g_balance_seeker_reload_ammo);
+
+ makevectors(self.v_angle);
+ W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/seeker_fire.wav", CH_WEAPON_A, 0);
+ w_shotorg += f_diff;
+ pointparticles(particleeffectnum("seeker_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+ //self.detornator = FALSE;
+
+ missile = spawn();
+ missile.owner = missile.realowner = self;
+ missile.classname = "seeker_missile";
+ missile.bot_dodge = TRUE;
+ missile.bot_dodgerating = WEP_CVAR(seeker, missile_damage);
+
+ missile.think = Seeker_Missile_Think;
+ missile.touch = Seeker_Missile_Touch;
+ missile.event_damage = Seeker_Missile_Damage;
+ missile.nextthink = time;// + 0.2;// + cvar("g_balance_seeker_missile_activate_delay");
+ missile.cnt = time + WEP_CVAR(seeker, missile_lifetime);
+ missile.enemy = m_target;
+ missile.solid = SOLID_BBOX;
+ missile.scale = 2;
+ missile.takedamage = DAMAGE_YES;
+ missile.health = WEP_CVAR(seeker, missile_health);
+ missile.damageforcescale = WEP_CVAR(seeker, missile_damageforcescale);
+ missile.damagedbycontents = TRUE;
+ //missile.think = Seeker_Missile_Animate; // csqc projectiles.
- else
++
+ if (missile.enemy != world)
+ missile.projectiledeathtype = WEP_SEEKER | HITTYPE_SECONDARY;
-
++ else
+ missile.projectiledeathtype = WEP_SEEKER;
+
+
+ setorigin (missile, w_shotorg);
+ setsize (missile, '-4 -4 -4', '4 4 4');
+ missile.movetype = MOVETYPE_FLYMISSILE;
+ missile.flags = FL_PROJECTILE;
+ missile.missile_flags = MIF_SPLASH | MIF_GUIDED_TAG;
- // Begin: FLAC, close range attack meant for defeating rockets which are coming at you.
++
+ W_SETUPPROJECTILEVELOCITY_UP(missile, g_balance_seeker_missile);
+
+ missile.angles = vectoangles (missile.velocity);
+
+ CSQCProjectile(missile, FALSE, PROJECTILE_SEEKER, TRUE);
+
+ other = missile; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+// ============================
- missile.use = Seeker_Flac_Explode;
++// Begin: FLAC, close range attack meant for defeating rockets which are coming at you.
+// ============================
+void Seeker_Flac_Explode ()
+{
+ self.event_damage = func_null;
+
+ RadiusDamage (self, self.realowner, WEP_CVAR(seeker, flac_damage), WEP_CVAR(seeker, flac_edgedamage), WEP_CVAR(seeker, flac_radius), world, world, WEP_CVAR(seeker, flac_force), self.projectiledeathtype, other);
+
+ remove (self);
+}
+
+void Seeker_Flac_Touch()
+{
+ PROJECTILE_TOUCH;
+
+ Seeker_Flac_Explode();
+}
+
+void Seeker_Fire_Flac()
+{
+ entity missile;
+ vector f_diff;
+ float c;
+
+ W_DecreaseAmmo(ammo_rockets, WEP_CVAR(seeker, flac_ammo), autocvar_g_balance_seeker_reload_ammo);
+
+ c = mod(self.bulletcounter, 4);
+ switch(c)
+ {
+ case 0:
+ f_diff = '-1.25 -3.75 0';
+ break;
+ case 1:
+ f_diff = '+1.25 -3.75 0';
+ break;
+ case 2:
+ f_diff = '-1.25 +3.75 0';
+ break;
+ case 3:
+ default:
+ f_diff = '+1.25 +3.75 0';
+ break;
+ }
+ W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/flac_fire.wav", CH_WEAPON_A, WEP_CVAR(seeker, flac_damage));
+ w_shotorg += f_diff;
+
+ pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+ missile = spawn ();
+ missile.owner = missile.realowner = self;
+ missile.classname = "missile";
+ missile.bot_dodge = TRUE;
+ missile.bot_dodgerating = WEP_CVAR(seeker, flac_damage);
+ missile.touch = Seeker_Flac_Explode;
- missile.movetype = MOVETYPE_FLY;
++ missile.use = Seeker_Flac_Explode;
+ missile.think = adaptor_think2use_hittype_splash;
+ missile.nextthink = time + WEP_CVAR(seeker, flac_lifetime) + WEP_CVAR(seeker, flac_lifetime_rand);
+ missile.solid = SOLID_BBOX;
- missile.missile_flags = MIF_SPLASH;
-
++ missile.movetype = MOVETYPE_FLY;
+ missile.projectiledeathtype = WEP_SEEKER;
+ missile.projectiledeathtype = WEP_SEEKER | HITTYPE_SECONDARY;
+ missile.flags = FL_PROJECTILE;
- //missile.angles = vectoangles (missile.velocity);
- //missile.scale = 0.4; // BUG: the model is too big
-
++ missile.missile_flags = MIF_SPLASH;
++
+ // csqc projectiles
-
++ //missile.angles = vectoangles (missile.velocity);
++ //missile.scale = 0.4; // BUG: the model is too big
++
+ setorigin (missile, w_shotorg);
+ setsize (missile, '-2 -2 -2', '2 2 2');
- // Begin: Tag and rocket controllers
++
+ W_SETUPPROJECTILEVELOCITY_UP(missile, g_balance_seeker_flac);
+ CSQCProjectile(missile, TRUE, PROJECTILE_FLAC, TRUE);
+
+ other = missile; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+// ============================
- for(tag = world; (tag = find(tag, classname, "tag_tracker")); )
++// Begin: Tag and rocket controllers
+// ============================
+entity Seeker_Tagged_Info(entity isowner, entity istarget)
+{
+ entity tag;
-
++ for(tag = world; (tag = find(tag, classname, "tag_tracker")); )
+ if ((tag.realowner == isowner) && (tag.tag_target == istarget))
+ return tag;
-
++
+ return world;
+}
+
+void Seeker_Attack()
+{
+ entity tracker, closest_target;
- else
++
+ closest_target = world;
+ for(tracker = world; (tracker = find(tracker, classname, "tag_tracker")); ) if (tracker.realowner == self)
+ {
+ if (closest_target)
+ {
+ if (vlen(self.origin - tracker.tag_target.origin) < vlen(self.origin - closest_target.origin))
+ closest_target = tracker.tag_target;
+ }
-
++ else
+ closest_target = tracker.tag_target;
+ }
-
++
+ traceline(self.origin + self.view_ofs, closest_target.origin, MOVE_NOMONSTERS, self);
+ if ((!closest_target) || ((trace_fraction < 1) && (trace_ent != closest_target)))
+ closest_target = world;
-
++
+ Seeker_Fire_Missile('0 0 0', closest_target);
+}
+
+void Seeker_Vollycontroller_Think() // TODO: Merge this with Seeker_Attack
+{
+ float c;
+ entity oldself,oldenemy;
+ self.cnt = self.cnt - 1;
+
+ if((!(self.realowner.items & IT_UNLIMITED_AMMO) && self.realowner.ammo_rockets < WEP_CVAR(seeker, missile_ammo)) || (self.cnt <= -1) || (self.realowner.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER))
+ {
+ remove(self);
+ return;
+ }
+
+ self.nextthink = time + WEP_CVAR(seeker, missile_delay) * W_WeaponRateFactor();
-
++
+ oldself = self;
+ self = self.realowner;
-
++
+ oldenemy = self.enemy;
+ self.enemy = oldself.enemy;
- void Seeker_Tracker_Think()
++
+ c = mod(self.cnt, 4);
+ switch(c)
+ {
+ case 0:
+ Seeker_Fire_Missile('-1.25 -3.75 0', self.enemy);
+ break;
+ case 1:
+ Seeker_Fire_Missile('+1.25 -3.75 0', self.enemy);
+ break;
+ case 2:
+ Seeker_Fire_Missile('-1.25 +3.75 0', self.enemy);
+ break;
+ case 3:
+ default:
+ Seeker_Fire_Missile('+1.25 +3.75 0', self.enemy);
+ break;
+ }
+
+ self.enemy = oldenemy;
+ self = oldself;
+}
+
-
++void Seeker_Tracker_Think()
+{
+ // commit suicide if: You die OR target dies OR you switch away from the seeker OR commit suicide if lifetime is up
+ if ((self.realowner.deadflag != DEAD_NO) || (self.tag_target.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER)
+ || (time > self.tag_time + WEP_CVAR(seeker, tag_tracker_lifetime)))
+ {
+ if (self)
+ {
+ WaypointSprite_Kill(self.tag_target.wps_tag_tracker);
+ remove(self);
+ }
+ return;
+ }
- // Begin: Tag projectile
++
+ // Update the think method information
+ self.nextthink = time;
+}
+
+// ============================
-
++// Begin: Tag projectile
+// ============================
+void Seeker_Tag_Explode ()
+{
+ //if(other==self.realowner)
+ // return;
+ Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER | HITTYPE_BOUNCE, other.species, self);
+
+ remove (self);
+}
+
+void Seeker_Tag_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ if (self.health <= 0)
+ return;
+ self.health = self.health - damage;
+ if (self.health <= 0)
+ Seeker_Tag_Explode();
+}
+
+void Seeker_Tag_Touch()
+{
+ vector dir;
+ vector org2;
+ entity e;
-
++
+ PROJECTILE_TOUCH;
+
+ dir = normalize (self.realowner.origin - self.origin);
+ org2 = findbetterlocation (self.origin, 8);
+
+ te_knightspike(org2);
+
+ self.event_damage = func_null;
+ Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER | HITTYPE_BOUNCE | HITTYPE_SECONDARY, other.species, self);
+
+ if (other.takedamage == DAMAGE_AIM && other.deadflag == DEAD_NO)
+ {
+ // check to see if this person is already tagged by me
+ entity tag = Seeker_Tagged_Info(self.realowner, other);
-
++
+ if (tag != world)
+ {
+ if (other.wps_tag_tracker && (WEP_CVAR(seeker, type) == 1)) // don't attach another waypointsprite without killing the old one first
+ WaypointSprite_Kill(other.wps_tag_tracker);
- {
++
+ tag.tag_time = time;
+ }
+ else
-
- if (WEP_CVAR(seeker, type) == 1)
++ {
+ //sprint(self.realowner, strcat("You just tagged ^2", other.netname, "^7 with a tracking device!\n"));
+ e = spawn();
+ e.cnt = WEP_CVAR(seeker, missile_count);
+ e.classname = "tag_tracker";
+ e.owner = self.owner;
+ e.realowner = self.realowner;
- else
++
++ if(WEP_CVAR(seeker, type) == 1)
+ {
+ e.tag_target = other;
+ e.tag_time = time;
+ e.think = Seeker_Tracker_Think;
+ }
-
++ else
+ {
+ e.enemy = other;
+ e.think = Seeker_Vollycontroller_Think;
+ }
-
- if (WEP_CVAR(seeker, type) == 1)
++
+ e.nextthink = time;
+ }
- //missile.missile_flags = MIF_..?;
++
++ if(WEP_CVAR(seeker, type) == 1)
+ {
+ WaypointSprite_Spawn("tagged-target", WEP_CVAR(seeker, tag_tracker_lifetime), 0, other, '0 0 64', self.realowner, 0, other, wps_tag_tracker, TRUE, RADARICON_TAGGED, '0.5 1 0');
+ WaypointSprite_UpdateRule(other.wps_tag_tracker, 0, SPRITERULE_DEFAULT);
+ }
+ }
+
+ remove(self);
+ return;
+}
+
+void Seeker_Fire_Tag()
+{
+ entity missile;
+ W_DecreaseAmmo(ammo_rockets, WEP_CVAR(seeker, tag_ammo), autocvar_g_balance_seeker_reload_ammo);
+
+ W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/tag_fire.wav", CH_WEAPON_A, WEP_CVAR(seeker, missile_damage) * WEP_CVAR(seeker, missile_count));
+
+ missile = spawn();
+ missile.owner = missile.realowner = self;
+ missile.classname = "seeker_tag";
+ missile.bot_dodge = TRUE;
+ missile.bot_dodgerating = 50;
+ missile.touch = Seeker_Tag_Touch;
+ missile.think = SUB_Remove;
+ missile.nextthink = time + WEP_CVAR(seeker, tag_lifetime);
+ missile.movetype = MOVETYPE_FLY;
+ missile.solid = SOLID_BBOX;
+
+ missile.takedamage = DAMAGE_YES;
+ missile.event_damage = Seeker_Tag_Damage;
+ missile.health = WEP_CVAR(seeker, tag_health);
+ missile.damageforcescale = WEP_CVAR(seeker, tag_damageforcescale);
+
+ setorigin (missile, w_shotorg);
+ setsize (missile, '-2 -2 -2', '2 2 2');
+
+ missile.flags = FL_PROJECTILE;
- if (WEP_CVAR(seeker, type) == 1)
++ //missile.missile_flags = MIF_..?;
+
+ missile.movetype = MOVETYPE_FLY;
+ W_SETUPPROJECTILEVELOCITY(missile, g_balance_seeker_tag);
+ missile.angles = vectoangles (missile.velocity);
+
+ CSQCProjectile(missile, TRUE, PROJECTILE_TAG, FALSE); // has sound
+
+ other = missile; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+// ============================
+// Begin: Genereal weapon functions
+// ============================
+
+float w_seeker(float req)
+{
+ float ammo_amount;
+
+ switch(req)
+ {
+ case WR_AIM:
+ {
-
++ if (WEP_CVAR(seeker, type) == 1)
+ if (Seeker_Tagged_Info(self, self.enemy) != world)
+ self.BUTTON_ATCK = bot_aim(WEP_CVAR(seeker, missile_speed_max), 0, WEP_CVAR(seeker, missile_lifetime), FALSE);
+ else
+ self.BUTTON_ATCK2 = bot_aim(WEP_CVAR(seeker, tag_speed), 0, WEP_CVAR(seeker, tag_lifetime), FALSE);
+ else
+ self.BUTTON_ATCK = bot_aim(WEP_CVAR(seeker, tag_speed), 0, WEP_CVAR(seeker, tag_lifetime), FALSE);
-
+ return TRUE;
+ }
+ case WR_THINK:
+ {
+ if(autocvar_g_balance_seeker_reload_ammo && self.clip_load < min(WEP_CVAR(seeker, missile_ammo), WEP_CVAR(seeker, tag_ammo))) // forced reload
+ WEP_ACTION(self.weapon, WR_RELOAD);
- if (WEP_CVAR(seeker, type) == 1)
++
+ else if (self.BUTTON_ATCK)
+ {
- else
++ if (WEP_CVAR(seeker, type) == 1)
+ {
+ if (weapon_prepareattack(0, WEP_CVAR(seeker, missile_refire)))
+ {
+ Seeker_Attack();
+ weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, missile_animtime), w_ready);
+ }
+ }
- if (WEP_CVAR(seeker, type) == 1)
++ else
+ {
+ if (weapon_prepareattack(0, WEP_CVAR(seeker, tag_refire)))
+ {
+ Seeker_Fire_Tag();
+ weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, tag_animtime), w_ready);
+ }
+ }
+ }
+
+ else if (self.BUTTON_ATCK2)
+ {
- else
++ if (WEP_CVAR(seeker, type) == 1)
+ {
+ if (weapon_prepareattack(0, WEP_CVAR(seeker, tag_refire)))
+ {
+ Seeker_Fire_Tag();
+ weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, tag_animtime), w_ready);
+ }
+ }
-
++ else
+ {
+ if (weapon_prepareattack(0, WEP_CVAR(seeker, flac_refire)))
+ {
+ Seeker_Fire_Flac();
+ weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, flac_animtime), w_ready);
+ }
+ }
+ }
- if (WEP_CVAR(seeker, type) == 1)
++
+ return TRUE;
+ }
+ case WR_INIT:
+ {
+ precache_model ("models/weapons/g_seeker.md3");
+ precache_model ("models/weapons/v_seeker.md3");
+ precache_model ("models/weapons/h_seeker.iqm");
+ precache_sound ("weapons/tag_fire.wav");
+ precache_sound ("weapons/flac_fire.wav");
+ precache_sound ("weapons/seeker_fire.wav");
+ WEP_SET_PROPS(SEEKER_SETTINGS(seeker), WEP_SEEKER)
+ return TRUE;
+ }
+ case WR_SETUP:
+ {
+ self.current_ammo = ammo_rockets;
+ return TRUE;
+ }
+ case WR_CHECKAMMO1:
+ {
-
++ if (WEP_CVAR(seeker, type) == 1)
+ {
+ ammo_amount = self.ammo_rockets >= WEP_CVAR(seeker, missile_ammo);
+ ammo_amount += self.(weapon_load[WEP_SEEKER]) >= WEP_CVAR(seeker, missile_ammo);
+ }
+ else
+ {
+ ammo_amount = self.ammo_rockets >= WEP_CVAR(seeker, tag_ammo);
+ ammo_amount += self.(weapon_load[WEP_SEEKER]) >= WEP_CVAR(seeker, tag_ammo);
+ }
- if (WEP_CVAR(seeker, type) == 1)
+ return ammo_amount;
+ }
+ case WR_CHECKAMMO2:
+ {
-
++ if (WEP_CVAR(seeker, type) == 1)
+ {
+ ammo_amount = self.ammo_rockets >= WEP_CVAR(seeker, tag_ammo);
+ ammo_amount += self.(weapon_load[WEP_SEEKER]) >= WEP_CVAR(seeker, tag_ammo);
+ }
+ else
+ {
+ ammo_amount = self.ammo_rockets >= WEP_CVAR(seeker, flac_ammo);
+ ammo_amount += self.(weapon_load[WEP_SEEKER]) >= WEP_CVAR(seeker, flac_ammo);
+ }
-
+ return ammo_amount;
+ }
+ case WR_CONFIG:
+ {
+ WEP_CONFIG_SETTINGS(SEEKER_SETTINGS(seeker))
+ return TRUE;
+ }
+ case WR_RELOAD:
+ {
+ W_Reload(min(WEP_CVAR(seeker, missile_ammo), WEP_CVAR(seeker, tag_ammo)), "weapons/reload.wav");
+ return TRUE;
+ }
+ case WR_SUICIDEMESSAGE:
+ {
+ return WEAPON_SEEKER_SUICIDE;
+ }
+ case WR_KILLMESSAGE:
+ {
+ if(w_deathtype & HITTYPE_SECONDARY)
+ return WEAPON_SEEKER_MURDER_TAG;
+ else
+ return WEAPON_SEEKER_MURDER_SPRAY;
+ }
+ }
+ return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_seeker(float req)
+{
+ switch(req)
+ {
+ case WR_IMPACTEFFECT:
+ {
+ vector org2;
+ org2 = w_org + w_backoff * 6;
+ if(w_deathtype & HITTYPE_BOUNCE)
+ {
+ if(w_deathtype & HITTYPE_SECONDARY)
+ {
+ if(!w_issilent)
+ sound(self, CH_SHOTS, "weapons/tag_impact.wav", 1, ATTEN_NORM);
+ }
+ else
+ {
+ pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1);
+ if(!w_issilent)
+ {
+ if (w_random<0.15)
+ sound(self, CH_SHOTS, "weapons/tagexp1.wav", 1, ATTEN_NORM);
+ else if (w_random<0.7)
+ sound(self, CH_SHOTS, "weapons/tagexp2.wav", 1, ATTEN_NORM);
+ else
+ sound(self, CH_SHOTS, "weapons/tagexp3.wav", 1, ATTEN_NORM);
+ }
+ }
+ }
+ else
+ {
+ pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1);
+ if(!w_issilent)
+ {
+ if (w_random<0.15)
+ sound(self, CH_SHOTS, "weapons/seekerexp1.wav", 1, ATTEN_NORM);
+ else if (w_random<0.7)
+ sound(self, CH_SHOTS, "weapons/seekerexp2.wav", 1, ATTEN_NORM);
+ else
+ sound(self, CH_SHOTS, "weapons/seekerexp3.wav", 1, ATTEN_NORM);
+ }
+ }
-
+ return TRUE;
+ }
+ case WR_INIT:
+ {
+ precache_sound("weapons/seekerexp1.wav");
+ precache_sound("weapons/seekerexp2.wav");
+ precache_sound("weapons/seekerexp3.wav");
+ precache_sound("weapons/tagexp1.wav");
+ precache_sound("weapons/tagexp2.wav");
+ precache_sound("weapons/tagexp3.wav");
+ precache_sound("weapons/tag_impact.wav");
+ return TRUE;
+ }
+ }
++
+ return TRUE;
+}
+#endif
+#endif
--- /dev/null
-
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id */ TUBA,
+/* function */ w_tuba,
+/* ammotype */ 0,
+/* impulse */ 1,
+/* flags */ WEP_FLAG_HIDDEN | WEP_TYPE_SPLASH,
+/* rating */ BOT_PICKUP_RATING_MID,
+/* model */ "tuba",
+/* netname */ "tuba",
+/* xgettext:no-c-format */
+/* fullname */ _("@!#%'n Tuba")
+);
+
+#define TUBA_SETTINGS(weapon) \
+ WEP_ADD_CVAR(weapon, MO_NONE, animtime) \
+ WEP_ADD_CVAR(weapon, MO_NONE, attenuation) \
+ WEP_ADD_CVAR(weapon, MO_NONE, damage) \
+ WEP_ADD_CVAR(weapon, MO_NONE, edgedamage) \
+ WEP_ADD_CVAR(weapon, MO_NONE, force) \
+ WEP_ADD_CVAR(weapon, MO_NONE, radius) \
+ WEP_ADD_CVAR(weapon, MO_NONE, refire) \
+ WEP_ADD_PROP(weapon, reloading_ammo, reload_ammo) \
+ WEP_ADD_PROP(weapon, reloading_time, reload_time) \
+ WEP_ADD_PROP(weapon, switchdelay_raise, switchdelay_raise) \
+ WEP_ADD_PROP(weapon, switchdelay_drop, switchdelay_drop)
+
+#ifdef SVQC
+TUBA_SETTINGS(tuba)
+.entity tuba_note;
+.float tuba_smoketime;
+.float tuba_instrument;
+
+#define MAX_TUBANOTES 32
+.float tuba_lastnotes_last;
+.float tuba_lastnotes_cnt; // over
+.vector tuba_lastnotes[MAX_TUBANOTES];
+#endif
+#else
+#ifdef SVQC
+void spawnfunc_weapon_tuba (void) { weapon_defaultspawnfunc(WEP_TUBA); }
+
+float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo)
+{
+ float i, j, mmin, mmax, nolength;
+ float n = tokenize_console(melody);
+ if(n > pl.tuba_lastnotes_cnt)
+ return FALSE;
+ float pitchshift = 0;
+
+ if(instrument >= 0)
+ if(pl.tuba_instrument != instrument)
+ return FALSE;
+
+ // verify notes...
+ nolength = FALSE;
+ for(i = 0; i < n; ++i)
+ {
+ vector v = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - i + MAX_TUBANOTES, MAX_TUBANOTES)]);
+ float ai = stof(argv(n - i - 1));
+ float np = floor(ai);
+ if(ai == np)
+ nolength = TRUE;
+ // n counts the last played notes BACKWARDS
+ // _x is start
+ // _y is end
+ // _z is note pitch
+ if(ignorepitch && i == 0)
+ {
+ pitchshift = np - v_z;
+ }
+ else
+ {
+ if(v_z + pitchshift != np)
+ return FALSE;
+ }
+ }
+
+ // now we know the right NOTES were played
+ if(!nolength)
+ {
+ // verify rhythm...
+ float ti = 0;
+ if(maxtempo > 0)
+ mmin = 240 / maxtempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec
+ else
+ mmin = 0;
+ if(mintempo > 0)
+ mmax = 240 / mintempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec
+ else
+ mmax = 240; // you won't try THAT hard... (tempo 1)
+ //print(sprintf("initial tempo rules: %f %f\n", mmin, mmax));
+
+ for(i = 0; i < n; ++i)
+ {
+ vector vi = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - i + MAX_TUBANOTES, MAX_TUBANOTES)]);
+ float ai = stof(argv(n - i - 1));
+ ti -= 1 / (ai - floor(ai));
+ float tj = ti;
+ for(j = i+1; j < n; ++j)
+ {
+ vector vj = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - j + MAX_TUBANOTES, MAX_TUBANOTES)]);
+ float aj = stof(argv(n - j - 1));
+ tj -= (aj - floor(aj));
+
+ // note i should be at m*ti+b
+ // note j should be at m*tj+b
+ // so:
+ // we have a LINE l, so that
+ // vi_x <= l(ti) <= vi_y
+ // vj_x <= l(tj) <= vj_y
+ // what is m?
+
+ // vi_x <= vi_y <= vj_x <= vj_y
+ // ti <= tj
+ //print(sprintf("first note: %f to %f, should be %f\n", vi_x, vi_y, ti));
+ //print(sprintf("second note: %f to %f, should be %f\n", vj_x, vj_y, tj));
+ //print(sprintf("m1 = %f\n", (vi_x - vj_y) / (ti - tj)));
+ //print(sprintf("m2 = %f\n", (vi_y - vj_x) / (ti - tj)));
+ mmin = max(mmin, (vi_x - vj_y) / (ti - tj)); // lower bound
+ mmax = min(mmax, (vi_y - vj_x) / (ti - tj)); // upper bound
+ }
+ }
+
+ if(mmin > mmax) // rhythm fail
+ return FALSE;
+ }
+
+ pl.tuba_lastnotes_cnt = 0;
+
+ return TRUE;
+}
+
+void W_Tuba_NoteOff()
+{
+ // we have a note:
+ // on: self.spawnshieldtime
+ // off: time
+ // note: self.cnt
+ if(self.owner.tuba_note == self)
+ {
+ self.owner.tuba_lastnotes_last = mod(self.owner.tuba_lastnotes_last + 1, MAX_TUBANOTES);
+ self.owner.(tuba_lastnotes[self.owner.tuba_lastnotes_last]) = eX * self.spawnshieldtime + eY * time + eZ * self.cnt;
+ self.owner.tuba_note = world;
+ self.owner.tuba_lastnotes_cnt = bound(0, self.owner.tuba_lastnotes_cnt + 1, MAX_TUBANOTES);
+
+ string s;
+ s = trigger_magicear_processmessage_forallears(self.owner, 0, world, string_null);
+ if(s != "")
+ {
+ // simulate a server message
+ switch(self.tuba_instrument)
+ {
+ default:
+ case 0: // Tuba
+ bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Tuba: ^7", s, "\n"));
+ break;
+ case 1:
+ bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Accordeon: ^7", s, "\n"));
+ break;
+ case 2:
+ bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Klein Bottle: ^7", s, "\n"));
+ break;
+ }
+ }
+ }
+ remove(self);
+}
+
+float Tuba_GetNote(entity pl, float hittype)
+{
+ float note;
+ float movestate;
+ movestate = 5;
+ if(pl.movement_x < 0) movestate -= 3;
+ if(pl.movement_x > 0) movestate += 3;
+ if(pl.movement_y < 0) movestate -= 1;
+ if(pl.movement_y > 0) movestate += 1;
+#ifdef GMQCC
+ note = 0;
+#endif
+ switch(movestate)
+ {
+ // layout: originally I wanted
+ // eb e e#=f
+ // B c d
+ // Gb G G#
+ // but then you only use forward and right key. So to make things more
+ // interesting, I swapped B with e#. Har har har...
+ // eb e B
+ // f=e# c d
+ // Gb G G#
+ case 1: note = -6; break; // Gb
+ case 2: note = -5; break; // G
+ case 3: note = -4; break; // G#
+ case 4: note = +5; break; // e#
+ default:
+ case 5: note = 0; break; // c
+ case 6: note = +2; break; // d
+ case 7: note = +3; break; // eb
+ case 8: note = +4; break; // e
+ case 9: note = -1; break; // B
+ }
+ if(pl.BUTTON_CROUCH)
+ note -= 12;
+ if(pl.BUTTON_JUMP)
+ note += 12;
+ if(hittype & HITTYPE_SECONDARY)
+ note += 7;
-
++
+ // we support two kinds of tubas, those tuned in Eb and those tuned in C
+ // kind of tuba currently is player slot number, or team number if in
+ // teamplay
+ // that way, holes in the range of notes are "plugged"
+ if(teamplay)
+ {
+ if(pl.team == NUM_TEAM_2 || pl.team == NUM_TEAM_4)
+ note += 3;
+ }
+ else
+ {
+ if(pl.clientcolors & 1)
+ note += 3;
+ }
- if not(self.tuba_note)
++
+ // total range of notes:
+ // 0
+ // *** ** ****
+ // *** ** ****
+ // *** ** ****
+ // *** ** ****
+ // *** ********************* ****
+ // -18.........................+12
+ // *** ********************* ****
+ // -18............................+15
+ // with jump: ... +24
+ // ... +27
+ return note;
+}
+
+float W_Tuba_NoteSendEntity(entity to, float sf)
+{
+ float f;
+
+ msg_entity = to;
+ if(!sound_allowed(MSG_ONE, self.realowner))
+ return FALSE;
+
+ WriteByte(MSG_ENTITY, ENT_CLIENT_TUBANOTE);
+ WriteByte(MSG_ENTITY, sf);
+ if(sf & 1)
+ {
+ WriteChar(MSG_ENTITY, self.cnt);
+ f = 0;
+ if(self.realowner != to)
+ f |= 1;
+ f |= 2 * self.tuba_instrument;
+ WriteByte(MSG_ENTITY, f);
+ }
+ if(sf & 2)
+ {
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+ }
+ return TRUE;
+}
+
+void W_Tuba_NoteThink()
+{
+ float dist_mult;
+ float vol0, vol1;
+ vector dir0, dir1;
+ vector v;
+ entity e;
+ if(time > self.teleport_time)
+ {
+ W_Tuba_NoteOff();
+ return;
+ }
+ self.nextthink = time;
+ dist_mult = WEP_CVAR(tuba, attenuation) / autocvar_snd_soundradius;
+ FOR_EACH_REALCLIENT(e)
+ if(e != self.realowner)
+ {
+ v = self.origin - (e.origin + e.view_ofs);
+ vol0 = max(0, 1 - vlen(v) * dist_mult);
+ dir0 = normalize(v);
+ v = self.realowner.origin - (e.origin + e.view_ofs);
+ vol1 = max(0, 1 - vlen(v) * dist_mult);
+ dir1 = normalize(v);
+ if(fabs(vol0 - vol1) > 0.005) // 0.5 percent change in volume
+ {
+ setorigin(self, self.realowner.origin);
+ self.SendFlags |= 2;
+ break;
+ }
+ if(dir0 * dir1 < 0.9994) // 2 degrees change in angle
+ {
+ setorigin(self, self.realowner.origin);
+ self.SendFlags |= 2;
+ break;
+ }
+ }
+}
+
+void W_Tuba_NoteOn(float hittype)
+{
+ vector o;
+ float n;
+
+ W_SetupShot(self, FALSE, 2, "", 0, WEP_CVAR(tuba, damage));
+
+ n = Tuba_GetNote(self, hittype);
+
+ hittype = 0;
+ if(self.tuba_instrument & 1)
+ hittype |= HITTYPE_SECONDARY;
+ if(self.tuba_instrument & 2)
+ hittype |= HITTYPE_BOUNCE;
+
+ if(self.tuba_note)
+ {
+ if(self.tuba_note.cnt != n || self.tuba_note.tuba_instrument != self.tuba_instrument)
+ {
+ entity oldself = self;
+ self = self.tuba_note;
+ W_Tuba_NoteOff();
+ self = oldself;
+ }
+ }
+
++ if (!self.tuba_note)
+ {
+ self.tuba_note = spawn();
+ self.tuba_note.owner = self.tuba_note.realowner = self;
+ self.tuba_note.cnt = n;
+ self.tuba_note.tuba_instrument = self.tuba_instrument;
+ self.tuba_note.think = W_Tuba_NoteThink;
+ self.tuba_note.nextthink = time;
+ self.tuba_note.spawnshieldtime = time;
+ Net_LinkEntity(self.tuba_note, FALSE, 0, W_Tuba_NoteSendEntity);
+ }
+
+ self.tuba_note.teleport_time = time + WEP_CVAR(tuba, refire) * 2 * W_WeaponRateFactor(); // so it can get prolonged safely
+
+ //sound(self, c, TUBA_NOTE(n), bound(0, VOL_BASE * cvar("g_balance_tuba_volume"), 1), autocvar_g_balance_tuba_attenuation);
+ RadiusDamage(self, self, WEP_CVAR(tuba, damage), WEP_CVAR(tuba, edgedamage), WEP_CVAR(tuba, radius), world, world, WEP_CVAR(tuba, force), hittype | WEP_TUBA, world);
+
+ o = gettaginfo(self.exteriorweaponentity, 0);
+ if(time > self.tuba_smoketime)
+ {
+ pointparticles(particleeffectnum("smoke_ring"), o + v_up * 45 + v_right * -6 + v_forward * 8, v_up * 100, 1);
+ self.tuba_smoketime = time + 0.25;
+ }
+}
+
+float w_tuba(float req)
+{
+ switch(req)
+ {
+ case WR_AIM:
+ {
+ // bots cannot play the Tuba well yet
+ // I think they should start with the recorder first
+ if(vlen(self.origin - self.enemy.origin) < WEP_CVAR(tuba, radius))
+ {
+ if(random() > 0.5)
+ self.BUTTON_ATCK = 1;
+ else
+ self.BUTTON_ATCK2 = 1;
+ }
+
+ return TRUE;
+ }
+ case WR_THINK:
+ {
+ if (self.BUTTON_ATCK)
+ if (weapon_prepareattack(0, WEP_CVAR(tuba, refire)))
+ {
+ W_Tuba_NoteOn(0);
+ //weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_tuba_animtime, w_ready);
+ weapon_thinkf(WFRAME_IDLE, WEP_CVAR(tuba, animtime), w_ready);
+ }
+ if (self.BUTTON_ATCK2)
+ if (weapon_prepareattack(1, WEP_CVAR(tuba, refire)))
+ {
+ W_Tuba_NoteOn(HITTYPE_SECONDARY);
+ //weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_tuba_animtime, w_ready);
+ weapon_thinkf(WFRAME_IDLE, WEP_CVAR(tuba, animtime), w_ready);
+ }
+ if(self.tuba_note)
+ {
+ if(!self.BUTTON_ATCK && !self.BUTTON_ATCK2)
+ {
+ entity oldself = self;
+ self = self.tuba_note;
+ W_Tuba_NoteOff();
+ self = oldself;
+ }
+ }
+
+ return TRUE;
+ }
+ case WR_INIT:
+ {
+ precache_model ("models/weapons/g_tuba.md3");
+ precache_model ("models/weapons/v_tuba.md3");
+ precache_model ("models/weapons/h_tuba.iqm");
+ precache_model ("models/weapons/v_akordeon.md3");
+ precache_model ("models/weapons/h_akordeon.iqm");
+ precache_model ("models/weapons/v_kleinbottle.md3");
+ precache_model ("models/weapons/h_kleinbottle.iqm");
+ WEP_SET_PROPS(TUBA_SETTINGS(tuba), WEP_TUBA)
+ return TRUE;
+ }
+ case WR_SETUP:
+ {
+ self.current_ammo = ammo_none;
+ self.tuba_instrument = 0;
+ return TRUE;
+ }
+ case WR_RELOAD:
+ {
+ // switch to alternate instruments :)
+ if(self.weaponentity.state == WS_READY)
+ {
+ switch(self.tuba_instrument)
+ {
+ case 0:
+ self.tuba_instrument = 1;
+ self.weaponname = "akordeon";
+ break;
+ case 1:
+ self.tuba_instrument = 2;
+ self.weaponname = "kleinbottle";
+ break;
+ case 2:
+ self.tuba_instrument = 0;
+ self.weaponname = "tuba";
+ break;
+ }
+ W_SetupShot(self, FALSE, 0, "", 0, 0);
+ pointparticles(particleeffectnum("teleport"), w_shotorg, '0 0 0', 1);
+ self.weaponentity.state = WS_INUSE;
+ weapon_thinkf(WFRAME_RELOAD, 0.5, w_ready);
+ }
+
+ return TRUE;
+ }
+ case WR_CHECKAMMO1:
+ case WR_CHECKAMMO2:
+ {
+ return TRUE; // tuba has infinite ammo
+ }
+ case WR_CONFIG:
+ {
+ WEP_CONFIG_SETTINGS(TUBA_SETTINGS(tuba))
+ return TRUE;
+ }
+ case WR_SUICIDEMESSAGE:
+ {
+ if(w_deathtype & HITTYPE_BOUNCE)
+ return WEAPON_KLEINBOTTLE_SUICIDE;
+ else if(w_deathtype & HITTYPE_SECONDARY)
+ return WEAPON_ACCORDEON_SUICIDE;
+ else
+ return WEAPON_TUBA_SUICIDE;
+ }
+ case WR_KILLMESSAGE:
+ {
+ if(w_deathtype & HITTYPE_BOUNCE)
+ return WEAPON_KLEINBOTTLE_MURDER;
+ else if(w_deathtype & HITTYPE_SECONDARY)
+ return WEAPON_ACCORDEON_MURDER;
+ else
+ return WEAPON_TUBA_MURDER;
+ }
+ }
+ return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_tuba(float req)
+{
+ // nothing to do here; particles of tuba are handled differently
+
+ return TRUE;
+}
+#endif
+#endif
--- /dev/null
- ACCUMULATE_FUNCTION(RegisterWeapons, register_weapons_done)
+#ifndef MENUQC
+#include "calculations.qh"
+#endif
+
+const float BOT_PICKUP_RATING_LOW = 2500;
+const float BOT_PICKUP_RATING_MID = 5000;
+const float BOT_PICKUP_RATING_HIGH = 10000;
+
+const float WEP_TYPE_OTHER = 0x00; // not for damaging people
+const float WEP_TYPE_SPLASH = 0x01; // splash damage
+const float WEP_TYPE_HITSCAN = 0x02; // hitscan
+const float WEP_TYPEMASK = 0x0F;
+const float WEP_FLAG_CANCLIMB = 0x10; // can be used for movement
+const float WEP_FLAG_NORMAL = 0x20; // in "most weapons" set
+const float WEP_FLAG_HIDDEN = 0x40; // hides from menu
+const float WEP_FLAG_RELOADABLE = 0x80; // can has reload
+const float WEP_FLAG_SUPERWEAPON = 0x100; // powerup timer
+const float WEP_FLAG_MUTATORBLOCKED = 0x200; // hides from impulse 99 etc. (mutators are allowed to clear this flag)
+
+const float MAX_SHOT_DISTANCE = 32768;
+
+// weapon requests // WEAPONTODO
+#define WR_SETUP 1 // (SERVER) setup weapon data
+#define WR_THINK 2 // (SERVER) logic to run every frame
+#define WR_CHECKAMMO1 3 // (SERVER) checks ammo for weapon
+#define WR_CHECKAMMO2 4 // (SERVER) checks ammo for weapon
+#define WR_AIM 5 // (SERVER) runs bot aiming code for this weapon
+#define WR_INIT 6 // (BOTH) precaches models/sounds used by this weapon
+#define WR_SUICIDEMESSAGE 7 // (SERVER) notification number for suicide message (may inspect w_deathtype for details)
+#define WR_KILLMESSAGE 8 // (SERVER) notification number for kill message (may inspect w_deathtype for details)
+#define WR_RELOAD 9 // (SERVER) does not need to do anything
+#define WR_RESETPLAYER 10 // (SERVER) does not need to do anything
+#define WR_IMPACTEFFECT 11 // (CLIENT) impact effect
+#define WR_SWITCHABLE 12 // (CLIENT) impact effect
+#define WR_PLAYERDEATH 13 // (SERVER) does not need to do anything
+#define WR_GONETHINK 14 // (SERVER) logic to run every frame, also if no longer having the weapon as long as the switch away has not been performed
+#define WR_CONFIG 15 // (ALL)
+
+// WEAPONTODO
+const float IT_UNLIMITED_WEAPON_AMMO = 1;
+// when this bit is set, using a weapon does not reduce ammo. Checkpoints can give this powerup.
+const float IT_UNLIMITED_SUPERWEAPONS = 2;
+// when this bit is set, superweapons don't expire. Checkpoints can give this powerup.
+const float IT_CTF_SHIELDED = 4; // set for the flag shield
+const float IT_USING_JETPACK = 8; // confirmation that button is pressed
+const float IT_JETPACK = 16; // actual item
+const float IT_FUEL_REGEN = 32; // fuel regeneration trigger
+WANT_CONST float IT_SHELLS = 256;
+WANT_CONST float IT_NAILS = 512;
+WANT_CONST float IT_ROCKETS = 1024;
+WANT_CONST float IT_CELLS = 2048;
+const float IT_SUPERWEAPON = 4096;
+const float IT_FUEL = 128;
+const float IT_STRENGTH = 8192;
+const float IT_INVINCIBLE = 16384;
+const float IT_HEALTH = 32768;
+// union:
+ // for items:
+ WANT_CONST float IT_KEY1 = 131072;
+ WANT_CONST float IT_KEY2 = 262144;
+ // for players:
+ const float IT_RED_FLAG_TAKEN = 32768;
+ const float IT_RED_FLAG_LOST = 65536;
+ const float IT_RED_FLAG_CARRYING = 98304;
+ const float IT_BLUE_FLAG_TAKEN = 131072;
+ const float IT_BLUE_FLAG_LOST = 262144;
+ const float IT_BLUE_FLAG_CARRYING = 393216;
+// end
+const float IT_5HP = 524288;
+const float IT_25HP = 1048576;
+const float IT_ARMOR_SHARD = 2097152;
+const float IT_ARMOR = 4194304;
+
+const float IT_AMMO = 3968; // IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS | IT_FUEL;
+const float IT_PICKUPMASK = 51; // IT_FUEL_REGEN | IT_JETPACK | IT_UNLIMITED_AMMO; // strength and invincible are handled separately
+const float IT_UNLIMITED_AMMO = 3; // IT_UNLIMITED_SUPERWEAPONS | IT_UNLIMITED_WEAPON_AMMO;
+
+const float AMMO_COUNT = 4; // amount of ammo types to show in the inventory panel
+
+// variables:
+string weaponorder_byid;
+
+// Weapon sets
+typedef vector WepSet;
+WepSet WepSet_FromWeapon(float a);
+#ifdef SVQC
+void WepSet_AddStat();
+void WriteWepSet(float dest, WepSet w);
+#endif
+#ifdef CSQC
+WepSet WepSet_GetFromStat();
+WepSet ReadWepSet();
+#endif
+
+// Weapon name macros
+#define WEP_FIRST 1
+#define WEP_MAXCOUNT 24 // Increase as needed. Can be up to three times as much.
+float WEP_COUNT;
+float WEP_LAST;
+WepSet WEPSET_ALL;
+WepSet WEPSET_SUPERWEAPONS;
+
+// functions:
+entity get_weaponinfo(float id);
+string W_FixWeaponOrder(string order, float complete);
+string W_NameWeaponOrder(string order);
+string W_NumberWeaponOrder(string order);
+
+// ammo types
+.float ammo_shells;
+.float ammo_nails;
+.float ammo_rockets;
+.float ammo_cells;
+.float ammo_fuel;
+.float ammo_batteries; // dummy
+
+// entity properties of weaponinfo:
+.float weapon; // WEP_...
+.WepSet weapons; // WEPSET_...
+.string netname; // short name
+.string message; // human readable name
+.float items; // IT_...
+.float(float) weapon_func; // w_...
+.string mdl; // modelname without g_, v_, w_
+.string model; // full name of g_ model
+.float spawnflags; // WEPSPAWNFLAG_... combined
+.float impulse; // weapon impulse
+.float bot_pickupbasevalue; // bot weapon priority
+.string model2; // wpn- sprite name
+..float ammo_field; // main ammo field
+
+// other useful macros
+#define WEP_ACTION(wpn,wrequest) (get_weaponinfo(wpn)).weapon_func(wrequest)
+
+// =====================
+// Weapon Registration
+// =====================
+
+float w_null(float dummy);
+void register_weapon(float id, WepSet bit, float(float) func, float ammotype, float i, float weapontype, float pickupbasevalue, string modelname, string shortname, string wname);
+void register_weapons_done();
+
+// note: the fabs call is just there to hide "if result is constant" warning
+#define REGISTER_WEAPON_2(id,bit,func,ammotype,i,weapontype,pickupbasevalue,modelname,shortname,wname) \
+ float id; \
+ WepSet bit; \
+ float func(float); \
+ void RegisterWeapons_##id() \
+ { \
+ WEP_LAST = (id = WEP_FIRST + WEP_COUNT); \
+ bit = WepSet_FromWeapon(id); \
+ WEPSET_ALL |= bit; \
+ if((weapontype) & WEP_FLAG_SUPERWEAPON) \
+ WEPSET_SUPERWEAPONS |= bit; \
+ ++WEP_COUNT; \
+ register_weapon(id,bit,func,ammotype,i,weapontype,pickupbasevalue,modelname,shortname,wname); \
+ } \
+ ACCUMULATE_FUNCTION(RegisterWeapons, RegisterWeapons_##id)
+#ifdef MENUQC
+#define REGISTER_WEAPON(id,func,ammotype,i,weapontype,pickupbasevalue,modelname,shortname,wname) \
+ REGISTER_WEAPON_2(WEP_##id,WEPSET_##id,w_null,ammotype,i,weapontype,pickupbasevalue,modelname,shortname,wname)
+#else
+#define REGISTER_WEAPON(id,func,ammotype,i,weapontype,pickupbasevalue,modelname,shortname,wname) \
+ REGISTER_WEAPON_2(WEP_##id,WEPSET_##id,func,ammotype,i,weapontype,pickupbasevalue,modelname,shortname,wname)
+#endif
+
+#define MO_NONE 0
+#define MO_PRI 1
+#define MO_SEC 2
+#define MO_BOTH 3
+
+#define WEP_DUPECHECK(dupecheck,cvar) \
+ #ifndef dupecheck \
+ #define dupecheck \
+ float cvar; \
+ #else \
+ #error DUPLICATE WEAPON CVAR: cvar \
+ #endif
+
+/*
+#define WEP_CLEAN_DUPECHECK(dupecheck) \
+ #ifdef WEP_CVAR_##weapon##_##name \
+ #undef WEP_CVAR_##weapon##_##name \
+ #endif
+*/
+
+#define WEP_ADD_CVAR(weapon,mode,name) \
+ #if mode == MO_PRI \
+ WEP_DUPECHECK(WEP_CVAR_P_##weapon##_##name, autocvar_g_balance_##weapon##_primary_##name) \
+ #endif \
+ #if mode == MO_SEC \
+ WEP_DUPECHECK(WEP_CVAR_S_##weapon##_##name, autocvar_g_balance_##weapon##_secondary_##name) \
+ #endif \
+ #if mode == MO_BOTH \
+ WEP_DUPECHECK(WEP_CVAR_P_##weapon##_##name, autocvar_g_balance_##weapon##_primary_##name) \
+ WEP_DUPECHECK(WEP_CVAR_S_##weapon##_##name, autocvar_g_balance_##weapon##_secondary_##name) \
+ #endif \
+ #if mode == MO_NONE \
+ WEP_DUPECHECK(WEP_CVAR_##weapon##_##name, autocvar_g_balance_##weapon##_##name) \
+ #endif
+
+#define WEP_CVAR(weapon,name) autocvar_g_balance_##weapon##_##name
+#define WEP_CVAR_PRI(weapon,name) WEP_CVAR(weapon, primary_##name)
+#define WEP_CVAR_SEC(weapon,name) WEP_CVAR(weapon, secondary_##name)
+#define WEP_CVAR_BOTH(weapon,mode,name) ((mode == MO_PRI) ? WEP_CVAR_PRI(weapon, name) : WEP_CVAR_SEC(weapon, name))
+
+#define WEP_ADD_PROP(weapon,prop,name) \
+ .float ##prop; \
+ WEP_DUPECHECK(WEP_CVAR_##weapon##_##name, autocvar_g_balance_##weapon##_##name)
+
+#define WEP_SET_PROP(wepid,weapon,prop,name) get_weaponinfo(##wepid).##prop = autocvar_g_balance_##weapon##_##name;
+
+#define WEP_SET_PROPS(wepsettings,wepid) \
+ #define WEP_ADD_CVAR(weapon,mode,name) \
+ #define WEP_ADD_PROP(weapon,prop,name) WEP_SET_PROP(wepid,weapon,prop,name) \
+ wepsettings \
+ #undef WEP_ADD_CVAR \
+ #undef WEP_ADD_PROP
+
+#include "all.qh"
+
+#undef WEP_ADD_CVAR
+#undef WEP_ADD_PROP
+#undef REGISTER_WEAPON
++ACCUMULATE_FUNCTION(RegisterWeapons, register_weapons_done);
+
+string W_FixWeaponOrder(string order, float complete);
+string W_NumberWeaponOrder(string order);
+string W_NameWeaponOrder(string order);
+string W_FixWeaponOrder_BuildImpulseList(string o);
+string W_FixWeaponOrder_AllowIncomplete(string order);
+string W_FixWeaponOrder_ForceComplete(string order);
+
+void W_RandomWeapons(entity e, float n);
+
+string W_Name(float weaponid);
+
+float W_AmmoItemCode(float wpn);
else if(self.aistatus & AI_STATUS_OUT_JUMPPAD)
self.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
- // If there is a trigger_hurt right below try to use the jetpack or make a rocketjump
+ // If there is a trigger_hurt right below try to use the jetpack or make a rocketjump // WEAPONTODO: move this to bot think!
if(skill>6)
- if not(self.flags & FL_ONGROUND)
+ if (!(self.flags & FL_ONGROUND))
{
tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 -65536', MOVE_NOMONSTERS, self);
if(tracebox_hits_trigger_hurt(self.origin, self.mins, self.maxs, trace_endpos ))
for (i = WEP_FIRST; i <= WEP_LAST; ++i)
{
e = get_weaponinfo(i);
- if((start_weapons | warmup_start_weapons) & WepSet_FromWeapon(i))
+ if(precache_weapons & WepSet_FromWeapon(i))
- weapon_action(i, WR_PRECACHE);
+ WEP_ACTION(i, WR_INIT);
}
start_ammo_shells = max(0, start_ammo_shells);
if(self.weaponentity.weapons)
{
self.weapons = self.weaponentity.weapons;
- weapon_action(WEP_PORTO, WR_RESETPLAYER);
+ WEP_ACTION(WEP_PORTO, WR_RESETPLAYER);
self.switchweapon = self.weaponentity.switchweapon;
W_SwitchWeapon(self.switchweapon);
-
+
self.weaponentity.weapons = '0 0 0';
}
}
self.ammo_cells = autocvar_g_minstagib_ammo_drop;
return FALSE;
}
-
+
- if(self.weapon == WEP_ROCKET_LAUNCHER || self.weapon == WEP_NEX)
+ if(self.weapon == WEP_DEVASTATOR || self.weapon == WEP_NEX)
{
entity e = spawn();
setorigin(e, self.origin);
round_handler.qc
-../common/explosion_equation.qc
-
mutators/base.qc
mutators/gamemode_assault.qc
- mutators/gamemode_arena.qc
mutators/gamemode_ca.qc
mutators/gamemode_ctf.qc
mutators/gamemode_domination.qc
-#define ISF_LOCATION 2
-#define ISF_MODEL 4
-#define ISF_STATUS 8
- #define ITS_STAYWEP 1
- #define ITS_ANIMATE1 2
- #define ITS_ANIMATE2 4
- #define ITS_AVAILABLE 8
- #define ITS_ALLOWFB 16
- #define ITS_ALLOWSI 32
- #define ITS_POWERUP 64
-#define ISF_COLORMAP 16
-#define ISF_DROP 32
-#define ISF_ANGLES 64
-
-.float ItemStatus;
-
#ifdef CSQC
-
-var float autocvar_cl_animate_items = 1;
-var float autocvar_cl_ghost_items = 0.45;
-var vector autocvar_cl_ghost_items_color = '-1 -1 -1';
-var float autocvar_cl_fullbright_items = 0;
-var vector autocvar_cl_weapon_stay_color = '2 0.5 0.5';
-var float autocvar_cl_weapon_stay_alpha = 0.75;
-var float autocvar_cl_simple_items = 0;
-var string autocvr_cl_simpleitems_postfix = "_simple";
-.float spawntime;
-.float gravity;
-.vector colormod;
void ItemDraw()
- {
+ {
if(self.gravity)
- {
+ {
Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy);
- if(self.move_flags & FL_ONGROUND)
+ if(self.move_flags & FL_ONGROUND)
{ // For some reason move_avelocity gets set to '0 0 0' here ...
self.oldorigin = self.origin;
self.gravity = 0;
if(wi.weapon)
{
POSTGIVE_WEAPON(e, j, "weapons/weaponpickup.wav", string_null);
- if not(save_weapons & WepSet_FromWeapon(j))
+ if (!(save_weapons & WepSet_FromWeapon(j)))
if(e.weapons & WepSet_FromWeapon(j))
- weapon_action(wi.weapon, WR_PRECACHE);
+ WEP_ACTION(wi.weapon, WR_INIT);
}
}
POSTGIVE_VALUE(e, strength_finished, 1, "misc/powerup.wav", "misc/poweroff.wav");
void turret_projectile_explode()
{
-
+
self.takedamage = DAMAGE_NO;
- self.event_damage = func_null;
+ self.event_damage = func_null;
#ifdef TURRET_DEBUG
float d;
- d = RadiusDamage (self, self.owner, self.owner.shot_dmg, 0, self.owner.shot_radius, self, self.owner.shot_force, self.totalfrags, world);
+ d = RadiusDamage (self, self.owner, self.owner.shot_dmg, 0, self.owner.shot_radius, self, world, self.owner.shot_force, self.totalfrags, world);
self.owner.tur_dbg_dmg_t_h = self.owner.tur_dbg_dmg_t_h + d;
self.owner.tur_dbg_dmg_t_f = self.owner.tur_dbg_dmg_t_f + self.owner.shot_dmg;
#else
if(self.enemy != world)
if(vlen(self.origin - self.enemy.origin) < self.owner.shot_radius * 3)
setorigin(self,self.enemy.origin + randomvec() * self.owner.shot_radius);
-
+
#ifdef TURRET_DEBUG
float d;
- d = RadiusDamage (self, self.owner, self.owner.shot_dmg, self.owner.shot_dmg, self.owner.shot_radius, self, self.owner.shot_force, self.totalfrags, world);
+ d = RadiusDamage (self, self.owner, self.owner.shot_dmg, self.owner.shot_dmg, self.owner.shot_radius, self, world, self.owner.shot_force, self.totalfrags, world);
self.owner.tur_dbg_dmg_t_h = self.owner.tur_dbg_dmg_t_h + d;
self.owner.tur_dbg_dmg_t_f = self.owner.tur_dbg_dmg_t_f + self.owner.shot_dmg;
#else
//.float bulletcounter;
void turret_machinegun_attack()
{
- fireBallisticBullet (self.tur_shotorg, self.tur_shotdir_updated,self.shot_spread, self.shot_speed, 5, self.shot_dmg, self.shot_force, DEATH_TURRET_MACHINEGUN, 0, 1, WEP_CVAR(uzi, bulletconstant)); // WEAPONTODO
- fireBallisticBullet (self.tur_shotorg, self.tur_shotdir_updated,self.shot_spread, self.shot_speed, 5, self.shot_dmg, self.shot_force, DEATH_TURRET_MACHINEGUN, 0, autocvar_g_balance_uzi_bulletconstant);
++ fireBallisticBullet (self.tur_shotorg, self.tur_shotdir_updated,self.shot_spread, self.shot_speed, 5, self.shot_dmg, self.shot_force, DEATH_TURRET_MACHINEGUN, 0, WEP_CVAR(uzi, bulletconstant)); // WEAPONTODO
endFireBallisticBullet();
UziFlash();
void walker_attack()
{
sound (self, CH_WEAPON_A, "weapons/uzi_fire.wav", VOL_BASE, ATTEN_NORM);
- fireBallisticBullet (self.tur_shotorg, self.tur_shotdir_updated, self.shot_spread, self.shot_speed, 5, self.shot_dmg, self.shot_force, DEATH_TURRET_WALK_GUN, 0, 1, WEP_CVAR(uzi, bulletconstant)); // WEAPONTODO
- fireBallisticBullet (self.tur_shotorg, self.tur_shotdir_updated, self.shot_spread, self.shot_speed, 5, self.shot_dmg, self.shot_force, DEATH_TURRET_WALK_GUN, 0, autocvar_g_balance_uzi_bulletconstant);
++ fireBallisticBullet (self.tur_shotorg, self.tur_shotdir_updated, self.shot_spread, self.shot_speed, 5, self.shot_dmg, self.shot_force, DEATH_TURRET_WALK_GUN, 0, WEP_CVAR(uzi, bulletconstant)); // WEAPONTODO
endFireBallisticBullet();
pointparticles(particleeffectnum("laser_muzzleflash"), self.tur_shotorg, self.tur_shotdir_updated * 1000, 1);
}
--- /dev/null
- if not(self.owner.cvar_cl_accuracy_data_share && autocvar_sv_accuracy_data_share)
+float accuracy_byte(float n, float d)
+{
+ //print(sprintf("accuracy: %d / %d\n", n, d));
+ if(n <= 0)
+ return 0;
+ if(n > d)
+ return 255;
+ return 1 + rint(n * 100.0 / d);
+}
+
+float accuracy_send(entity to, float sf)
+{
+ float w, f;
+ entity a;
+ WriteByte(MSG_ENTITY, ENT_CLIENT_ACCURACY);
+
+ a = self.owner;
+ if(IS_SPEC(a))
+ a = a.enemy;
+ a = a.accuracy;
+
+ if(to != a.owner)
++ if (!(self.owner.cvar_cl_accuracy_data_share && autocvar_sv_accuracy_data_share))
+ sf = 0;
+ // note: zero sendflags can never be sent... so we can use that to say that we send no accuracy!
+ WriteInt24_t(MSG_ENTITY, sf);
+ if(sf == 0)
+ return TRUE;
+ // note: we know that client and server agree about SendFlags...
+ for(w = 0, f = 1; w <= WEP_LAST - WEP_FIRST; ++w)
+ {
+ if(sf & f)
+ WriteByte(MSG_ENTITY, accuracy_byte(self.(accuracy_hit[w]), self.(accuracy_fired[w])));
+ if(f == 0x800000)
+ f = 1;
+ else
+ f *= 2;
+ }
+ return TRUE;
+}
+
+// init/free
+void accuracy_init(entity e)
+{
+ e.accuracy = spawn();
+ e.accuracy.owner = e;
+ e.accuracy.classname = "accuracy";
+ e.accuracy.drawonlytoclient = e;
+ Net_LinkEntity(e.accuracy, FALSE, 0, accuracy_send);
+}
+
+void accuracy_free(entity e)
+{
+ remove(e.accuracy);
+}
+
+// force a resend of a player's accuracy stats
+void accuracy_resend(entity e)
+{
+ e.accuracy.SendFlags = 0xFFFFFF;
+}
+
+// update accuracy stats
+.float hit_time;
+.float fired_time;
+
+void accuracy_add(entity e, float w, float fired, float hit)
+{
+ entity a;
+ float b;
+ if(IS_INDEPENDENT_PLAYER(e))
+ return;
+ a = e.accuracy;
+ if(!a || !(hit || fired))
+ return;
+ w -= WEP_FIRST;
+ b = accuracy_byte(a.(accuracy_hit[w]), a.(accuracy_fired[w]));
+ if(hit)
+ a.(accuracy_hit[w]) += hit;
+ if(fired)
+ a.(accuracy_fired[w]) += fired;
+
+ if(hit && a.hit_time != time) // only run this once per frame
+ {
+ a.(accuracy_cnt_hit[w]) += 1;
+ a.hit_time = time;
+ }
+
+ if(fired && a.fired_time != time) // only run this once per frame
+ {
+ a.(accuracy_cnt_fired[w]) += 1;
+ a.fired_time = time;
+ }
+
+ if(b == accuracy_byte(a.(accuracy_hit[w]), a.(accuracy_fired[w])))
+ return;
+ w = pow(2, mod(w, 24));
+ a.SendFlags |= w;
+ FOR_EACH_CLIENT(a)
+ if(IS_SPEC(a))
+ if(a.enemy == e)
+ a.SendFlags |= w;
+}
+
+float accuracy_isgooddamage(entity attacker, entity targ)
+{
+ if(!warmup_stage)
+ if(IS_CLIENT(targ))
+ if(targ.deadflag == DEAD_NO)
+ if(DIFF_TEAM(attacker, targ))
+ return TRUE;
+ return FALSE;
+}
+
+float accuracy_canbegooddamage(entity attacker)
+{
+ if(!warmup_stage)
+ return TRUE;
+ return FALSE;
+}
--- /dev/null
-
+.float csqcprojectile_type;
+
+float CSQCProjectile_SendEntity(entity to, float sf)
+{
+ float ft, fr;
+
+ // note: flag 0x08 = no trail please (teleport bit)
+ sf = sf & 0x0F;
+
+ if(self.csqcprojectile_clientanimate)
+ sf |= 0x80; // client animated, not interpolated
+
+ if(self.flags & FL_ONGROUND)
+ sf |= 0x40;
+
+ ft = fr = 0;
+ if(self.fade_time != 0 || self.fade_rate != 0)
+ {
+ ft = (self.fade_time - time) / sys_frametime;
+ fr = (1 / self.fade_rate) / sys_frametime;
+ if(ft <= 255 && fr <= 255 && fr >= 1)
+ sf |= 0x20;
+ }
+
+ if(self.gravity != 0)
+ sf |= 0x10;
+
+ WriteByte(MSG_ENTITY, ENT_CLIENT_PROJECTILE);
+ WriteByte(MSG_ENTITY, sf);
+
+ if(sf & 1)
+ {
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+
+ if(sf & 0x80)
+ {
+ WriteCoord(MSG_ENTITY, self.velocity_x);
+ WriteCoord(MSG_ENTITY, self.velocity_y);
+ WriteCoord(MSG_ENTITY, self.velocity_z);
+ if(sf & 0x10)
+ WriteCoord(MSG_ENTITY, self.gravity);
+ }
+
+ if(sf & 0x20)
+ {
+ WriteByte(MSG_ENTITY, ft);
+ WriteByte(MSG_ENTITY, fr);
+ }
+ }
+
+ if(sf & 2)
+ WriteByte(MSG_ENTITY, self.csqcprojectile_type); // TODO maybe put this into sf?
-
++
+ return 1;
+}
+
+.vector csqcprojectile_oldorigin;
+void CSQCProjectile_Check(entity e)
+{
+ if(e.csqcprojectile_clientanimate)
+ if(e.flags & FL_ONGROUND)
+ if(e.origin != e.csqcprojectile_oldorigin)
+ UpdateCSQCProjectile(e);
+ e.csqcprojectile_oldorigin = e.origin;
+}
+
+void CSQCProjectile(entity e, float clientanimate, float type, float docull)
+{
+ Net_LinkEntity(e, docull, 0, CSQCProjectile_SendEntity);
-
++
+ e.csqcprojectile_clientanimate = clientanimate;
++
+ if(e.movetype == MOVETYPE_TOSS || e.movetype == MOVETYPE_BOUNCE)
+ {
+ if(e.gravity == 0)
+ e.gravity = 1;
+ }
+ else
+ e.gravity = 0;
+
+ if(!sound_allowed(MSG_BROADCAST, e))
+ type |= 0x80;
+ e.csqcprojectile_type = type;
+}
+
++// FIXME HACK
++float ItemSend(entity to, float sf);
++void ItemUpdate(entity item);
++// END HACK
+void UpdateCSQCProjectile(entity e)
+{
+ if(e.SendEntity == CSQCProjectile_SendEntity)
+ {
+ // send new origin data
+ e.SendFlags |= 0x01;
+ }
++// FIXME HACK
++ else if(e.SendEntity == ItemSend)
++ {
++ ItemUpdate(e);
++ }
++// END HACK
+}
+
+void UpdateCSQCProjectileAfterTeleport(entity e)
+{
+ if(e.SendEntity == CSQCProjectile_SendEntity)
+ {
+ // send new origin data
+ e.SendFlags |= 0x01;
+ // mark as teleported
+ e.SendFlags |= 0x08;
+ }
+}
--- /dev/null
- FOR_EACH_REALCLIENT(msg_entity) if(msg_entity != self) if(!msg_entity.railgunhit) if not(IS_SPEC(msg_entity) && msg_entity.enemy == self) // we use realclient, so spectators can hear the whoosh too
+// this function calculates w_shotorg and w_shotdir based on the weapon model
+// offset, trueaim and antilag, and won't put w_shotorg inside a wall.
+// make sure you call makevectors first (FIXME?)
+void W_SetupShot_Dir_ProjectileSize_Range(entity ent, vector s_forward, vector mi, vector ma, float antilag, float recoil, string snd, float chan, float maxdamage, float range)
+{
+ float nudge = 1; // added to traceline target and subtracted from result
+ float oldsolid;
+ vector vecs, dv;
+ oldsolid = ent.dphitcontentsmask;
+ if(ent.weapon == WEP_RIFLE)
+ ent.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_CORPSE;
+ else
+ ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
+ if(antilag)
+ WarpZone_traceline_antilag(world, ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
+ // passing world, because we do NOT want it to touch dphitcontentsmask
+ else
+ WarpZone_TraceLine(ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NOMONSTERS, ent);
+ ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
+
+ vector vf, vr, vu;
+ vf = v_forward;
+ vr = v_right;
+ vu = v_up;
+ w_shotend = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); // warpzone support
+ v_forward = vf;
+ v_right = vr;
+ v_up = vu;
+
+ // un-adjust trueaim if shotend is too close
+ if(vlen(w_shotend - (ent.origin + ent.view_ofs)) < autocvar_g_trueaim_minrange)
+ w_shotend = ent.origin + ent.view_ofs + s_forward * autocvar_g_trueaim_minrange;
+
+ // track max damage
+ if(accuracy_canbegooddamage(ent))
+ accuracy_add(ent, ent.weapon, maxdamage, 0);
+
+ W_HitPlotAnalysis(ent, v_forward, v_right, v_up);
+
+ if(ent.weaponentity.movedir_x > 0)
+ vecs = ent.weaponentity.movedir;
+ else
+ vecs = '0 0 0';
+
+ dv = v_right * -vecs_y + v_up * vecs_z;
+ w_shotorg = ent.origin + ent.view_ofs + dv;
+
+ // now move the shotorg forward as much as requested if possible
+ if(antilag)
+ {
+ if(ent.antilag_debug)
+ tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent, ent.antilag_debug);
+ else
+ tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
+ }
+ else
+ tracebox(w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent);
+ w_shotorg = trace_endpos - v_forward * nudge;
+ // calculate the shotdir from the chosen shotorg
+ w_shotdir = normalize(w_shotend - w_shotorg);
+
+ if (antilag)
+ if (!ent.cvar_cl_noantilag)
+ {
+ if (autocvar_g_antilag == 1) // switch to "ghost" if not hitting original
+ {
+ traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
+ if (!trace_ent.takedamage)
+ {
+ traceline_antilag_force (ent, w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
+ if (trace_ent.takedamage && IS_PLAYER(trace_ent))
+ {
+ entity e;
+ e = trace_ent;
+ traceline(w_shotorg, e.origin, MOVE_NORMAL, ent);
+ if(trace_ent == e)
+ w_shotdir = normalize(trace_ent.origin - w_shotorg);
+ }
+ }
+ }
+ else if(autocvar_g_antilag == 3) // client side hitscan
+ {
+ // this part MUST use prydon cursor
+ if (ent.cursor_trace_ent) // client was aiming at someone
+ if (ent.cursor_trace_ent != ent) // just to make sure
+ if (ent.cursor_trace_ent.takedamage) // and that person is killable
+ if (IS_PLAYER(ent.cursor_trace_ent)) // and actually a player
+ {
+ // verify that the shot would miss without antilag
+ // (avoids an issue where guns would always shoot at their origin)
+ traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
+ if (!trace_ent.takedamage)
+ {
+ // verify that the shot would hit if altered
+ traceline(w_shotorg, ent.cursor_trace_ent.origin, MOVE_NORMAL, ent);
+ if (trace_ent == ent.cursor_trace_ent)
+ w_shotdir = normalize(ent.cursor_trace_ent.origin - w_shotorg);
+ else
+ print("antilag fail\n");
+ }
+ }
+ }
+ }
+
+ ent.dphitcontentsmask = oldsolid; // restore solid type (generally SOLID_SLIDEBOX)
+
+ if (!autocvar_g_norecoil)
+ ent.punchangle_x = recoil * -1;
+
+ if (snd != "")
+ {
+ sound (ent, chan, snd, VOL_BASE, ATTN_NORM);
+ W_PlayStrengthSound(ent);
+ }
+
+ // nudge w_shotend so a trace to w_shotend hits
+ w_shotend = w_shotend + normalize(w_shotend - w_shotorg) * nudge;
+}
+
+vector W_CalculateProjectileVelocity(vector pvelocity, vector mvelocity, float forceAbsolute)
+{
+ vector mdirection;
+ float mspeed;
+ vector outvelocity;
+
+ mvelocity = mvelocity * g_weaponspeedfactor;
+
+ mdirection = normalize(mvelocity);
+ mspeed = vlen(mvelocity);
+
+ outvelocity = get_shotvelocity(pvelocity, mdirection, mspeed, (forceAbsolute ? 0 : autocvar_g_projectiles_newton_style), autocvar_g_projectiles_newton_style_2_minfactor, autocvar_g_projectiles_newton_style_2_maxfactor);
+
+ return outvelocity;
+}
+
+#if 0
+float mspercallsum;
+float mspercallsstyle;
+float mspercallcount;
+#endif
+void W_SetupProjectileVelocityEx(entity missile, vector dir, vector upDir, float pSpeed, float pUpSpeed, float pZSpeed, float spread, float forceAbsolute)
+{
+ if(missile.owner == world)
+ error("Unowned missile");
+
+ dir = dir + upDir * (pUpSpeed / pSpeed);
+ dir_z += pZSpeed / pSpeed;
+ pSpeed *= vlen(dir);
+ dir = normalize(dir);
+
+#if 0
+ if(autocvar_g_projectiles_spread_style != mspercallsstyle)
+ {
+ mspercallsum = mspercallcount = 0;
+ mspercallsstyle = autocvar_g_projectiles_spread_style;
+ }
+ mspercallsum -= gettime(GETTIME_HIRES);
+#endif
+ dir = W_CalculateSpread(dir, spread, g_weaponspreadfactor, autocvar_g_projectiles_spread_style);
+#if 0
+ mspercallsum += gettime(GETTIME_HIRES);
+ mspercallcount += 1;
+ print("avg: ", ftos(mspercallcount / mspercallsum), " per sec\n");
+#endif
+
+ missile.velocity = W_CalculateProjectileVelocity(missile.owner.velocity, pSpeed * dir, forceAbsolute);
+}
+
+void W_SetupProjectileVelocity(entity missile, float pSpeed, float spread) // WEAPONTODO
+{
+ W_SetupProjectileVelocityEx(missile, w_shotdir, v_up, pSpeed, 0, 0, spread, FALSE);
+}
+
+
+// ====================
+// Ballistics Tracing
+// ====================
+
+void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype)
+{
+ vector hitloc, force, endpoint, dir;
+ entity ent, endent;
+ float endq3surfaceflags;
+ float totaldmg;
+ entity o;
+
+ float length;
+ vector beampos;
+ string snd;
+ entity pseudoprojectile;
+ float f, ffs;
+
+ pseudoprojectile = world;
+
+ railgun_start = start;
+ railgun_end = end;
+
+ dir = normalize(end - start);
+ length = vlen(end - start);
+ force = dir * bforce;
+
+ // go a little bit into the wall because we need to hit this wall later
+ end = end + dir;
+
+ totaldmg = 0;
+
+ // trace multiple times until we hit a wall, each obstacle will be made
+ // non-solid so we can hit the next, while doing this we spawn effects and
+ // note down which entities were hit so we can damage them later
+ o = self;
+ while (1)
+ {
+ if(self.antilag_debug)
+ WarpZone_traceline_antilag (self, start, end, FALSE, o, self.antilag_debug);
+ else
+ WarpZone_traceline_antilag (self, start, end, FALSE, o, ANTILAG_LATENCY(self));
+ if(o && WarpZone_trace_firstzone)
+ {
+ o = world;
+ continue;
+ }
+
+ if(trace_ent.solid == SOLID_BSP || trace_ent.solid == SOLID_SLIDEBOX)
+ Damage_DamageInfo(trace_endpos, bdamage, 0, 0, force, deathtype, trace_ent.species, self);
+
+ // if it is world we can't hurt it so stop now
+ if (trace_ent == world || trace_fraction == 1)
+ break;
+
+ // make the entity non-solid so we can hit the next one
+ trace_ent.railgunhit = TRUE;
+ trace_ent.railgunhitloc = end;
+ trace_ent.railgunhitsolidbackup = trace_ent.solid;
+ trace_ent.railgundistance = vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - start);
+ trace_ent.railgunforce = WarpZone_TransformVelocity(WarpZone_trace_transform, force);
+
+ // stop if this is a wall
+ if (trace_ent.solid == SOLID_BSP)
+ break;
+
+ // make the entity non-solid
+ trace_ent.solid = SOLID_NOT;
+ }
+
+ endpoint = trace_endpos;
+ endent = trace_ent;
+ endq3surfaceflags = trace_dphitq3surfaceflags;
+
+ // find all the entities the railgun hit and restore their solid state
+ ent = findfloat(world, railgunhit, TRUE);
+ while (ent)
+ {
+ // restore their solid type
+ ent.solid = ent.railgunhitsolidbackup;
+ ent = findfloat(ent, railgunhit, TRUE);
+ }
+
+ // spawn a temporary explosion entity for RadiusDamage calls
+ //explosion = spawn();
+
+ // Find all non-hit players the beam passed close by
+ if(deathtype == WEP_MINSTANEX || deathtype == WEP_NEX)
+ {
- self.last_yoda = time;
++ FOR_EACH_REALCLIENT(msg_entity)
++ if(msg_entity != self)
++ if(!msg_entity.railgunhit)
++ if(!(IS_SPEC(msg_entity) && msg_entity.enemy == self)) // we use realclient, so spectators can hear the whoosh too
+ {
+ // nearest point on the beam
+ beampos = start + dir * bound(0, (msg_entity.origin - start) * dir, length);
+
+ f = bound(0, 1 - vlen(beampos - msg_entity.origin) / 512, 1);
+ if(f <= 0)
+ continue;
+
+ snd = strcat("weapons/nexwhoosh", ftos(floor(random() * 3) + 1), ".wav");
+
+ if(!pseudoprojectile)
+ pseudoprojectile = spawn(); // we need this so the sound uses the "entchannel4" volume
+ soundtoat(MSG_ONE, pseudoprojectile, beampos, CH_SHOTS, snd, VOL_BASE * f, ATTEN_NONE);
+ }
+
+ if(pseudoprojectile)
+ remove(pseudoprojectile);
+ }
+
+ // find all the entities the railgun hit and hurt them
+ ent = findfloat(world, railgunhit, TRUE);
+ while (ent)
+ {
+ // get the details we need to call the damage function
+ hitloc = ent.railgunhitloc;
+
+ f = ExponentialFalloff(mindist, maxdist, halflifedist, ent.railgundistance);
+ ffs = ExponentialFalloff(mindist, maxdist, forcehalflifedist, ent.railgundistance);
+
+ if(accuracy_isgooddamage(self.realowner, ent))
+ totaldmg += bdamage * f;
+
+ // apply the damage
+ if (ent.takedamage)
+ Damage (ent, self, self, bdamage * f, deathtype, hitloc, ent.railgunforce * ffs);
+
+ // create a small explosion to throw gibs around (if applicable)
+ //setorigin (explosion, hitloc);
+ //RadiusDamage (explosion, self, 10, 0, 50, world, world, 300, deathtype);
+
+ ent.railgunhitloc = '0 0 0';
+ ent.railgunhitsolidbackup = SOLID_NOT;
+ ent.railgunhit = FALSE;
+ ent.railgundistance = 0;
+
+ // advance to the next entity
+ ent = findfloat(ent, railgunhit, TRUE);
+ }
+
+ // calculate hits and fired shots for hitscan
+ accuracy_add(self, self.weapon, 0, min(bdamage, totaldmg));
+
+ trace_endpos = endpoint;
+ trace_ent = endent;
+ trace_dphitq3surfaceflags = endq3surfaceflags;
+}
+
+void W_BallisticBullet_Hit (void)
+{
+ float f, q, g;
+
+ f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
+ q = 1 + self.dmg_edge / self.dmg;
+
+ if(other.solid == SOLID_BSP || other.solid == SOLID_SLIDEBOX)
+ Damage_DamageInfo(self.origin, self.dmg * f, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * f, self.projectiledeathtype, other.species, self);
+
+ if(other && other != self.enemy)
+ {
+ endzcurveparticles();
+
+ yoda = 0;
+ railgun_start = self.origin - 2 * frametime * self.velocity;
+ railgun_end = self.origin + 2 * frametime * self.velocity;
+ g = accuracy_isgooddamage(self.realowner, other);
+ Damage(other, self, self.realowner, self.dmg * f, self.projectiledeathtype, self.origin, self.dmg_force * normalize(self.velocity) * f);
+
+ /*if(yoda && (time > (self.last_yoda + 5)))
+ {
+ Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA);
- // special case for zero density and zero bullet constant:
++ self.last_yoda = time;
+ }*/
+
+ // calculate hits for ballistic weapons
+ if(g)
+ {
+ // do not exceed 100%
+ q = min(self.dmg * q, self.dmg_total + f * self.dmg) - self.dmg_total;
+ self.dmg_total += f * self.dmg;
+ accuracy_add(self.realowner, self.realowner.weapon, 0, q);
+ }
+ }
+
+ self.enemy = other; // don't hit the same player twice with the same bullet
+}
+
+void W_BallisticBullet_LeaveSolid_think()
+{
+ setorigin(self, self.W_BallisticBullet_LeaveSolid_origin);
+ self.velocity = self.W_BallisticBullet_LeaveSolid_velocity;
+
+ self.think = self.W_BallisticBullet_LeaveSolid_think_save;
+ self.nextthink = max(time, self.W_BallisticBullet_LeaveSolid_nextthink_save);
+ self.W_BallisticBullet_LeaveSolid_think_save = func_null;
+
+ self.flags &= ~FL_ONGROUND;
+
+ if(self.enemy.solid == SOLID_BSP)
+ {
+ float f;
+ f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
+ Damage_DamageInfo(self.origin, 0, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * -f, self.projectiledeathtype, 0, self);
+ }
+
+ UpdateCSQCProjectile(self);
+}
+
+float W_BallisticBullet_LeaveSolid(float eff)
+{
+ // move the entity along its velocity until it's out of solid, then let it resume
+ vector vel = self.velocity;
+ float dt, dst, velfactor, v0, vs;
+ float maxdist;
+ float E0_m, Es_m;
+ float constant = self.dmg_radius * (other.ballistics_density ? other.ballistics_density : 1);
+
+ // outside the world? forget it
+ if(self.origin_x > world.maxs_x || self.origin_y > world.maxs_y || self.origin_z > world.maxs_z || self.origin_x < world.mins_x || self.origin_y < world.mins_y || self.origin_z < world.mins_z)
+ return 0;
+
- if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
- if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
++ // special case for zero density and zero bullet constant:
+
+ if(self.dmg_radius == 0)
+ {
+ if(other.ballistics_density < 0)
+ constant = 0; // infinite travel distance
+ else
+ return 0; // no penetration
+ }
+ else
+ {
+ if(other.ballistics_density < 0)
+ constant = 0; // infinite travel distance
+ else if(other.ballistics_density == 0)
+ constant = self.dmg_radius;
+ else
+ constant = self.dmg_radius * other.ballistics_density;
+ }
+
+ // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
+ v0 = vlen(vel);
+
+ E0_m = 0.5 * v0 * v0;
+
+ if(constant)
+ {
+ maxdist = E0_m / constant;
+ // maxdist = 0.5 * v0 * v0 / constant
+ // dprint("max dist = ", ftos(maxdist), "\n");
+
+ if(maxdist <= autocvar_g_ballistics_mindistance)
+ return 0;
+ }
+ else
+ {
+ maxdist = vlen(other.maxs - other.mins) + 1; // any distance, as long as we leave the entity
+ }
+
+ traceline_inverted (self.origin, self.origin + normalize(vel) * maxdist, MOVE_NORMAL, self, TRUE);
+ if(trace_fraction == 1) // 1: we never got out of solid
+ return 0;
+
+ self.W_BallisticBullet_LeaveSolid_origin = trace_endpos;
+
+ dst = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - self.origin));
+ // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
+ Es_m = E0_m - constant * dst;
+ if(Es_m <= 0)
+ {
+ // roundoff errors got us
+ return 0;
+ }
+ vs = sqrt(2 * Es_m);
+ velfactor = vs / v0;
+
+ dt = dst / (0.5 * (v0 + vs));
+ // this is not correct, but the differential equations have no analytic
+ // solution - and these times are very small anyway
+ //print("dt = ", ftos(dt), "\n");
+
+ self.W_BallisticBullet_LeaveSolid_think_save = self.think;
+ self.W_BallisticBullet_LeaveSolid_nextthink_save = self.nextthink;
+ self.think = W_BallisticBullet_LeaveSolid_think;
+ self.nextthink = time + dt;
+
+ vel = vel * velfactor;
+
+ self.velocity = '0 0 0';
+ self.flags |= FL_ONGROUND; // prevent moving
+ self.W_BallisticBullet_LeaveSolid_velocity = vel;
+
+ if(eff >= 0)
+ if(vlen(trace_endpos - self.origin) > 4)
+ {
+ endzcurveparticles();
+ trailparticles(self, eff, self.origin, trace_endpos);
+ }
+
+ return 1;
+}
+
+void W_BallisticBullet_Touch (void)
+{
+ //float density;
+
+ if(self.think == W_BallisticBullet_LeaveSolid_think) // skip this!
+ return;
+
+ PROJECTILE_TOUCH;
+ W_BallisticBullet_Hit ();
+
+ if(self.dmg_radius < 0) // these NEVER penetrate solid
+ {
+ remove(self);
+ return;
+ }
+
+ // if we hit "weapclip", bail out
+ //
+ // rationale of this check:
+ //
+ // any shader that is solid, nodraw AND trans is meant to clip weapon
+ // shots and players, but has no other effect!
+ //
+ // if it is not trans, it is caulk and should not have this side effect
+ //
+ // matching shaders:
+ // common/weapclip (intended)
+ // common/noimpact (is supposed to eat projectiles, but is erased farther above)
+ if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
- void fireBallisticBullet(vector start, vector dir, float spread, float pSpeed, float lifetime, float damage, float force, float dtype, float tracereffects, float gravityfactor, float bulletconstant)
++ if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID))
++ if (!(trace_dphitcontents & DPCONTENTS_OPAQUE))
+ {
+ remove(self);
+ return;
+ }
+
+ // go through solid!
+ if(!W_BallisticBullet_LeaveSolid(-1))
+ {
+ remove(self);
+ return;
+ }
+
+ self.projectiledeathtype |= HITTYPE_BOUNCE;
+}
+
+void endFireBallisticBullet() // WEAPONTODO
+{
+ endzcurveparticles();
+}
+
+void fireBallisticBullet_trace_callback(vector start, vector hit, vector end)
+{
+ if(vlen(trace_endpos - fireBallisticBullet_trace_callback_ent.origin) > 16)
+ zcurveparticles_from_tracetoss(fireBallisticBullet_trace_callback_eff, fireBallisticBullet_trace_callback_ent.origin, trace_endpos, fireBallisticBullet_trace_callback_ent.velocity);
+ WarpZone_trace_forent = world;
+ self.owner = world;
+}
+
- float antilagging;
-
- antilagging = (autocvar_g_antilag_bullets && (pSpeed >= autocvar_g_antilag_bullets));
++void fireBallisticBullet(vector start, vector dir, float spread, float pSpeed, float lifetime, float damage, float force, float dtype, float tracereffects, float bulletconstant)
+{
+ float lag, dt, savetime; //, density;
+ entity pl, oldself;
- if(gravityfactor > 0)
- {
- proj.movetype = MOVETYPE_TOSS;
- proj.gravity = gravityfactor;
- }
- else
- proj.movetype = MOVETYPE_FLY;
+
+ entity proj;
+ proj = spawn();
+ proj.classname = "bullet";
+ proj.owner = proj.realowner = self;
+ PROJECTILE_MAKETRIGGER(proj);
- W_SetupProjectileVelocityEx(proj, dir, v_up, pSpeed, 0, 0, spread, antilagging);
++ proj.movetype = MOVETYPE_FLY;
+ proj.think = SUB_Remove;
+ proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed);
- if(antilagging)
- {
- float eff;
-
- if(tracereffects & EF_RED)
- eff = particleeffectnum("tr_rifle");
- else if(tracereffects & EF_BLUE)
- eff = particleeffectnum("tr_rifle_weak");
- else
- eff = particleeffectnum("tr_bullet");
-
- // NOTE: this may severely throw off weapon balance
- lag = ANTILAG_LATENCY(self);
- if(lag < 0.001)
- lag = 0;
- if not(IS_REAL_CLIENT(self))
- lag = 0;
- if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
- lag = 0; // only do hitscan, but no antilag
++ W_SetupProjectileVelocityEx(proj, dir, v_up, pSpeed, 0, 0, spread, TRUE);
+ proj.angles = vectoangles(proj.velocity);
+ if(bulletconstant > 0)
+ proj.dmg_radius = autocvar_g_ballistics_materialconstant / bulletconstant;
+ else if(bulletconstant == 0)
+ proj.dmg_radius = 0;
+ else
+ proj.dmg_radius = -1;
+ // so: bulletconstant = bullet mass / area of bullet circle
+ setorigin(proj, start);
+ proj.flags = FL_PROJECTILE;
+
+ proj.touch = W_BallisticBullet_Touch;
+ proj.dmg = damage;
+ proj.dmg_force = force;
+ proj.projectiledeathtype = dtype;
+
+ proj.oldvelocity = proj.velocity;
+
+ other = proj; MUTATOR_CALLHOOK(EditProjectile);
+
- if(lag)
- FOR_EACH_PLAYER(pl)
- if(pl != self)
- antilag_takeback(pl, time - lag);
-
- oldself = self;
- self = proj;
++ float eff;
+
- savetime = frametime;
- frametime = 0.05;
++ if(tracereffects & EF_RED)
++ eff = particleeffectnum("tr_rifle");
++ else if(tracereffects & EF_BLUE)
++ eff = particleeffectnum("tr_rifle_weak");
++ else
++ eff = particleeffectnum("tr_bullet");
+
- for(;;)
- {
- // DP tracetoss is stupid and always traces in 0.05s
- // ticks. This makes it trace in 0.05*0.125s ticks
- // instead.
- vector v0;
- float g0;
- v0 = self.velocity;
- g0 = self.gravity;
- self.velocity = self.velocity * 0.125;
- self.gravity *= 0.125 * 0.125;
- trace_fraction = 0;
- fireBallisticBullet_trace_callback_ent = self;
- fireBallisticBullet_trace_callback_eff = eff;
- WarpZone_TraceToss_ThroughZone(self, self.owner, world, fireBallisticBullet_trace_callback);
- self.velocity = v0;
- self.gravity = g0;
-
- if(trace_fraction == 1)
- break;
- // won't hit anything anytime soon (DP's
- // tracetoss does 200 tics of, here,
- // 0.05*0.125s, that is, 1.25 seconds
++ // NOTE: this may severely throw off weapon balance
++ lag = ANTILAG_LATENCY(self);
++ if(lag < 0.001)
++ lag = 0;
++ if (!IS_REAL_CLIENT(self))
++ lag = 0;
++ if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
++ lag = 0; // only do hitscan, but no antilag
+
- other = trace_ent;
- dt = WarpZone_tracetoss_time * 0.125; // this is only approximate!
- setorigin(self, trace_endpos);
- self.velocity = WarpZone_tracetoss_velocity * (1 / 0.125);
++ if(lag)
++ FOR_EACH_PLAYER(pl)
++ if(pl != self)
++ antilag_takeback(pl, time - lag);
+
- if(!SUB_OwnerCheck())
- {
- if(SUB_NoImpactCheck())
- break;
++ oldself = self;
++ self = proj;
+
- // hit the player
- W_BallisticBullet_Hit();
- }
++ savetime = frametime;
++ frametime = 0.05;
+
- if(proj.dmg_radius < 0) // these NEVER penetrate solid
- break;
++ for(;;)
++ {
++ // DP tracetoss is stupid and always traces in 0.05s
++ // ticks. This makes it trace in 0.05*0.125s ticks
++ // instead.
++ vector v0;
++ v0 = self.velocity;
++ self.velocity = self.velocity * 0.125;
++ trace_fraction = 0;
++ fireBallisticBullet_trace_callback_ent = self;
++ fireBallisticBullet_trace_callback_eff = eff;
++ WarpZone_TraceToss_ThroughZone(self, self.owner, world, fireBallisticBullet_trace_callback);
++ self.velocity = v0;
++
++ if(trace_fraction == 1)
++ break;
++ // won't hit anything anytime soon (DP's
++ // tracetoss does 200 tics of, here,
++ // 0.05*0.125s, that is, 1.25 seconds
+
- // if we hit "weapclip", bail out
- //
- // rationale of this check:
- //
- // any shader that is solid, nodraw AND trans is meant to clip weapon
- // shots and players, but has no other effect!
- //
- // if it is not trans, it is caulk and should not have this side effect
- //
- // matching shaders:
- // common/weapclip (intended)
- // common/noimpact (is supposed to eat projectiles, but is erased farther above)
- if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
- if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
- if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
++ other = trace_ent;
++ dt = WarpZone_tracetoss_time * 0.125; // this is only approximate!
++ setorigin(self, trace_endpos);
++ self.velocity = WarpZone_tracetoss_velocity * (1 / 0.125);
+
- // go through solid!
- if(!W_BallisticBullet_LeaveSolid((other && (other.solid != SOLID_BSP)) ? eff : -1))
- break;
++ if(!SUB_OwnerCheck())
++ {
++ if(SUB_NoImpactCheck())
+ break;
+
- W_BallisticBullet_LeaveSolid_think();
++ // hit the player
++ W_BallisticBullet_Hit();
++ }
+
- self.projectiledeathtype |= HITTYPE_BOUNCE;
- }
- frametime = savetime;
- self = oldself;
++ if(proj.dmg_radius < 0) // these NEVER penetrate solid
++ break;
+
- if(lag)
- FOR_EACH_PLAYER(pl)
- if(pl != self)
- antilag_restore(pl);
++ // if we hit "weapclip", bail out
++ //
++ // rationale of this check:
++ //
++ // any shader that is solid, nodraw AND trans is meant to clip weapon
++ // shots and players, but has no other effect!
++ //
++ // if it is not trans, it is caulk and should not have this side effect
++ //
++ // matching shaders:
++ // common/weapclip (intended)
++ // common/noimpact (is supposed to eat projectiles, but is erased farther above)
++ if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
++ if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID))
++ if (!(trace_dphitcontents & DPCONTENTS_OPAQUE))
++ break;
+
- remove(proj);
++ // go through solid!
++ if(!W_BallisticBullet_LeaveSolid((other && (other.solid != SOLID_BSP)) ? eff : -1))
++ break;
+
- return;
++ W_BallisticBullet_LeaveSolid_think();
+
- if(tracereffects & EF_RED)
- CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING_TRACER, TRUE);
- else if(tracereffects & EF_BLUE)
- CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING, TRUE);
- else
- CSQCProjectile(proj, TRUE, PROJECTILE_BULLET, TRUE);
++ self.projectiledeathtype |= HITTYPE_BOUNCE;
+ }
++ frametime = savetime;
++ self = oldself;
+
- if not (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
- Damage_DamageInfo(trace_endpos, damage, 0, 0, dir * max(1, force), dtype, trace_ent.species, self);
++ if(lag)
++ FOR_EACH_PLAYER(pl)
++ if(pl != self)
++ antilag_restore(pl);
++
++ remove(proj);
++
++ return;
+}
+
+void fireBullet (vector start, vector dir, float spread, float damage, float force, float dtype, float tracer)
+{
+ vector end;
+
+ dir = normalize(dir + randomvec() * spread);
+ end = start + dir * MAX_SHOT_DISTANCE;
+ if(self.antilag_debug)
+ traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
+ else
+ traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
+
+ end = trace_endpos;
+
+ if (pointcontents (trace_endpos) != CONTENT_SKY)
+ {
++ if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT))
++ Damage_DamageInfo(trace_endpos, damage, 0, 0, dir * max(1, force), dtype, trace_ent.species, self);
+
+ Damage (trace_ent, self, self, damage, dtype, trace_endpos, dir * force);
+ }
+ trace_endpos = end;
+}
--- /dev/null
-
+/*
+===========================================================================
+
+ CLIENT WEAPONSYSTEM CODE
+ Bring back W_Weaponframe
+
+===========================================================================
+*/
+
+.float weapon_frametime;
+
+float W_WeaponRateFactor()
+{
+ float t;
+ t = 1.0 / g_weaponratefactor;
+
+ return t;
+}
+
+// VorteX: static frame globals
+const float WFRAME_DONTCHANGE = -1;
+const float WFRAME_FIRE1 = 0;
+const float WFRAME_FIRE2 = 1;
+const float WFRAME_IDLE = 2;
+const float WFRAME_RELOAD = 3;
+.float wframe;
+
+void(float fr, float t, void() func) weapon_thinkf;
+
+float CL_Weaponentity_CustomizeEntityForClient()
+{
+ self.viewmodelforclient = self.owner;
+ if(IS_SPEC(other))
+ if(other.enemy == self.owner)
+ self.viewmodelforclient = other;
+ return TRUE;
+}
+
+/*
+ * supported formats:
+ *
+ * 1. simple animated model, muzzle flash handling on h_ model:
+ * h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
+ * tags:
+ * shot = muzzle end (shot origin, also used for muzzle flashes)
+ * shell = casings ejection point (must be on the right hand side of the gun)
+ * weapon = attachment for v_tuba.md3
+ * v_tuba.md3 - first and third person model
+ * g_tuba.md3 - pickup model
+ *
+ * 2. simple animated model, muzzle flash handling on v_ model:
+ * h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
+ * tags:
+ * weapon = attachment for v_tuba.md3
+ * v_tuba.md3 - first and third person model
+ * tags:
+ * shot = muzzle end (shot origin, also used for muzzle flashes)
+ * shell = casings ejection point (must be on the right hand side of the gun)
+ * g_tuba.md3 - pickup model
+ *
+ * 3. fully animated model, muzzle flash handling on h_ model:
+ * h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
+ * tags:
+ * shot = muzzle end (shot origin, also used for muzzle flashes)
+ * shell = casings ejection point (must be on the right hand side of the gun)
+ * handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes)
+ * v_tuba.md3 - third person model
+ * g_tuba.md3 - pickup model
+ *
+ * 4. fully animated model, muzzle flash handling on v_ model:
+ * h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
+ * tags:
+ * shot = muzzle end (shot origin)
+ * shell = casings ejection point (must be on the right hand side of the gun)
+ * v_tuba.md3 - third person model
+ * tags:
+ * shot = muzzle end (for muzzle flashes)
+ * g_tuba.md3 - pickup model
+ */
+
+// writes:
+// self.origin, self.angles
+// self.weaponentity
+// self.movedir, self.view_ofs
+// attachment stuff
+// anim stuff
+// to free:
+// call again with ""
+// remove the ent
+void CL_WeaponEntity_SetModel(string name)
+{
+ float v_shot_idx;
+ if (name != "")
+ {
+ // if there is a child entity, hide it until we're sure we use it
+ if (self.weaponentity)
+ self.weaponentity.model = "";
+ setmodel(self, strcat("models/weapons/v_", name, ".md3")); // precision set below
+ v_shot_idx = gettagindex(self, "shot"); // used later
+ if(!v_shot_idx)
+ v_shot_idx = gettagindex(self, "tag_shot");
+
+ setmodel(self, strcat("models/weapons/h_", name, ".iqm")); // precision set below
+ // preset some defaults that work great for renamed zym files (which don't need an animinfo)
+ self.anim_fire1 = animfixfps(self, '0 1 0.01', '0 0 0');
+ self.anim_fire2 = animfixfps(self, '1 1 0.01', '0 0 0');
+ self.anim_idle = animfixfps(self, '2 1 0.01', '0 0 0');
+ self.anim_reload = animfixfps(self, '3 1 0.01', '0 0 0');
+
+ // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model)
+ // if we don't, this is a "real" animated model
+ if(gettagindex(self, "weapon"))
+ {
+ if (!self.weaponentity)
+ self.weaponentity = spawn();
+ setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
+ setattachment(self.weaponentity, self, "weapon");
+ }
+ else if(gettagindex(self, "tag_weapon"))
+ {
+ if (!self.weaponentity)
+ self.weaponentity = spawn();
+ setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
+ setattachment(self.weaponentity, self, "tag_weapon");
+ }
+ else
+ {
+ if(self.weaponentity)
+ remove(self.weaponentity);
+ self.weaponentity = world;
+ }
+
+ setorigin(self,'0 0 0');
+ self.angles = '0 0 0';
+ self.frame = 0;
+ self.viewmodelforclient = world;
+
+ float idx;
+
+ if(v_shot_idx) // v_ model attached to invisible h_ model
+ {
+ self.movedir = gettaginfo(self.weaponentity, v_shot_idx);
+ }
+ else
+ {
+ idx = gettagindex(self, "shot");
+ if(!idx)
+ idx = gettagindex(self, "tag_shot");
+ if(idx)
+ self.movedir = gettaginfo(self, idx);
+ else
+ {
+ print("WARNING: weapon model ", self.model, " does not support the 'shot' tag, will display shots TOTALLY wrong\n");
+ self.movedir = '0 0 0';
+ }
+ }
+
+ if(self.weaponentity) // v_ model attached to invisible h_ model
+ {
+ idx = gettagindex(self.weaponentity, "shell");
+ if(!idx)
+ idx = gettagindex(self.weaponentity, "tag_shell");
+ if(idx)
+ self.spawnorigin = gettaginfo(self.weaponentity, idx);
+ }
+ else
+ idx = 0;
+ if(!idx)
+ {
+ idx = gettagindex(self, "shell");
+ if(!idx)
+ idx = gettagindex(self, "tag_shell");
+ if(idx)
+ self.spawnorigin = gettaginfo(self, idx);
+ else
+ {
+ print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n");
+ self.spawnorigin = self.movedir;
+ }
+ }
+
+ if(v_shot_idx)
+ {
+ self.oldorigin = '0 0 0'; // use regular attachment
+ }
+ else
+ {
+ if(self.weaponentity)
+ {
+ idx = gettagindex(self, "weapon");
+ if(!idx)
+ idx = gettagindex(self, "tag_weapon");
+ }
+ else
+ {
+ idx = gettagindex(self, "handle");
+ if(!idx)
+ idx = gettagindex(self, "tag_handle");
+ }
+ if(idx)
+ {
+ self.oldorigin = self.movedir - gettaginfo(self, idx);
+ }
+ else
+ {
+ print("WARNING: weapon model ", self.model, " does not support the 'handle' tag and neither does the v_ model support the 'shot' tag, will display muzzle flashes TOTALLY wrong\n");
+ self.oldorigin = '0 0 0'; // there is no way to recover from this
+ }
+ }
+
+ self.viewmodelforclient = self.owner;
+ }
+ else
+ {
+ self.model = "";
+ if(self.weaponentity)
+ remove(self.weaponentity);
+ self.weaponentity = world;
+ self.movedir = '0 0 0';
+ self.spawnorigin = '0 0 0';
+ self.oldorigin = '0 0 0';
+ self.anim_fire1 = '0 1 0.01';
+ self.anim_fire2 = '0 1 0.01';
+ self.anim_idle = '0 1 0.01';
+ self.anim_reload = '0 1 0.01';
+ }
+
+ self.view_ofs = '0 0 0';
+
+ if(self.movedir_x >= 0)
+ {
+ vector v0;
+ v0 = self.movedir;
+ self.movedir = shotorg_adjust(v0, FALSE, FALSE);
+ self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0;
+ }
+ self.owner.stat_shotorg = compressShotOrigin(self.movedir);
+ self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly
+
+ self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount
+
+ // check if an instant weapon switch occurred
+ setorigin(self, self.view_ofs);
+ // reset animstate now
+ self.wframe = WFRAME_IDLE;
+ setanim(self, self.anim_idle, TRUE, FALSE, TRUE);
+}
+
+vector CL_Weapon_GetShotOrg(float wpn)
+{
+ entity wi, oldself;
+ vector ret;
+ wi = get_weaponinfo(wpn);
+ oldself = self;
+ self = spawn();
+ CL_WeaponEntity_SetModel(wi.mdl);
+ ret = self.movedir;
+ CL_WeaponEntity_SetModel("");
+ remove(self);
+ self = oldself;
+ return ret;
+}
+
+void CL_Weaponentity_Think()
+{
+ float tb;
+ self.nextthink = time;
+ if (intermission_running)
+ self.frame = self.anim_idle_x;
+ if (self.owner.weaponentity != self)
+ {
+ if (self.weaponentity)
+ remove(self.weaponentity);
+ remove(self);
+ return;
+ }
+ if (self.owner.deadflag != DEAD_NO)
+ {
+ self.model = "";
+ if (self.weaponentity)
+ self.weaponentity.model = "";
+ return;
+ }
+ if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
+ {
+ self.weaponname = self.owner.weaponname;
+ self.dmg = self.owner.modelindex;
+ self.deadflag = self.owner.deadflag;
+
+ CL_WeaponEntity_SetModel(self.owner.weaponname);
+ }
+
+ tb = (self.effects & (EF_TELEPORT_BIT | EF_RESTARTANIM_BIT));
+ self.effects = self.owner.effects & EFMASK_CHEAP;
+ self.effects &= ~EF_LOWPRECISION;
+ self.effects &= ~EF_FULLBRIGHT; // can mask team color, so get rid of it
+ self.effects &= ~EF_TELEPORT_BIT;
+ self.effects &= ~EF_RESTARTANIM_BIT;
+ self.effects |= tb;
+
+ if(self.owner.alpha == default_player_alpha)
+ self.alpha = default_weapon_alpha;
+ else if(self.owner.alpha != 0)
+ self.alpha = self.owner.alpha;
+ else
+ self.alpha = 1;
+
+ self.glowmod = self.owner.weaponentity_glowmod;
+ self.colormap = self.owner.colormap;
+ if (self.weaponentity)
+ {
+ self.weaponentity.effects = self.effects;
+ self.weaponentity.alpha = self.alpha;
+ self.weaponentity.colormap = self.colormap;
+ self.weaponentity.glowmod = self.glowmod;
+ }
+
+ self.angles = '0 0 0';
- if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
++
+ float f = (self.owner.weapon_nextthink - time);
+ if (self.state == WS_RAISE && !intermission_running)
+ {
+ entity newwep = get_weaponinfo(self.owner.switchweapon);
+ f = f * g_weaponratefactor / max(f, newwep.switchdelay_raise);
+ //print(sprintf("CL_Weaponentity_Think(): cvar: %s, value: %f, nextthink: %f\n", sprintf("g_balance_%s_switchdelay_raise", newwep.netname), cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname)), (self.owner.weapon_nextthink - time)));
+ self.angles_x = -90 * f * f;
+ }
+ else if (self.state == WS_DROP && !intermission_running)
+ {
+ entity oldwep = get_weaponinfo(self.owner.weapon);
+ f = 1 - f * g_weaponratefactor / max(f, oldwep.switchdelay_drop);
+ //print(sprintf("CL_Weaponentity_Think(): cvar: %s, value: %f, nextthink: %f\n", sprintf("g_balance_%s_switchdelay_drop", oldwep.netname), cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname)), (self.owner.weapon_nextthink - time)));
+ self.angles_x = -90 * f * f;
+ }
+ else if (self.state == WS_CLEAR)
+ {
+ f = 1;
+ self.angles_x = -90 * f * f;
+ }
+}
+
+void CL_ExteriorWeaponentity_Think()
+{
+ float tag_found;
+ self.nextthink = time;
+ if (self.owner.exteriorweaponentity != self)
+ {
+ remove(self);
+ return;
+ }
+ if (self.owner.deadflag != DEAD_NO)
+ {
+ self.model = "";
+ return;
+ }
+ if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
+ {
+ self.weaponname = self.owner.weaponname;
+ self.dmg = self.owner.modelindex;
+ self.deadflag = self.owner.deadflag;
+ if (self.owner.weaponname != "")
+ setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below
+ else
+ self.model = "";
+
+ if((tag_found = gettagindex(self.owner, "tag_weapon")))
+ {
+ self.tag_index = tag_found;
+ self.tag_entity = self.owner;
+ }
+ else
+ setattachment(self, self.owner, "bip01 r hand");
+ }
+ self.effects = self.owner.effects;
+ self.effects |= EF_LOWPRECISION;
+ self.effects = self.effects & EFMASK_CHEAP; // eat performance
+ if(self.owner.alpha == default_player_alpha)
+ self.alpha = default_weapon_alpha;
+ else if(self.owner.alpha != 0)
+ self.alpha = self.owner.alpha;
+ else
+ self.alpha = 1;
+
+ self.glowmod = self.owner.weaponentity_glowmod;
+ self.colormap = self.owner.colormap;
+
+ CSQCMODEL_AUTOUPDATE();
+}
+
+// spawning weaponentity for client
+void CL_SpawnWeaponentity()
+{
+ self.weaponentity = spawn();
+ self.weaponentity.classname = "weaponentity";
+ self.weaponentity.solid = SOLID_NOT;
+ self.weaponentity.owner = self;
+ setmodel(self.weaponentity, ""); // precision set when changed
+ setorigin(self.weaponentity, '0 0 0');
+ self.weaponentity.angles = '0 0 0';
+ self.weaponentity.viewmodelforclient = self;
+ self.weaponentity.flags = 0;
+ self.weaponentity.think = CL_Weaponentity_Think;
+ self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient;
+ self.weaponentity.nextthink = time;
+
+ self.exteriorweaponentity = spawn();
+ self.exteriorweaponentity.classname = "exteriorweaponentity";
+ self.exteriorweaponentity.solid = SOLID_NOT;
+ self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity;
+ self.exteriorweaponentity.owner = self;
+ setorigin(self.exteriorweaponentity, '0 0 0');
+ self.exteriorweaponentity.angles = '0 0 0';
+ self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think;
+ self.exteriorweaponentity.nextthink = time;
+
+ {
+ entity oldself = self;
+ self = self.exteriorweaponentity;
+ CSQCMODEL_AUTOINIT();
+ self = oldself;
+ }
+}
+
+// Weapon subs
+void w_clear()
+{
+ if (self.weapon != -1)
+ {
+ self.weapon = 0;
+ self.switchingweapon = 0;
+ }
+ if (self.weaponentity)
+ {
+ self.weaponentity.state = WS_CLEAR;
+ self.weaponentity.effects = 0;
+ }
+}
+
+void w_ready()
+{
+ if (self.weaponentity)
+ self.weaponentity.state = WS_READY;
+ weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);
+}
+
+.float prevdryfire;
+.float prevwarntime;
+float weapon_prepareattack_checkammo(float secondary)
+{
-
++ if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+ if (!WEP_ACTION(self.weapon, WR_CHECKAMMO1 + secondary))
+ {
+ // always keep the Mine Layer if we placed mines, so that we can detonate them
+ entity mine;
+ if(self.weapon == WEP_MINE_LAYER)
+ for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
+ return FALSE;
+
+ if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons
+ {
+ sound (self, CH_WEAPON_A, "weapons/dryfire.wav", VOL_BASE, ATTEN_NORM);
+ self.prevdryfire = time;
+ }
+
+ if(WEP_ACTION(self.weapon, WR_CHECKAMMO2 - secondary)) // check if the other firing mode has enough ammo
+ {
+ if(time - self.prevwarntime > 1)
+ {
+ Send_Notification(
+ NOTIF_ONE,
+ self,
+ MSG_MULTI,
+ ITEM_WEAPON_PRIMORSEC,
+ self.weapon,
+ secondary,
+ (1 - secondary)
+ );
+ }
+ self.prevwarntime = time;
+ }
+ else // this weapon is totally unable to fire, switch to another one
+ {
+ W_SwitchToOtherWeapon(self);
+ }
- if not(e.spawnflags & WEP_FLAG_RELOADABLE)
++
+ return FALSE;
+ }
+ return TRUE;
+}
+.float race_penalty;
+float weapon_prepareattack_check(float secondary, float attacktime)
+{
+ if(!weapon_prepareattack_checkammo(secondary))
+ return FALSE;
+
+ //if sv_ready_restart_after_countdown is set, don't allow the player to shoot
+ //if all players readied up and the countdown is running
+ if(time < game_starttime || time < self.race_penalty) {
+ return FALSE;
+ }
+
+ if (timeout_status == TIMEOUT_ACTIVE) //don't allow the player to shoot while game is paused
+ return FALSE;
+
+ // do not even think about shooting if switching
+ if(self.switchweapon != self.weapon)
+ return FALSE;
+
+ if(attacktime >= 0)
+ {
+ // don't fire if previous attack is not finished
+ if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5)
+ return FALSE;
+ // don't fire while changing weapon
+ if (self.weaponentity.state != WS_READY)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+float weapon_prepareattack_do(float secondary, float attacktime)
+{
+ self.weaponentity.state = WS_INUSE;
+
+ self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire
+
+ // if the weapon hasn't been firing continuously, reset the timer
+ if(attacktime >= 0)
+ {
+ if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5)
+ {
+ ATTACK_FINISHED(self) = time;
+ //dprint("resetting attack finished to ", ftos(time), "\n");
+ }
+ ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor();
+ }
+ self.bulletcounter += 1;
+ //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");
+ return TRUE;
+}
+float weapon_prepareattack(float secondary, float attacktime)
+{
+ if(weapon_prepareattack_check(secondary, attacktime))
+ {
+ weapon_prepareattack_do(secondary, attacktime);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+void weapon_thinkf(float fr, float t, void() func)
+{
+ vector a;
+ vector of, or, ou;
+ float restartanim;
+
+ if(fr == WFRAME_DONTCHANGE)
+ {
+ fr = self.weaponentity.wframe;
+ restartanim = FALSE;
+ }
+ else if (fr == WFRAME_IDLE)
+ restartanim = FALSE;
+ else
+ restartanim = TRUE;
+
+ of = v_forward;
+ or = v_right;
+ ou = v_up;
+
+ if (self.weaponentity)
+ {
+ self.weaponentity.wframe = fr;
+ a = '0 0 0';
+ if (fr == WFRAME_IDLE)
+ a = self.weaponentity.anim_idle;
+ else if (fr == WFRAME_FIRE1)
+ a = self.weaponentity.anim_fire1;
+ else if (fr == WFRAME_FIRE2)
+ a = self.weaponentity.anim_fire2;
+ else // if (fr == WFRAME_RELOAD)
+ a = self.weaponentity.anim_reload;
+ a_z *= g_weaponratefactor;
+ setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim);
+ }
+
+ v_forward = of;
+ v_right = or;
+ v_up = ou;
+
+ if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)
+ {
+ backtrace("Tried to override initial weapon think function - should this really happen?");
+ }
+
+ t *= W_WeaponRateFactor();
+
+ // VorteX: haste can be added here
+ if (self.weapon_think == w_ready)
+ {
+ self.weapon_nextthink = time;
+ //dprint("started firing at ", ftos(time), "\n");
+ }
+ if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5)
+ {
+ self.weapon_nextthink = time;
+ //dprint("reset weapon animation timer at ", ftos(time), "\n");
+ }
+ self.weapon_nextthink = self.weapon_nextthink + t;
+ self.weapon_think = func;
+ //dprint("next ", ftos(self.weapon_nextthink), "\n");
+
+ if((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t)
+ {
+ if(self.weapon == WEP_SHOTGUN && fr == WFRAME_FIRE2)
+ animdecide_setaction(self, ANIMACTION_MELEE, restartanim);
+ else
+ animdecide_setaction(self, ANIMACTION_SHOOT, restartanim);
+ }
+ else
+ {
+ if(self.anim_upper_action == ANIMACTION_SHOOT || self.anim_upper_action == ANIMACTION_MELEE)
+ self.anim_upper_action = 0;
+ }
+}
+
+float forbidWeaponUse()
+{
+ if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown)
+ return 1;
+ if(round_handler_IsActive() && !round_handler_IsRoundStarted())
+ return 1;
+ if(self.player_blocked)
+ return 1;
+ if(self.freezetag_frozen)
+ return 1;
+ return 0;
+}
+
+void W_WeaponFrame()
+{
+ vector fo, ri, up;
+
+ if (frametime)
+ self.weapon_frametime = frametime;
+
+ if (!self.weaponentity || self.health < 1)
+ return; // Dead player can't use weapons and injure impulse commands
+
+ if(forbidWeaponUse())
+ if(self.weaponentity.state != WS_CLEAR)
+ {
+ w_ready();
+ return;
+ }
+
+ if(!self.switchweapon)
+ {
+ self.weapon = 0;
+ self.switchingweapon = 0;
+ self.weaponentity.state = WS_CLEAR;
+ self.weaponname = "";
+ self.items &= ~IT_AMMO;
+ return;
+ }
+
+ makevectors(self.v_angle);
+ fo = v_forward; // save them in case the weapon think functions change it
+ ri = v_right;
+ up = v_up;
+
+ // Change weapon
+ if (self.weapon != self.switchweapon)
+ {
+ if (self.weaponentity.state == WS_CLEAR)
+ {
+ // end switching!
+ self.switchingweapon = self.switchweapon;
+ entity newwep = get_weaponinfo(self.switchweapon);
+
+ self.items &= ~IT_AMMO;
+ self.items = self.items | (newwep.items & IT_AMMO);
+
+ // the two weapon entities will notice this has changed and update their models
+ self.weapon = self.switchweapon;
+ self.weaponname = newwep.mdl;
+ self.bulletcounter = 0; // WEAPONTODO
+ WEP_ACTION(self.switchweapon, WR_SETUP);
+ self.weaponentity.state = WS_RAISE;
+
+ // set our clip load to the load of the weapon we switched to, if it's reloadable
+ if(newwep.spawnflags & WEP_FLAG_RELOADABLE && newwep.reloading_ammo) // prevent accessing undefined cvars
+ {
+ self.clip_load = self.(weapon_load[self.switchweapon]);
+ self.clip_size = newwep.reloading_ammo;
+ }
+ else
+ self.clip_load = self.clip_size = 0;
+
+ // VorteX: add player model weapon select frame here
+ // setcustomframe(PlayerWeaponRaise);
+ weapon_thinkf(WFRAME_IDLE, newwep.switchdelay_raise, w_ready);
+ //print(sprintf("W_WeaponFrame(): cvar: %s, value: %f\n", sprintf("g_balance_%s_switchdelay_raise", newwep.netname), cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname))));
+ }
+ else if (self.weaponentity.state == WS_DROP)
+ {
+ // in dropping phase we can switch at any time
+ self.switchingweapon = self.switchweapon;
+ }
+ else if (self.weaponentity.state == WS_READY)
+ {
+ // start switching!
+ self.switchingweapon = self.switchweapon;
+
+ entity oldwep = get_weaponinfo(self.weapon);
+
+#ifndef INDEPENDENT_ATTACK_FINISHED
+ if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5)
+ {
+#endif
+ sound (self, CH_WEAPON_SINGLE, "weapons/weapon_switch.wav", VOL_BASE, ATTN_NORM);
+ self.weaponentity.state = WS_DROP;
+ // set up weapon switch think in the future, and start drop anim
+ weapon_thinkf(WFRAME_DONTCHANGE, oldwep.switchdelay_drop, w_clear);
+ //print(sprintf("W_WeaponFrame(): cvar: %s, value: %f\n", sprintf("g_balance_%s_switchdelay_drop", oldwep.netname), cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname))));
+#ifndef INDEPENDENT_ATTACK_FINISHED
+ }
+#endif
+ }
+ }
+
+ // LordHavoc: network timing test code
+ //if (self.button0)
+ // print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n");
+
+ float w;
+ w = self.weapon;
+
+ // call the think code which may fire the weapon
+ // and do so multiple times to resolve framerate dependency issues if the
+ // server framerate is very low and the weapon fire rate very high
+ float c;
+ c = 0;
+ while (c < W_TICSPERFRAME)
+ {
+ c = c + 1;
+ if(w && !(self.weapons & WepSet_FromWeapon(w)))
+ {
+ if(self.weapon == self.switchweapon)
+ W_SwitchWeapon_Force(self, w_getbestweapon(self));
+ w = 0;
+ }
+
+ v_forward = fo;
+ v_right = ri;
+ v_up = up;
+
+ if(w)
+ WEP_ACTION(self.weapon, WR_THINK);
+ else
+ WEP_ACTION(self.weapon, WR_GONETHINK);
+
+ if (time + self.weapon_frametime * 0.5 >= self.weapon_nextthink)
+ {
+ if(self.weapon_think)
+ {
+ v_forward = fo;
+ v_right = ri;
+ v_up = up;
+ self.weapon_think();
+ }
+ else
+ bprint("\{1}^1ERROR: undefined weapon think function for ", self.netname, "\n");
+ }
+ }
+
+#if 0
+ if (self.items & IT_CELLS)
+ self.currentammo = self.ammo_cells;
+ else if (self.items & IT_ROCKETS)
+ self.currentammo = self.ammo_rockets;
+ else if (self.items & IT_NAILS)
+ self.currentammo = self.ammo_nails;
+ else if (self.items & IT_SHELLS)
+ self.currentammo = self.ammo_shells;
+ else
+ self.currentammo = 1;
+#endif
+}
+
+void W_AttachToShotorg(entity flash, vector offset)
+{
+ entity xflash;
+ flash.owner = self;
+ flash.angles_z = random() * 360;
+
+ if(gettagindex(self.weaponentity, "shot"))
+ setattachment(flash, self.weaponentity, "shot");
+ else
+ setattachment(flash, self.weaponentity, "tag_shot");
+ setorigin(flash, offset);
+
+ xflash = spawn();
+ copyentity(flash, xflash);
+
+ flash.viewmodelforclient = self;
+
+ if(self.weaponentity.oldorigin_x > 0)
+ {
+ setattachment(xflash, self.exteriorweaponentity, "");
+ setorigin(xflash, self.weaponentity.oldorigin + offset);
+ }
+ else
+ {
+ if(gettagindex(self.exteriorweaponentity, "shot"))
+ setattachment(xflash, self.exteriorweaponentity, "shot");
+ else
+ setattachment(xflash, self.exteriorweaponentity, "tag_shot");
+ setorigin(xflash, offset);
+ }
+}
+
+void W_DecreaseAmmo(.float ammo_type, float ammo_use, float ammo_reload) // WEAPONTODO: why does this have ammo_type?
+{
+ if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !ammo_reload)
+ return;
+
+ // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
+ if(ammo_reload)
+ {
+ self.clip_load -= ammo_use;
+ self.(weapon_load[self.weapon]) = self.clip_load;
+ }
+ else
+ self.(self.current_ammo) -= ammo_use;
+}
+
+// weapon reloading code
+
+.float reload_ammo_amount, reload_ammo_min, reload_time;
+.float reload_complain;
+.string reload_sound;
+
+void W_ReloadedAndReady()
+{
+ // finish the reloading process, and do the ammo transfer
+
+ self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
+
+ // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load
+ if(!self.reload_ammo_min || self.items & IT_UNLIMITED_WEAPON_AMMO)
+ self.clip_load = self.reload_ammo_amount;
+ else
+ {
+ while(self.clip_load < self.reload_ammo_amount && self.(self.current_ammo)) // make sure we don't add more ammo than we have
+ {
+ self.clip_load += 1;
+ self.(self.current_ammo) -= 1;
+ }
+ }
+ self.(weapon_load[self.weapon]) = self.clip_load;
+
+ // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
+ // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
+ // so your weapon is disabled for a few seconds without reason
+
+ //ATTACK_FINISHED(self) -= self.reload_time - 1;
+
+ w_ready();
+}
+
+void W_Reload(float sent_ammo_min, string sent_sound)
+{
+ // set global values to work with
+ entity e;
+ e = get_weaponinfo(self.weapon);
+
+ self.reload_ammo_min = sent_ammo_min;
+ self.reload_ammo_amount = e.reloading_ammo;;
+ self.reload_time = e.reloading_time;
+ self.reload_sound = sent_sound;
+
+ // don't reload weapons that don't have the RELOADABLE flag
- if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
++ if (!(e.spawnflags & WEP_FLAG_RELOADABLE))
+ {
+ dprint("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n");
+ return;
+ }
+
+ // return if reloading is disabled for this weapon
+ if(!self.reload_ammo_amount)
+ return;
+
+ // our weapon is fully loaded, no need to reload
+ if (self.clip_load >= self.reload_ammo_amount)
+ return;
+
+ // no ammo, so nothing to load
+ if(!self.(self.current_ammo) && self.reload_ammo_min)
- if not(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2))
++ if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+ {
+ if(IS_REAL_CLIENT(self) && self.reload_complain < time)
+ {
+ play2(self, "weapons/unavailable.wav");
+ sprint(self, strcat("You don't have enough ammo to reload the ^2", W_Name(self.weapon), "\n"));
+ self.reload_complain = time + 1;
+ }
+ // switch away if the amount of ammo is not enough to keep using this weapon
++ if (!(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2)))
+ {
+ self.clip_load = -1; // reload later
+ W_SwitchToOtherWeapon(self);
+ }
+ return;
+ }
+
+ if (self.weaponentity)
+ {
+ if (self.weaponentity.wframe == WFRAME_RELOAD)
+ return;
+
+ // allow switching away while reloading, but this will cause a new reload!
+ self.weaponentity.state = WS_READY;
+ }
+
+ // now begin the reloading process
+
+ sound(self, CH_WEAPON_SINGLE, self.reload_sound, VOL_BASE, ATTEN_NORM);
+
+ // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
+ // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
+ // so your weapon is disabled for a few seconds without reason
+
+ //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1;
+
+ weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady);
+
+ if(self.clip_load < 0)
+ self.clip_load = 0;
+ self.old_clip_load = self.clip_load;
+ self.clip_load = self.(weapon_load[self.weapon]) = -1;
+}