From: Samual Lenks Date: Thu, 5 Sep 2013 18:19:01 +0000 (-0400) Subject: Merge remote-tracking branch 'origin/master' into samual/weapons X-Git-Tag: xonotic-v0.8.0~152^2~327 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=3c7ef5b449d479f9a2e24fdddf767f87208c9be6;p=xonotic%2Fxonotic-data.pk3dir.git Merge remote-tracking branch 'origin/master' into samual/weapons Conflicts: qcsrc/client/hook.qc qcsrc/common/constants.qh qcsrc/common/weapons/w_electro.qc qcsrc/common/weapons/w_fireball.qc qcsrc/common/weapons/w_seeker.qc qcsrc/common/weapons/w_shockwave.qc qcsrc/common/weapons/weapons.qh qcsrc/server/cl_weapons.qc qcsrc/server/defs.qh qcsrc/server/miscfunctions.qc qcsrc/server/mutators/gamemode_nexball.qc qcsrc/server/t_items.qc qcsrc/server/tturrets/units/unit_walker.qc qcsrc/server/w_crylink.qc qcsrc/server/w_grenadelauncher.qc qcsrc/server/w_hagar.qc qcsrc/server/w_hlac.qc qcsrc/server/w_hook.qc qcsrc/server/w_laser.qc qcsrc/server/w_minelayer.qc qcsrc/server/w_minstanex.qc qcsrc/server/w_nex.qc qcsrc/server/w_rifle.qc qcsrc/server/w_rocketlauncher.qc qcsrc/server/w_uzi.qc qcsrc/server/weapons/tracing.qc qcsrc/server/weapons/weaponsystem.qc --- 3c7ef5b449d479f9a2e24fdddf767f87208c9be6 diff --cc oldbalanceXPM.cfg index 8f62776d9,000000000..3b6dd9897 mode 100644,000000..100644 --- a/oldbalanceXPM.cfg +++ b/oldbalanceXPM.cfg @@@ -1,781 -1,0 +1,781 @@@ +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 200 ++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_damage 80 +set g_balance_rocketlauncher_edgedamage 40 +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.1 +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 90 // 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.2 +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 +// }}} diff --cc qcsrc/client/hook.qc index 662de9fc7,ab4891530..162063427 --- a/qcsrc/client/hook.qc +++ b/qcsrc/client/hook.qc @@@ -274,11 -274,11 +274,11 @@@ void Ent_ReadHook(float bIsNew, float t setmodel(self, "models/hook.md3"); self.drawmask = MASK_NORMAL; break; - case ENT_CLIENT_LGBEAM: + case ENT_CLIENT_ELECTRO_BEAM: - sound (self, CH_SHOTS_SINGLE, "weapons/lgbeam_fly.wav", VOL_BASE, ATTN_NORM); + sound (self, CH_SHOTS_SINGLE, "weapons/lgbeam_fly.wav", VOL_BASE, ATTEN_NORM); break; - case ENT_CLIENT_GAUNTLET: - sound (self, CH_SHOTS_SINGLE, "weapons/gauntletbeam_fly.wav", VOL_BASE, ATTEN_NORM); + case ENT_CLIENT_ARC_BEAM: - sound (self, CH_SHOTS_SINGLE, "weapons/lgbeam_fly.wav", VOL_BASE, ATTN_NORM); ++ sound (self, CH_SHOTS_SINGLE, "weapons/lgbeam_fly.wav", VOL_BASE, ATTEN_NORM); break; } } diff --cc qcsrc/client/weapons/projectile.qc index de45141a1,000000000..b86c234bd mode 100644,000000..100644 --- a/qcsrc/client/weapons/projectile.qc +++ b/qcsrc/client/weapons/projectile.qc @@@ -1,535 -1,0 +1,535 @@@ +.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; ++ //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: + 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_GLOWING: + case PROJECTILE_BULLET_GLOWING_TRACER: + adddynamiclight(self.origin, 50 * a, '1 1 0'); + break; + 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; ++ 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_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; + 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; + + 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, ATTN_NORM); ++ 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, ATTN_NORM); ++ 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, ATTN_NORM); ++ 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, ATTN_NORM); ++ 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, ATTN_NORM); ++ 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, ATTN_NORM); ++ 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, ATTN_NORM); ++ 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, ATTN_NORM); ++ 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"); + +} diff --cc qcsrc/common/constants.qh index e102227a5,b3dafaaf7..822d61a72 --- a/qcsrc/common/constants.qh +++ b/qcsrc/common/constants.qh @@@ -382,11 -384,29 +385,10 @@@ const float SPECIES_RESERVED = 15 // we can use this frags value for both // water levels - float WATERLEVEL_NONE = 0; - float WATERLEVEL_WETFEET = 1; - float WATERLEVEL_SWIMMING = 2; - float WATERLEVEL_SUBMERGED = 3; - + const float WATERLEVEL_NONE = 0; + const float WATERLEVEL_WETFEET = 1; + const float WATERLEVEL_SWIMMING = 2; + const float WATERLEVEL_SUBMERGED = 3; - -const float MAX_SHOT_DISTANCE = 32768; - -// weapon requests -const float WR_SETUP = 1; // (SVQC) setup weapon data -const float WR_THINK = 2; // (SVQC) logic to run every frame -const float WR_CHECKAMMO1 = 3; // (SVQC) checks ammo for weapon -const float WR_CHECKAMMO2 = 4; // (SVQC) checks ammo for weapon -const float WR_AIM = 5; // (SVQC) runs bot aiming code for this weapon -const float WR_PRECACHE = 6; // (CSQC and SVQC) precaches models/sounds used by this weapon -const float WR_SUICIDEMESSAGE = 7; // (SVQC) notification number for suicide message (may inspect w_deathtype for details) -const float WR_KILLMESSAGE = 8; // (SVQC) notification number for kill message (may inspect w_deathtype for details) -const float WR_RELOAD = 9; // (SVQC) does not need to do anything -const float WR_RESETPLAYER = 10; // (SVQC) does not need to do anything -const float WR_IMPACTEFFECT = 11; // (CSQC) impact effect -const float WR_SWITCHABLE = 12; // (CSQC) impact effect -const float WR_PLAYERDEATH = 13; // (SVQC) does not need to do anything -const float WR_GONETHINK = 14; // (SVQC) logic to run every frame, also if no longer having the weapon as long as the switch away has not been performed - #define SERVERFLAG_ALLOW_FULLBRIGHT 1 #define SERVERFLAG_TEAMPLAY 2 #define SERVERFLAG_PLAYERSTATS 4 diff --cc qcsrc/common/weapons/w_electro.qc index 14ae101cd,000000000..2b79a92b2 mode 100644,000000..100644 --- a/qcsrc/common/weapons/w_electro.qc +++ b/qcsrc/common/weapons/w_electro.qc @@@ -1,638 -1,0 +1,638 @@@ +#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]; +#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, TRUE); + while (e) + { + if (e.classname == "plasma") + { + // change owner to whoever caused the combo explosion + 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(IsDifferentTeam(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, ATTN_NORM); ++ 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"); + + if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_combo ? 1 : -1))) + 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 not(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, ATTN_NORM); ++ 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, ATTN_NORM); ++ 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, ATTN_NORM); ++ 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, ATTN_NORM); ++ 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 diff --cc qcsrc/common/weapons/w_fireball.qc index c547b8c63,000000000..83fe87ad2 mode 100644,000000..100644 --- a/qcsrc/common/weapons/w_fireball.qc +++ b/qcsrc/common/weapons/w_fireball.qc @@@ -1,471 -1,0 +1,471 @@@ +#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 || IsDifferentTeam(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 || IsDifferentTeam(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, ATTN_NORM); ++ 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, ATTN_NORM * 0.25); // long range boom ++ 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 diff --cc qcsrc/common/weapons/w_hook.qc index a9b817006,000000000..c80358703 mode 100644,000000..100644 --- a/qcsrc/common/weapons/w_hook.qc +++ b/qcsrc/common/weapons/w_hook.qc @@@ -1,355 -1,0 +1,355 @@@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ HOOK, +/* function */ w_hook, +/* ammotype */ IT_CELLS|IT_FUEL, +/* impulse */ 0, +/* flags */ WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ 0, +/* model */ "hookgun", +/* netname */ "hook", +/* fullname */ _("Grappling Hook") +); + +#define HOOK_SETTINGS(weapon) \ + WEP_ADD_CVAR(weapon, MO_BOTH, animtime) \ + WEP_ADD_CVAR(weapon, MO_BOTH, ammo) \ + WEP_ADD_CVAR(weapon, MO_BOTH, refire) \ + WEP_ADD_CVAR(weapon, MO_PRI, hooked_ammo) \ + WEP_ADD_CVAR(weapon, MO_PRI, hooked_time_free) \ + WEP_ADD_CVAR(weapon, MO_PRI, hooked_time_max) \ + WEP_ADD_CVAR(weapon, MO_SEC, damage) \ + WEP_ADD_CVAR(weapon, MO_SEC, duration) \ + WEP_ADD_CVAR(weapon, MO_SEC, edgedamage) \ + WEP_ADD_CVAR(weapon, MO_SEC, force) \ + WEP_ADD_CVAR(weapon, MO_SEC, gravity) \ + WEP_ADD_CVAR(weapon, MO_SEC, lifetime) \ + WEP_ADD_CVAR(weapon, MO_SEC, power) \ + WEP_ADD_CVAR(weapon, MO_SEC, radius) \ + WEP_ADD_CVAR(weapon, MO_SEC, speed) \ + WEP_ADD_CVAR(weapon, MO_SEC, health) \ + WEP_ADD_CVAR(weapon, MO_SEC, damageforcescale) \ + 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 +HOOK_SETTINGS(hook) + +.float dmg; +.float dmg_edge; +.float dmg_radius; +.float dmg_force; +.float dmg_power; +.float dmg_duration; +.float dmg_last; +.float hook_refire; +.float hook_time_hooked; +.float hook_time_fueldecrease; +#endif +#else +#ifdef SVQC + +void spawnfunc_weapon_hook() +{ + if(g_grappling_hook) // offhand hook + { + startitem_failed = TRUE; + remove(self); + return; + } + weapon_defaultspawnfunc(WEP_HOOK); +} + +void W_Hook_ExplodeThink (void) +{ + float dt, dmg_remaining_next, f; + + dt = time - self.teleport_time; + dmg_remaining_next = pow(bound(0, 1 - dt / self.dmg_duration, 1), self.dmg_power); + + f = self.dmg_last - dmg_remaining_next; + self.dmg_last = dmg_remaining_next; + + RadiusDamage (self, self.realowner, self.dmg * f, self.dmg_edge * f, self.dmg_radius, self.realowner, world, self.dmg_force * f, self.projectiledeathtype, world); + self.projectiledeathtype |= HITTYPE_BOUNCE; + //RadiusDamage (self, world, self.dmg * f, self.dmg_edge * f, self.dmg_radius, world, world, self.dmg_force * f, self.projectiledeathtype, world); + + if(dt < self.dmg_duration) + self.nextthink = time + 0.05; // soon + else + remove(self); +} + +void W_Hook_Explode2 (void) +{ + self.event_damage = func_null; + self.touch = func_null; + self.effects |= EF_NODRAW; + + self.think = W_Hook_ExplodeThink; + self.nextthink = time; + self.dmg = WEP_CVAR_SEC(hook, damage); + self.dmg_edge = WEP_CVAR_SEC(hook, edgedamage); + self.dmg_radius = WEP_CVAR_SEC(hook, radius); + self.dmg_force = WEP_CVAR_SEC(hook, force); + self.dmg_power = WEP_CVAR_SEC(hook, power); + self.dmg_duration = WEP_CVAR_SEC(hook, duration); + self.teleport_time = time; + self.dmg_last = 1; + self.movetype = MOVETYPE_NONE; +} + +void W_Hook_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) + W_PrepareExplosionByDamage(self.realowner, W_Hook_Explode2); +} + +void W_Hook_Touch2 (void) +{ + PROJECTILE_TOUCH; + self.use(); +} + +void W_Hook_Attack2() +{ + entity gren; + + W_DecreaseAmmo(ammo_cells, WEP_CVAR_SEC(hook, ammo), FALSE); + W_SetupShot (self, FALSE, 4, "weapons/hookbomb_fire.wav", CH_WEAPON_A, WEP_CVAR_SEC(hook, damage)); + + gren = spawn (); + gren.owner = gren.realowner = self; + gren.classname = "hookbomb"; + gren.bot_dodge = TRUE; + gren.bot_dodgerating = WEP_CVAR_SEC(hook, damage); + gren.movetype = MOVETYPE_TOSS; + PROJECTILE_MAKETRIGGER(gren); + gren.projectiledeathtype = WEP_HOOK | HITTYPE_SECONDARY; + setorigin(gren, w_shotorg); + setsize(gren, '0 0 0', '0 0 0'); + + gren.nextthink = time + WEP_CVAR_SEC(hook, lifetime); + gren.think = adaptor_think2use_hittype_splash; + gren.use = W_Hook_Explode2; + gren.touch = W_Hook_Touch2; + + gren.takedamage = DAMAGE_YES; + gren.health = WEP_CVAR_SEC(hook, health); + gren.damageforcescale = WEP_CVAR_SEC(hook, damageforcescale); + gren.event_damage = W_Hook_Damage; + gren.damagedbycontents = TRUE; + gren.missile_flags = MIF_SPLASH | MIF_ARC; + + gren.velocity = '0 0 1' * WEP_CVAR_SEC(hook, speed); + if(autocvar_g_projectiles_newton_style) + gren.velocity = gren.velocity + self.velocity; + + gren.gravity = WEP_CVAR_SEC(hook, gravity); + //W_SetupProjectileVelocity(gren); // just falling down! + + gren.angles = '0 0 0'; + gren.flags = FL_PROJECTILE; + + CSQCProjectile(gren, TRUE, PROJECTILE_HOOKBOMB, TRUE); + + other = gren; MUTATOR_CALLHOOK(EditProjectile); +} + +float w_hook(float req) +{ + float hooked_time_max, hooked_fuel; + + switch(req) + { + case WR_AIM: + { + // no bot AI for hook (yet?) + return TRUE; + } + case WR_THINK: + { + if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK)) + { + if(!self.hook) + if not(self.hook_state & HOOK_WAITING_FOR_RELEASE) + if not(self.hook_state & HOOK_FIRING) + if (time > self.hook_refire) + if (weapon_prepareattack(0, -1)) + { + W_DecreaseAmmo(ammo_fuel, WEP_CVAR_PRI(hook, ammo), FALSE); + self.hook_state |= HOOK_FIRING; + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hook, animtime), w_ready); + } + } + + if (self.BUTTON_ATCK2) + { + if (weapon_prepareattack(1, WEP_CVAR_SEC(hook, refire))) + { + W_Hook_Attack2(); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hook, animtime), w_ready); + } + } + + if(self.hook) + { + // if hooked, no bombs, and increase the timer + self.hook_refire = max(self.hook_refire, time + WEP_CVAR_PRI(hook, refire) * W_WeaponRateFactor()); + + // hook also inhibits health regeneration, but only for 1 second + if not(self.items & IT_UNLIMITED_WEAPON_AMMO) + self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen); + } + + if(self.hook && self.hook.state == 1) + { + hooked_time_max = WEP_CVAR_PRI(hook, hooked_time_max); + if (hooked_time_max > 0) + { + if ( time > self.hook_time_hooked + hooked_time_max ) + self.hook_state |= HOOK_REMOVING; + } + + hooked_fuel = WEP_CVAR_PRI(hook, hooked_ammo); + if (hooked_fuel > 0) + { + if ( time > self.hook_time_fueldecrease ) + { + if not(self.items & IT_UNLIMITED_WEAPON_AMMO) + { + if ( self.ammo_fuel >= (time - self.hook_time_fueldecrease) * hooked_fuel ) + { + W_DecreaseAmmo(ammo_fuel, (time - self.hook_time_fueldecrease) * hooked_fuel, FALSE); + self.hook_time_fueldecrease = time; + // decrease next frame again + } + else + { + self.ammo_fuel = 0; + self.hook_state |= HOOK_REMOVING; + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + } + } + } + } + } + else + { + self.hook_time_hooked = time; + self.hook_time_fueldecrease = time + WEP_CVAR_PRI(hook, hooked_time_free); + } + + if (self.BUTTON_CROUCH) + { - self.hook_state &~= HOOK_PULLING; ++ self.hook_state &= ~HOOK_PULLING; + if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK)) - self.hook_state &~= HOOK_RELEASING; ++ self.hook_state &= ~HOOK_RELEASING; + else + self.hook_state |= HOOK_RELEASING; + } + else + { + self.hook_state |= HOOK_PULLING; - self.hook_state &~= HOOK_RELEASING; ++ self.hook_state &= ~HOOK_RELEASING; + + if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK)) + { + // already fired + if(self.hook) + self.hook_state |= HOOK_WAITING_FOR_RELEASE; + } + else + { + self.hook_state |= HOOK_REMOVING; - self.hook_state &~= HOOK_WAITING_FOR_RELEASE; ++ self.hook_state &= ~HOOK_WAITING_FOR_RELEASE; + } + } + + return TRUE; + } + case WR_INIT: + { + precache_model ("models/weapons/g_hookgun.md3"); + precache_model ("models/weapons/v_hookgun.md3"); + precache_model ("models/weapons/h_hookgun.iqm"); + precache_sound ("weapons/hook_impact.wav"); // done by g_hook.qc + precache_sound ("weapons/hook_fire.wav"); + precache_sound ("weapons/hookbomb_fire.wav"); + WEP_SET_PROPS(HOOK_SETTINGS(hook), WEP_HOOK) + return TRUE; + } + case WR_SETUP: + { + self.current_ammo = ammo_fuel; - self.hook_state &~= HOOK_WAITING_FOR_RELEASE; ++ self.hook_state &= ~HOOK_WAITING_FOR_RELEASE; + return TRUE; + } + case WR_CHECKAMMO1: + { + if(self.hook) + return self.ammo_fuel > 0; + else + return self.ammo_fuel >= WEP_CVAR_PRI(hook, ammo); + } + case WR_CHECKAMMO2: + { + return self.ammo_cells >= WEP_CVAR_SEC(hook, ammo); + } + case WR_CONFIG: + { + WEP_CONFIG_SETTINGS(HOOK_SETTINGS(hook)) + return TRUE; + } + case WR_RESETPLAYER: + { + self.hook_refire = time; + return TRUE; + } + case WR_SUICIDEMESSAGE: + { + return FALSE; + } + case WR_KILLMESSAGE: + { + return WEAPON_HOOK_MURDER; + } + } + return TRUE; +} +#endif +#ifdef CSQC +float w_hook(float req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 2; + pointparticles(particleeffectnum("hookbomb_explode"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/hookbomb_impact.wav", VOL_BASE, ATTN_NORM); + + return TRUE; + } + case WR_INIT: + { + precache_sound("weapons/hookbomb_impact.wav"); + return TRUE; + } + } + return TRUE; +} +#endif +#endif diff --cc qcsrc/common/weapons/w_porto.qc index 5625a40a1,000000000..53d28c0b8 mode 100644,000000..100644 --- a/qcsrc/common/weapons/w_porto.qc +++ b/qcsrc/common/weapons/w_porto.qc @@@ -1,410 -1,0 +1,410 @@@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ PORTO, +/* function */ w_porto, +/* ammotype */ 0, +/* impulse */ 0, +/* flags */ WEP_TYPE_OTHER | WEP_FLAG_SUPERWEAPON, +/* rating */ 0, +/* model */ "porto" , +/* netname */ "porto", +/* fullname */ _("Port-O-Launch") +); + +#define PORTO_SETTINGS(weapon) \ + WEP_ADD_CVAR(weapon, MO_BOTH, animtime) \ + WEP_ADD_CVAR(weapon, MO_BOTH, lifetime) \ + WEP_ADD_CVAR(weapon, MO_BOTH, refire) \ + WEP_ADD_CVAR(weapon, MO_BOTH, speed) \ + WEP_ADD_CVAR(weapon, MO_NONE, secondary) \ + 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 +PORTO_SETTINGS(porto) +.entity porto_current; +.vector porto_v_angle; // holds "held" view angles +.float porto_v_angle_held; +.vector right_vector; +#endif +#else +#ifdef SVQC +void spawnfunc_weapon_porto (void) { weapon_defaultspawnfunc(WEP_PORTO); } + +void W_Porto_Success (void) +{ + if(self.realowner == world) + { + objerror("Cannot succeed successfully: no owner\n"); + return; + } + + self.realowner.porto_current = world; + remove(self); +} + +string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo); +void W_Porto_Fail (float failhard) +{ + if(self.realowner == world) + { + objerror("Cannot fail successfully: no owner\n"); + return; + } + + // no portals here! + if(self.cnt < 0) + { + Portal_ClearWithID(self.realowner, self.portal_id); + } + + self.realowner.porto_current = world; + - if(self.cnt < 0 && !failhard && self.realowner.playerid == self.playerid && self.realowner.deadflag == DEAD_NO && !WEPSET_CONTAINS_EW(self.realowner, WEP_PORTO)) ++ if(self.cnt < 0 && !failhard && self.realowner.playerid == self.playerid && self.realowner.deadflag == DEAD_NO && !(self.realowner.weapons & WEPSET_PORTO)) + { + setsize (self, '-16 -16 0', '16 16 32'); + setorigin(self, self.origin + trace_plane_normal); + if(move_out_of_solid(self)) + { + self.flags = FL_ITEM; + self.velocity = trigger_push_calculatevelocity(self.origin, self.realowner, 128); + tracetoss(self, self); + if(vlen(trace_endpos - self.realowner.origin) < 128) + { + W_ThrowNewWeapon(self.realowner, WEP_PORTO, 0, self.origin, self.velocity); + centerprint(self.realowner, "^1Portal deployment failed.\n\n^2Catch it to try again!"); + } + } + } + remove(self); +} + +void W_Porto_Remove (entity p) +{ + if(p.porto_current.realowner == p && p.porto_current.classname == "porto") + { + entity oldself; + oldself = self; + self = p.porto_current; + W_Porto_Fail(1); + self = oldself; + } +} + +void W_Porto_Think (void) +{ + trace_plane_normal = '0 0 0'; + if(self.realowner.playerid != self.playerid) + remove(self); + else + W_Porto_Fail(0); +} + +void W_Porto_Touch (void) +{ + vector norm; + + // do not use PROJECTILE_TOUCH here + // FIXME but DO handle warpzones! + + if(other.classname == "portal") + return; // handled by the portal + + norm = trace_plane_normal; + if(trace_ent.iscreature) + { + traceline(trace_ent.origin, trace_ent.origin + '0 0 2' * PL_MIN_z, MOVE_WORLDONLY, self); + if(trace_fraction >= 1) + return; + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP) + return; + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) + return; + } + + if(self.realowner.playerid != self.playerid) + { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + remove(self); + } + else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP) + { - spamsound(self, CH_SHOTS, "porto/bounce.wav", VOL_BASE, ATTN_NORM); ++ spamsound(self, CH_SHOTS, "porto/bounce.wav", VOL_BASE, ATTEN_NORM); + // just reflect + self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * trace_plane_normal); + self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * trace_plane_normal)); + } + else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) + { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + W_Porto_Fail(0); + if(self.cnt < 0) + Portal_ClearAll_PortalsOnly(self.realowner); + } + else if(self.cnt == 0) + { + // in-portal only + if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) + { - sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM); + trace_plane_normal = norm; + centerprint(self.realowner, "^1In^7-portal created."); + W_Porto_Success(); + } + else + { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + trace_plane_normal = norm; + W_Porto_Fail(0); + } + } + else if(self.cnt == 1) + { + // out-portal only + if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) + { - sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM); + trace_plane_normal = norm; + centerprint(self.realowner, "^4Out^7-portal created."); + W_Porto_Success(); + } + else + { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + trace_plane_normal = norm; + W_Porto_Fail(0); + } + } + else if(self.effects & EF_RED) + { + self.effects += EF_BLUE - EF_RED; + if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) + { - sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM); + trace_plane_normal = norm; + centerprint(self.realowner, "^1In^7-portal created."); + self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * norm); + self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * norm)); + CSQCProjectile(self, TRUE, PROJECTILE_PORTO_BLUE, TRUE); // change type + } + else + { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + trace_plane_normal = norm; + Portal_ClearAll_PortalsOnly(self.realowner); + W_Porto_Fail(0); + } + } + else + { + if(self.realowner.portal_in.portal_id == self.portal_id) + { + if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) + { - sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM); + trace_plane_normal = norm; + centerprint(self.realowner, "^4Out^7-portal created."); + W_Porto_Success(); + } + else + { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + Portal_ClearAll_PortalsOnly(self.realowner); + W_Porto_Fail(0); + } + } + else + { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + Portal_ClearAll_PortalsOnly(self.realowner); + W_Porto_Fail(0); + } + } +} + +void W_Porto_Attack (float type) +{ + entity gren; + + W_SetupShot (self, FALSE, 4, "porto/fire.wav", CH_WEAPON_A, 0); + // always shoot from the eye + w_shotdir = v_forward; + w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward; + + //pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + gren = spawn (); + gren.cnt = type; + gren.owner = gren.realowner = self; + gren.playerid = self.playerid; + gren.classname = "porto"; + gren.bot_dodge = TRUE; + gren.bot_dodgerating = 200; + gren.movetype = MOVETYPE_BOUNCEMISSILE; + PROJECTILE_MAKETRIGGER(gren); + gren.effects = EF_RED; + gren.scale = 4; + setorigin(gren, w_shotorg); + setsize(gren, '0 0 0', '0 0 0'); + + gren.nextthink = time + WEP_CVAR_BOTH(porto, (type <= 0), lifetime); + gren.think = W_Porto_Think; + gren.touch = W_Porto_Touch; + + if(self.items & IT_STRENGTH) + W_SetupProjectileVelocity(gren, WEP_CVAR_BOTH(porto, (type <= 0), speed) * autocvar_g_balance_powerup_strength_force, 0); + else + W_SetupProjectileVelocity(gren, WEP_CVAR_BOTH(porto, (type <= 0), speed), 0); + + gren.angles = vectoangles (gren.velocity); + gren.flags = FL_PROJECTILE; + + gren.portal_id = time; + self.porto_current = gren; + gren.playerid = self.playerid; + fixedmakevectors(fixedvectoangles(gren.velocity)); + gren.right_vector = v_right; + + gren.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP; + + if(type > 0) + CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_BLUE, TRUE); + else + CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_RED, TRUE); + + other = gren; MUTATOR_CALLHOOK(EditProjectile); +} + +float w_nexball_weapon(float req); +float w_porto(float req) +{ + //vector v_angle_save; + + if (g_nexball) { return w_nexball_weapon(req); } + + switch(req) + { + case WR_AIM: + { + self.BUTTON_ATCK = FALSE; + self.BUTTON_ATCK2 = FALSE; + if(!WEP_CVAR(porto, secondary)) + if(bot_aim(WEP_CVAR_PRI(porto, speed), 0, WEP_CVAR_PRI(porto, lifetime), FALSE)) + self.BUTTON_ATCK = TRUE; + + return TRUE; + } + case WR_CONFIG: + { + WEP_CONFIG_SETTINGS(PORTO_SETTINGS(porto)) + return TRUE; + } + case WR_THINK: + { + if(WEP_CVAR(porto, secondary)) + { + if (self.BUTTON_ATCK) + if (!self.porto_current) + if (!self.porto_forbidden) + if (weapon_prepareattack(0, WEP_CVAR_PRI(porto, refire))) + { + W_Porto_Attack(0); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready); + } + + if (self.BUTTON_ATCK2) + if (!self.porto_current) + if (!self.porto_forbidden) + if (weapon_prepareattack(1, WEP_CVAR_SEC(porto, refire))) + { + W_Porto_Attack(1); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(porto, animtime), w_ready); + } + } + else + { + if(self.porto_v_angle_held) + { + if(!self.BUTTON_ATCK2) + { + self.porto_v_angle_held = 0; + + ClientData_Touch(self); + } + } + else + { + if(self.BUTTON_ATCK2) + { + self.porto_v_angle = self.v_angle; + self.porto_v_angle_held = 1; + + ClientData_Touch(self); + } + } + if(self.porto_v_angle_held) + makevectors(self.porto_v_angle); // override the previously set angles + + if (self.BUTTON_ATCK) + if (!self.porto_current) + if (!self.porto_forbidden) + if (weapon_prepareattack(0, WEP_CVAR_PRI(porto, refire))) + { + W_Porto_Attack(-1); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready); + } + } + + return TRUE; + } + case WR_INIT: + { + precache_model ("models/weapons/g_porto.md3"); + precache_model ("models/weapons/v_porto.md3"); + precache_model ("models/weapons/h_porto.iqm"); + precache_model ("models/portal.md3"); + precache_sound ("porto/bounce.wav"); + precache_sound ("porto/create.wav"); + precache_sound ("porto/expire.wav"); + precache_sound ("porto/explode.wav"); + precache_sound ("porto/fire.wav"); + precache_sound ("porto/unsupported.wav"); + WEP_SET_PROPS(PORTO_SETTINGS(porto), WEP_PORTO) + return TRUE; + } + case WR_SETUP: + { + self.current_ammo = ammo_none; + return TRUE; + } + case WR_RESETPLAYER: + { + self.porto_current = world; + return TRUE; + } + } + return TRUE; +} +#endif +#ifdef CSQC +float w_porto(float req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + print("Since when does Porto send DamageInfo?\n"); + return TRUE; + } + case WR_INIT: + { + // nothing to do + return TRUE; + } + } + return TRUE; +} +#endif +#endif diff --cc qcsrc/common/weapons/w_seeker.qc index 55ad43459,000000000..aa3d69dc5 mode 100644,000000..100644 --- a/qcsrc/common/weapons/w_seeker.qc +++ b/qcsrc/common/weapons/w_seeker.qc @@@ -1,779 -1,0 +1,779 @@@ +#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. + + 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; + + W_SETUPPROJECTILEVELOCITY_UP(missile, g_balance_seeker_missile); + + missile.angles = vectoangles (missile.velocity); + + CSQCProjectile(missile, FALSE, PROJECTILE_SEEKER, TRUE); + + other = missile; MUTATOR_CALLHOOK(EditProjectile); +} + +// ============================ +// 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.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.movetype = MOVETYPE_FLY; + missile.projectiledeathtype = WEP_SEEKER; + missile.projectiledeathtype = WEP_SEEKER | HITTYPE_SECONDARY; + missile.flags = FL_PROJECTILE; + 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'); + + W_SETUPPROJECTILEVELOCITY_UP(missile, g_balance_seeker_flac); + CSQCProjectile(missile, TRUE, PROJECTILE_FLAC, TRUE); + + other = missile; MUTATOR_CALLHOOK(EditProjectile); +} + +// ============================ +// 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; + + 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; + + 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; + } + + // 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 + { + //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; + + 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; + } + + e.nextthink = time; + } + + 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; + //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); + + else if (self.BUTTON_ATCK) + { + 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); + } + } + 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) + { + 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); + } + } + } + + 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); + } + + 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, ATTN_NORM); ++ 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, ATTN_NORM); ++ sound(self, CH_SHOTS, "weapons/tagexp1.wav", 1, ATTEN_NORM); + else if (w_random<0.7) - sound(self, CH_SHOTS, "weapons/tagexp2.wav", 1, ATTN_NORM); ++ sound(self, CH_SHOTS, "weapons/tagexp2.wav", 1, ATTEN_NORM); + else - sound(self, CH_SHOTS, "weapons/tagexp3.wav", 1, ATTN_NORM); ++ 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, ATTN_NORM); ++ sound(self, CH_SHOTS, "weapons/seekerexp1.wav", 1, ATTEN_NORM); + else if (w_random<0.7) - sound(self, CH_SHOTS, "weapons/seekerexp2.wav", 1, ATTN_NORM); ++ sound(self, CH_SHOTS, "weapons/seekerexp2.wav", 1, ATTEN_NORM); + else - sound(self, CH_SHOTS, "weapons/seekerexp3.wav", 1, ATTN_NORM); ++ 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 diff --cc qcsrc/common/weapons/w_shockwave.qc index 333a9ffaf,000000000..a926a7a78 mode 100644,000000..100644 --- a/qcsrc/common/weapons/w_shockwave.qc +++ b/qcsrc/common/weapons/w_shockwave.qc @@@ -1,320 -1,0 +1,320 @@@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ SHOTGUN, +/* function */ w_shotgun, +/* ammotype */ IT_SHELLS, +/* impulse */ 2, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_LOW, +/* model */ "shotgun", +/* netname */ "shotgun", +/* fullname */ _("Shotgun") +); +#else +#ifdef SVQC +void spawnfunc_weapon_shotgun() +{ + if(autocvar_sv_q3acompat_machineshotgunswap) + if(self.classname != "droppedweapon") + { + weapon_defaultspawnfunc(WEP_UZI); + return; + } + weapon_defaultspawnfunc(WEP_SHOTGUN); +} + + +void W_Shotgun_Attack (void) +{ + float sc; + float ammoamount; + float bullets; + float d; + float f; + float spread; + float bulletspeed; + float bulletconstant; + entity flash; + + ammoamount = autocvar_g_balance_shotgun_primary_ammo; + bullets = autocvar_g_balance_shotgun_primary_bullets; + d = autocvar_g_balance_shotgun_primary_damage; + f = autocvar_g_balance_shotgun_primary_force; + spread = autocvar_g_balance_shotgun_primary_spread; + bulletspeed = autocvar_g_balance_shotgun_primary_speed; + bulletconstant = autocvar_g_balance_shotgun_primary_bulletconstant; + + W_DecreaseAmmo(ammo_shells, ammoamount, autocvar_g_balance_shotgun_reload_ammo); + + W_SetupShot (self, autocvar_g_antilag_bullets && bulletspeed >= autocvar_g_antilag_bullets, 5, "weapons/shotgun_fire.wav", CH_WEAPON_A, d * bullets); + for (sc = 0;sc < bullets;sc = sc + 1) + fireBallisticBullet(w_shotorg, w_shotdir, spread, bulletspeed, 5, d, f, WEP_SHOTGUN, 0, 1, bulletconstant); + endFireBallisticBullet(); + + pointparticles(particleeffectnum("shotgun_muzzleflash"), w_shotorg, w_shotdir * 1000, autocvar_g_balance_shotgun_primary_ammo); + + // casing code + if (autocvar_g_casings >= 1) + for (sc = 0;sc < ammoamount;sc = sc + 1) + SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 30) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 1, self); + + // muzzle flash for 1st person view + flash = spawn(); + setmodel(flash, "models/uziflash.md3"); // precision set below + flash.think = SUB_Remove; + flash.nextthink = time + 0.06; + flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; + W_AttachToShotorg(flash, '5 0 0'); +} + +.float swing_prev; +.entity swing_alreadyhit; +void shotgun_meleethink (void) +{ + // declarations + float i, f, swing, swing_factor, swing_damage, meleetime, is_player; + entity target_victim; + vector targpos; + + if(!self.cnt) // set start time of melee + { + self.cnt = time; + W_PlayStrengthSound(self.realowner); + } + + makevectors(self.realowner.v_angle); // update values for v_* vectors + + // calculate swing percentage based on time + meleetime = autocvar_g_balance_shotgun_secondary_melee_time * W_WeaponRateFactor(); + swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); + f = ((1 - swing) * autocvar_g_balance_shotgun_secondary_melee_traces); + + // check to see if we can still continue, otherwise give up now + if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_shotgun_secondary_melee_no_doubleslap) + { + remove(self); + return; + } + + // if okay, perform the traces needed for this frame + for(i=self.swing_prev; i < f; ++i) + { + swing_factor = ((1 - (i / autocvar_g_balance_shotgun_secondary_melee_traces)) * 2 - 1); + + targpos = (self.realowner.origin + self.realowner.view_ofs + + (v_forward * autocvar_g_balance_shotgun_secondary_melee_range) + + (v_up * swing_factor * autocvar_g_balance_shotgun_secondary_melee_swing_up) + + (v_right * swing_factor * autocvar_g_balance_shotgun_secondary_melee_swing_side)); + + WarpZone_traceline_antilag(self, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self, ANTILAG_LATENCY(self.realowner)); + + // draw lightning beams for debugging + //te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5); + //te_customflash(targpos, 40, 2, '1 1 1'); + + is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body"); + + if((trace_fraction < 1) // if trace is good, apply the damage and remove self + && (trace_ent.takedamage == DAMAGE_AIM) + && (trace_ent != self.swing_alreadyhit) + && (is_player || autocvar_g_balance_shotgun_secondary_melee_nonplayerdamage)) + { + target_victim = trace_ent; // so it persists through other calls + + if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught. + swing_damage = (autocvar_g_balance_shotgun_secondary_damage * min(1, swing_factor + 1)); + else + swing_damage = (autocvar_g_balance_shotgun_secondary_melee_nonplayerdamage * min(1, swing_factor + 1)); + + //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n")); + + Damage(target_victim, self.realowner, self.realowner, + swing_damage, WEP_SHOTGUN | HITTYPE_SECONDARY, + self.realowner.origin + self.realowner.view_ofs, + v_forward * autocvar_g_balance_shotgun_secondary_force); + + if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_SHOTGUN, 0, swing_damage); } + + // draw large red flash for debugging + //te_customflash(targpos, 200, 2, '15 0 0'); + + if(autocvar_g_balance_shotgun_secondary_melee_multihit) // allow multiple hits with one swing, but not against the same player twice. + { + self.swing_alreadyhit = target_victim; + continue; // move along to next trace + } + else + { + remove(self); + return; + } + } + } + + if(time >= self.cnt + meleetime) + { + // melee is finished + remove(self); + return; + } + else + { + // set up next frame + self.swing_prev = i; + self.nextthink = time; + } +} + +void W_Shotgun_Attack2 (void) +{ - sound (self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM); ++ sound (self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTEN_NORM); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_shotgun_secondary_animtime, w_ready); + + entity meleetemp; + meleetemp = spawn(); + meleetemp.realowner = self; + meleetemp.think = shotgun_meleethink; + meleetemp.nextthink = time + autocvar_g_balance_shotgun_secondary_melee_delay * W_WeaponRateFactor(); + W_SetupShot_Range(self, TRUE, 0, "", 0, autocvar_g_balance_shotgun_secondary_damage, autocvar_g_balance_shotgun_secondary_melee_range); +} + +void spawnfunc_weapon_shotgun(); // defined in t_items.qc + +.float shotgun_primarytime; + +float w_shotgun(float req) +{ + float ammo_amount; + + switch(req) + { + case WR_AIM: + { + if(vlen(self.origin-self.enemy.origin) <= autocvar_g_balance_shotgun_secondary_melee_range) + self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); + else + { + if(autocvar_g_antilag_bullets) + self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); + else + self.BUTTON_ATCK = bot_aim(autocvar_g_balance_shotgun_primary_speed, 0, 0.001, FALSE); + } + + return TRUE; + } + case WR_THINK: + { + if(autocvar_g_balance_shotgun_reload_ammo && self.clip_load < autocvar_g_balance_shotgun_primary_ammo) // forced reload + { + // don't force reload an empty shotgun if its melee attack is active + if not(autocvar_g_balance_shotgun_secondary && self.ammo_shells < autocvar_g_balance_shotgun_primary_ammo) + WEP_ACTION(self.weapon, WR_RELOAD); + } + else + { + if (self.BUTTON_ATCK) + { + if (time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary + { + if(weapon_prepareattack(0, autocvar_g_balance_shotgun_primary_animtime)) + { + W_Shotgun_Attack(); + self.shotgun_primarytime = time + autocvar_g_balance_shotgun_primary_refire * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_shotgun_primary_animtime, w_ready); + } + } + } + } + if (self.clip_load >= 0) // we are not currently reloading + if (!self.crouch) // no crouchmelee please + if (self.BUTTON_ATCK2 && autocvar_g_balance_shotgun_secondary) + if (weapon_prepareattack(1, autocvar_g_balance_shotgun_secondary_refire)) + { + // attempt forcing playback of the anim by switching to another anim (that we never play) here... + weapon_thinkf(WFRAME_FIRE1, 0, W_Shotgun_Attack2); + } + + return TRUE; + } + case WR_INIT: + { + precache_model ("models/uziflash.md3"); + precache_model ("models/weapons/g_shotgun.md3"); + precache_model ("models/weapons/v_shotgun.md3"); + precache_model ("models/weapons/h_shotgun.iqm"); + precache_sound ("misc/itempickup.wav"); + precache_sound ("weapons/shotgun_fire.wav"); + precache_sound ("weapons/shotgun_melee.wav"); + return TRUE; + } + case WR_SETUP: + { + self.current_ammo = ammo_shells; + return TRUE; + } + case WR_CHECKAMMO1: + { + ammo_amount = self.ammo_shells >= autocvar_g_balance_shotgun_primary_ammo; + ammo_amount += self.(weapon_load[WEP_SHOTGUN]) >= autocvar_g_balance_shotgun_primary_ammo; + return ammo_amount; + } + case WR_CHECKAMMO2: + { + // melee attack is always available + return TRUE; + } + case WR_RELOAD: + { + W_Reload(autocvar_g_balance_shotgun_primary_ammo, "weapons/reload.wav"); + return TRUE; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_THINKING_WITH_PORTALS; + } + case WR_KILLMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_SHOTGUN_MURDER_SLAP; + else + return WEAPON_SHOTGUN_MURDER; + } + } + return TRUE; +} +#endif +#ifdef CSQC +.float prevric; +float w_shotgun(float req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 2; + pointparticles(particleeffectnum("shotgun_impact"), org2, w_backoff * 1000, 1); + if(!w_issilent && time - self.prevric > 0.25) + { + if(w_random < 0.0165) - sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTEN_NORM); + else if(w_random < 0.033) - sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTEN_NORM); + else if(w_random < 0.05) - sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTEN_NORM); + self.prevric = time; + } + return TRUE; + } + case WR_INIT: + { + precache_sound("weapons/ric1.wav"); + precache_sound("weapons/ric2.wav"); + precache_sound("weapons/ric3.wav"); + return TRUE; + } + } + return TRUE; +} +#endif +#endif diff --cc qcsrc/common/weapons/weapons.qc index 72c081be9,000000000..2f9dd2c92 mode 100644,000000..100644 --- a/qcsrc/common/weapons/weapons.qc +++ b/qcsrc/common/weapons/weapons.qc @@@ -1,193 -1,0 +1,260 @@@ +#ifndef MENUQC +#include "calculations.qc" +#endif +#include "all.qh" + +// WEAPON PLUGIN SYSTEM +entity weapon_info[WEP_MAXCOUNT]; +entity dummy_weapon_info; + - void register_weapon(float id, float(float) func, float ammotype, float i, float weapontype, float pickupbasevalue, string modelname, string shortname, string wname) ++#if WEP_MAXCOUNT > 72 ++# error Kein Weltraum links auf dem Gerät ++#endif ++ ++WepSet WepSet_FromWeapon(float a) { ++ a -= WEP_FIRST; ++#if WEP_MAXCOUNT > 24 ++ if(a >= 24) { ++ a -= 24; ++#if WEP_MAXCOUNT > 48 ++ if(a >= 24) { ++ a -= 24; ++ return '0 0 1' * power2of(a); ++ } ++#endif ++ return '0 1 0' * power2of(a); ++ } ++#endif ++ return '1 0 0' * power2of(a); ++} ++#ifdef SVQC ++void WepSet_AddStat() ++{ ++ addstat(STAT_WEAPONS, AS_INT, weapons_x); ++#if WEP_MAXCOUNT > 24 ++ addstat(STAT_WEAPONS2, AS_INT, weapons_y); ++#if WEP_MAXCOUNT > 48 ++ addstat(STAT_WEAPONS3, AS_INT, weapons_z); ++#endif ++#endif ++} ++void WriteWepSet(float dst, WepSet w) ++{ ++#if WEP_MAXCOUNT > 48 ++ WriteInt72_t(dst, w); ++#elif WEP_MAXCOUNT > 24 ++ WriteInt48_t(dst, w); ++#else ++ WriteInt24_t(dst, w_x); ++#endif ++} ++#endif ++#ifdef CSQC ++WepSet WepSet_GetFromStat() ++{ ++ WepSet w = '0 0 0'; ++ w_x = getstati(STAT_WEAPONS); ++#if WEP_MAXCOUNT > 24 ++ w_y = getstati(STAT_WEAPONS2); ++#if WEP_MAXCOUNT > 48 ++ w_z = getstati(STAT_WEAPONS3); ++#endif ++#endif ++ return w; ++} ++WepSet ReadWepSet() ++{ ++#if WEP_MAXCOUNT > 48 ++ return ReadInt72_t(); ++#elif WEP_MAXCOUNT > 24 ++ return ReadInt48_t(); ++#else ++ return ReadInt24_t() * '1 0 0'; ++#endif ++} ++#endif ++ ++void register_weapon(float id, WepSet bit, float(float) func, float ammotype, float i, float weapontype, float pickupbasevalue, string modelname, string shortname, string wname) +{ + entity e; + weapon_info[id - 1] = e = spawn(); + e.classname = "weapon_info"; + e.weapon = id; - WEPSET_COPY_EW(e, id); ++ e.weapons = bit; + e.netname = shortname; + e.message = wname; + e.items = ammotype; + e.weapon_func = func; + e.mdl = modelname; + e.model = strzone(strcat("models/weapons/g_", modelname, ".md3")); + e.spawnflags = weapontype; + e.model2 = strzone(strcat("wpn-", e.mdl)); + e.impulse = i; + e.bot_pickupbasevalue = pickupbasevalue; + if(ammotype & IT_SHELLS) + e.ammo_field = ammo_shells; + else if(ammotype & IT_NAILS) + e.ammo_field = ammo_nails; + else if(ammotype & IT_ROCKETS) + e.ammo_field = ammo_rockets; + else if(ammotype & IT_CELLS) + e.ammo_field = ammo_cells; + else if(ammotype & IT_FUEL) + e.ammo_field = ammo_fuel; + else + e.ammo_field = ammo_batteries; + + #ifndef MENUQC + func(WR_INIT); + #endif +} +float w_null(float dummy) +{ + return 0; +} +void register_weapons_done() +{ + dummy_weapon_info = spawn(); + dummy_weapon_info.classname = "weapon_info"; + dummy_weapon_info.weapon = 0; // you can recognize dummies by this - WEPSET_CLEAR_E(dummy_weapon_info); ++ dummy_weapon_info.weapons = '0 0 0'; + dummy_weapon_info.netname = ""; + dummy_weapon_info.message = "AOL CD Thrower"; + dummy_weapon_info.items = 0; + dummy_weapon_info.weapon_func = w_null; + dummy_weapon_info.mdl = ""; + dummy_weapon_info.model = ""; + dummy_weapon_info.spawnflags = 0; + dummy_weapon_info.model2 = ""; + dummy_weapon_info.impulse = -1; + dummy_weapon_info.bot_pickupbasevalue = 0; + + float i; + weaponorder_byid = ""; + for(i = WEP_MAXCOUNT; i >= 1; --i) + if(weapon_info[i-1]) + weaponorder_byid = strcat(weaponorder_byid, " ", ftos(i)); + weaponorder_byid = strzone(substring(weaponorder_byid, 1, strlen(weaponorder_byid) - 1)); +} +entity get_weaponinfo(float id) +{ + entity w; + if(id < WEP_FIRST || id > WEP_LAST) + return dummy_weapon_info; + w = weapon_info[id - 1]; + if(w) + return w; + return dummy_weapon_info; +} +string W_FixWeaponOrder(string order, float complete) +{ + return fixPriorityList(order, WEP_FIRST, WEP_LAST, 230 - WEP_FIRST, complete); +} +string W_NameWeaponOrder_MapFunc(string s) +{ + entity wi; + if(s == "0" || stof(s)) + { + wi = get_weaponinfo(stof(s)); + if(wi != dummy_weapon_info) + return wi.netname; + } + return s; +} +string W_NameWeaponOrder(string order) +{ + return mapPriorityList(order, W_NameWeaponOrder_MapFunc); +} +string W_NumberWeaponOrder_MapFunc(string s) +{ + float i; + if(s == "0" || stof(s)) + return s; + for(i = WEP_FIRST; i <= WEP_LAST; ++i) + if(s == get_weaponinfo(i).netname) + return ftos(i); + return s; +} +string W_NumberWeaponOrder(string order) +{ + return mapPriorityList(order, W_NumberWeaponOrder_MapFunc); +} + +float W_FixWeaponOrder_BuildImpulseList_buf[WEP_MAXCOUNT]; +string W_FixWeaponOrder_BuildImpulseList_order; +void W_FixWeaponOrder_BuildImpulseList_swap(float i, float j, entity pass) +{ + float h; + h = W_FixWeaponOrder_BuildImpulseList_buf[i]; + W_FixWeaponOrder_BuildImpulseList_buf[i] = W_FixWeaponOrder_BuildImpulseList_buf[j]; + W_FixWeaponOrder_BuildImpulseList_buf[j] = h; +} +float W_FixWeaponOrder_BuildImpulseList_cmp(float i, float j, entity pass) +{ + entity e1, e2; + float d; + e1 = get_weaponinfo(W_FixWeaponOrder_BuildImpulseList_buf[i]); + e2 = get_weaponinfo(W_FixWeaponOrder_BuildImpulseList_buf[j]); + d = mod(e1.impulse + 9, 10) - mod(e2.impulse + 9, 10); + if(d != 0) + return -d; // high impulse first! + return + strstrofs(strcat(" ", W_FixWeaponOrder_BuildImpulseList_order, " "), sprintf(" %d ", W_FixWeaponOrder_BuildImpulseList_buf[i]), 0) + - + strstrofs(strcat(" ", W_FixWeaponOrder_BuildImpulseList_order, " "), sprintf(" %d ", W_FixWeaponOrder_BuildImpulseList_buf[j]), 0) + ; // low char index first! +} +string W_FixWeaponOrder_BuildImpulseList(string o) +{ + float i; + W_FixWeaponOrder_BuildImpulseList_order = o; + for(i = WEP_FIRST; i <= WEP_LAST; ++i) + W_FixWeaponOrder_BuildImpulseList_buf[i - WEP_FIRST] = i; + heapsort(WEP_LAST - WEP_FIRST + 1, W_FixWeaponOrder_BuildImpulseList_swap, W_FixWeaponOrder_BuildImpulseList_cmp, world); + o = ""; + for(i = WEP_FIRST; i <= WEP_LAST; ++i) + o = strcat(o, " ", ftos(W_FixWeaponOrder_BuildImpulseList_buf[i - WEP_FIRST])); + W_FixWeaponOrder_BuildImpulseList_order = string_null; + return substring(o, 1, -1); +} + +string W_FixWeaponOrder_AllowIncomplete(string order) +{ + return W_FixWeaponOrder(order, 0); +} + +string W_FixWeaponOrder_ForceComplete(string order) +{ + if(order == "") + order = W_NumberWeaponOrder(cvar_defstring("cl_weaponpriority")); + return W_FixWeaponOrder(order, 1); +} + +void W_RandomWeapons(entity e, float n) +{ + float i, j; - WEPSET_DECLARE_A(remaining); - WEPSET_DECLARE_A(result); - WEPSET_COPY_AE(remaining, e); - WEPSET_CLEAR_A(result); ++ WepSet remaining; ++ WepSet result; ++ remaining = e.weapons; ++ result = '0 0 0'; + for(i = 0; i < n; ++i) + { + RandomSelection_Init(); + for(j = WEP_FIRST; j <= WEP_LAST; ++j) - if(WEPSET_CONTAINS_AW(remaining, j)) ++ if(remaining & WepSet_FromWeapon(j)) + RandomSelection_Add(world, j, string_null, 1, 1); - WEPSET_OR_AW(result, RandomSelection_chosen_float); - WEPSET_ANDNOT_AW(remaining, RandomSelection_chosen_float); ++ result |= WepSet_FromWeapon(RandomSelection_chosen_float); ++ remaining &= ~WepSet_FromWeapon(RandomSelection_chosen_float); + } - WEPSET_COPY_EA(e, result); ++ e.weapons = result; +} + +string W_Name(float weaponid) +{ + return (get_weaponinfo(weaponid)).message; +} + +float W_AmmoItemCode(float wpn) +{ + return (get_weaponinfo(wpn)).items & IT_AMMO; +} diff --cc qcsrc/common/weapons/weapons.qh index a4ced87a9,000000000..362a3d84a mode 100644,000000..100644 --- a/qcsrc/common/weapons/weapons.qh +++ b/qcsrc/common/weapons/weapons.qh @@@ -1,337 -1,0 +1,238 @@@ +#ifndef MENUQC +#include "calculations.qh" +#endif + - float BOT_PICKUP_RATING_LOW = 2500; - float BOT_PICKUP_RATING_MID = 5000; - float BOT_PICKUP_RATING_HIGH = 10000; - - float WEP_TYPE_OTHER = 0x00; // not for damaging people - float WEP_TYPE_SPLASH = 0x01; // splash damage - float WEP_TYPE_HITSCAN = 0x02; // hitscan - float WEP_TYPEMASK = 0x0F; - float WEP_FLAG_CANCLIMB = 0x10; // can be used for movement - float WEP_FLAG_NORMAL = 0x20; // in "most weapons" set - float WEP_FLAG_HIDDEN = 0x40; // hides from menu - float WEP_FLAG_RELOADABLE = 0x80; // can has reload - float WEP_FLAG_SUPERWEAPON = 0x100; // powerup timer - float WEP_FLAG_MUTATORBLOCKED = 0x200; // hides from impulse 99 etc. (mutators are allowed to clear this flag) - - float MAX_SHOT_DISTANCE = 32768; ++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 - float IT_UNLIMITED_WEAPON_AMMO = 1; ++const float IT_UNLIMITED_WEAPON_AMMO = 1; +// when this bit is set, using a weapon does not reduce ammo. Checkpoints can give this powerup. - float IT_UNLIMITED_SUPERWEAPONS = 2; ++const float IT_UNLIMITED_SUPERWEAPONS = 2; +// when this bit is set, superweapons don't expire. Checkpoints can give this powerup. - float IT_CTF_SHIELDED = 4; // set for the flag shield - float IT_USING_JETPACK = 8; // confirmation that button is pressed - float IT_JETPACK = 16; // actual item - float IT_FUEL_REGEN = 32; // fuel regeneration trigger - float IT_SHELLS = 256; - float IT_NAILS = 512; - float IT_ROCKETS = 1024; - float IT_CELLS = 2048; - float IT_SUPERWEAPON = 4096; - float IT_FUEL = 128; - float IT_STRENGTH = 8192; - float IT_INVINCIBLE = 16384; - float IT_HEALTH = 32768; ++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: - float IT_KEY1 = 131072; - float IT_KEY2 = 262144; ++ WANT_CONST float IT_KEY1 = 131072; ++ WANT_CONST float IT_KEY2 = 262144; + // for players: - float IT_RED_FLAG_TAKEN = 32768; - float IT_RED_FLAG_LOST = 65536; - float IT_RED_FLAG_CARRYING = 98304; - float IT_BLUE_FLAG_TAKEN = 131072; - float IT_BLUE_FLAG_LOST = 262144; - float IT_BLUE_FLAG_CARRYING = 393216; ++ 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 - float IT_5HP = 524288; - float IT_25HP = 1048576; - float IT_ARMOR_SHARD = 2097152; - float IT_ARMOR = 4194304; ++const float IT_5HP = 524288; ++const float IT_25HP = 1048576; ++const float IT_ARMOR_SHARD = 2097152; ++const float IT_ARMOR = 4194304; + - float IT_AMMO = 3968; // IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS | IT_FUEL; - float IT_PICKUPMASK = 51; // IT_FUEL_REGEN | IT_JETPACK | IT_UNLIMITED_AMMO; // strength and invincible are handled separately - float IT_UNLIMITED_AMMO = 3; // IT_UNLIMITED_SUPERWEAPONS | IT_UNLIMITED_WEAPON_AMMO; ++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; + - float AMMO_COUNT = 4; // amount of ammo types to show in the inventory panel ++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 - // also, weaponinfo ents can act as a WEPSET - - - // =================== - // Weapon Operations - // =================== - #if 1 - # define WEP_MAXCOUNT 24 - // default storage - .float _WS_weapons; - # define WEPSET_BIT(a) power2of((a) - WEP_FIRST) - # define WEPSET_DECLARE_A(a) float _WS_##a - # define WEPSET_CLEAR_E(e) ((e)._WS_weapons = 0) - # define WEPSET_CLEAR_A(a) (_WS_##a = 0) - # define WEPSET_EMPTY_E(e) ((e)._WS_weapons == 0) - # define WEPSET_EMPTY_A(a) (_WS_##a == 0) - # define WEPSET_COPY_AS(a) (_WS_##a = getstati(STAT_WEAPONS)) - # define WEPSET_ADDSTAT() addstat(STAT_WEAPONS, AS_INT, _WS_weapons) - # define WEPSET_WRITE_E(dest,a) WriteInt24_t(dest, (a)._WS_weapons) - # define WEPSET_WRITE_A(dest,a) WriteInt24_t(dest, _WS_##a) - # define WEPSET_WRITE_W(dest,a) WriteInt24_t(dest, WEPSET_BIT(a)) - # define WEPSET_READ_E(a) (a)._WS_weapons = ReadInt24_t() - # define WEPSET_READ_A(a) (_WS_##a) = ReadInt24_t() - # define WEPSET_OP1_EE(a,b,mergeop,x) ((a)._WS_weapons x (b)._WS_weapons) - # define WEPSET_OP2_EE(a,b,mergeop,x,y) ((a)._WS_weapons x (b)._WS_weapons y (a)._WS_weapons) - # define WEPSET_OP1_EA(a,b,mergeop,x) ((a)._WS_weapons x _WS_##b) - # define WEPSET_OP2_EA(a,b,mergeop,x,y) ((a)._WS_weapons x _WS_##b y (a)._WS_weapons) - # define WEPSET_OP1_EW(a,b,mergeop,x) ((a)._WS_weapons x WEPSET_BIT(b)) - # define WEPSET_OP2_EW(a,b,mergeop,x,y) ((a)._WS_weapons x WEPSET_BIT(b) y (a)._WS_weapons) - # define WEPSET_OP1_AE(a,b,mergeop,x) (_WS_##a x (b)._WS_weapons) - # define WEPSET_OP2_AE(a,b,mergeop,x,y) (_WS_##a x (b)._WS_weapons y _WS_##a) - # define WEPSET_OP1_AA(a,b,mergeop,x) (_WS_##a x _WS_##b) - # define WEPSET_OP2_AA(a,b,mergeop,x,y) (_WS_##a x _WS_##b y _WS_##a) - # define WEPSET_OP1_AW(a,b,mergeop,x) (_WS_##a x WEPSET_BIT(b)) - # define WEPSET_OP2_AW(a,b,mergeop,x,y) (_WS_##a x WEPSET_BIT(b) y _WS_##a) - #else - # define WEP_MAXCOUNT 48 - # define WEP_FIRST2 25 - .float _WS1_weapons; - .float _WS2_weapons; - # define WEPSET_BIT1(a) (((a) < WEP_FIRST2) ? power2of((a) - WEP_FIRST) : 0) - # define WEPSET_BIT2(a) (((a) >= WEP_FIRST2) ? power2of((a) - WEP_FIRST2) : 0) - # define WEPSET_DECLARE_A(a) float _WS1_##a, _WS2_##a - # define WEPSET_CLEAR_E(e) ((e)._WS1_weapons = (e)._WS2_weapons = 0) - # define WEPSET_CLEAR_A(a) ((_WS1_##a) = (_WS2_##a) = 0) - # define WEPSET_EMPTY_E(e) ((e)._WS1_weapons == 0 && (e)._WS2_weapons == 0) - # define WEPSET_EMPTY_A(a) ((_WS1_##a) == 0 && (_WS2_##a) == 0) - # define WEPSET_COPY_AS(a) ((_WS1_##a) = getstati(STAT_WEAPONS), (_WS2_##a) = getstati(STAT_WEAPONS2)) - # define WEPSET_ADDSTAT() addstat(STAT_WEAPONS, AS_INT, _WS1_weapons); addstat(STAT_WEAPONS2, AS_INT, _WS2_weapons) - # define WEPSET_WRITE_E(dest,a) WriteInt24_t(dest, (a)._WS1_weapons); WriteInt24_t(dest, (a)._WS2_weapons) - # define WEPSET_WRITE_A(dest,a) WriteInt24_t(dest, _WS1_##a); WriteInt24_t(dest, _WS2_##a) - # define WEPSET_WRITE_W(dest,a) WriteInt24_t(dest, WEPSET_BIT1(a)); WriteInt24_t(dest, WEPSET_BIT2(a)) - # define WEPSET_READ_E(a) (a)._WS1_weapons = ReadInt24_t(); (a)._WS2_weapons = ReadInt24_t() - # define WEPSET_READ_A(a) (_WS1_##a) = ReadInt24_t(); (_WS2_##a) = ReadInt24_t() - # define WEPSET_OP1_EE(a,b,mergeop,x) (((a)._WS1_weapons x (b)._WS1_weapons) mergeop ((a)._WS2_weapons x (b)._WS2_weapons)) - # define WEPSET_OP2_EE(a,b,mergeop,x,y) (((a)._WS1_weapons x (b)._WS1_weapons y (a)._WS1_weapons) mergeop ((a)._WS2_weapons x (b)._WS2_weapons y (a)._WS2_weapons)) - # define WEPSET_OP1_EA(a,b,mergeop,x) (((a)._WS1_weapons x _WS1_##b) mergeop ((a)._WS2_weapons x _WS2_##b)) - # define WEPSET_OP2_EA(a,b,mergeop,x,y) (((a)._WS1_weapons x _WS1_##b y (a)._WS1_weapons) mergeop ((a)._WS2_weapons x _WS2_##b y (a)._WS2_weapons)) - # define WEPSET_OP1_EW(a,b,mergeop,x) (((a)._WS1_weapons x WEPSET_BIT1(b)) mergeop ((a)._WS2_weapons x WEPSET_BIT2(b))) - # define WEPSET_OP2_EW(a,b,mergeop,x,y) (((a)._WS1_weapons x WEPSET_BIT1(b) y (a)._WS1_weapons) mergeop ((a)._WS2_weapons x WEPSET_BIT2(b) y (a)._WS2_weapons)) - # define WEPSET_OP1_AE(a,b,mergeop,x) ((_WS1_##a x (b)._WS1_weapons) mergeop (_WS2_##a x (b)._WS2_weapons)) - # define WEPSET_OP2_AE(a,b,mergeop,x,y) ((_WS1_##a x (b)._WS1_weapons y _WS1_##a) mergeop (_WS2_##a x (b)._WS2_weapons y _WS2_##a)) - # define WEPSET_OP1_AA(a,b,mergeop,x) ((_WS1_##a x _WS1_##b) mergeop (_WS2_##a x _WS2_##b)) - # define WEPSET_OP2_AA(a,b,mergeop,x,y) ((_WS1_##a x _WS1_##b y _WS1_##a) mergeop (_WS2_##a x _WS2_##b y _WS2_##a)) - # define WEPSET_OP1_AW(a,b,mergeop,x) ((_WS1_##a x WEPSET_BIT1(b)) mergeop (_WS2_##a x WEPSET_BIT2(b))) - # define WEPSET_OP2_AW(a,b,mergeop,x,y) ((_WS1_##a x WEPSET_BIT1(b) y _WS1_##a) mergeop (_WS2_##a x WEPSET_BIT2(b) y _WS2_##a)) - #endif - - #define XX , - - #define WEPSET_COPY_EE(a,b) WEPSET_OP1_EE(a,b,XX,=) - #define WEPSET_EQ_EE(a,b) WEPSET_OP1_EE(a,b,&&,==) - #define WEPSET_OR_EE(a,b) WEPSET_OP1_EE(a,b,XX,|=) - #define WEPSET_AND_EE(a,b) WEPSET_OP2_EE(a,b,XX,=,&) - #define WEPSET_ANDNOT_EE(a,b) WEPSET_OP1_EE(a,b,XX,&~=) - #define WEPSET_CONTAINS_ANY_EE(a,b) !!(WEPSET_OP1_EE(a,b,||,&)) - #define WEPSET_CONTAINS_ALL_EE(a,b) WEPSET_OP2_EE(b,a,&&,==,&) - - #define WEPSET_COPY_EA(a,b) WEPSET_OP1_EA(a,b,XX,=) - #define WEPSET_EQ_EA(a,b) WEPSET_OP1_EA(a,b,&&,==) - #define WEPSET_OR_EA(a,b) WEPSET_OP1_EA(a,b,XX,|=) - #define WEPSET_AND_EA(a,b) WEPSET_OP2_EA(a,b,XX,=,&) - #define WEPSET_ANDNOT_EA(a,b) WEPSET_OP1_EA(a,b,XX,&~=) - #define WEPSET_CONTAINS_ANY_EA(a,b) !!(WEPSET_OP1_EA(a,b,||,&)) - #define WEPSET_CONTAINS_ALL_EA(a,b) WEPSET_OP2_EA(b,a,&&,==,&) - - #define WEPSET_COPY_EW(a,b) WEPSET_OP1_EW(a,b,XX,=) - #define WEPSET_EQ_EW(a,b) WEPSET_OP1_EW(a,b,&&,==) - #define WEPSET_OR_EW(a,b) WEPSET_OP1_EW(a,b,XX,|=) - #define WEPSET_AND_EW(a,b) WEPSET_OP2_EW(a,b,XX,=,&) - #define WEPSET_ANDNOT_EW(a,b) WEPSET_OP1_EW(a,b,XX,&~=) - #define WEPSET_CONTAINS_EW(a,b) !!(WEPSET_OP1_EW(a,b,||,&)) - - #define WEPSET_COPY_AE(a,b) WEPSET_OP1_AE(a,b,XX,=) - #define WEPSET_EQ_AE(a,b) WEPSET_OP1_AE(a,b,&&,==) - #define WEPSET_OR_AE(a,b) WEPSET_OP1_AE(a,b,XX,|=) - #define WEPSET_AND_AE(a,b) WEPSET_OP2_AE(a,b,XX,=,&) - #define WEPSET_ANDNOT_AE(a,b) WEPSET_OP1_AE(a,b,XX,&~=) - #define WEPSET_CONTAINS_ANY_AE(a,b) !!(WEPSET_OP1_AE(a,b,||,&)) - #define WEPSET_CONTAINS_ALL_AE(a,b) WEPSET_OP2_AE(b,a,&&,==,&) - - #define WEPSET_COPY_AA(a,b) WEPSET_OP1_AA(a,b,XX,=) - #define WEPSET_EQ_AA(a,b) WEPSET_OP1_AA(a,b,&&,==) - #define WEPSET_OR_AA(a,b) WEPSET_OP1_AA(a,b,XX,|=) - #define WEPSET_AND_AA(a,b) WEPSET_OP2_AA(a,b,XX,=,&) - #define WEPSET_ANDNOT_AA(a,b) WEPSET_OP1_AA(a,b,XX,&~=) - #define WEPSET_CONTAINS_ANY_AA(a,b) !!(WEPSET_OP1_AA(a,b,||,&)) - #define WEPSET_CONTAINS_ALL_AA(a,b) WEPSET_OP2_AA(b,a,&&,==,&) - - #define WEPSET_COPY_AW(a,b) WEPSET_OP1_AW(a,b,XX,=) - #define WEPSET_EQ_AW(a,b) WEPSET_OP1_AW(a,b,&&,==) - #define WEPSET_OR_AW(a,b) WEPSET_OP1_AW(a,b,XX,|=) - #define WEPSET_AND_AW(a,b) WEPSET_OP2_AW(a,b,XX,=,&) - #define WEPSET_ANDNOT_AW(a,b) WEPSET_OP1_AW(a,b,XX,&~=) - #define WEPSET_CONTAINS_AW(a,b) !!(WEPSET_OP1_AW(a,b,||,&)) - - WEPSET_DECLARE_A(WEPBIT_ALL); - WEPSET_DECLARE_A(WEPBIT_SUPERWEAPONS); + +// 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, float(float) func, float ammotype, float i, float weapontype, float pickupbasevalue, string modelname, string shortname, string wname); ++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(); + - #define WEP_FIRST 1 - float WEP_COUNT; - float WEP_LAST; - +// note: the fabs call is just there to hide "if result is constant" warning - #define REGISTER_WEAPON_2(id,func,ammotype,i,weapontype,pickupbasevalue,modelname,shortname,wname) \ ++#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); \ - WEPSET_OR_AW(WEPBIT_ALL, id); \ - if(fabs(weapontype & WEP_FLAG_SUPERWEAPON)) \ - WEPSET_OR_AW(WEPBIT_SUPERWEAPONS, id); \ ++ bit = WepSet_FromWeapon(id); \ ++ WEPSET_ALL |= bit; \ ++ if((weapontype) & WEP_FLAG_SUPERWEAPON) \ ++ WEPSET_SUPERWEAPONS |= bit; \ + ++WEP_COUNT; \ - register_weapon(id,func,ammotype,i,weapontype,pickupbasevalue,modelname,shortname,wname); \ ++ 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,w_null,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,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); diff --cc qcsrc/server/bot/havocbot/havocbot.qc index 5296b0ea7,7e23903ed..31631454e --- a/qcsrc/server/bot/havocbot/havocbot.qc +++ b/qcsrc/server/bot/havocbot/havocbot.qc @@@ -88,11 -88,11 +88,11 @@@ void havocbot_ai( if (self.bot_aimtarg) { self.aistatus |= AI_STATUS_ATTACKING; - self.aistatus &~= AI_STATUS_ROAMING; + self.aistatus &= ~AI_STATUS_ROAMING; - if(!WEPSET_EMPTY_E(self)) + if(self.weapons) { - weapon_action(self.weapon, WR_AIM); + WEP_ACTION(self.weapon, WR_AIM); if (autocvar_bot_nofire || IS_INDEPENDENT_PLAYER(self)) { self.BUTTON_ATCK = FALSE; @@@ -542,9 -542,9 +542,9 @@@ void havocbot_movetogoal( } } else if(self.aistatus & AI_STATUS_OUT_JUMPPAD) - 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) { diff --cc qcsrc/server/defs.qh index 5228e575c,17b75aeab..95f121713 --- a/qcsrc/server/defs.qh +++ b/qcsrc/server/defs.qh @@@ -191,12 -190,16 +191,13 @@@ void w_ready() .float weapon_nextthink; .void() weapon_think; -//float PLAYER_WEAPONSELECTION_DELAY = ); -const float PLAYER_WEAPONSELECTION_SPEED = 18; -const vector PLAYER_WEAPONSELECTION_RANGE = '0 20 -40'; + // weapon states (self.weaponentity.state) - float WS_CLEAR = 0; // no weapon selected - float WS_RAISE = 1; // raise frame - float WS_DROP = 2; // deselecting frame - float WS_INUSE = 3; // fire state - float WS_READY = 4; // idle frame + const float WS_CLEAR = 0; // no weapon selected + const float WS_RAISE = 1; // raise frame + const float WS_DROP = 2; // deselecting frame + const float WS_INUSE = 3; // fire state + const float WS_READY = 4; // idle frame // there is 2 weapon tics that can run in one server frame #define W_TICSPERFRAME 2 diff --cc qcsrc/server/func_breakable.qc index 1537cd8d4,8dca538f1..142bd7a8b --- a/qcsrc/server/func_breakable.qc +++ b/qcsrc/server/func_breakable.qc @@@ -164,10 -164,10 +164,10 @@@ void func_breakable_destroy() func_breakable_destroyed(); if(self.noise) - sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM); + sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM); if(self.dmg) - RadiusDamage(self, activator, self.dmg, self.dmg_edge, self.dmg_radius, self, self.dmg_force, DEATH_HURTTRIGGER, world); + RadiusDamage(self, activator, self.dmg, self.dmg_edge, self.dmg_radius, self, world, self.dmg_force, DEATH_HURTTRIGGER, world); if(self.cnt) pointparticles(self.cnt, self.absmin * 0.5 + self.absmax * 0.5, '0 0 0', self.count); diff --cc qcsrc/server/miscfunctions.qc index 771595b7f,6a2458000..921d0b464 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@@ -860,8 -861,8 +860,8 @@@ void readplayerstartcvars( for (i = WEP_FIRST; i <= WEP_LAST; ++i) { e = get_weaponinfo(i); - if(WEPSET_CONTAINS_AW(start_weapons, i) || WEPSET_CONTAINS_AW(warmup_start_weapons, i)) + if((start_weapons | warmup_start_weapons) & WepSet_FromWeapon(i)) - weapon_action(i, WR_PRECACHE); + WEP_ACTION(i, WR_INIT); } start_ammo_shells = max(0, start_ammo_shells); diff --cc qcsrc/server/mutators/gamemode_nexball.qc index db0de8b35,5831250d2..731927a77 --- a/qcsrc/server/mutators/gamemode_nexball.qc +++ b/qcsrc/server/mutators/gamemode_nexball.qc @@@ -139,10 -139,10 +139,10 @@@ void GiveBall(entity plyr, entity ball ownr = self; self = plyr; - WEPSET_COPY_EE(self.weaponentity, self); + self.weaponentity.weapons = self.weapons; self.weaponentity.switchweapon = self.weapon; - WEPSET_COPY_EW(self, WEP_PORTO); + self.weapons = WEPSET_PORTO; - weapon_action(WEP_PORTO, WR_RESETPLAYER); + WEP_ACTION(WEP_PORTO, WR_RESETPLAYER); self.switchweapon = WEP_PORTO; W_SwitchWeapon(WEP_PORTO); self = ownr; @@@ -917,10 -917,10 +917,10 @@@ MUTATOR_HOOKFUNCTION(nexball_PlayerPreT } else { - if(!WEPSET_EMPTY_E(self.weaponentity)) + if(self.weaponentity.weapons) { - WEPSET_COPY_EE(self, self.weaponentity); + 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); diff --cc qcsrc/server/t_items.qc index ea34a427e,31aa5caf7..69223cc02 --- a/qcsrc/server/t_items.qc +++ b/qcsrc/server/t_items.qc @@@ -1096,7 -1139,183 +1096,6 @@@ void StartItem (string itemmodel, strin return; } } - -float weaponswapping; -float internalteam; - -void weapon_defaultspawnfunc(float wpn) -{ - entity e; - float t; - var .float ammofield; - string s; - entity oldself; - float i, j; - float f; - - if(self.classname != "droppedweapon" && self.classname != "replacedweapon") - { - e = get_weaponinfo(wpn); - - if(e.spawnflags & WEP_FLAG_MUTATORBLOCKED) - { - objerror("Attempted to spawn a mutator-blocked weapon rejected"); - startitem_failed = TRUE; - return; - } - - s = W_Apply_Weaponreplace(e.netname); - ret_string = s; - other = e; - MUTATOR_CALLHOOK(SetWeaponreplace); - s = ret_string; - if(s == "") - { - remove(self); - startitem_failed = TRUE; - return; - } - t = tokenize_console(s); - if(t >= 2) - { - self.team = --internalteam; - oldself = self; - for(i = 1; i < t; ++i) - { - s = argv(i); - for(j = WEP_FIRST; j <= WEP_LAST; ++j) - { - e = get_weaponinfo(j); - if(e.netname == s) - { - self = spawn(); - copyentity(oldself, self); - self.classname = "replacedweapon"; - weapon_defaultspawnfunc(j); - break; - } - } - if(j > WEP_LAST) - { - print("The weapon replace list for ", oldself.classname, " contains an unknown weapon ", s, ". Skipped.\n"); - } - } - self = oldself; - } - if(t >= 1) // always the case! - { - s = argv(0); - wpn = 0; - for(j = WEP_FIRST; j <= WEP_LAST; ++j) - { - e = get_weaponinfo(j); - if(e.netname == s) - { - wpn = j; - break; - } - } - if(j > WEP_LAST) - { - print("The weapon replace list for ", self.classname, " contains an unknown weapon ", s, ". Skipped.\n"); - } - } - if(wpn == 0) - { - remove(self); - startitem_failed = TRUE; - return; - } - } - - e = get_weaponinfo(wpn); - - if(!self.respawntime) - { - if(e.weapons & WEPSET_SUPERWEAPONS) - { - self.respawntime = g_pickup_respawntime_superweapon; - self.respawntimejitter = g_pickup_respawntimejitter_superweapon; - } - else - { - self.respawntime = g_pickup_respawntime_weapon; - self.respawntimejitter = g_pickup_respawntimejitter_weapon; - } - } - - if(e.weapons & WEPSET_SUPERWEAPONS) - if(!self.superweapons_finished) - self.superweapons_finished = autocvar_g_balance_superweapons_time; - - if(e.items) - { - for(i = 0, j = 1; i < 24; ++i, j *= 2) - { - if(e.items & j) - { - ammofield = Item_CounterField(j); - if(!self.ammofield) - self.ammofield = cvar(strcat("g_pickup_", Item_CounterFieldName(j), "_weapon")); - } - } - } - - // pickup anyway - if(g_pickup_weapons_anyway) - self.pickup_anyway = TRUE; - - f = FL_WEAPON; - - // no weapon-stay on superweapons - if(e.weapons & WEPSET_SUPERWEAPONS) - f |= FL_NO_WEAPON_STAY; - - // weapon stay isn't supported for teamed weapons - if(self.team) - f |= FL_NO_WEAPON_STAY; - - StartItem(e.model, "weapons/weaponpickup.wav", self.respawntime, self.respawntimejitter, e.message, 0, e.weapon, f, weapon_pickupevalfunc, e.bot_pickupbasevalue); - if (self.modelindex) // don't precache if self was removed - weapon_action(e.weapon, WR_PRECACHE); -} - -void spawnfunc_weapon_shotgun (void); -void spawnfunc_weapon_uzi (void) { - if(autocvar_sv_q3acompat_machineshotgunswap) - if(self.classname != "droppedweapon") - { - weapon_defaultspawnfunc(WEP_SHOTGUN); - return; - } - weapon_defaultspawnfunc(WEP_UZI); -} - -void spawnfunc_weapon_shotgun (void) { - if(autocvar_sv_q3acompat_machineshotgunswap) - if(self.classname != "droppedweapon") - { - weapon_defaultspawnfunc(WEP_UZI); - return; - } - weapon_defaultspawnfunc(WEP_SHOTGUN); -} - -void spawnfunc_weapon_nex (void) -{ - weapon_defaultspawnfunc(WEP_NEX); -} - -void spawnfunc_weapon_minstanex (void) -{ - weapon_defaultspawnfunc(WEP_MINSTANEX); -} - -void spawnfunc_weapon_rocketlauncher (void) -{ - weapon_defaultspawnfunc(WEP_ROCKET_LAUNCHER); -} -- void spawnfunc_item_rockets (void) { if(!self.ammo_rockets) self.ammo_rockets = g_pickup_rockets; @@@ -1321,9 -1541,9 +1320,9 @@@ void spawnfunc_target_items (void e = get_weaponinfo(j); if(argv(i) == e.netname) { - WEPSET_OR_EW(self, j); + self.weapons |= WepSet_FromWeapon(j); if(self.spawnflags == 0 || self.spawnflags == 2) - weapon_action(e.weapon, WR_PRECACHE); + WEP_ACTION(e.weapon, WR_INIT); break; } } @@@ -1435,10 -1655,17 +1434,10 @@@ void spawnfunc_item_jetpack(void StartItem ("models/items/g_jetpack.md3", "misc/itempickup.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup, "Jet pack", IT_JETPACK, 0, FL_POWERUP, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW); } - -#define OP_SET 0 -#define OP_MIN 1 -#define OP_MAX 2 -#define OP_PLUS 3 -#define OP_MINUS 4 - float GiveWeapon(entity e, float wpn, float op, float val) { - float v0, v1; - v0 = WEPSET_CONTAINS_EW(e, wpn); + WepSet v0, v1; + v0 = (e.weapons & WepSet_FromWeapon(wpn)); switch(op) { case OP_SET: @@@ -1544,7 -1771,14 +1543,6 @@@ void GiveRot(entity e, float v0, float else if(v0 > v1) e.regenfield = max(e.regenfield, time + regentime); } - -#define PREGIVE_WEAPONS(e) WepSet save_weapons; save_weapons = e.weapons -#define PREGIVE(e,f) float save_##f; save_##f = (e).f -#define POSTGIVE_WEAPON(e,b,snd_incr,snd_decr) GiveSound((e), !!(save_weapons & WepSet_FromWeapon(b)), !!(e.weapons & WepSet_FromWeapon(b)), 0, snd_incr, snd_decr) -#define POSTGIVE_BIT(e,f,b,snd_incr,snd_decr) GiveSound((e), save_##f & (b), (e).f & (b), 0, snd_incr, snd_decr) -#define POSTGIVE_VALUE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr) -#define POSTGIVE_VALUE_ROT(e,f,t,rotfield,rottime,regenfield,regentime,snd_incr,snd_decr) GiveRot((e), save_##f, (e).f, rotfield, rottime, regenfield, regentime); GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr) -- float GiveItems(entity e, float beginarg, float endarg) { float got, i, j, val, op; @@@ -1705,9 -1939,9 +1703,9 @@@ if(wi.weapon) { POSTGIVE_WEAPON(e, j, "weapons/weaponpickup.wav", string_null); - if not(WEPSET_CONTAINS_AW(save_weapons, j)) - if(WEPSET_CONTAINS_EW(e, j)) + if not(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"); diff --cc qcsrc/server/t_items.qh index 89bbbc5d1,000000000..a64c8396a mode 100644,000000..100644 --- a/qcsrc/server/t_items.qh +++ b/qcsrc/server/t_items.qh @@@ -1,125 -1,0 +1,125 @@@ +#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(); + +void ItemDrawSimple(); + +void ItemRead(float _IsNew); + +#endif + +#ifdef SVQC +float autocvar_sv_simple_items; +float ItemSend(entity to, float sf); + + +float have_pickup_item(void); + +#define ITEM_RESPAWN_TICKS 10 + +#define ITEM_RESPAWNTIME(i) ((i).respawntime + crandom() * (i).respawntimejitter) + // range: respawntime - respawntimejitter .. respawntime + respawntimejitter +#define ITEM_RESPAWNTIME_INITIAL(i) (ITEM_RESPAWN_TICKS + random() * ((i).respawntime + (i).respawntimejitter - ITEM_RESPAWN_TICKS)) + // range: 10 .. respawntime + respawntimejitter + +string Item_CounterFieldName(float it); + +.float max_armorvalue; +.float pickup_anyway; + +void Item_Show (entity e, float mode); + +void Item_Respawn (void); + +void Item_RespawnCountdown (void); +void Item_ScheduleRespawnIn(entity e, float t); + +void Item_ScheduleRespawn(entity e); + +void Item_ScheduleInitialRespawn(entity e); +float ITEM_MODE_NONE = 0; +float ITEM_MODE_HEALTH = 1; +float ITEM_MODE_ARMOR = 2; +float ITEM_MODE_FUEL = 3; +float Item_GiveAmmoTo(entity item, entity player, .float ammofield, float ammomax, float mode); + +float Item_GiveTo(entity item, entity player); + +void Item_Touch (void); + +void Item_Reset(); + +void Item_FindTeam(); +// Savage: used for item garbage-collection +// TODO: perhaps nice special effect? +void RemoveItem(void); // WEAPONTODO + +// pickup evaluation functions +// these functions decide how desirable an item is to the bots + +float generic_pickupevalfunc(entity player, entity item);// {return item.bot_pickupbasevalue;} // WEAPONTODO + +float weapon_pickupevalfunc(entity player, entity item); + +float commodity_pickupevalfunc(entity player, entity item); + +void Item_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force); // WEAPONTODO + +.float is_item; +void StartItem (string itemmodel, string pickupsound, float defaultrespawntime, float defaultrespawntimejitter, string itemname, float itemid, float weaponid, float itemflags, float(entity player, entity item) pickupevalfunc, float pickupbasevalue); + + +void target_items_use (void); + +#define OP_SET 0 +#define OP_MIN 1 +#define OP_MAX 2 +#define OP_PLUS 3 +#define OP_MINUS 4 + +float GiveWeapon(entity e, float wpn, float op, float val); + +float GiveBit(entity e, .float fld, float bit, float op, float val); + +float GiveValue(entity e, .float fld, float op, float val); + +void GiveSound(entity e, float v0, float v1, float t, string snd_incr, string snd_decr); + +void GiveRot(entity e, float v0, float v1, .float rotfield, float rottime, .float regenfield, float regentime); + - #define PREGIVE_WEAPONS(e) WEPSET_DECLARE_A(save_weapons); WEPSET_COPY_AE(save_weapons, e) ++#define PREGIVE_WEAPONS(e) WepSet save_weapons; save_weapons = e.weapons +#define PREGIVE(e,f) float save_##f; save_##f = (e).f - #define POSTGIVE_WEAPON(e,b,snd_incr,snd_decr) GiveSound((e), WEPSET_CONTAINS_AW(save_weapons, b), WEPSET_CONTAINS_EW(e, b), 0, snd_incr, snd_decr) ++#define POSTGIVE_WEAPON(e,b,snd_incr,snd_decr) GiveSound((e), !!(save_weapons & WepSet_FromWeapon(b)), !!(e.weapons & WepSet_FromWeapon(b)), 0, snd_incr, snd_decr) +#define POSTGIVE_BIT(e,f,b,snd_incr,snd_decr) GiveSound((e), save_##f & (b), (e).f & (b), 0, snd_incr, snd_decr) +#define POSTGIVE_VALUE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr) +#define POSTGIVE_VALUE_ROT(e,f,t,rotfield,rottime,regenfield,regentime,snd_incr,snd_decr) GiveRot((e), save_##f, (e).f, rotfield, rottime, regenfield, regentime); GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr) + +float GiveItems(entity e, float beginarg, float endarg); +#endif diff --cc qcsrc/server/tturrets/units/unit_walker.qc index c9312b34f,5e0feea35..30a4c60eb --- a/qcsrc/server/tturrets/units/unit_walker.qc +++ b/qcsrc/server/tturrets/units/unit_walker.qc @@@ -515,8 -514,8 +515,8 @@@ void walker_postthink( void walker_attack() { - sound (self, CH_WEAPON_A, "weapons/uzi_fire.wav", VOL_BASE, ATTN_NORM); + 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, 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, 1, WEP_CVAR(uzi, bulletconstant)); // WEAPONTODO endFireBallisticBullet(); pointparticles(particleeffectnum("laser_muzzleflash"), self.tur_shotorg, self.tur_shotdir_updated * 1000, 1); } diff --cc qcsrc/server/weapons/common.qc index 98fd53353,000000000..6108c38a2 mode 100644,000000..100644 --- a/qcsrc/server/weapons/common.qc +++ b/qcsrc/server/weapons/common.qc @@@ -1,89 -1,0 +1,89 @@@ + +void W_GiveWeapon (entity e, float wep) +{ + entity oldself; + + if (!wep) + return; + - WEPSET_OR_EW(e, wep); ++ e.weapons |= WepSet_FromWeapon(wep); + + oldself = self; + self = e; + + if(IS_PLAYER(other)) + { Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_WEAPON_GOT, wep); } + + self = oldself; +} + +void W_PlayStrengthSound(entity player) // void W_PlayStrengthSound +{ + if((player.items & IT_STRENGTH) + && ((time > player.prevstrengthsound + autocvar_sv_strengthsound_antispam_time) // prevent insane sound spam + || (time > player.prevstrengthsoundattempt + autocvar_sv_strengthsound_antispam_refire_threshold))) + { - sound(player, CH_TRIGGER, "weapons/strength_fire.wav", VOL_BASE, ATTN_NORM); ++ sound(player, CH_TRIGGER, "weapons/strength_fire.wav", VOL_BASE, ATTEN_NORM); + player.prevstrengthsound = time; + } + player.prevstrengthsoundattempt = time; +} + +float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception) +{ + float is_from_contents = (deathtype == DEATH_SLIME || deathtype == DEATH_LAVA); + float is_from_owner = (inflictor == projowner); + float is_from_exception = (exception != -1); + + //dprint(strcat("W_CheckProjectileDamage: from_contents ", ftos(is_from_contents), " : from_owner ", ftos(is_from_owner), " : exception ", strcat(ftos(is_from_exception), " (", ftos(exception), "). \n"))); + + if(autocvar_g_projectiles_damage <= -2) + { + return FALSE; // no damage to projectiles at all, not even with the exceptions + } + else if(autocvar_g_projectiles_damage == -1) + { + if(is_from_exception) + return (exception); // if exception is detected, allow it to override + else + return FALSE; // otherwise, no other damage is allowed + } + else if(autocvar_g_projectiles_damage == 0) + { + if(is_from_exception) + return (exception); // if exception is detected, allow it to override + else if not(is_from_contents) + return FALSE; // otherwise, only allow damage from contents + } + else if(autocvar_g_projectiles_damage == 1) + { + if(is_from_exception) + return (exception); // if exception is detected, allow it to override + else if not(is_from_contents || is_from_owner) + return FALSE; // otherwise, only allow self damage and damage from contents + } + else if(autocvar_g_projectiles_damage == 2) // allow any damage, but override for exceptions + { + if(is_from_exception) + return (exception); // if exception is detected, allow it to override + } + + return TRUE; // if none of these return, then allow damage anyway. +} + +void W_PrepareExplosionByDamage(entity attacker, void() explode) +{ + self.takedamage = DAMAGE_NO; + self.event_damage = func_null; + + if(IS_CLIENT(attacker) && !autocvar_g_projectiles_keep_owner) + { + self.owner = attacker; + self.realowner = attacker; + } + + // do not explode NOW but in the NEXT FRAME! + // because recursive calls to RadiusDamage are not allowed + self.nextthink = time; + self.think = explode; +} diff --cc qcsrc/server/weapons/selection.qc index dda999ac4,000000000..c23279591 mode 100644,000000..100644 --- a/qcsrc/server/weapons/selection.qc +++ b/qcsrc/server/weapons/selection.qc @@@ -1,273 -1,0 +1,273 @@@ +// switch between weapons +void Send_WeaponComplain(entity e, float wpn, string wpnname, float type) +{ + msg_entity = e; + WriteByte(MSG_ONE, SVC_TEMPENTITY); + WriteByte(MSG_ONE, TE_CSQC_WEAPONCOMPLAIN); + WriteByte(MSG_ONE, wpn); + WriteString(MSG_ONE, wpnname); + WriteByte(MSG_ONE, type); +} + +float client_hasweapon(entity cl, float wpn, float andammo, float complain) +{ + float f; + entity oldself; + + if(time < self.hasweapon_complain_spam) + complain = 0; + - if(wpn == WEP_HOOK && !g_grappling_hook && autocvar_g_nades && !WEPSET_CONTAINS_EW(cl, wpn) && !WEPSET_CONTAINS_AW(weaponsInMap, wpn)) ++ if(wpn == WEP_HOOK && !g_grappling_hook && autocvar_g_nades && !((cl.weapons | weaponsInMap) & WepSet_FromWeapon(wpn))) + complain = 0; + + if(complain) + self.hasweapon_complain_spam = time + 0.2; + + if (wpn < WEP_FIRST || wpn > WEP_LAST) + { + if (complain) + sprint(self, "Invalid weapon\n"); + return FALSE; + } - if (WEPSET_CONTAINS_EW(cl, wpn)) ++ if (cl.weapons & WepSet_FromWeapon(wpn)) + { + if (andammo) + { + if(cl.items & IT_UNLIMITED_WEAPON_AMMO) + { + f = 1; + } + else + { + oldself = self; + self = cl; + f = WEP_ACTION(wpn, WR_CHECKAMMO1); + f = f + WEP_ACTION(wpn, WR_CHECKAMMO2); + + // always allow selecting the Mine Layer if we placed mines, so that we can detonate them + entity mine; + if(wpn == WEP_MINE_LAYER) + for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self) + f = 1; + + self = oldself; + } + if (!f) + { + if (complain) + if(IS_REAL_CLIENT(cl)) + { + play2(cl, "weapons/unavailable.wav"); + Send_WeaponComplain (cl, wpn, W_Name(wpn), 0); + } + return FALSE; + } + } + return TRUE; + } + if (complain) + { + // DRESK - 3/16/07 + // Report Proper Weapon Status / Modified Weapon Ownership Message - if (WEPSET_CONTAINS_AW(weaponsInMap, wpn)) ++ if (weaponsInMap & WepSet_FromWeapon(wpn)) + { + Send_WeaponComplain(cl, wpn, W_Name(wpn), 1); + + if(autocvar_g_showweaponspawns) + { + entity e; + string s; + + e = get_weaponinfo(wpn); + s = e.model2; + + for(e = world; (e = findfloat(e, weapon, wpn)); ) + { + if(e.classname == "droppedweapon") + continue; + if not(e.flags & FL_ITEM) + continue; + WaypointSprite_Spawn( + s, + 1, 0, + world, e.origin, + self, 0, + world, enemy, + 0, + RADARICON_NONE, '0 0 0' + ); + } + } + } + else + { + Send_WeaponComplain (cl, wpn, W_Name(wpn), 2); + } + + play2(cl, "weapons/unavailable.wav"); + } + return FALSE; +} + +float W_GetCycleWeapon(entity pl, string weaponorder, float dir, float imp, float complain, float skipmissing) +{ + // We cannot tokenize in this function, as GiveItems calls this + // function. Thus we must use car/cdr. + float weaponwant, first_valid, prev_valid, switchtonext, switchtolast, c; + string rest; + switchtonext = switchtolast = 0; + first_valid = prev_valid = 0; + float weaponcur; + + if(skipmissing || pl.selectweapon == 0) + weaponcur = pl.switchweapon; + else + weaponcur = pl.selectweapon; + + if(dir == 0) + switchtonext = 1; + + c = 0; + + rest = weaponorder; + while(rest != "") + { + weaponwant = stof(car(rest)); rest = cdr(rest); + if(imp >= 0) + if((get_weaponinfo(weaponwant)).impulse != imp) + continue; + + ++c; + + if(!skipmissing || client_hasweapon(pl, weaponwant, TRUE, FALSE)) + { + if(switchtonext) + return weaponwant; + if(!first_valid) + first_valid = weaponwant; + if(weaponwant == weaponcur) + { + if(dir >= 0) + switchtonext = 1; + else if(prev_valid) + return prev_valid; + else + switchtolast = 1; + } + prev_valid = weaponwant; + } + } + if(first_valid) + { + if(switchtolast) + return prev_valid; + else + return first_valid; + } + // complain (but only for one weapon on the button that has been pressed) + if(complain) + { + self.weaponcomplainindex += 1; + c = mod(self.weaponcomplainindex, c) + 1; + rest = weaponorder; + while(rest != "") + { + weaponwant = stof(car(rest)); rest = cdr(rest); + if(imp >= 0) + if((get_weaponinfo(weaponwant)).impulse != imp) + continue; + + --c; + if(c == 0) + { + client_hasweapon(pl, weaponwant, TRUE, TRUE); + break; + } + } + } + return 0; +} + +void W_SwitchWeapon_Force(entity e, float w) +{ + e.cnt = e.switchweapon; + e.switchweapon = w; + e.selectweapon = w; +} + +// perform weapon to attack (weaponstate and attack_finished check is here) +void W_SwitchToOtherWeapon(entity pl) +{ + // hack to ensure it switches to an OTHER weapon (in case the other fire mode still has ammo, we want that anyway) + float w, ww; + w = pl.weapon; - if(WEPSET_CONTAINS_EW(pl, w)) ++ if(pl.weapons & WepSet_FromWeapon(w)) + { - WEPSET_ANDNOT_EW(pl, w); ++ pl.weapons &= ~WepSet_FromWeapon(w); + ww = w_getbestweapon(pl); - WEPSET_OR_EW(pl, w); ++ pl.weapons |= WepSet_FromWeapon(w); + } + else + ww = w_getbestweapon(pl); + if(ww) + W_SwitchWeapon_Force(pl, ww); +} + +void W_SwitchWeapon(float imp) +{ + if (self.switchweapon != imp) + { + if (client_hasweapon(self, imp, TRUE, TRUE)) + W_SwitchWeapon_Force(self, imp); + else + self.selectweapon = imp; // update selectweapon ANYWAY + } + else { WEP_ACTION(self.weapon, WR_RELOAD); } +} + +void W_CycleWeapon(string weaponorder, float dir) +{ + float w; + w = W_GetCycleWeapon(self, weaponorder, dir, -1, 1, TRUE); + if(w > 0) + W_SwitchWeapon(w); +} + +void W_NextWeaponOnImpulse(float imp) +{ + float w; + w = W_GetCycleWeapon(self, self.cvar_cl_weaponpriority, +1, imp, 1, (self.cvar_cl_weaponimpulsemode == 0)); + if(w > 0) + W_SwitchWeapon(w); +} + +// next weapon +void W_NextWeapon(float list) +{ + if(list == 0) + W_CycleWeapon(weaponorder_byid, -1); + else if(list == 1) + W_CycleWeapon(self.weaponorder_byimpulse, -1); + else if(list == 2) + W_CycleWeapon(self.cvar_cl_weaponpriority, -1); +} + +// prev weapon +void W_PreviousWeapon(float list) +{ + if(list == 0) + W_CycleWeapon(weaponorder_byid, +1); + else if(list == 1) + W_CycleWeapon(self.weaponorder_byimpulse, +1); + else if(list == 2) + W_CycleWeapon(self.cvar_cl_weaponpriority, +1); +} + +// previously used if exists and has ammo, (second) best otherwise +void W_LastWeapon(void) +{ + if(client_hasweapon(self, self.cnt, TRUE, FALSE)) + W_SwitchWeapon(self.cnt); + else + W_SwitchToOtherWeapon(self); +} diff --cc qcsrc/server/weapons/spawning.qc index 30b7b0beb,000000000..65188fef3 mode 100644,000000..100644 --- a/qcsrc/server/weapons/spawning.qc +++ b/qcsrc/server/weapons/spawning.qc @@@ -1,153 -1,0 +1,153 @@@ +string W_Apply_Weaponreplace(string in) +{ + float n = tokenize_console(in); + string out = ""; + float i; + for(i = 0; i < n; ++i) + { + string s = argv(i); + string r = cvar_string(strcat("g_weaponreplace_", s)); + if(r == "") + out = strcat(out, " ", s); + else if(r != "0") + out = strcat(out, " ", r); + } + return substring(out, 1, -1); +} + +void weapon_defaultspawnfunc(float wpn) +{ + entity e; + float t; + var .float ammofield; + string s; + entity oldself; + float i, j; + float f; + + if(self.classname != "droppedweapon" && self.classname != "replacedweapon") + { + e = get_weaponinfo(wpn); + + if(e.spawnflags & WEP_FLAG_MUTATORBLOCKED) + { + objerror("Attempted to spawn a mutator-blocked weapon rejected"); + startitem_failed = TRUE; + return; + } + + s = W_Apply_Weaponreplace(e.netname); + ret_string = s; + other = e; + MUTATOR_CALLHOOK(SetWeaponreplace); + s = ret_string; + if(s == "") + { + remove(self); + startitem_failed = TRUE; + return; + } + t = tokenize_console(s); + if(t >= 2) + { + self.team = --internalteam; + oldself = self; + for(i = 1; i < t; ++i) + { + s = argv(i); + for(j = WEP_FIRST; j <= WEP_LAST; ++j) + { + e = get_weaponinfo(j); + if(e.netname == s) + { + self = spawn(); + copyentity(oldself, self); + self.classname = "replacedweapon"; + weapon_defaultspawnfunc(j); + break; + } + } + if(j > WEP_LAST) + { + print("The weapon replace list for ", oldself.classname, " contains an unknown weapon ", s, ". Skipped.\n"); + } + } + self = oldself; + } + if(t >= 1) // always the case! + { + s = argv(0); + wpn = 0; + for(j = WEP_FIRST; j <= WEP_LAST; ++j) + { + e = get_weaponinfo(j); + if(e.netname == s) + { + wpn = j; + break; + } + } + if(j > WEP_LAST) + { + print("The weapon replace list for ", self.classname, " contains an unknown weapon ", s, ". Skipped.\n"); + } + } + if(wpn == 0) + { + remove(self); + startitem_failed = TRUE; + return; + } + } + + e = get_weaponinfo(wpn); + + if(!self.respawntime) + { - if(WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS)) ++ if(e.weapons & WEPSET_SUPERWEAPONS) + { + self.respawntime = g_pickup_respawntime_superweapon; + self.respawntimejitter = g_pickup_respawntimejitter_superweapon; + } + else + { + self.respawntime = g_pickup_respawntime_weapon; + self.respawntimejitter = g_pickup_respawntimejitter_weapon; + } + } + - if(WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS)) ++ if(e.weapons & WEPSET_SUPERWEAPONS) + if(!self.superweapons_finished) + self.superweapons_finished = autocvar_g_balance_superweapons_time; + + if(e.items) + { + for(i = 0, j = 1; i < 24; ++i, j *= 2) + { + if(e.items & j) + { + ammofield = Item_CounterField(j); + if(!self.ammofield) + self.ammofield = cvar(strcat("g_pickup_", Item_CounterFieldName(j), "_weapon")); + } + } + } + + // pickup anyway + if(g_pickup_weapons_anyway) + self.pickup_anyway = TRUE; + + f = FL_WEAPON; + + // no weapon-stay on superweapons - if(WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS)) ++ if(e.weapons & WEPSET_SUPERWEAPONS) + f |= FL_NO_WEAPON_STAY; + + // weapon stay isn't supported for teamed weapons + if(self.team) + f |= FL_NO_WEAPON_STAY; + + StartItem(e.model, "weapons/weaponpickup.wav", self.respawntime, self.respawntimejitter, e.message, 0, e.weapon, f, weapon_pickupevalfunc, e.bot_pickupbasevalue); + if (self.modelindex) // don't precache if self was removed + WEP_ACTION(e.weapon, WR_INIT); +} diff --cc qcsrc/server/weapons/throwing.qc index 31c8f89be,000000000..80a97d1e2 mode 100644,000000..100644 --- a/qcsrc/server/weapons/throwing.qc +++ b/qcsrc/server/weapons/throwing.qc @@@ -1,190 -1,0 +1,190 @@@ +void thrown_wep_think() +{ + self.owner = world; + float timeleft = self.savenextthink - time; + if(timeleft > 1) + SUB_SetFade(self, self.savenextthink - 1, 1); + else if(timeleft > 0) + SUB_SetFade(self, time, timeleft); + else + SUB_VanishOrRemove(self); +} + +// returns amount of ammo used as string, or -1 for failure, or 0 for no ammo count +string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo) +{ + entity oldself, wep; + float wa, thisammo, i, j; + string s; + var .float ammofield; + + wep = spawn(); + + setorigin(wep, org); + wep.classname = "droppedweapon"; + wep.velocity = velo; + wep.owner = wep.enemy = own; + wep.flags |= FL_TOSSED; + wep.colormap = own.colormap; + - if(WEPSET_CONTAINS_AW(WEPBIT_SUPERWEAPONS, wpn)) ++ if(WepSet_FromWeapon(wpn) & WEPSET_SUPERWEAPONS) + { + if(own.items & IT_UNLIMITED_SUPERWEAPONS) + { + wep.superweapons_finished = time + autocvar_g_balance_superweapons_time; + } + else + { + float superweapons = 1; + for(i = WEP_FIRST; i <= WEP_LAST; ++i) - if(WEPSET_CONTAINS_AW(WEPBIT_SUPERWEAPONS, i)) - if(WEPSET_CONTAINS_EW(own, i)) ++ if(WepSet_FromWeapon(i) & WEPSET_SUPERWEAPONS) ++ if(own.weapons & WepSet_FromWeapon(i)) + ++superweapons; + if(superweapons <= 1) + { + wep.superweapons_finished = own.superweapons_finished; + own.superweapons_finished = 0; + } + else + { + float timeleft = own.superweapons_finished - time; + float weptimeleft = timeleft / superweapons; + wep.superweapons_finished = time + weptimeleft; + own.superweapons_finished -= weptimeleft; + } + } + } + + wa = W_AmmoItemCode(wpn); + if(wa == 0) + { + oldself = self; + self = wep; + weapon_defaultspawnfunc(wpn); + self = oldself; + if(startitem_failed) + return string_null; + wep.glowmod = own.weaponentity_glowmod; + wep.think = thrown_wep_think; + wep.savenextthink = wep.nextthink; + wep.nextthink = min(wep.nextthink, time + 0.5); + wep.pickup_anyway = TRUE; // these are ALWAYS pickable + return ""; + } + else + { + s = ""; + oldself = self; + self = wep; + weapon_defaultspawnfunc(wpn); + self = oldself; + if(startitem_failed) + return string_null; + if(doreduce && g_weapon_stay == 2) + { + for(i = 0, j = 1; i < 24; ++i, j *= 2) + { + if(wa & j) + { + ammofield = Item_CounterField(j); + + // if our weapon is loaded, give its load back to the player + if(self.(weapon_load[self.weapon]) > 0) + { + own.ammofield += self.(weapon_load[self.weapon]); + self.(weapon_load[self.weapon]) = -1; // schedule the weapon for reloading + } + + wep.ammofield = 0; + } + } + } + else if(doreduce) + { + for(i = 0, j = 1; i < 24; ++i, j *= 2) + { + if(wa & j) + { + ammofield = Item_CounterField(j); + + // if our weapon is loaded, give its load back to the player + if(self.(weapon_load[self.weapon]) > 0) + { + own.ammofield += self.(weapon_load[self.weapon]); + self.(weapon_load[self.weapon]) = -1; // schedule the weapon for reloading + } + + thisammo = min(own.ammofield, wep.ammofield); + wep.ammofield = thisammo; + own.ammofield -= thisammo; + s = strcat(s, " and ", ftos(thisammo), " ", Item_CounterFieldName(j)); + } + } + s = substring(s, 5, -1); + } + wep.glowmod = own.weaponentity_glowmod; + wep.think = thrown_wep_think; + wep.savenextthink = wep.nextthink; + wep.nextthink = min(wep.nextthink, time + 0.5); + wep.pickup_anyway = TRUE; // these are ALWAYS pickable + + return s; + } +} + +float W_IsWeaponThrowable(float w) +{ + float wa; + + if (!autocvar_g_pickup_items) + return 0; + if (g_weaponarena) + return 0; + if (g_cts) + return 0; + if (g_nexball && w == WEP_GRENADE_LAUNCHER) + return 0; + if(w == 0) + return 0; + + wa = W_AmmoItemCode(w); - if(WEPSET_CONTAINS_AW(start_weapons, w)) ++ if(start_weapons & WepSet_FromWeapon(w)) + { + // start weapons that take no ammo can't be dropped (this prevents dropping the laser, as long as it continues to use no ammo) + if(start_items & IT_UNLIMITED_WEAPON_AMMO) + return 0; + if(wa == 0) + return 0; + } + + return 1; +} + +// toss current weapon +void W_ThrowWeapon(vector velo, vector delta, float doreduce) +{ + float w; + string a; + + w = self.weapon; + if (w == 0) + return; // just in case + if(MUTATOR_CALLHOOK(ForbidThrowCurrentWeapon)) + return; + if(!autocvar_g_weapon_throwable) + return; + if(self.weaponentity.state != WS_READY) + return; + if(!W_IsWeaponThrowable(w)) + return; + - if(!WEPSET_CONTAINS_EW(self, w)) ++ if(!(self.weapons & WepSet_FromWeapon(w))) + return; - WEPSET_ANDNOT_EW(self, w); ++ self.weapons &= ~WepSet_FromWeapon(w); + + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + a = W_ThrowNewWeapon(self, w, doreduce, self.origin + delta, velo); + + if not(a) return; + Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_WEAPON_DROP, a, w); +} diff --cc qcsrc/server/weapons/tracing.qc index dd215ba14,000000000..6fcab0dc7 mode 100644,000000..100644 --- a/qcsrc/server/weapons/tracing.qc +++ b/qcsrc/server/weapons/tracing.qc @@@ -1,717 -1,0 +1,717 @@@ +// 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) + { + 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 + { + // 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, ATTN_NONE); ++ 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); + 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; ++ 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; + + // 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) + if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID) + if not(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; +} + +void fireBallisticBullet(vector start, vector dir, float spread, float pSpeed, float lifetime, float damage, float force, float dtype, float tracereffects, float gravityfactor, float bulletconstant) +{ + float lag, dt, savetime; //, density; + entity pl, oldself; + float antilagging; + + antilagging = (autocvar_g_antilag_bullets && (pSpeed >= autocvar_g_antilag_bullets)); + + entity proj; + proj = spawn(); + proj.classname = "bullet"; + proj.owner = proj.realowner = self; + PROJECTILE_MAKETRIGGER(proj); + if(gravityfactor > 0) + { + proj.movetype = MOVETYPE_TOSS; + proj.gravity = gravityfactor; + } + else + proj.movetype = MOVETYPE_FLY; + proj.think = SUB_Remove; + proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed); + W_SetupProjectileVelocityEx(proj, dir, v_up, pSpeed, 0, 0, spread, antilagging); + 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(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 + + if(lag) + FOR_EACH_PLAYER(pl) + if(pl != self) + antilag_takeback(pl, time - lag); + + oldself = self; + self = proj; + + savetime = frametime; + frametime = 0.05; + + 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 + + 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(!SUB_OwnerCheck()) + { + if(SUB_NoImpactCheck()) + break; + + // hit the player + W_BallisticBullet_Hit(); + } + + if(proj.dmg_radius < 0) // these NEVER penetrate solid + break; + + // 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) + break; + + // go through solid! + if(!W_BallisticBullet_LeaveSolid((other && (other.solid != SOLID_BSP)) ? eff : -1)) + break; + + W_BallisticBullet_LeaveSolid_think(); + + self.projectiledeathtype |= HITTYPE_BOUNCE; + } + frametime = savetime; + self = oldself; + + if(lag) + FOR_EACH_PLAYER(pl) + if(pl != self) + antilag_restore(pl); + + remove(proj); + + return; + } + + 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); +} + +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 not (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; +} diff --cc qcsrc/server/weapons/weaponsystem.qc index 0d9082703,000000000..2586c2ee4 mode 100644,000000..100644 --- a/qcsrc/server/weapons/weaponsystem.qc +++ b/qcsrc/server/weapons/weaponsystem.qc @@@ -1,943 -1,0 +1,943 @@@ +/* +=========================================================================== + + 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 - float WFRAME_DONTCHANGE = -1; - float WFRAME_FIRE1 = 0; - float WFRAME_FIRE2 = 1; - float WFRAME_IDLE = 2; - float WFRAME_RELOAD = 3; ++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 &= ~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'; + + 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 not(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, ATTN_NORM); ++ 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); + } + + 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; ++ 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 &= ~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 && !WEPSET_CONTAINS_EW(self, w)) ++ 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(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(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 not(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, ATTN_NORM); ++ 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; +}