From 29af6ee750b1027a33ef36d3c85ab144d5ddb2ef Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 1 Feb 2015 19:11:27 +1100 Subject: [PATCH] Port the so-called "mod pack" to a branch for easier maintenance and eventually porting of features to separate branches --- qcsrc/Makefile | 1 + qcsrc/client/Main.qc | 201 +- qcsrc/client/View.qc | 228 +- qcsrc/client/announcer.qc | 17 + qcsrc/client/autocvars.qh | 42 +- qcsrc/client/command/cl_cmd.qc | 30 +- qcsrc/client/conquest.qc | 149 + qcsrc/client/conquest.qh | 17 + qcsrc/client/controlpoint.qc | 210 ++ qcsrc/client/controlpoint.qh | 8 + qcsrc/client/csqcmodel_hooks.qc | 30 +- qcsrc/client/damage.qc | 7 +- qcsrc/client/generator.qc | 250 ++ qcsrc/client/generator.qh | 8 + qcsrc/client/hook.qc | 4 + qcsrc/client/hud.qc | 1598 +++++++-- qcsrc/client/hud.qh | 60 +- qcsrc/client/hud_config.qc | 13 +- qcsrc/client/main.qh | 17 + qcsrc/client/mapvoting.qc | 18 +- qcsrc/client/miscfunctions.qc | 10 + qcsrc/client/particles.qc | 10 +- qcsrc/client/player_skeleton.qc | 11 +- qcsrc/client/player_skeleton.qh | 3 + qcsrc/client/progs.src | 42 +- qcsrc/client/scoreboard.qc | 84 +- qcsrc/client/teamradar.qc | 28 + qcsrc/client/waypointsprites.qc | 36 +- qcsrc/client/waypointsprites.qh | 3 + qcsrc/client/weapons/projectile.qc | 23 +- qcsrc/common/animdecide.qc | 2 + qcsrc/common/animdecide.qh | 1 + qcsrc/common/buffs.qc | 1 - qcsrc/common/buffs.qh | 11 + qcsrc/common/command/generic.qc | 1 + qcsrc/common/command/script.qc | 63 + qcsrc/common/command/script.qh | 11 + qcsrc/common/command/script/ast.qh | 959 ++++++ qcsrc/common/command/script/lex.qh | 213 ++ qcsrc/common/command/script/parse.qh | 445 +++ qcsrc/common/command/script/string.qh | 131 + qcsrc/common/constants.qh | 34 +- qcsrc/common/csqcmodel_settings.qh | 14 +- qcsrc/common/deathtypes.qh | 37 +- qcsrc/common/effects.qc | 109 + qcsrc/common/effects.qh | 155 + qcsrc/common/jeff.qh | 49 + qcsrc/common/mapinfo.qc | 55 +- qcsrc/common/mapinfo.qh | 18 +- qcsrc/common/minigames/cl_minigames.qc | 399 +++ qcsrc/common/minigames/cl_minigames.qh | 115 + qcsrc/common/minigames/cl_minigames_hud.qc | 698 ++++ qcsrc/common/minigames/minigame/all.qh | 99 + qcsrc/common/minigames/minigame/nmm.qc | 762 +++++ qcsrc/common/minigames/minigame/ttt.qc | 688 ++++ qcsrc/common/minigames/minigames.qc | 129 + qcsrc/common/minigames/minigames.qh | 119 + qcsrc/common/minigames/sv_minigames.qc | 428 +++ qcsrc/common/minigames/sv_minigames.qh | 51 + qcsrc/common/monsters/all.qh | 14 + qcsrc/common/monsters/monster/afrit.qc | 192 ++ qcsrc/common/monsters/monster/creeper.qc | 125 + qcsrc/common/monsters/monster/demon.qc | 127 + qcsrc/common/monsters/monster/enforcer.qc | 178 + qcsrc/common/monsters/monster/mage.qc | 136 +- qcsrc/common/monsters/monster/ogre.qc | 370 +++ qcsrc/common/monsters/monster/rotfish.qc | 97 + qcsrc/common/monsters/monster/rottweiler.qc | 101 + qcsrc/common/monsters/monster/scrag.qc | 161 + qcsrc/common/monsters/monster/shambler.qc | 118 +- qcsrc/common/monsters/monster/spawn.qc | 136 + qcsrc/common/monsters/monster/spider.qc | 71 +- qcsrc/common/monsters/monster/vore.qc | 232 ++ qcsrc/common/monsters/monster/wyvern.qc | 67 +- qcsrc/common/monsters/monster/zombie.qc | 86 +- qcsrc/common/monsters/monsters.qc | 30 +- qcsrc/common/monsters/monsters.qh | 35 +- qcsrc/common/monsters/spawn.qc | 36 +- qcsrc/common/monsters/sv_monsters.qc | 902 ++--- qcsrc/common/monsters/sv_monsters.qh | 122 +- qcsrc/common/notifications.qc | 16 +- qcsrc/common/notifications.qh | 312 +- qcsrc/common/playerstats.qc | 2 +- qcsrc/common/playerstats.qh | 2 + qcsrc/common/stats.qh | 52 +- qcsrc/common/teams.qh | 4 +- qcsrc/common/turrets/all.qh | 12 + .../turrets/checkpoint.qc} | 5 +- qcsrc/common/turrets/cl_turrets.qc | 446 +++ qcsrc/common/turrets/cl_turrets.qh | 1 + qcsrc/common/turrets/sv_turrets.qc | 1394 ++++++++ qcsrc/common/turrets/sv_turrets.qh | 106 + .../turrets/targettrigger.qc} | 8 +- qcsrc/common/turrets/turrets.qc | 78 + qcsrc/common/turrets/turrets.qh | 197 ++ qcsrc/common/turrets/unit/ewheel.qc | 311 ++ qcsrc/common/turrets/unit/flac.qc | 103 + qcsrc/common/turrets/unit/fusionreactor.qc | 116 + qcsrc/common/turrets/unit/hellion.qc | 160 + qcsrc/common/turrets/unit/hk.qc | 361 ++ qcsrc/common/turrets/unit/machinegun.qc | 79 + qcsrc/common/turrets/unit/mlrs.qc | 90 + qcsrc/common/turrets/unit/phaser.qc | 173 + qcsrc/common/turrets/unit/plasma.qc | 118 + qcsrc/common/turrets/unit/plasma_dual.qc | 116 + qcsrc/common/turrets/unit/tesla.qc | 217 ++ qcsrc/common/turrets/unit/walker.qc | 696 ++++ qcsrc/common/turrets/util.qc | 331 ++ qcsrc/common/util.qc | 25 +- qcsrc/common/util.qh | 39 +- qcsrc/common/vehicles/all.qh | 8 + qcsrc/common/vehicles/cl_vehicles.qc | 126 + qcsrc/common/vehicles/cl_vehicles.qh | 15 + qcsrc/common/vehicles/sv_vehicles.qc | 1264 +++++++ .../vehicles/sv_vehicles.qh} | 101 +- .../vehicles/unit}/bumblebee.qc | 938 ++++-- qcsrc/common/vehicles/unit/racer.qc | 909 +++++ qcsrc/common/vehicles/unit/raptor.qc | 1222 +++++++ qcsrc/common/vehicles/unit/spiderbot.qc | 1080 ++++++ qcsrc/common/vehicles/unit/tankll48.qc | 743 +++++ qcsrc/common/vehicles/unit/yugo.qc | 661 ++++ qcsrc/common/vehicles/vehicles.qc | 87 + qcsrc/common/vehicles/vehicles.qh | 89 + qcsrc/common/vehicles/vehicles_include.qc | 8 + qcsrc/common/vehicles/vehicles_include.qh | 8 + qcsrc/common/weapons/all.qh | 4 + qcsrc/common/weapons/w_arc.qc | 28 +- qcsrc/common/weapons/w_blaster.qc | 12 +- qcsrc/common/weapons/w_crylink.qc | 24 +- qcsrc/common/weapons/w_devastator.qc | 32 +- qcsrc/common/weapons/w_electro.qc | 85 +- qcsrc/common/weapons/w_fireball.qc | 29 +- qcsrc/common/weapons/w_hagar.qc | 36 +- qcsrc/common/weapons/w_hlac.qc | 16 +- qcsrc/common/weapons/w_hmg.qc | 16 +- qcsrc/common/weapons/w_hook.qc | 15 +- qcsrc/common/weapons/w_lightsabre.qc | 337 ++ qcsrc/common/weapons/w_machinegun.qc | 24 +- qcsrc/common/weapons/w_minelayer.qc | 42 +- qcsrc/common/weapons/w_mortar.qc | 66 +- qcsrc/common/weapons/w_porto.qc | 15 +- qcsrc/common/weapons/w_revolver.qc | 242 ++ qcsrc/common/weapons/w_rifle.qc | 20 +- qcsrc/common/weapons/w_rpc.qc | 14 +- qcsrc/common/weapons/w_seeker.qc | 24 +- qcsrc/common/weapons/w_shockwave.qc | 17 +- qcsrc/common/weapons/w_shotgun.qc | 21 +- qcsrc/common/weapons/w_tuba.qc | 18 +- qcsrc/common/weapons/w_vaporizer.qc | 842 ++++- qcsrc/common/weapons/w_vortex.qc | 26 +- qcsrc/common/weapons/weapons.qc | 59 +- qcsrc/common/weapons/weapons.qh | 8 + qcsrc/csqcmodellib/cl_player.qc | 5 +- qcsrc/dpdefs/csprogsdefs.qc | 1 + qcsrc/dpdefs/menudefs.qc | 33 + qcsrc/menu/progs.src | 2 + qcsrc/server/autocvars.qh | 372 ++- qcsrc/server/bot/aim.qc | 38 +- qcsrc/server/bot/bot.qc | 3 +- qcsrc/server/bot/bot.qh | 6 +- qcsrc/server/bot/havocbot/havocbot.qc | 40 +- qcsrc/server/bot/havocbot/role_keyhunt.qc | 212 -- qcsrc/server/bot/havocbot/role_onslaught.qc | 369 --- qcsrc/server/bot/havocbot/roles.qc | 24 +- qcsrc/server/cheats.qc | 21 +- qcsrc/server/cl_client.qc | 445 ++- qcsrc/server/cl_impulse.qc | 28 +- qcsrc/server/cl_physics.qc | 183 +- qcsrc/server/cl_player.qc | 76 +- qcsrc/server/cl_weapons.qc | 549 ++++ qcsrc/server/command/cmd.qc | 192 +- qcsrc/server/command/common.qc | 145 +- qcsrc/server/command/sv_cmd.qc | 238 +- qcsrc/server/command/sv_cmd.qh | 4 +- qcsrc/server/command/vote.qc | 22 +- qcsrc/server/controlpoint.qc | 36 + qcsrc/server/controlpoint.qh | 5 + qcsrc/server/defs.qh | 53 +- qcsrc/server/func_breakable.qc | 1 + qcsrc/server/g_damage.qc | 92 +- qcsrc/server/g_hook.qc | 114 +- qcsrc/server/g_subs.qc | 4 +- qcsrc/server/g_tetris.qc | 1257 ------- qcsrc/server/g_triggers.qc | 46 +- qcsrc/server/g_world.qc | 127 +- qcsrc/server/generator.qc | 35 + qcsrc/server/generator.qh | 5 + qcsrc/server/jeff.qc | 331 ++ qcsrc/server/miscfunctions.qc | 201 +- qcsrc/server/mutators/base.qc | 2 +- qcsrc/server/mutators/base.qh | 36 +- qcsrc/server/mutators/gamemode_assault.qc | 114 +- qcsrc/server/mutators/gamemode_assault.qh | 5 +- qcsrc/server/mutators/gamemode_ca.qc | 58 +- qcsrc/server/mutators/gamemode_conquest.qc | 928 ++++++ qcsrc/server/mutators/gamemode_conquest.qh | 44 + qcsrc/server/mutators/gamemode_ctf.qc | 567 +++- qcsrc/server/mutators/gamemode_ctf.qh | 39 +- qcsrc/server/mutators/gamemode_cts.qc | 14 + qcsrc/server/mutators/gamemode_deathmatch.qc | 36 + qcsrc/server/mutators/gamemode_domination.qc | 582 ++-- qcsrc/server/mutators/gamemode_domination.qh | 25 + qcsrc/server/mutators/gamemode_freezetag.qc | 75 +- qcsrc/server/mutators/gamemode_freezetag.qh | 8 + qcsrc/server/mutators/gamemode_infection.qc | 294 ++ qcsrc/server/mutators/gamemode_infection.qh | 13 + qcsrc/server/mutators/gamemode_invasion.qc | 38 +- qcsrc/server/mutators/gamemode_jailbreak.qc | 1344 ++++++++ qcsrc/server/mutators/gamemode_jailbreak.qh | 86 + qcsrc/server/mutators/gamemode_keepaway.qc | 28 +- qcsrc/server/mutators/gamemode_keyhunt.qc | 2252 ++++++++----- qcsrc/server/mutators/gamemode_keyhunt.qh | 142 +- qcsrc/server/mutators/gamemode_lms.qc | 22 +- qcsrc/server/mutators/gamemode_nexball.qc | 36 +- qcsrc/server/mutators/gamemode_onslaught.qc | 2926 ++++++++++------- qcsrc/server/mutators/gamemode_onslaught.qh | 94 + qcsrc/server/mutators/gamemode_race.qc | 17 + qcsrc/server/mutators/gamemode_tdm.qc | 7 + qcsrc/server/mutators/gamemode_vip.qc | 716 ++++ qcsrc/server/mutators/gamemode_vip.qh | 34 + qcsrc/server/mutators/mutator_bloodloss.qc | 2 + qcsrc/server/mutators/mutator_buffs.qc | 131 +- qcsrc/server/mutators/mutator_buffs.qh | 3 - qcsrc/server/mutators/mutator_campcheck.qc | 14 +- qcsrc/server/mutators/mutator_dodging.qc | 45 +- qcsrc/server/mutators/mutator_freeze.qc | 303 ++ qcsrc/server/mutators/mutator_freeze.qh | 6 + qcsrc/server/mutators/mutator_hats.qc | 180 + qcsrc/server/mutators/mutator_instagib.qc | 234 +- qcsrc/server/mutators/mutator_instagib.qh | 3 + qcsrc/server/mutators/mutator_itemeditor.qc | 419 +++ qcsrc/server/mutators/mutator_multijump.qc | 22 +- qcsrc/server/mutators/mutator_nades.qc | 148 +- qcsrc/server/mutators/mutator_nades.qh | 4 +- qcsrc/server/mutators/mutator_new_toys.qc | 9 +- qcsrc/server/mutators/mutator_nix.qc | 5 - qcsrc/server/mutators/mutator_overkill.qc | 141 +- .../server/mutators/mutator_physical_items.qc | 18 +- qcsrc/server/mutators/mutator_piggyback.qc | 295 ++ qcsrc/server/mutators/mutator_piggyback.qh | 12 + .../mutators/mutator_random_vehicles.qc | 43 + qcsrc/server/mutators/mutator_riflearena.qc | 108 + .../mutators/mutator_spawn_near_teammate.qc | 16 +- qcsrc/server/mutators/mutator_superspec.qc | 52 +- qcsrc/server/mutators/mutator_touchexplode.qc | 8 +- qcsrc/server/mutators/mutator_walljump.qc | 75 + .../mutators/mutator_zombie_apocalypse.qc | 48 + qcsrc/server/mutators/mutators.qc | 22 +- qcsrc/server/mutators/mutators.qh | 16 +- qcsrc/server/mutators/mutators_include.qc | 13 + qcsrc/server/mutators/mutators_include.qh | 11 +- qcsrc/server/mutators/sandbox.qc | 67 +- qcsrc/server/portals.qc | 6 +- qcsrc/server/progs.src | 61 +- qcsrc/server/race.qc | 20 + qcsrc/server/race.qh | 16 + qcsrc/server/scores.qc | 39 +- qcsrc/server/scores.qh | 9 + qcsrc/server/scores_rules.qc | 3 - qcsrc/server/spawnpoints.qc | 40 +- qcsrc/server/spawnpoints.qh | 2 + qcsrc/server/steerlib.qc | 2 +- qcsrc/server/sv_main.qc | 6 +- qcsrc/server/t_halflife.qc | 2 +- qcsrc/server/t_items.qc | 273 +- qcsrc/server/t_items.qh | 10 +- qcsrc/server/t_jumppads.qc | 2 +- qcsrc/server/t_quake3.qc | 1 + qcsrc/server/t_teleporters.qc | 6 +- qcsrc/server/teamplay.qc | 218 +- qcsrc/server/teamplay.qh | 8 + qcsrc/server/tturrets/include/turrets.qh | 29 - .../server/tturrets/include/turrets_early.qh | 473 --- .../server/tturrets/system/system_aimprocs.qc | 73 - qcsrc/server/tturrets/system/system_damage.qc | 131 - qcsrc/server/tturrets/system/system_main.qc | 1378 -------- qcsrc/server/tturrets/system/system_misc.qc | 355 -- .../tturrets/system/system_scoreprocs.qc | 130 - qcsrc/server/tturrets/units/unit_ewheel.qc | 310 -- qcsrc/server/tturrets/units/unit_flac.qc | 74 - .../tturrets/units/unit_fusionreactor.qc | 94 - qcsrc/server/tturrets/units/unit_hellion.qc | 126 - qcsrc/server/tturrets/units/unit_hk.qc | 336 -- .../server/tturrets/units/unit_machinegun.qc | 52 - qcsrc/server/tturrets/units/unit_mlrs.qc | 63 - qcsrc/server/tturrets/units/unit_phaser.qc | 142 - qcsrc/server/tturrets/units/unit_plasma.qc | 173 - qcsrc/server/tturrets/units/unit_tessla.qc | 191 -- qcsrc/server/tturrets/units/unit_walker.qc | 643 ---- qcsrc/server/vehicles/racer.qc | 683 ---- qcsrc/server/vehicles/raptor.qc | 949 ------ qcsrc/server/vehicles/spiderbot.qc | 883 ----- qcsrc/server/vehicles/vehicles.qc | 1446 -------- qcsrc/server/vehicles/vehicles.qh | 10 - qcsrc/server/waypointsprites.qc | 19 +- qcsrc/server/weapons/common.qc | 13 +- qcsrc/server/weapons/common.qh | 7 +- qcsrc/server/weapons/hitplot.qc | 2 +- qcsrc/server/weapons/selection.qc | 10 +- qcsrc/server/weapons/tracing.qc | 51 +- qcsrc/server/weapons/weaponsystem.qc | 36 +- qcsrc/server/weapons/weaponsystem.qh | 2 +- 302 files changed, 38493 insertions(+), 16677 deletions(-) create mode 100644 qcsrc/client/conquest.qc create mode 100644 qcsrc/client/conquest.qh create mode 100644 qcsrc/client/controlpoint.qc create mode 100644 qcsrc/client/controlpoint.qh create mode 100644 qcsrc/client/generator.qc create mode 100644 qcsrc/client/generator.qh create mode 100644 qcsrc/common/command/script.qc create mode 100644 qcsrc/common/command/script.qh create mode 100644 qcsrc/common/command/script/ast.qh create mode 100644 qcsrc/common/command/script/lex.qh create mode 100644 qcsrc/common/command/script/parse.qh create mode 100644 qcsrc/common/command/script/string.qh create mode 100644 qcsrc/common/effects.qc create mode 100644 qcsrc/common/effects.qh create mode 100644 qcsrc/common/jeff.qh create mode 100644 qcsrc/common/minigames/cl_minigames.qc create mode 100644 qcsrc/common/minigames/cl_minigames.qh create mode 100644 qcsrc/common/minigames/cl_minigames_hud.qc create mode 100644 qcsrc/common/minigames/minigame/all.qh create mode 100644 qcsrc/common/minigames/minigame/nmm.qc create mode 100644 qcsrc/common/minigames/minigame/ttt.qc create mode 100644 qcsrc/common/minigames/minigames.qc create mode 100644 qcsrc/common/minigames/minigames.qh create mode 100644 qcsrc/common/minigames/sv_minigames.qc create mode 100644 qcsrc/common/minigames/sv_minigames.qh create mode 100644 qcsrc/common/monsters/monster/afrit.qc create mode 100644 qcsrc/common/monsters/monster/creeper.qc create mode 100644 qcsrc/common/monsters/monster/demon.qc create mode 100644 qcsrc/common/monsters/monster/enforcer.qc create mode 100644 qcsrc/common/monsters/monster/ogre.qc create mode 100644 qcsrc/common/monsters/monster/rotfish.qc create mode 100644 qcsrc/common/monsters/monster/rottweiler.qc create mode 100644 qcsrc/common/monsters/monster/scrag.qc create mode 100644 qcsrc/common/monsters/monster/spawn.qc create mode 100644 qcsrc/common/monsters/monster/vore.qc create mode 100644 qcsrc/common/turrets/all.qh rename qcsrc/{server/tturrets/units/unit_checkpoint.qc => common/turrets/checkpoint.qc} (97%) create mode 100644 qcsrc/common/turrets/cl_turrets.qc create mode 100644 qcsrc/common/turrets/cl_turrets.qh create mode 100644 qcsrc/common/turrets/sv_turrets.qc create mode 100644 qcsrc/common/turrets/sv_turrets.qh rename qcsrc/{server/tturrets/units/unit_targettrigger.qc => common/turrets/targettrigger.qc} (82%) create mode 100644 qcsrc/common/turrets/turrets.qc create mode 100644 qcsrc/common/turrets/turrets.qh create mode 100644 qcsrc/common/turrets/unit/ewheel.qc create mode 100644 qcsrc/common/turrets/unit/flac.qc create mode 100644 qcsrc/common/turrets/unit/fusionreactor.qc create mode 100644 qcsrc/common/turrets/unit/hellion.qc create mode 100644 qcsrc/common/turrets/unit/hk.qc create mode 100644 qcsrc/common/turrets/unit/machinegun.qc create mode 100644 qcsrc/common/turrets/unit/mlrs.qc create mode 100644 qcsrc/common/turrets/unit/phaser.qc create mode 100644 qcsrc/common/turrets/unit/plasma.qc create mode 100644 qcsrc/common/turrets/unit/plasma_dual.qc create mode 100644 qcsrc/common/turrets/unit/tesla.qc create mode 100644 qcsrc/common/turrets/unit/walker.qc create mode 100644 qcsrc/common/turrets/util.qc create mode 100644 qcsrc/common/vehicles/all.qh create mode 100644 qcsrc/common/vehicles/cl_vehicles.qc create mode 100644 qcsrc/common/vehicles/cl_vehicles.qh create mode 100644 qcsrc/common/vehicles/sv_vehicles.qc rename qcsrc/{server/vehicles/vehicles_def.qh => common/vehicles/sv_vehicles.qh} (57%) rename qcsrc/{server/vehicles => common/vehicles/unit}/bumblebee.qc (51%) create mode 100644 qcsrc/common/vehicles/unit/racer.qc create mode 100644 qcsrc/common/vehicles/unit/raptor.qc create mode 100644 qcsrc/common/vehicles/unit/spiderbot.qc create mode 100644 qcsrc/common/vehicles/unit/tankll48.qc create mode 100644 qcsrc/common/vehicles/unit/yugo.qc create mode 100644 qcsrc/common/vehicles/vehicles.qc create mode 100644 qcsrc/common/vehicles/vehicles.qh create mode 100644 qcsrc/common/vehicles/vehicles_include.qc create mode 100644 qcsrc/common/vehicles/vehicles_include.qh create mode 100644 qcsrc/common/weapons/w_lightsabre.qc create mode 100644 qcsrc/common/weapons/w_revolver.qc delete mode 100644 qcsrc/server/bot/havocbot/role_keyhunt.qc delete mode 100644 qcsrc/server/bot/havocbot/role_onslaught.qc create mode 100644 qcsrc/server/cl_weapons.qc create mode 100644 qcsrc/server/controlpoint.qc create mode 100644 qcsrc/server/controlpoint.qh delete mode 100644 qcsrc/server/g_tetris.qc create mode 100644 qcsrc/server/generator.qc create mode 100644 qcsrc/server/generator.qh create mode 100644 qcsrc/server/jeff.qc create mode 100644 qcsrc/server/mutators/gamemode_conquest.qc create mode 100644 qcsrc/server/mutators/gamemode_conquest.qh create mode 100644 qcsrc/server/mutators/gamemode_deathmatch.qc create mode 100644 qcsrc/server/mutators/gamemode_freezetag.qh create mode 100644 qcsrc/server/mutators/gamemode_infection.qc create mode 100644 qcsrc/server/mutators/gamemode_infection.qh create mode 100644 qcsrc/server/mutators/gamemode_jailbreak.qc create mode 100644 qcsrc/server/mutators/gamemode_jailbreak.qh create mode 100644 qcsrc/server/mutators/gamemode_onslaught.qh create mode 100644 qcsrc/server/mutators/gamemode_vip.qc create mode 100644 qcsrc/server/mutators/gamemode_vip.qh create mode 100644 qcsrc/server/mutators/mutator_freeze.qc create mode 100644 qcsrc/server/mutators/mutator_freeze.qh create mode 100644 qcsrc/server/mutators/mutator_hats.qc create mode 100644 qcsrc/server/mutators/mutator_instagib.qh create mode 100644 qcsrc/server/mutators/mutator_itemeditor.qc create mode 100644 qcsrc/server/mutators/mutator_piggyback.qc create mode 100644 qcsrc/server/mutators/mutator_piggyback.qh create mode 100644 qcsrc/server/mutators/mutator_random_vehicles.qc create mode 100644 qcsrc/server/mutators/mutator_riflearena.qc create mode 100644 qcsrc/server/mutators/mutator_walljump.qc create mode 100644 qcsrc/server/mutators/mutator_zombie_apocalypse.qc create mode 100644 qcsrc/server/teamplay.qh delete mode 100644 qcsrc/server/tturrets/include/turrets.qh delete mode 100644 qcsrc/server/tturrets/include/turrets_early.qh delete mode 100644 qcsrc/server/tturrets/system/system_aimprocs.qc delete mode 100644 qcsrc/server/tturrets/system/system_damage.qc delete mode 100644 qcsrc/server/tturrets/system/system_main.qc delete mode 100644 qcsrc/server/tturrets/system/system_misc.qc delete mode 100644 qcsrc/server/tturrets/system/system_scoreprocs.qc delete mode 100644 qcsrc/server/tturrets/units/unit_ewheel.qc delete mode 100644 qcsrc/server/tturrets/units/unit_flac.qc delete mode 100644 qcsrc/server/tturrets/units/unit_fusionreactor.qc delete mode 100644 qcsrc/server/tturrets/units/unit_hellion.qc delete mode 100644 qcsrc/server/tturrets/units/unit_hk.qc delete mode 100644 qcsrc/server/tturrets/units/unit_machinegun.qc delete mode 100644 qcsrc/server/tturrets/units/unit_mlrs.qc delete mode 100644 qcsrc/server/tturrets/units/unit_phaser.qc delete mode 100644 qcsrc/server/tturrets/units/unit_plasma.qc delete mode 100644 qcsrc/server/tturrets/units/unit_tessla.qc delete mode 100644 qcsrc/server/tturrets/units/unit_walker.qc delete mode 100644 qcsrc/server/vehicles/racer.qc delete mode 100644 qcsrc/server/vehicles/raptor.qc delete mode 100644 qcsrc/server/vehicles/spiderbot.qc delete mode 100644 qcsrc/server/vehicles/vehicles.qc delete mode 100644 qcsrc/server/vehicles/vehicles.qh diff --git a/qcsrc/Makefile b/qcsrc/Makefile index 9fde71e10..f24c1cc12 100644 --- a/qcsrc/Makefile +++ b/qcsrc/Makefile @@ -13,6 +13,7 @@ QCCFLAGS_WTFS ?= \ QCCFLAGS ?= \ -std=gmqcc \ -O3 -flno \ + -DSTUFFTO_ENABLED \ -Werror -fno-bail-on-werror -Wall \ -fftepp -fftepp-predefs -Wcpp -futf8 \ $(QCCFLAGS_WTFS) \ diff --git a/qcsrc/client/Main.qc b/qcsrc/client/Main.qc index d6b00ec9f..2f3cd456b 100644 --- a/qcsrc/client/Main.qc +++ b/qcsrc/client/Main.qc @@ -56,6 +56,19 @@ void CSQC_Init(void) break; maxclients = i; + localcmd("\nalias nobobbing \"cl_bob 0; cl_bob2 0; cl_followmodel 0; cl_leanmodel 0\"\n"); + localcmd("\nalias quickmenu \"cl_cmd hud quickmenu\"\n"); + localcmd("\nalias physics \"cmd physics ${* ?}\"\n"); + localcmd("\nalias editmob \"cmd editmob ${* ?}\"\n"); + localcmd("\nalias spawnmob \"cmd editmob spawn ${* ?}\"\n"); + localcmd("\nalias killmob \"cmd editmob kill ${* ?}\"\n"); + if(getkeybind(K_F8) == "") { localcmd("\nbind f8 \"cl_cmd hud quickmenu\"\n"); } + if(getkeybind(K_F9) == "") { localcmd("\nbind f9 \"cl_cmd hud minigame\"\n"); } + + localcmd("\nalias weapon_hmg \"impulse 248\"\n"); + localcmd("\nalias weapon_shockwave \"impulse 250\"\n"); + localcmd("\nalias weapon_arc \"impulse 251\"\n"); + //registercommand("hud_configure"); //registercommand("hud_save"); //registercommand("menu_action"); @@ -65,8 +78,89 @@ void CSQC_Init(void) registercvar("hud_usecsqc", "1"); registercvar("scoreboard_columns", "default"); + registercvar("cl_freeze", "0"); + registercvar("cl_pony", "0"); + registercvar("cl_pony_skin", "0"); + registercvar("cl_sparkle", "0"); + registercvar("cl_multijump", "0"); + registercvar("cl_dodging", "0"); + registercvar("cl_spawn_near_teammate", "1"); + registercvar("cl_damnfurries", "0"); + registercvar("cl_robot", "0"); + registercvar("cl_thestars", "0"); + registercvar("cl_charge", "0"); + registercvar("cl_magical_hax", ""); registercvar("cl_nade_type", "3"); registercvar("cl_pokenade_type", "zombie"); + registercvar("cl_autovote", ""); + registercvar("cl_nocarry", "0"); + registercvar("cl_buffs_autoreplace", "1"); + registercvar("cl_physics", "default"); // default is invalid, so it will be overridden by standard physics cvars + + // these need to be defined for quickmenu to work + registercvar("hud_panel_quickmenu_pos", "0.010000 0.410000"); + registercvar("hud_panel_quickmenu_size", "0.210000 0.250000"); + registercvar("hud_panel_quickmenu_bg", ""); + registercvar("hud_panel_quickmenu_bg_color", ""); + registercvar("hud_panel_quickmenu_bg_color_team", ""); + registercvar("hud_panel_quickmenu_bg_alpha", ""); + registercvar("hud_panel_quickmenu_bg_border", ""); + registercvar("hud_panel_quickmenu_bg_padding", ""); + registercvar("hud_panel_quickmenu_align", "0"); + + // these need to be defined for buffs to work + registercvar("hud_panel_buffs", "1"); + registercvar("hud_panel_buffs_pos", "0.45 0.855"); + registercvar("hud_panel_buffs_size", "0.05 0.07"); + registercvar("hud_panel_buffs_bg", ""); + registercvar("hud_panel_buffs_bg_color", ""); + registercvar("hud_panel_buffs_bg_color_team", ""); + registercvar("hud_panel_buffs_bg_alpha", ""); + registercvar("hud_panel_buffs_bg_border", ""); + registercvar("hud_panel_buffs_bg_padding", ""); + //registercvar("hud_panel_buffs_iconalign", "0"); + + + // these need to be defined for minigames to work + registercvar("hud_panel_minigameboard", "1"); + registercvar("hud_panel_minigameboard_pos", "0.22 0.15"); + registercvar("hud_panel_minigameboard_size", "0.50 0.60"); + registercvar("hud_panel_minigameboard_bg", "border_small"); + registercvar("hud_panel_minigameboard_bg_color", ""); + registercvar("hud_panel_minigameboard_bg_color_team", ""); + registercvar("hud_panel_minigameboard_bg_alpha", ""); + registercvar("hud_panel_minigameboard_bg_border", ""); + registercvar("hud_panel_minigameboard_bg_padding", ""); + + registercvar("hud_panel_minigamestatus", "1"); + registercvar("hud_panel_minigamestatus_pos", "0.74 0.15"); + registercvar("hud_panel_minigamestatus_size", "0.2 0.60"); + registercvar("hud_panel_minigamestatus_bg", "border_small"); + registercvar("hud_panel_minigamestatus_bg_color", ""); + registercvar("hud_panel_minigamestatus_bg_color_team", ""); + registercvar("hud_panel_minigamestatus_bg_alpha", ""); + registercvar("hud_panel_minigamestatus_bg_border", ""); + registercvar("hud_panel_minigamestatus_bg_padding", ""); + + registercvar("hud_panel_minigamehelp", "1"); + registercvar("hud_panel_minigamehelp_pos", "0.22 0.78"); + registercvar("hud_panel_minigamehelp_size", "0.50 0.20"); + registercvar("hud_panel_minigamehelp_bg", ""); + registercvar("hud_panel_minigamehelp_bg_color", ""); + registercvar("hud_panel_minigamehelp_bg_color_team", ""); + registercvar("hud_panel_minigamehelp_bg_alpha", ""); + registercvar("hud_panel_minigamehelp_bg_border", ""); + registercvar("hud_panel_minigamehelp_bg_padding", ""); + + registercvar("hud_panel_minigamemenu", "0"); + registercvar("hud_panel_minigamemenu_pos", "0 0.26"); + registercvar("hud_panel_minigamemenu_size", "0.2 0.49"); + registercvar("hud_panel_minigamemenu_bg", "border_small"); + registercvar("hud_panel_minigamemenu_bg_color", ""); + registercvar("hud_panel_minigamemenu_bg_color_team", ""); + registercvar("hud_panel_minigamemenu_bg_alpha", ""); + registercvar("hud_panel_minigamemenu_bg_border", ""); + registercvar("hud_panel_minigamemenu_bg_padding", ""); gametype = 0; @@ -87,12 +181,16 @@ void CSQC_Init(void) // needs to be done so early because of the constants they create CALL_ACCUMULATED_FUNCTION(RegisterWeapons); - CALL_ACCUMULATED_FUNCTION(RegisterMonsters); + CALL_ACCUMULATED_FUNCTION(RegisterTurrets); + CALL_ACCUMULATED_FUNCTION(RegisterVehicles); CALL_ACCUMULATED_FUNCTION(RegisterGametypes); CALL_ACCUMULATED_FUNCTION(RegisterNotifications); CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes); CALL_ACCUMULATED_FUNCTION(RegisterHUD_Panels); CALL_ACCUMULATED_FUNCTION(RegisterBuffs); + CALL_ACCUMULATED_FUNCTION(RegisterEffects); + + initialize_minigames(); WaypointSprite_Load(); @@ -100,13 +198,13 @@ void CSQC_Init(void) precache_model("null"); precache_sound("misc/hit.wav"); precache_sound("misc/typehit.wav"); + precache_model("models/ctf/shield.md3"); + generator_precache(); Projectile_Precache(); Hook_Precache(); GibSplash_Precache(); Casings_Precache(); - Vehicles_Precache(); - turrets_precache(); Tuba_Precache(); CSQCPlayer_Precache(); @@ -171,6 +269,9 @@ void Shutdown(void) if (!(calledhooks & HOOK_END)) localcmd("\ncl_hook_gameend\n"); } + + deactivate_minigame(); + HUD_MinigameMenu_Close(); } .float has_team; @@ -284,6 +385,22 @@ void Playerchecker_Think() self.nextthink = time + 0.2; } +void FPSReporter_Think() +{ + localcmd("\ncmd report fps ", ftos(floor(cl_fps)), "\n"); + self.nextthink = time + sv_showfps; +} + +void FPSReporter_Init() +{ + if(!sv_showfps) + return; + + entity e = spawn(); + e.think = FPSReporter_Think; + e.nextthink = time + 1; +} + void Porto_Init(); void TrueAim_Init(); void PostInit(void) @@ -295,6 +412,9 @@ void PostInit(void) Porto_Init(); TrueAim_Init(); + FPSReporter_Init(); + + fovlock = -1; postinit = true; } @@ -316,9 +436,18 @@ float CSQC_InputEvent(float bInputType, float nPrimary, float nSecondary) if (HUD_Panel_InputEvent(bInputType, nPrimary, nSecondary)) return true; + if ( HUD_Radar_InputEvent(bInputType, nPrimary, nSecondary) ) + return true; + + if (HUD_QuickMenu_InputEvent(bInputType, nPrimary, nSecondary)) + return true; + if (MapVote_InputEvent(bInputType, nPrimary, nSecondary)) return true; + if (HUD_Minigame_InputEvent(bInputType, nPrimary, nSecondary)) + return true; + if(menu_visible && menu_action) if(menu_action(bInputType, nPrimary, nSecondary)) return TRUE; @@ -501,6 +630,26 @@ void Ent_ClientData() else angles_held_status = 0; + if(f & 16) + { + fovlock = ReadByte(); + num_spectators = ReadByte(); + + if(fovlock <= 0) + fovlock = -1; + + float i, slot; + + for(i = 0; i < MAX_SPECTATORS; ++i) + spectatorlist[i] = 0; // reset list first + + for(i = 0; i < num_spectators; ++i) + { + slot = ReadByte(); + spectatorlist[i] = slot - 1; + } + } + if(newspectatee_status != spectatee_status) { // clear race stuff @@ -635,6 +784,32 @@ void Ent_ReadAccuracy(void) } } +.float jb_camera_active; +void ent_jailcamera() +{ + float sf = ReadByte(); + + if(sf & 1) + { + self.origin_x = ReadCoord(); + self.origin_y = ReadCoord(); + self.origin_z = ReadCoord(); + setorigin(self, self.origin); + + self.angles_x = ReadAngle(); + self.angles_y = ReadAngle(); + self.angles_z = 0; + + self.classname = "jailcamera"; + self.jb_camera_active = FALSE; + } + + if(sf & 2) + { + self.jb_camera_active = ReadByte(); + } +} + void Spawn_Draw(void) { pointparticles(self.cnt, self.origin + '0 0 28', '0 0 2', bound(0, frametime, 0.1)); @@ -733,7 +908,7 @@ void Ent_ReadSpawnEvent(float is_new) button_zoom = FALSE; } } - + HUD_Radar_Hide_Maximized(); //printf("Ent_ReadSpawnEvent(is_new = %d); origin = %s, entnum = %d, localentnum = %d\n", is_new, vtos(self.origin), entnum, player_localentnum); } @@ -819,13 +994,19 @@ void CSQC_Ent_Update(float bIsNewEntity) case ENT_CLIENT_ACCURACY: Ent_ReadAccuracy(); break; case ENT_CLIENT_AUXILIARYXHAIR: Net_AuXair2(bIsNewEntity); break; case ENT_CLIENT_TURRET: ent_turret(); break; + case ENT_CLIENT_GENERATOR: ent_generator(); break; + case ENT_CLIENT_CONTROLPOINT_ICON: ent_cpicon(); break; case ENT_CLIENT_MODEL: CSQCModel_Read(bIsNewEntity); break; case ENT_CLIENT_ITEM: ItemRead(bIsNewEntity); break; case ENT_CLIENT_BUMBLE_RAYGUN: bumble_raygun_read(bIsNewEntity); break; + case ENT_CLIENT_CONQUEST_CONTROLPOINT: conquest_controlpoint_read(bIsNewEntity); break; case ENT_CLIENT_SPAWNPOINT: Ent_ReadSpawnPoint(bIsNewEntity); break; case ENT_CLIENT_SPAWNEVENT: Ent_ReadSpawnEvent(bIsNewEntity); break; case ENT_CLIENT_NOTIFICATION: Read_Notification(bIsNewEntity); break; case ENT_CLIENT_HEALING_ORB: ent_healer(); break; + case ENT_CLIENT_JAILCAMERA: ent_jailcamera(); break; + case ENT_CLIENT_MINIGAME: ent_read_minigame(); break; + case ENT_CLIENT_EFFECT: Read_Effect(bIsNewEntity); break; default: //error(strcat(_("unknown entity type in CSQC_Ent_Update: %d\n"), self.enttype)); @@ -987,6 +1168,14 @@ void Ent_Init() g_trueaim_minrange = ReadCoord(); g_balance_porto_secondary = ReadByte(); + vaporizer_delay = ReadByte(); + + sv_showfps = ReadByte(); + + sv_announcer = strzone(ReadString()); + + if(sv_announcer != "") { Announcer_Precache(); } + if(!postinit) PostInit(); } @@ -1248,6 +1437,10 @@ float CSQC_Parse_TempEntity() Net_ReadShockwaveParticle(); bHandled = true; break; + case TE_CSQC_SUPERBLASTPARTICLE: + Net_ReadSuperBlastParticle(); + bHandled = true; + break; default: // No special logic for this temporary entity; return 0 so the engine can handle it bHandled = false; diff --git a/qcsrc/client/View.qc b/qcsrc/client/View.qc index 310b47d70..6ec92b9ed 100644 --- a/qcsrc/client/View.qc +++ b/qcsrc/client/View.qc @@ -104,6 +104,7 @@ vector GetCurrentFov(float fov) zoomdir = button_zoom; if(hud == HUD_NORMAL) + if(switchweapon == activeweapon) if((activeweapon == WEP_VORTEX && vortex_scope) || (activeweapon == WEP_RIFLE && rifle_scope)) // do NOT use switchweapon here zoomdir += button_attack2; if(spectatee_status > 0 || isdemo()) @@ -367,8 +368,6 @@ const float CAMERA_CHASE = 2; float reticle_type; string reticle_image; string NextFrameCommand; -void CSQC_SPIDER_HUD(); -void CSQC_RAPTOR_HUD(); vector freeze_org, freeze_ang; entity nightvision_noise, nightvision_noise2; @@ -399,6 +398,8 @@ float WantEventchase() return TRUE; if(spectatee_status >= 0) { + if(hud != HUD_NORMAL && (autocvar_cl_eventchase_vehicle || spectatee_status > 0)) + return TRUE; if(autocvar_cl_eventchase_nexball && gametype == MAPINFO_TYPE_NEXBALL && !(WepSet_GetFromStat() & WepSet_FromWeapon(WEP_PORTO))) return TRUE; if(autocvar_cl_eventchase_death && (getstati(STAT_HEALTH) <= 0)) @@ -493,13 +494,13 @@ void UpdateHitsound() } } +float last_beam; void UpdateCrosshair() { static float rainbow_last_flicker; static vector rainbow_prev_color; entity e = self; float f, i, j; - vector v; if(getstati(STAT_FROZEN)) drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, ((getstatf(STAT_REVIVE_PROGRESS)) ? ('0.25 0.90 1' + ('1 0 0' * getstatf(STAT_REVIVE_PROGRESS)) + ('0 1 1' * getstatf(STAT_REVIVE_PROGRESS) * -1)) : '0.25 0.90 1'), autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); else if (getstatf(STAT_HEALING_ORB)>time) @@ -510,6 +511,11 @@ void UpdateCrosshair() DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_NADE_TIMER), '0.25 0.90 1' + ('1 0 0' * getstatf(STAT_NADE_TIMER)) - ('0 1 1' * getstatf(STAT_NADE_TIMER)), autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); drawstring_aspect(eY * 0.64 * vid_conheight, ((autocvar_cl_nade_timer == 2) ? _("Nade timer") : ""), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL); } + else if(getstatf(STAT_CAPTURE_PROGRESS)) + { + DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_CAPTURE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); + drawstring_aspect(eY * 0.64 * vid_conheight, _("Capture progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL); + } else if(getstatf(STAT_REVIVE_PROGRESS)) { DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); @@ -520,8 +526,12 @@ void UpdateCrosshair() if(autocvar_viewsize < 120) CSQC_common_hud(); + float tempcamera = (gametype == MAPINFO_TYPE_JAILBREAK && spectatee_status >= 0 && getstati(STAT_ROUNDLOST) && !getstati(STAT_PRISONED)); + // crosshair goes VERY LAST - if(!scoreboard_active && !camera_active && intermission != 2 && spectatee_status != -1 && hud == HUD_NORMAL) + if(!scoreboard_active && !camera_active && intermission != 2 && + spectatee_status != -1 && hud == HUD_NORMAL && autocvar_chase_active >= 0 && + !HUD_QuickMenu_IsOpened() && !tempcamera && !HUD_MinigameMenu_IsOpened() ) { if (!autocvar_crosshair_enabled) // main toggle for crosshair rendering return; @@ -551,7 +561,7 @@ void UpdateCrosshair() shottype = TrueAimCheck(); if(shottype == SHOTTYPE_HITWORLD) { - v = wcross_origin - wcross_oldorigin; + vector v = wcross_origin - wcross_oldorigin; v_x /= vid_conwidth; v_y /= vid_conheight; if(vlen(v) > 0.01) @@ -762,6 +772,50 @@ void UpdateCrosshair() wcross_alpha_prev = wcross_alpha; wcross_color_prev = wcross_color; + if(!vaporizer_delay) + vaporizer_delay = 0.75; + + entity me = playerslots[player_localnum]; + + if(activeweapon == WEP_VAPORIZER) + if(time >= getstatf(STAT_GAMESTARTTIME)) + if(time >= getstatf(STAT_ROUNDSTARTTIME)) + if(!autocvar_chase_active) + if(!getstatf(STAT_FROZEN)) + if(!spectatee_status) + if(button_attack) + if(autocvar_cl_vaporizerbeam || (me.ping >= 200 && autocvar_cl_vaporizerbeam != -1)) + if(time >= last_beam) + { + string s; + float hit = 0; + vector v = '0 0 1' * getstati(STAT_VIEWHEIGHT); + + s = strcat("TE_TEI_G3", Static_Team_ColorName_Upper(myteam)); + + WarpZone_TraceLine(view_origin, view_origin + v + view_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, world); + + if(trace_fraction < 1 && !trace_ent.entnum) + hit = 0; + else + hit = 1; + + if(hit) + s = strcat(s, "_HIT"); + + WarpZone_TrailParticles(self, particleeffectnum(s), view_origin, view_origin + v + view_forward * MAX_SHOT_DISTANCE); + + //sound(self, CH_SHOTS, "weapons/minstanexfire.wav", VOL_BASE, ATTEN_LARGE); + + if(!(trace_dphitq3surfaceflags & (Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT))) + if(autocvar_cl_particles_newvortexbeam && (getstati(STAT_ALLOW_OLDVORTEXBEAM) || isdemo())) + pointparticles(particleeffectnum("nex_impact_new"), trace_endpos, '0 0 0', 1); + else + pointparticles(particleeffectnum("nex_impact"), trace_endpos, '0 0 0', 1); + + last_beam = time + vaporizer_delay; + } + wcross_scale *= 1 - autocvar__menu_alpha; wcross_alpha *= 1 - autocvar__menu_alpha; wcross_size = draw_getimagesize(wcross_name) * wcross_scale; @@ -963,17 +1017,34 @@ void UpdateCrosshair() } } +float fps_start; +float fps_frames; + + +void FPSCounter_Update() +{ + if(time - fps_start >= 1) { + cl_fps = fps_frames; + fps_frames = 0; + fps_start = time; + } + + fps_frames++; +} + #define BUTTON_3 4 #define BUTTON_4 8 float cl_notice_run(); float prev_myteam; +float oldfov; +.float health; void CSQC_UpdateView(float w, float h) { entity e; float fov; float f, i; vector vf_size, vf_min; - float a; + float pink_noise; execute_next_frame(); @@ -993,6 +1064,7 @@ void CSQC_UpdateView(float w, float h) else view_quality = 1; + button_attack = (input_buttons & 1); button_attack2 = (input_buttons & BUTTON_3); button_zoom = (input_buttons & BUTTON_4); @@ -1050,18 +1122,41 @@ void CSQC_UpdateView(float w, float h) // event chase camera if(autocvar_chase_active <= 0) // greater than 0 means it's enabled manually, and this code is skipped { - if(WantEventchase()) + float ons_roundlost = (gametype == MAPINFO_TYPE_ONSLAUGHT && getstati(STAT_ROUNDLOST)); + float vehicle_chase = (hud != HUD_NORMAL && (autocvar_cl_eventchase_vehicle || spectatee_status > 0)); + entity gen = world; + + if(ons_roundlost) + { + entity e; + for(e = world; (e = find(e, classname, "onslaught_generator")); ) + { + if(e.health <= 0) + { + gen = e; + break; + } + } + if(!gen) + ons_roundlost = FALSE; // don't enforce the 3rd person camera if there is no dead generator to show + } + if(WantEventchase() || (!autocvar_cl_orthoview && ons_roundlost)) { eventchase_running = TRUE; // make special vector since we can't use view_origin (It is one frame old as of this code, it gets set later with the results this code makes.) vector current_view_origin = (csqcplayer ? csqcplayer.origin : pmove_org); + if(ons_roundlost) { current_view_origin = gen.origin; } // detect maximum viewoffset and use it - if(autocvar_cl_eventchase_viewoffset) + vector view_offset = autocvar_cl_eventchase_viewoffset; + if(vehicle_chase && autocvar_cl_eventchase_vehicle_viewoffset) { view_offset = autocvar_cl_eventchase_vehicle_viewoffset; } + if(ons_roundlost) { view_offset = autocvar_cl_eventchase_generator_viewoffset; } + + if(view_offset) { - WarpZone_TraceLine(current_view_origin, current_view_origin + autocvar_cl_eventchase_viewoffset + ('0 0 1' * autocvar_cl_eventchase_maxs_z), MOVE_WORLDONLY, self); - if(trace_fraction == 1) { current_view_origin += autocvar_cl_eventchase_viewoffset; } + WarpZone_TraceLine(current_view_origin, current_view_origin + view_offset + ('0 0 1' * autocvar_cl_eventchase_maxs_z), MOVE_WORLDONLY, self); + if(trace_fraction == 1) { current_view_origin += view_offset; } else { current_view_origin_z += max(0, (trace_endpos_z - current_view_origin_z) - autocvar_cl_eventchase_maxs_z); } } @@ -1071,10 +1166,14 @@ void CSQC_UpdateView(float w, float h) if(!autocvar_chase_active) { cvar_set("chase_active", "-1"); } // make the camera smooth back - if(autocvar_cl_eventchase_speed && eventchase_current_distance < autocvar_cl_eventchase_distance) - eventchase_current_distance += autocvar_cl_eventchase_speed * (autocvar_cl_eventchase_distance - eventchase_current_distance) * frametime; // slow down the further we get - else if(eventchase_current_distance != autocvar_cl_eventchase_distance) - eventchase_current_distance = autocvar_cl_eventchase_distance; + float chase_distance = autocvar_cl_eventchase_distance; + if(vehicle_chase && autocvar_cl_eventchase_vehicle_distance) { chase_distance = autocvar_cl_eventchase_vehicle_distance; } + if(ons_roundlost) { chase_distance = autocvar_cl_eventchase_generator_distance; } + + if(autocvar_cl_eventchase_speed && eventchase_current_distance < chase_distance) + eventchase_current_distance += autocvar_cl_eventchase_speed * (chase_distance - eventchase_current_distance) * frametime; // slow down the further we get + else if(eventchase_current_distance != chase_distance) + eventchase_current_distance = chase_distance; makevectors(view_angles); @@ -1108,7 +1207,7 @@ void CSQC_UpdateView(float w, float h) } // do lockview after event chase camera so that it still applies whenever necessary. - if(autocvar_cl_lockview || (!autocvar_hud_cursormode && (autocvar__hud_configure && spectatee_status <= 0 || intermission > 1))) + if(autocvar_cl_lockview || (!autocvar_hud_cursormode && (autocvar__hud_configure && spectatee_status <= 0 || intermission > 1 || HUD_QuickMenu_IsOpened()))) { setproperty(VF_ORIGIN, freeze_org); setproperty(VF_ANGLES, freeze_ang); @@ -1230,6 +1329,13 @@ void CSQC_UpdateView(float w, float h) Announcer(); fov = autocvar_fov; + + if(fov != oldfov) + { + localcmd(strcat("\ncmd report fov ", ftos(fov), "\n")); + oldfov = fov; + } + if(fov <= 59.5) { if(!zoomscript_caught) @@ -1311,11 +1417,50 @@ void CSQC_UpdateView(float w, float h) vid_conheight = autocvar_vid_conheight; vid_pixelheight = autocvar_vid_pixelheight; + if(spectatee_status <= 0) + fovlock = -1; + if(autocvar_cl_orthoview) { setproperty(VF_FOV, GetOrthoviewFOV(ov_worldmin, ov_worldmax, ov_mid, ov_org)); } + else if(fovlock > 0 && autocvar_cl_specfov) { setproperty(VF_FOV, GetCurrentFov(fovlock)); } else { setproperty(VF_FOV, GetCurrentFov(fov)); } - // Camera for demo playback - if(camera_active) + float tempcamera = FALSE; + + if(gametype == MAPINFO_TYPE_JAILBREAK) + if(spectatee_status >= 0) + if(getstati(STAT_ROUNDLOST)) + if(!getstati(STAT_PRISONED)) + tempcamera = TRUE; + + if(tempcamera) + { + if(!camera_drawviewmodel_locked) + { + camera_drawviewmodel_locked = TRUE; + camera_drawviewmodel_backup = autocvar_r_drawviewmodel; + } + + entity cam = world, e; + for(e = world; (e = find(e, classname, "jailcamera")); ) + if(e.jb_camera_active) + { + cam = e; + break; // found our camera + } + + if(cam) + { + setproperty(VF_ORIGIN, cam.origin); + setproperty(VF_ANGLES, cam.angles); + cvar_settemp("r_drawviewmodel", "0"); + } + } + else if(camera_drawviewmodel_locked) + { + camera_drawviewmodel_locked = FALSE; + cvar_set("r_drawviewmodel", ftos(camera_drawviewmodel_backup)); + } + else if(camera_active) // Camera for demo playback { if(autocvar_camera_enable) CSQC_Demo_Camera(); @@ -1389,7 +1534,7 @@ void CSQC_UpdateView(float w, float h) drawfill('0 0 0', autocvar_vid_conwidth * '1 0 0' + autocvar_vid_conheight * '0 1 0', '0.5 1 0.3', 1, DRAWFLAG_MODULATE); // draw BG - a = Noise_Pink(nightvision_noise, frametime * 1.5) * 0.05 + 0.15; + pink_noise = Noise_Pink(nightvision_noise, frametime * 1.5) * 0.05 + 0.15; rgb = '1 1 1'; tc_00 = '0 0 0' + '0.2 0 0' * sin(time * 0.3) + '0 0.3 0' * cos(time * 0.7); tc_01 = '0 2.25 0' + '0.6 0 0' * cos(time * 1.2) - '0 0.3 0' * sin(time * 2.2); @@ -1397,24 +1542,24 @@ void CSQC_UpdateView(float w, float h) //tc_11 = '1 1 0' + '0.6 0 0' * sin(time * 0.6) + '0 0.3 0' * cos(time * 0.1); tc_11 = tc_01 + tc_10 - tc_00; R_BeginPolygon("gfx/nightvision-bg.tga", DRAWFLAG_ADDITIVE); - R_PolygonVertex('0 0 0', tc_00, rgb, a); - R_PolygonVertex(autocvar_vid_conwidth * '1 0 0', tc_10, rgb, a); - R_PolygonVertex(autocvar_vid_conwidth * '1 0 0' + autocvar_vid_conheight * '0 1 0', tc_11, rgb, a); - R_PolygonVertex(autocvar_vid_conheight * '0 1 0', tc_01, rgb, a); + R_PolygonVertex('0 0 0', tc_00, rgb, pink_noise); + R_PolygonVertex(autocvar_vid_conwidth * '1 0 0', tc_10, rgb, pink_noise); + R_PolygonVertex(autocvar_vid_conwidth * '1 0 0' + autocvar_vid_conheight * '0 1 0', tc_11, rgb, pink_noise); + R_PolygonVertex(autocvar_vid_conheight * '0 1 0', tc_01, rgb, pink_noise); R_EndPolygon(); // draw FG - a = Noise_Pink(nightvision_noise2, frametime * 0.1) * 0.05 + 0.12; + pink_noise = Noise_Pink(nightvision_noise2, frametime * 0.1) * 0.05 + 0.12; rgb = '0.3 0.6 0.4' + '0.1 0.4 0.2' * Noise_White(nightvision_noise2, frametime); tc_00 = '0 0 0' + '1 0 0' * Noise_White(nightvision_noise2, frametime) + '0 1 0' * Noise_White(nightvision_noise2, frametime); tc_01 = tc_00 + '0 3 0' * (1 + Noise_White(nightvision_noise2, frametime) * 0.2); tc_10 = tc_00 + '2 0 0' * (1 + Noise_White(nightvision_noise2, frametime) * 0.3); tc_11 = tc_01 + tc_10 - tc_00; R_BeginPolygon("gfx/nightvision-fg.tga", DRAWFLAG_ADDITIVE); - R_PolygonVertex('0 0 0', tc_00, rgb, a); - R_PolygonVertex(autocvar_vid_conwidth * '1 0 0', tc_10, rgb, a); - R_PolygonVertex(autocvar_vid_conwidth * '1 0 0' + autocvar_vid_conheight * '0 1 0', tc_11, rgb, a); - R_PolygonVertex(autocvar_vid_conheight * '0 1 0', tc_01, rgb, a); + R_PolygonVertex('0 0 0', tc_00, rgb, pink_noise); + R_PolygonVertex(autocvar_vid_conwidth * '1 0 0', tc_10, rgb, pink_noise); + R_PolygonVertex(autocvar_vid_conwidth * '1 0 0' + autocvar_vid_conheight * '0 1 0', tc_11, rgb, pink_noise); + R_PolygonVertex(autocvar_vid_conheight * '0 1 0', tc_01, rgb, pink_noise); R_EndPolygon(); } @@ -1657,9 +1802,11 @@ void CSQC_UpdateView(float w, float h) } // edge detection postprocess handling done second (used by hud_powerup) - float sharpen_intensity = 0, strength_finished = getstatf(STAT_STRENGTH_FINISHED), invincible_finished = getstatf(STAT_INVINCIBLE_FINISHED); - if (strength_finished - time > 0) { sharpen_intensity += (strength_finished - time); } - if (invincible_finished - time > 0) { sharpen_intensity += (invincible_finished - time); } + float sharpen_intensity = 0, buffs = getstati(STAT_BUFFS, 0, 24); + entity e; + for(e = Buff_Type_first; e; e = e.enemy) + if(buffs & e.items) + ++sharpen_intensity; sharpen_intensity = bound(0, ((getstati(STAT_HEALTH) > 0) ? sharpen_intensity : 0), 5); // Check to see if player is alive (if not, set 0) - also bound to fade out starting at 5 seconds. @@ -1740,20 +1887,17 @@ void CSQC_UpdateView(float w, float h) if(autocvar__hud_configure) HUD_Panel_Mouse(); + else if(HUD_QuickMenu_IsOpened()) + HUD_QuickMenu_Mouse(); + else if ( HUD_MinigameMenu_IsOpened() || minigame_isactive() ) + HUD_Minigame_Mouse(); + else + HUD_Radar_Mouse(); if(hud && !intermission) - { - if(hud == HUD_SPIDERBOT) - CSQC_SPIDER_HUD(); - else if(hud == HUD_WAKIZASHI) - CSQC_WAKIZASHI_HUD(); - else if(hud == HUD_RAPTOR) - CSQC_RAPTOR_HUD(); - else if(hud == HUD_BUMBLEBEE) - CSQC_BUMBLE_HUD(); - else if(hud == HUD_BUMBLEBEE_GUN) - CSQC_BUMBLE_GUN_HUD(); - } + VEH_ACTION(hud, VR_HUD); + + FPSCounter_Update(); cl_notice_run(); diff --git a/qcsrc/client/announcer.qc b/qcsrc/client/announcer.qc index f49b847d6..e77a54431 100644 --- a/qcsrc/client/announcer.qc +++ b/qcsrc/client/announcer.qc @@ -1,5 +1,22 @@ float announcer_1min; float announcer_5min; +string AnnouncerOption() +{ + if(autocvar_cl_announcer_force || sv_announcer == "" || autocvar_cl_announcer != "default") { return autocvar_cl_announcer; } + + return sv_announcer; // use server side announcer if available +} + +void Announcer_Precache() +{ +#define MSG_ANNCE_NOTIF(default,name,channel,sound,volume,position) \ + precache_sound(sprintf("announcer/%s/%s.wav", AnnouncerOption(), sound)); + + MSG_ANNCE_NOTIFICATIONS + +#undef MSG_ANNCE_NOTIF +} + void Announcer_Countdown() { float starttime = getstatf(STAT_GAMESTARTTIME); diff --git a/qcsrc/client/autocvars.qh b/qcsrc/client/autocvars.qh index 1355bafe7..c799cb2b1 100644 --- a/qcsrc/client/autocvars.qh +++ b/qcsrc/client/autocvars.qh @@ -21,6 +21,7 @@ float autocvar_cl_allow_uid2name; string autocvar_cl_announcer; var float autocvar_cl_announcer_antispam = 2; var float autocvar_cl_announcer_maptime = 3; +float autocvar_cl_announcer_force; float autocvar_cl_autodemo_delete; float autocvar_cl_autodemo_delete_keeprecords; float autocvar_cl_casings; @@ -58,9 +59,11 @@ float autocvar_cl_orthoview; float autocvar_cl_orthoview_nofog; float autocvar_cl_particlegibs; float autocvar_cl_particles_oldvortexbeam; +float autocvar_cl_particles_newvortexbeam; float autocvar_cl_particles_quality; float autocvar_cl_projectiles_sloppy; float autocvar_cl_readpicture_force; +float autocvar_cl_nexuiz_hook; var float autocvar_cl_reticle = 1; var float autocvar_cl_reticle_normal_alpha = 1; var float autocvar_cl_reticle_weapon = 1; @@ -74,9 +77,6 @@ var float autocvar_cl_spawnzoom = 1; var float autocvar_cl_spawnzoom_speed = 1; var float autocvar_cl_spawnzoom_factor = 2; float autocvar_cl_stripcolorcodes; -var float autocvar_cl_vehicle_spiderbot_cross_alpha = 0.6; -var float autocvar_cl_vehicle_spiderbot_cross_size = 1; -var float autocvar_cl_vehicles_hud_tactical = 1; float autocvar_cl_velocityzoom_enabled; float autocvar_cl_velocityzoom_factor; var float autocvar_cl_velocityzoom_type = 3; @@ -145,6 +145,7 @@ float autocvar_crosshair_ring_reload; float autocvar_crosshair_ring_reload_alpha; float autocvar_crosshair_ring_reload_size; float autocvar_crosshair_size; +float autocvar_cl_vaporizerbeam; float autocvar_ekg; float autocvar_fov; float autocvar_g_balance_damagepush_speedfactor; @@ -300,15 +301,16 @@ float autocvar_hud_panel_powerups_baralign; float autocvar_hud_panel_powerups_flip; float autocvar_hud_panel_powerups_iconalign; float autocvar_hud_panel_powerups_progressbar; -float autocvar_hud_panel_buffs; +var float autocvar_hud_panel_buffs = 1; //float autocvar_hud_panel_buffs_iconalign; -string autocvar_hud_panel_powerups_progressbar_shield; -string autocvar_hud_panel_powerups_progressbar_strength; string autocvar_hud_panel_powerups_progressbar_superweapons; float autocvar_hud_panel_powerups_text; float autocvar_hud_panel_pressedkeys; float autocvar_hud_panel_pressedkeys_aspect; float autocvar_hud_panel_pressedkeys_attack; +float autocvar_hud_panel_quickmenu_translatecommands; +var string autocvar_hud_panel_quickmenu_file = "quickmenu.txt"; +var float autocvar_hud_panel_quickmenu_time = 15; float autocvar_hud_panel_racetimer; float autocvar_hud_panel_radar; float autocvar_hud_panel_radar_foreground_alpha; @@ -333,7 +335,7 @@ float autocvar_hud_panel_weapons_ammo; float autocvar_hud_panel_weapons_ammo_alpha; string autocvar_hud_panel_weapons_ammo_color; float autocvar_hud_panel_weapons_ammo_full_cells; -float autocvar_hud_panel_weapons_ammo_full_plasma; +var float autocvar_hud_panel_weapons_ammo_full_plasma = 180; float autocvar_hud_panel_weapons_ammo_full_fuel; float autocvar_hud_panel_weapons_ammo_full_nails; float autocvar_hud_panel_weapons_ammo_full_rockets; @@ -355,6 +357,8 @@ float autocvar_hud_panel_weapons_timeout_fadebgmin; float autocvar_hud_panel_weapons_timeout_fadefgmin; var float autocvar_hud_panel_weapons_timeout_speed_in = 0.25; var float autocvar_hud_panel_weapons_timeout_speed_out = 0.75; +//float autocvar_hud_panel_quickmenu; +float autocvar_hud_panel_quickmenu_align; vector autocvar_hud_progressbar_acceleration_color; vector autocvar_hud_progressbar_acceleration_neg_color; float autocvar_hud_progressbar_alpha; @@ -362,9 +366,7 @@ vector autocvar_hud_progressbar_armor_color; vector autocvar_hud_progressbar_fuel_color; vector autocvar_hud_progressbar_health_color; vector autocvar_hud_progressbar_nexball_color; -vector autocvar_hud_progressbar_shield_color; vector autocvar_hud_progressbar_speed_color; -vector autocvar_hud_progressbar_strength_color; vector autocvar_hud_progressbar_superweapons_color; float autocvar_hud_showbinds; float autocvar_hud_showbinds_limit; @@ -393,10 +395,10 @@ string autocvar_menu_skin; float autocvar_r_fakelight; float autocvar_r_fullbright; float autocvar_r_letterbox; -float autocvar_scoreboard_accuracy; -float autocvar_scoreboard_accuracy_doublerows; +var float autocvar_scoreboard_accuracy = 1; float autocvar_scoreboard_accuracy_nocolors; float autocvar_scoreboard_alpha_bg; +var float autocvar_scoreboard_extras = 1; var float autocvar_scoreboard_alpha_fg = 1.0; var float autocvar_scoreboard_alpha_name = 0.9; var float autocvar_scoreboard_alpha_name_self = 1; @@ -422,17 +424,22 @@ float autocvar_vid_conwidth; float autocvar_vid_pixelheight; float autocvar_viewsize; float autocvar_cl_hitsound; -var float autocvar_cl_hitsound_min_pitch = 0.75; // minimal difference in minsta +var float autocvar_cl_hitsound_min_pitch = 0.75; var float autocvar_cl_hitsound_max_pitch = 1.5; var float autocvar_cl_hitsound_nom_damage = 25; float autocvar_cl_hitsound_antispam_time; var float autocvar_cl_eventchase_death = 1; var float autocvar_cl_eventchase_nexball = 1; +float autocvar_cl_eventchase_vehicle; var float autocvar_cl_eventchase_distance = 140; var float autocvar_cl_eventchase_speed = 1.3; var vector autocvar_cl_eventchase_maxs = '12 12 8'; var vector autocvar_cl_eventchase_mins = '-12 -12 -8'; var vector autocvar_cl_eventchase_viewoffset = '0 0 20'; +var vector autocvar_cl_eventchase_vehicle_viewoffset = '0 0 80'; +var float autocvar_cl_eventchase_vehicle_distance = 250; +var vector autocvar_cl_eventchase_generator_viewoffset = '0 0 80'; +var float autocvar_cl_eventchase_generator_distance = 400; float autocvar_cl_lerpexcess; string autocvar__togglezoom; float autocvar_cl_damageeffect; @@ -449,6 +456,7 @@ float autocvar_cl_loddistance2 = 2048; float autocvar_cl_forceplayermodels; float autocvar_cl_forceplayercolors; string autocvar_cl_forcemyplayermodel; +float autocvar_cl_forcemyplayermodel_always; float autocvar_cl_forcemyplayerskin; float autocvar_cl_forcemyplayercolors; float autocvar__cl_color; @@ -457,6 +465,11 @@ string autocvar__cl_playermodel; float autocvar_cl_deathglow; float autocvar_developer_csqcentities; float autocvar_g_jetpack_attenuation; +var float autocvar_cl_conquest_capture_ring_size_min = 32; +var float autocvar_cl_conquest_capture_ring_size_max = 128; +float autocvar_cl_conquest_capture_ring_hud; +float autocvar_cl_conquest_capture_text_align; +float autocvar_cl_showspectators; var string autocvar_crosshair_hmg = ""; var vector autocvar_crosshair_hmg_color = '0.2 1.0 0.2'; var float autocvar_crosshair_hmg_alpha = 1; @@ -465,4 +478,7 @@ var string autocvar_crosshair_rpc = ""; var vector autocvar_crosshair_rpc_color = '0.2 1.0 0.2'; var float autocvar_crosshair_rpc_alpha = 1; var float autocvar_crosshair_rpc_size = 1; -float autocvar_cl_nade_timer; +float autocvar_r_drawviewmodel; +float autocvar_cl_crouch; +var float autocvar_cl_nade_timer = 1; +float autocvar_cl_specfov; // disable by default? diff --git a/qcsrc/client/command/cl_cmd.qc b/qcsrc/client/command/cl_cmd.qc index 9aae77dde..ff1dd763d 100644 --- a/qcsrc/client/command/cl_cmd.qc +++ b/qcsrc/client/command/cl_cmd.qc @@ -238,6 +238,24 @@ void LocalCommand_hud(float request, float argc) return; } + case "quickmenu": + { + if(HUD_QuickMenu_IsOpened()) + HUD_QuickMenu_Close(); + else + HUD_QuickMenu_Open(); + return; + } + + case "minigame": + { + if(HUD_MinigameMenu_IsOpened()) + HUD_MinigameMenu_Close(); + else + HUD_MinigameMenu_Open(); + return; + } + case "save": { if(argv(2)) @@ -266,9 +284,15 @@ void LocalCommand_hud(float request, float argc) case "radar": { if(argv(2)) - hud_panel_radar_maximized = InterpretBoolean(argv(2)); + HUD_Radar_Show_Maximized(InterpretBoolean(argv(2)),0); else - hud_panel_radar_maximized = !hud_panel_radar_maximized; + HUD_Radar_Show_Maximized(!hud_panel_radar_maximized,0); + return; + } + + case "clickradar": + { + HUD_Radar_Show_Maximized(!hud_panel_radar_mouse,1); return; } } @@ -283,7 +307,7 @@ void LocalCommand_hud(float request, float argc) print(" 'configname' is the name to save to for \"save\" action,\n"); print(" 'radartoggle' is to control hud_panel_radar_maximized for \"radar\" action,\n"); print(" and 'layout' is how to organize the scoreboard columns for the set action.\n"); - print(" Full list of commands here: \"configure, save, scoreboard_columns_help, scoreboard_columns_set, radar.\"\n"); + print(" Full list of commands here: \"configure, quickmenu, save, scoreboard_columns_help, scoreboard_columns_set, radar.\"\n"); return; } } diff --git a/qcsrc/client/conquest.qc b/qcsrc/client/conquest.qc new file mode 100644 index 000000000..839709238 --- /dev/null +++ b/qcsrc/client/conquest.qc @@ -0,0 +1,149 @@ +.float cq_factor; +void conquest_controlpoint_draw2d() +{ + float dist = vlen(self.origin - view_origin); + + if(self.cq_capdistance > 0) + if(dist > self.cq_capdistance) + return; + + string img = "gfx/crosshair_ring.tga"; + vector psize = draw_getimagesize(img); + vector loc = project_3d_to_2d(self.origin + '0 0 128'); // - 0.5 * psize; + vector textloc = loc; + if(!(loc_z < 0 || loc_x < 0 || loc_y < 0 || loc_x > vid_conwidth || loc_y > vid_conheight) || autocvar_cl_conquest_capture_ring_hud) + { + loc_z = psize_z = textloc_z = 0; + float imgsize = autocvar_cl_conquest_capture_ring_size_min; + vector textsize; + if(autocvar_cl_conquest_capture_ring_hud) + { + loc = eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight; + textloc = eY * 0.64 * vid_conheight; + textsize = eX * vid_conwidth + eY * 0.025 * vid_conheight; + imgsize = 0.1 * vid_conheight; + } + else + { + imgsize = max(autocvar_cl_conquest_capture_ring_size_min, (1 - dist / self.cq_capdistance) * autocvar_cl_conquest_capture_ring_size_max); + textsize = '4 2 0' * autocvar_cl_conquest_capture_ring_size_min; + if(autocvar_cl_conquest_capture_text_align == 1) + textloc_x -= textsize_x / 2; + else if(autocvar_cl_conquest_capture_text_align == 2) + textloc_x -= textsize_x; + } + + //drawstring_aspect(eY * 0.64 * vid_conheight, _("Capture progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL); + float player_team = (GetPlayerColor(player_localnum) + 1); + string msg = ""; + if(self.team == player_team) + { + switch(self.cq_status) + { + default: + case CP_NEUTRAL: msg = sprintf(_("Capturing %s"), self.netname); break; + case CP_CAPTURED: msg = sprintf(_("Defending %s"), self.netname); break; + } + } + else if(spectatee_status) + msg = sprintf(_("Observing %s"), self.netname); + else if(self.team && self.cq_status == CP_CAPTURED) + msg = sprintf(_("Liberating %s"), self.netname); + + if(msg != "") + { + drawstring_aspect(textloc, msg, textsize, '1 1 1', 1, DRAWFLAG_NORMAL); + DrawCircleClippedPic(loc, imgsize, img, self.cq_factor, self.teamradar_color, 1, DRAWFLAG_ADDITIVE); + } + } +} + +.float cq_rate; +void conquest_controlpoint_predraw() +{ + self.enemy.angles_y += 32 * frametime; + float step = self.cq_rate * frametime; + if ( self.cq_factor < self.health - step/2 ) + self.cq_factor += step; + else if ( self.cq_factor > self.health + step/2 ) + self.cq_factor -= step; + setorigin(self.enemy, self.origin + '0 0 1' * (50 + (self.cq_factor * 140))); +} + +void conquest_controlpoint_remove() +{ + if(self.enemy) + remove(self.enemy); + + if(self.netname) + strunzone(self.netname); +} + +void conquest_controlpoint_read(float bIsNew) +{ + float sf = ReadByte(); + + if(sf & CQSF_SETUP) + { + self.origin_x = ReadCoord(); + self.origin_y = ReadCoord(); + self.origin_z = ReadCoord(); + setorigin(self, self.origin); + self.drawmask = MASK_NORMAL; + + self.angles_y = ReadCoord(); + + self.cq_capdistance = ReadLong(); + + precache_model("models/conquest/stand.md3"); + setmodel(self, "models/conquest/stand.md3"); + setsize(self, CQ_CP_MIN, CQ_CP_MAX); + self.predraw = conquest_controlpoint_predraw; + self.draw2d = conquest_controlpoint_draw2d; + self.cq_rate = 0.2; + + self.teamradar_icon = RADARICON_CONTROLPOINT; + + if(!self.enemy) + self.enemy = spawn(); + + precache_model("models/conquest/flag.md3"); + setmodel(self.enemy, "models/conquest/flag.md3"); + self.enemy.drawmask = MASK_NORMAL; + self.enemy.scale = 1.5; + self.entremove = conquest_controlpoint_remove; + } + + if(sf & CQSF_STATE) + { + self.cq_status = ReadByte(); + } + + if(sf & CQSF_TEAM) + { + self.team = ReadByte(); + self.teamradar_color = Team_ColorRGB(self.team - 1); + + if(self.cq_status == CP_CAPTURED) + self.enemy.colormap = self.colormap = 1024 + (self.team - 1) * 17; + else + self.enemy.colormap = self.colormap = 1024 * 17; + } + + if(sf & CQSF_HEALTH) + { + float newhealth = ReadByte(); + if(newhealth != 0) { newhealth /= 255; } + self.health = newhealth; + self.cq_rate = ReadLong() / 100 * 0.2; + self.teamradar_color = Team_ColorRGB(self.team - 1) * self.health + '1 1 1' * (1-self.health); + } + + if(sf & CQSF_NAME) + { + if(self.netname) + strunzone(self.netname); + + self.netname = strzone(ReadString()); + } +} diff --git a/qcsrc/client/conquest.qh b/qcsrc/client/conquest.qh new file mode 100644 index 000000000..6cf1b7cbb --- /dev/null +++ b/qcsrc/client/conquest.qh @@ -0,0 +1,17 @@ +//csqc networking flags +#define CQSF_SETUP 1 //! Initial setup, responsible for communicating location, y-angle and model +#define CQSF_TEAM 2 //! What team point belong to +#define CQSF_HEALTH 4 //! Capture progress. Networked as 0--255 +#define CQSF_STATE 8 //! Captured or not +#define CQSF_NAME 16 //! Display name (can be defined by mapper) + +.float cq_status; +#define CP_NEUTRAL 1 +#define CP_CAPTURED 2 + +.float cq_capdistance; + +#define CQ_CP_MIN ('-35 -35 -3') +#define CQ_CP_MAX ('35 35 195') + +.float health; diff --git a/qcsrc/client/controlpoint.qc b/qcsrc/client/controlpoint.qc new file mode 100644 index 000000000..c01835c9f --- /dev/null +++ b/qcsrc/client/controlpoint.qc @@ -0,0 +1,210 @@ +float cpicon_precached; +.float count; +.float pain_finished; + +.float iscaptured; + +.vector cp_origin, cp_bob_origin; +.float cp_bob_spd; + +.vector cp_bob_dmg; + +.vector punchangle; + +.float max_health; + +.entity icon_realmodel; + +void cpicon_precache() +{ + if(cpicon_precached) + return; // already precached + + precache_model("models/onslaught/controlpoint_icon_dmg3.md3"); + precache_model("models/onslaught/controlpoint_icon_dmg2.md3"); + precache_model("models/onslaught/controlpoint_icon_dmg1.md3"); + precache_model("models/onslaught/controlpoint_icon.md3"); + + cpicon_precached = TRUE; +} + +void cpicon_draw() +{ + if(time < self.move_time) { return; } + + if(self.cp_bob_dmg_z > 0) + self.cp_bob_dmg_z = self.cp_bob_dmg_z - 3 * frametime; + else + self.cp_bob_dmg_z = 0; + self.cp_bob_origin_z = 4 * PI * (1 - cos(self.cp_bob_spd)); + self.cp_bob_spd = self.cp_bob_spd + 1.875 * frametime; + self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1)); + + if(!self.iscaptured) self.alpha = self.health / self.max_health; + + if(self.iscaptured) + { + if (self.punchangle_x > 0) + { + self.punchangle_x = self.punchangle_x - 60 * frametime; + if (self.punchangle_x < 0) + self.punchangle_x = 0; + } + else if (self.punchangle_x < 0) + { + self.punchangle_x = self.punchangle_x + 60 * frametime; + if (self.punchangle_x > 0) + self.punchangle_x = 0; + } + + if (self.punchangle_y > 0) + { + self.punchangle_y = self.punchangle_y - 60 * frametime; + if (self.punchangle_y < 0) + self.punchangle_y = 0; + } + else if (self.punchangle_y < 0) + { + self.punchangle_y = self.punchangle_y + 60 * frametime; + if (self.punchangle_y > 0) + self.punchangle_y = 0; + } + + if (self.punchangle_z > 0) + { + self.punchangle_z = self.punchangle_z - 60 * frametime; + if (self.punchangle_z < 0) + self.punchangle_z = 0; + } + else if (self.punchangle_z < 0) + { + self.punchangle_z = self.punchangle_z + 60 * frametime; + if (self.punchangle_z > 0) + self.punchangle_z = 0; + } + + self.angles_x = self.punchangle_x; + self.angles_y = self.punchangle_y + self.move_angles_y; + self.angles_z = self.punchangle_z; + self.move_angles_y = self.move_angles_y + 45 * frametime; + } + + setorigin(self, self.cp_origin + self.cp_bob_origin + self.cp_bob_dmg); +} + +void cpicon_damage(float hp) +{ + if(!self.iscaptured) { return; } + + if(hp < self.max_health * 0.25) + setmodel(self, "models/onslaught/controlpoint_icon_dmg3.md3"); + else if(hp < self.max_health * 0.50) + setmodel(self, "models/onslaught/controlpoint_icon_dmg2.md3"); + else if(hp < self.max_health * 0.75) + setmodel(self, "models/onslaught/controlpoint_icon_dmg1.md3"); + else if(hp <= self.max_health || hp >= self.max_health) + setmodel(self, "models/onslaught/controlpoint_icon.md3"); + + self.punchangle = (2 * randomvec() - '1 1 1') * 45; + + self.cp_bob_dmg_z = (2 * random() - 1) * 15; + self.pain_finished = time + 1; + self.colormod = '2 2 2'; + + setsize(self, CPICON_MIN, CPICON_MAX); +} + +void cpicon_construct() +{ + self.netname = "Control Point Icon"; + + setmodel(self, "models/onslaught/controlpoint_icon.md3"); + setsize(self, CPICON_MIN, CPICON_MAX); + + if(self.icon_realmodel == world) + { + self.icon_realmodel = spawn(); + setmodel(self.icon_realmodel, "null"); + setorigin(self.icon_realmodel, self.origin); + setsize(self.icon_realmodel, CPICON_MIN, CPICON_MAX); + self.icon_realmodel.movetype = MOVETYPE_NOCLIP; + self.icon_realmodel.solid = SOLID_NOT; + self.icon_realmodel.move_origin = self.icon_realmodel.origin; + } + + if(self.iscaptured) { self.icon_realmodel.solid = SOLID_BBOX; } + + self.move_movetype = MOVETYPE_NOCLIP; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NOCLIP; + self.move_origin = self.origin; + self.move_time = time; + self.drawmask = MASK_NORMAL; + self.alpha = 1; + self.draw = cpicon_draw; + self.cp_origin = self.origin; + self.cp_bob_origin = '0 0 0.1'; + self.cp_bob_spd = 0; +} + +.vector glowmod; +void cpicon_changeteam() +{ + if(self.team) + { + self.glowmod = Team_ColorRGB(self.team - 1); + self.teamradar_color = Team_ColorRGB(self.team - 1); + self.colormap = 1024 + (self.team - 1) * 17; + } + else + { + self.colormap = 1024; + self.glowmod = '1 1 0'; + self.teamradar_color = '1 1 0'; + } +} + +void ent_cpicon() +{ + float sf; + sf = ReadByte(); + + if(sf & CPSF_SETUP) + { + self.origin_x = ReadCoord(); + self.origin_y = ReadCoord(); + self.origin_z = ReadCoord(); + setorigin(self, self.origin); + + self.health = ReadByte(); + self.max_health = ReadByte(); + self.count = ReadByte(); + self.team = ReadByte(); + self.iscaptured = ReadByte(); + + if(!self.count) + self.count = (self.health - self.max_health) * frametime; + + cpicon_changeteam(); + cpicon_precache(); + cpicon_construct(); + } + + if(sf & CPSF_STATUS) + { + float _tmp; + _tmp = ReadByte(); + if(_tmp != self.team) + { + self.team = _tmp; + cpicon_changeteam(); + } + + _tmp = ReadByte(); + + if(_tmp != self.health) + cpicon_damage(_tmp); + + self.health = _tmp; + } +} diff --git a/qcsrc/client/controlpoint.qh b/qcsrc/client/controlpoint.qh new file mode 100644 index 000000000..5b18f86f2 --- /dev/null +++ b/qcsrc/client/controlpoint.qh @@ -0,0 +1,8 @@ +const vector CPICON_MIN = '-32 -32 -9'; +const vector CPICON_MAX = '32 32 25'; + +float CPSF_STATUS = 4; +float CPSF_SETUP = 8; + +void ent_cpicon(); +void cpicon_precache(); diff --git a/qcsrc/client/csqcmodel_hooks.qc b/qcsrc/client/csqcmodel_hooks.qc index 4cb416063..7d326f01d 100644 --- a/qcsrc/client/csqcmodel_hooks.qc +++ b/qcsrc/client/csqcmodel_hooks.qc @@ -187,7 +187,7 @@ void CSQCPlayer_ModelAppearance_Apply(float islocalplayer) else isfriend = islocalplayer; - if(autocvar_cl_forcemyplayermodel != "" && forceplayermodels_myisgoodmodel && isfriend) + if(autocvar_cl_forcemyplayermodel != "" && forceplayermodels_myisgoodmodel && (isfriend || autocvar_cl_forcemyplayermodel_always)) { self.model = forceplayermodels_mymodel; self.modelindex = forceplayermodels_mymodelindex; @@ -408,9 +408,9 @@ void CSQCModel_AutoTagIndex_Apply(void) if(self.tag_entity) { // the best part is: IT EXISTS - if(substring(self.model, 0, 17) == "models/weapons/v_") + if(substring(self.model, 0, 14) == "models/weapons") { - if(substring(self.tag_entity.model, 0, 17) == "models/weapons/h_") + if(substring(self.tag_entity.model, 0, 14) == "models/weapons") { self.tag_index = gettagindex(self.tag_entity, "weapon"); if(!self.tag_index) @@ -429,8 +429,28 @@ void CSQCModel_AutoTagIndex_Apply(void) self.tag_index = self.tag_entity.bone_weapon; } } + else if(substring(self.model, 0, 12) == "models/hats/") + { + if(substring(self.tag_entity.model, 0, 12) == "models/hats/") + { + self.tag_index = gettagindex(self.tag_entity, "tag_head"); + if(!self.tag_index) + self.tag_index = gettagindex(self.tag_entity, "head"); + if(!self.tag_index) + { + // we need to prevent this from 'appening + self.tag_entity = world; + self.drawmask = 0; + } + } + else if(self.tag_entity.isplayermodel) + { + skeleton_loadinfo(self.tag_entity); + self.tag_index = self.tag_entity.bone_hat; + } + } - if(substring(self.tag_entity.model, 0, 17) == "models/weapons/v_") + if(substring(self.tag_entity.model, 0, 14) == "models/weapons") { self.tag_index = gettagindex(self.tag_entity, "shot"); if(!self.tag_index) @@ -579,6 +599,8 @@ void CSQCModel_Effects_Apply(void) void CSQCPlayer_Precache() { precache_sound("misc/jetpack_fly.wav"); + if(autocvar_cl_forcemyplayermodel != "") + precache_model(autocvar_cl_forcemyplayermodel); } // general functions diff --git a/qcsrc/client/damage.qc b/qcsrc/client/damage.qc index 162ef6a07..e01659c4e 100644 --- a/qcsrc/client/damage.qc +++ b/qcsrc/client/damage.qc @@ -233,6 +233,11 @@ void Ent_DamageInfo(float isNew) sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_MIN); pointparticles(particleeffectnum("explosion_big"), self.origin, w_backoff * 1000, 1); break; + case DEATH_VH_TANK_DEATH: + case DEATH_VH_TANKLL48: + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_MIN); + pointparticles(particleeffectnum("explosion_big"), self.origin, w_backoff * 1000, 1); + break; case DEATH_VH_RAPT_CANNON: sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTEN_NORM); @@ -310,7 +315,7 @@ void Ent_DamageInfo(float isNew) pointparticles(particleeffectnum("electro_impact"), self.origin, w_backoff * 1000, 1); break; - case DEATH_TURRET_WALK_MEELE: + case DEATH_TURRET_WALK_MELEE: sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTEN_MIN); pointparticles(particleeffectnum("TE_SPARK"), self.origin, w_backoff * 1000, 1); break; diff --git a/qcsrc/client/generator.qc b/qcsrc/client/generator.qc new file mode 100644 index 000000000..4ee798f01 --- /dev/null +++ b/qcsrc/client/generator.qc @@ -0,0 +1,250 @@ +float generator_precached; +.float count; +.float max_health; + +void generator_precache() +{ + if(generator_precached) + return; // already precached + + precache_model("models/onslaught/generator.md3"); + precache_model("models/onslaught/generator_dead.md3"); + precache_model("models/onslaught/generator_dmg1.md3"); + precache_model("models/onslaught/generator_dmg2.md3"); + precache_model("models/onslaught/generator_dmg3.md3"); + precache_model("models/onslaught/generator_dmg4.md3"); + precache_model("models/onslaught/generator_dmg5.md3"); + precache_model("models/onslaught/generator_dmg6.md3"); + precache_model("models/onslaught/generator_dmg7.md3"); + precache_model("models/onslaught/generator_dmg8.md3"); + precache_model("models/onslaught/generator_dmg9.md3"); + precache_model("models/onslaught/generator_dead.md3"); + + precache_model("models/onslaught/ons_ray.md3"); + precache_sound("onslaught/shockwave.wav"); + precache_sound("weapons/grenade_impact.wav"); + precache_sound("weapons/rocket_impact.wav"); + precache_sound("onslaught/electricity_explode.wav"); + + generator_precached = TRUE; +} + +void ons_generator_ray_draw() +{ + if(time < self.move_time) + return; + + self.move_time = time + 0.05; + + if(self.count > 10) + { + remove(self); + return; + } + + if(self.count > 5) + self.alpha -= 0.1; + else + self.alpha += 0.1; + + self.scale += 0.2; + self.count +=1; +} + +void ons_generator_ray_spawn(vector org) +{ + entity e; + e = spawn(); + e.classname = "ons_ray"; + setmodel(e, "models/onslaught/ons_ray.md3"); + setorigin(e, org); + e.angles = randomvec() * 360; + e.move_origin = org; + e.movetype = MOVETYPE_NONE; + e.alpha = 0; + e.scale = random() * 5 + 8; + e.move_time = time + 0.05; + e.drawmask = MASK_NORMAL; + e.draw = ons_generator_ray_draw; +} + +void generator_draw() +{ + if(time < self.move_time) + return; + + if(self.health > 0) + { + // damaged fx (less probable the more damaged is the generator) + if(random() < 0.9 - self.health / self.max_health) + if(random() < 0.01) + { + pointparticles(particleeffectnum("electro_ballexplode"), self.origin + randompos('-50 -50 -20', '50 50 50'), '0 0 0', 1); + sound(self, CH_TRIGGER, "onslaught/electricity_explode.wav", VOL_BASE, ATTEN_NORM); + } + else + pointparticles(particleeffectnum("torch_small"), self.origin + randompos('-60 -60 -20', '60 60 60'), '0 0 0', 1); + + self.move_time = time + 0.1; + + return; + } + + if(self.count <= 0) + return; + + vector org; + float i; + + // White shockwave + if(self.count==40||self.count==20) + { + sound(self, CH_TRIGGER, "onslaught/shockwave.wav", VOL_BASE, ATTEN_NORM); + pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 6); + } + + // rays + if(random() > 0.25) + { + ons_generator_ray_spawn(self.origin); + } + + // Spawn fire balls + for(i=0;i < 10;++i) + { + org = self.origin + randompos('-30 -30 -30' * i + '0 0 -20', '30 30 30' * i + '0 0 20'); + pointparticles(particleeffectnum("onslaught_generator_gib_explode"), org, '0 0 0', 1); + } + + // Short explosion sound + small explosion + if(random() < 0.25) + { + te_explosion(self.origin); + sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM); + } + + // Particles + org = self.origin + randompos(self.mins + '8 8 8', self.maxs + '-8 -8 -8'); + pointparticles(particleeffectnum("onslaught_generator_smallexplosion"), org, '0 0 0', 1); + + // Final explosion + if(self.count==1) + { + org = self.origin; + te_explosion(org); + pointparticles(particleeffectnum("onslaught_generator_finalexplosion"), org, '0 0 0', 1); + sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); + } + + self.move_time = time + 0.05; + + self.count -= 1; +} + +void generator_damage(float hp) +{ + if(hp <= 0) + setmodel(self, "models/onslaught/generator_dead.md3"); + else if(hp < self.max_health * 0.10) + setmodel(self, "models/onslaught/generator_dmg9.md3"); + else if(hp < self.max_health * 0.20) + setmodel(self, "models/onslaught/generator_dmg8.md3"); + else if(hp < self.max_health * 0.30) + setmodel(self, "models/onslaught/generator_dmg7.md3"); + else if(hp < self.max_health * 0.40) + setmodel(self, "models/onslaught/generator_dmg6.md3"); + else if(hp < self.max_health * 0.50) + setmodel(self, "models/onslaught/generator_dmg5.md3"); + else if(hp < self.max_health * 0.60) + setmodel(self, "models/onslaught/generator_dmg4.md3"); + else if(hp < self.max_health * 0.70) + setmodel(self, "models/onslaught/generator_dmg3.md3"); + else if(hp < self.max_health * 0.80) + setmodel(self, "models/onslaught/generator_dmg2.md3"); + else if(hp < self.max_health * 0.90) + setmodel(self, "models/onslaught/generator_dmg1.md3"); + else if(hp <= self.max_health || hp >= self.max_health) + setmodel(self, "models/onslaught/generator.md3"); + + setsize(self, GENERATOR_MIN, GENERATOR_MAX); +} + +void generator_construct() +{ + self.netname = "Generator"; + self.classname = "onslaught_generator"; + + setorigin(self, self.origin); + setmodel(self, "models/onslaught/generator.md3"); + setsize(self, GENERATOR_MIN, GENERATOR_MAX); + + self.move_movetype = MOVETYPE_NOCLIP; + self.solid = SOLID_BBOX; + self.movetype = MOVETYPE_NOCLIP; + self.move_origin = self.origin; + self.move_time = time; + self.drawmask = MASK_NORMAL; + self.alpha = 1; + self.draw = generator_draw; +} + +.vector glowmod; +void generator_changeteam() +{ + if(self.team) + { + self.glowmod = Team_ColorRGB(self.team - 1); + self.teamradar_color = Team_ColorRGB(self.team - 1); + self.colormap = 1024 + (self.team - 1) * 17; + } + else + { + self.colormap = 1024; + self.glowmod = '1 1 0'; + self.teamradar_color = '1 1 0'; + } +} + +void ent_generator() +{ + float sf; + sf = ReadByte(); + + if(sf & GSF_SETUP) + { + self.origin_x = ReadCoord(); + self.origin_y = ReadCoord(); + self.origin_z = ReadCoord(); + setorigin(self, self.origin); + + self.health = ReadByte(); + self.max_health = ReadByte(); + self.count = ReadByte(); + self.team = ReadByte(); + + if(!self.count) + self.count = 40; + + generator_changeteam(); + generator_precache(); + generator_construct(); + } + + if(sf & GSF_STATUS) + { + float _tmp; + _tmp = ReadByte(); + if(_tmp != self.team) + { + self.team = _tmp; + generator_changeteam(); + } + + _tmp = ReadByte(); + + if(_tmp != self.health) + generator_damage(_tmp); + + self.health = _tmp; + } +} diff --git a/qcsrc/client/generator.qh b/qcsrc/client/generator.qh new file mode 100644 index 000000000..6ba1ce858 --- /dev/null +++ b/qcsrc/client/generator.qh @@ -0,0 +1,8 @@ +const vector GENERATOR_MIN = '-52 -52 -14'; +const vector GENERATOR_MAX = '52 52 75'; + +float GSF_STATUS = 4; +float GSF_SETUP = 8; + +void ent_generator(); +void generator_precache(); \ No newline at end of file diff --git a/qcsrc/client/hook.qc b/qcsrc/client/hook.qc index 8b2ffca19..1b5d83ec1 100644 --- a/qcsrc/client/hook.qc +++ b/qcsrc/client/hook.qc @@ -122,6 +122,9 @@ void Draw_GrapplingHook() { default: case ENT_CLIENT_HOOK: + string suffix = ""; + if(autocvar_cl_nexuiz_hook) + suffix = "_nexuiz"; intensity = 1; offset = 0; switch(t) @@ -132,6 +135,7 @@ void Draw_GrapplingHook() case NUM_TEAM_4: tex = "particles/hook_pink"; rgb = '1 0.3 1'; break; default: tex = "particles/hook_white"; rgb = getcsqcplayercolor(self.sv_entnum); break; } + if(suffix) { tex = strcat(tex, suffix); } break; case ENT_CLIENT_ARC_BEAM: // todo intensity = bound(0.2, 1 + Noise_Pink(self, frametime) * 1 + Noise_Burst(self, frametime, 0.03) * 0.3, 2); diff --git a/qcsrc/client/hud.qc b/qcsrc/client/hud.qc index 413f05cff..a8bd4ce65 100644 --- a/qcsrc/client/hud.qc +++ b/qcsrc/client/hud.qc @@ -766,6 +766,7 @@ void HUD_Weapons(void) case ammo_shells: ammo_full = autocvar_hud_panel_weapons_ammo_full_shells; break; case ammo_nails: ammo_full = autocvar_hud_panel_weapons_ammo_full_nails; break; case ammo_rockets: ammo_full = autocvar_hud_panel_weapons_ammo_full_rockets; break; + case ammo_supercells: case ammo_cells: ammo_full = autocvar_hud_panel_weapons_ammo_full_cells; break; case ammo_plasma: ammo_full = autocvar_hud_panel_weapons_ammo_full_plasma; break; case ammo_fuel: ammo_full = autocvar_hud_panel_weapons_ammo_full_fuel; break; @@ -1184,16 +1185,14 @@ void DrawNumIcon(vector myPos, vector mySize, float x, string icon, float vertic // void HUD_Powerups(void) { - float strength_time, shield_time, superweapons_time; + float superweapons_time; if(!autocvar__hud_configure) { if(!autocvar_hud_panel_powerups) return; if(spectatee_status == -1) return; - if(!(getstati(STAT_ITEMS, 0, 24) & (IT_STRENGTH | IT_INVINCIBLE | IT_SUPERWEAPON))) return; + if(!(getstati(STAT_ITEMS, 0, 24) & IT_SUPERWEAPON)) return; if (getstati(STAT_HEALTH) <= 0) return; - strength_time = bound(0, getstatf(STAT_STRENGTH_FINISHED) - time, 99); - shield_time = bound(0, getstatf(STAT_INVINCIBLE_FINISHED) - time, 99); superweapons_time = bound(0, getstatf(STAT_SUPERWEAPONS_FINISHED) - time, 99); if (getstati(STAT_ITEMS, 0, 24) & IT_UNLIMITED_SUPERWEAPONS) @@ -1205,8 +1204,6 @@ void HUD_Powerups(void) } else { - strength_time = 15; - shield_time = 27; superweapons_time = 13; } @@ -1218,7 +1215,7 @@ void HUD_Powerups(void) pos = panel_pos; mySize = panel_size; - HUD_Panel_DrawBg(bound(0, max(strength_time, shield_time, superweapons_time), 1)); + HUD_Panel_DrawBg(bound(0, superweapons_time, 1)); if(panel_bg_padding) { pos += '1 1 0' * panel_bg_padding; @@ -1227,139 +1224,39 @@ void HUD_Powerups(void) float panel_ar = mySize_x/mySize_y; float is_vertical = (panel_ar < 1); - vector shield_offset = '0 0 0', strength_offset = '0 0 0', superweapons_offset = '0 0 0'; + vector superweapons_offset = '0 0 0'; float superweapons_is = -1; - if(superweapons_time) - { - if(strength_time) - { - if(shield_time) - superweapons_is = 0; - else - superweapons_is = 2; - } - else - { - if(shield_time) - superweapons_is = 1; - else - superweapons_is = 2; - } - } + if(superweapons_time) { superweapons_is = 2; } // FIXME handle superweapons here - if(superweapons_is == 0) + if (panel_ar >= 4 || (panel_ar >= 1/4 && panel_ar < 1)) { - if (panel_ar >= 4 || (panel_ar >= 1/4 && panel_ar < 1)) - { - mySize_x *= (1.0 / 3.0); - superweapons_offset_x = mySize_x; - if (autocvar_hud_panel_powerups_flip) - shield_offset_x = 2*mySize_x; - else - strength_offset_x = 2*mySize_x; - } - else - { - mySize_y *= (1.0 / 3.0); - superweapons_offset_y = mySize_y; - if (autocvar_hud_panel_powerups_flip) - shield_offset_y = 2*mySize_y; - else - strength_offset_y = 2*mySize_y; - } + mySize_x *= 0.5; } else { - if (panel_ar >= 4 || (panel_ar >= 1/4 && panel_ar < 1)) - { - mySize_x *= 0.5; - if (autocvar_hud_panel_powerups_flip) - shield_offset_x = mySize_x; - else - strength_offset_x = mySize_x; - } - else - { - mySize_y *= 0.5; - if (autocvar_hud_panel_powerups_flip) - shield_offset_y = mySize_y; - else - strength_offset_y = mySize_y; - } + mySize_y *= 0.5; } - float shield_baralign, strength_baralign, superweapons_baralign; - float shield_iconalign, strength_iconalign, superweapons_iconalign; + float superweapons_baralign; + float superweapons_iconalign; if (autocvar_hud_panel_powerups_flip) { - strength_baralign = (autocvar_hud_panel_powerups_baralign == 2 || autocvar_hud_panel_powerups_baralign == 1); - shield_baralign = (autocvar_hud_panel_powerups_baralign == 3 || autocvar_hud_panel_powerups_baralign == 1); - strength_iconalign = (autocvar_hud_panel_powerups_iconalign == 2 || autocvar_hud_panel_powerups_iconalign == 1); - shield_iconalign = (autocvar_hud_panel_powerups_iconalign == 3 || autocvar_hud_panel_powerups_iconalign == 1); + superweapons_baralign = (autocvar_hud_panel_powerups_baralign == 3 || autocvar_hud_panel_powerups_baralign == 1); + superweapons_iconalign = (autocvar_hud_panel_powerups_iconalign == 3 || autocvar_hud_panel_powerups_iconalign == 1); } else { - shield_baralign = (autocvar_hud_panel_powerups_baralign == 2 || autocvar_hud_panel_powerups_baralign == 1); - strength_baralign = (autocvar_hud_panel_powerups_baralign == 3 || autocvar_hud_panel_powerups_baralign == 1); - shield_iconalign = (autocvar_hud_panel_powerups_iconalign == 2 || autocvar_hud_panel_powerups_iconalign == 1); - strength_iconalign = (autocvar_hud_panel_powerups_iconalign == 3 || autocvar_hud_panel_powerups_iconalign == 1); - } - - if(superweapons_is == 0) - { - superweapons_iconalign = strength_iconalign; - superweapons_baralign = 2; - } - else if(superweapons_is == 1) - { - superweapons_offset = strength_offset; - superweapons_iconalign = strength_iconalign; - superweapons_baralign = strength_baralign; - } - else // if(superweapons_is == 2) - { - superweapons_offset = shield_offset; - superweapons_iconalign = shield_iconalign; - superweapons_baralign = shield_baralign; - } - - if(shield_time) - { - const float maxshield = 30; - float shield = ceil(shield_time); - if(autocvar_hud_panel_powerups_progressbar) - HUD_Panel_DrawProgressBar(pos + shield_offset, mySize, autocvar_hud_panel_powerups_progressbar_shield, shield/maxshield, is_vertical, shield_baralign, autocvar_hud_progressbar_shield_color, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL); - if(autocvar_hud_panel_powerups_text) - { - if(shield > 1) - DrawNumIcon(pos + shield_offset, mySize, shield, "shield", is_vertical, shield_iconalign, '1 1 1', 1); - if(shield <= 5) - DrawNumIcon_expanding(pos + shield_offset, mySize, shield, "shield", is_vertical, shield_iconalign, '1 1 1', 1, bound(0, (shield - shield_time) / 0.5, 1)); - } - } - - if(strength_time) - { - const float maxstrength = 30; - float strength = ceil(strength_time); - if(autocvar_hud_panel_powerups_progressbar) - HUD_Panel_DrawProgressBar(pos + strength_offset, mySize, autocvar_hud_panel_powerups_progressbar_strength, strength/maxstrength, is_vertical, strength_baralign, autocvar_hud_progressbar_strength_color, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL); - if(autocvar_hud_panel_powerups_text) - { - if(strength > 1) - DrawNumIcon(pos + strength_offset, mySize, strength, "strength", is_vertical, strength_iconalign, '1 1 1', 1); - if(strength <= 5) - DrawNumIcon_expanding(pos + strength_offset, mySize, strength, "strength", is_vertical, strength_iconalign, '1 1 1', 1, bound(0, (strength - strength_time) / 0.5, 1)); - } + superweapons_baralign = (autocvar_hud_panel_powerups_baralign == 2 || autocvar_hud_panel_powerups_baralign == 1); + superweapons_iconalign = (autocvar_hud_panel_powerups_iconalign == 2 || autocvar_hud_panel_powerups_iconalign == 1); } if(superweapons_time) { - const float maxsuperweapons = 30; + float maxsuperweapons = 30; float superweapons = ceil(superweapons_time); if(autocvar_hud_panel_powerups_progressbar) HUD_Panel_DrawProgressBar(pos + superweapons_offset, mySize, autocvar_hud_panel_powerups_progressbar_superweapons, superweapons/maxsuperweapons, is_vertical, superweapons_baralign, autocvar_hud_progressbar_superweapons_color, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL); @@ -1875,6 +1772,168 @@ void HUD_Timer(void) // Radar (#6) // + +float HUD_Radar_Clickable() +{ + return hud_panel_radar_mouse && !hud_panel_radar_temp_hidden; +} + +void HUD_Radar_Show_Maximized(float show,float clickable) +{ + hud_panel_radar_maximized = show; + hud_panel_radar_temp_hidden = 0; + + if ( show ) + { + if (clickable) + { + if(autocvar_hud_cursormode) + setcursormode(1); + hud_panel_radar_mouse = 1; + } + } + else if ( hud_panel_radar_mouse ) + { + hud_panel_radar_mouse = 0; + mouseClicked = 0; + if(autocvar_hud_cursormode) + if(!mv_active) + setcursormode(0); + } +} +void HUD_Radar_Hide_Maximized() +{ + HUD_Radar_Show_Maximized(false,false); +} + + +float HUD_Radar_InputEvent(float bInputType, float nPrimary, float nSecondary) +{ + if(!hud_panel_radar_maximized || !hud_panel_radar_mouse || + autocvar__hud_configure || mv_active) + return false; + + if(bInputType == 3) + { + mousepos_x = nPrimary; + mousepos_y = nSecondary; + return true; + } + + if(nPrimary == K_MOUSE1) + { + if(bInputType == 0) // key pressed + mouseClicked |= S_MOUSE1; + else if(bInputType == 1) // key released + mouseClicked -= (mouseClicked & S_MOUSE1); + } + else if(nPrimary == K_MOUSE2) + { + if(bInputType == 0) // key pressed + mouseClicked |= S_MOUSE2; + else if(bInputType == 1) // key released + mouseClicked -= (mouseClicked & S_MOUSE2); + } + else if ( nPrimary == K_ESCAPE && bInputType == 0 ) + { + HUD_Radar_Hide_Maximized(); + } + else + { + // allow console/use binds to work without hiding the map + string con_keys; + float keys; + float i; + con_keys = strcat(findkeysforcommand("toggleconsole", 0)," ",findkeysforcommand("+use", 0)) ; + keys = tokenize(con_keys); // findkeysforcommand returns data for this + for (i = 0; i < keys; ++i) + { + if(nPrimary == stof(argv(i))) + return false; + } + + if ( getstati(STAT_HEALTH) <= 0 ) + { + // Show scoreboard + if ( bInputType < 2 ) + { + con_keys = findkeysforcommand("+showscores", 0); + keys = tokenize(con_keys); + for (i = 0; i < keys; ++i) + { + if ( nPrimary == stof(argv(i)) ) + { + hud_panel_radar_temp_hidden = bInputType == 0; + return false; + } + } + } + } + else if ( bInputType == 0 ) + HUD_Radar_Hide_Maximized(); + + return false; + } + + return true; +} + +void HUD_Radar_Mouse() +{ + if ( !hud_panel_radar_mouse ) return; + if(mv_active) return; + + if ( intermission ) + { + HUD_Radar_Hide_Maximized(); + return; + } + + if(mouseClicked & S_MOUSE2) + { + HUD_Radar_Hide_Maximized(); + return; + } + + if(!autocvar_hud_cursormode) + { + mousepos = mousepos + getmousepos() * autocvar_menu_mouse_speed; + + mousepos_x = bound(0, mousepos_x, vid_conwidth); + mousepos_y = bound(0, mousepos_y, vid_conheight); + } + + HUD_Panel_UpdateCvars(); + + + panel_size = autocvar_hud_panel_radar_maximized_size; + panel_size_x = bound(0.2, panel_size_x, 1) * vid_conwidth; + panel_size_y = bound(0.2, panel_size_y, 1) * vid_conheight; + panel_pos_x = (vid_conwidth - panel_size_x) / 2; + panel_pos_y = (vid_conheight - panel_size_y) / 2; + + if(mouseClicked & S_MOUSE1) + { + // click outside + if ( mousepos_x < panel_pos_x || mousepos_x > panel_pos_x + panel_size_x || + mousepos_y < panel_pos_y || mousepos_y > panel_pos_y + panel_size_y ) + { + HUD_Radar_Hide_Maximized(); + return; + } + vector pos = teamradar_texcoord_to_3dcoord(teamradar_2dcoord_to_texcoord(mousepos),view_origin_z); + string thecommand = ((gametype == MAPINFO_TYPE_CONQUEST) ? "cq_spawn" : "ons_spawn"); + localcmd(sprintf("cmd %s %f %f %f",thecommand,pos_x,pos_y,pos_z)); + + HUD_Radar_Hide_Maximized(); + return; + } + + + vector cursorsize = '32 32 0'; + drawpic(mousepos-'8 4 0', strcat("gfx/menu/", autocvar_menu_skin, "/cursor.tga"), cursorsize, '1 1 1', 0.8, DRAWFLAG_NORMAL); +} + void HUD_Radar(void) { if (!autocvar__hud_configure) @@ -1895,6 +1954,9 @@ void HUD_Radar(void) } } + if ( hud_panel_radar_temp_hidden ) + return; + HUD_Panel_UpdateCvars(); float f = 0; @@ -2048,8 +2110,27 @@ void HUD_Radar(void) for(tm = world; (tm = find(tm, classname, "radarlink")); ) draw_teamradar_link(tm.origin, tm.velocity, tm.team); + + vector coord; + vector brightcolor; for(tm = world; (tm = findflags(tm, teamradar_icon, 0xFFFFFF)); ) + { + if ( hud_panel_radar_mouse ) + if ( tm.health > 0 ) + if ( tm.team == myteam+1 ) + { + coord = teamradar_texcoord_to_2dcoord(teamradar_3dcoord_to_texcoord(tm.origin)); + if ( vlen(mousepos-coord) < 8 ) + { + brightcolor_x = min(1,tm.teamradar_color_x*1.5); + brightcolor_y = min(1,tm.teamradar_color_y*1.5); + brightcolor_z = min(1,tm.teamradar_color_z*1.5); + drawpic(coord - '8 8 0', "gfx/teamradar_icon_glow", '16 16 0', brightcolor, panel_fg_alpha, 0); + } + } + draw_teamradar_icon(tm.origin, tm.teamradar_icon, tm, tm.teamradar_color, panel_fg_alpha); + } for(tm = world; (tm = find(tm, classname, "entcs_receiver")); ) { color2 = GetPlayerColor(tm.sv_entnum); @@ -2059,6 +2140,21 @@ void HUD_Radar(void) draw_teamradar_player(view_origin, view_angles, '1 1 1'); drawresetcliparea(); + + if ( hud_panel_radar_mouse ) + { + string message = "Click to select teleport destination"; + + if ( getstati(STAT_HEALTH) <= 0 ) + { + message = "Click to select spawn location"; + } + + drawcolorcodedstring(pos + '0.5 0 0' * (mySize_x - stringwidth(message, TRUE, hud_fontsize)) - '0 1 0' * hud_fontsize_y * 2, + message, hud_fontsize, hud_panel_radar_foreground_alpha, DRAWFLAG_NORMAL); + + hud_panel_radar_bottom = pos_y + mySize_y + hud_fontsize_y; + } } // Score (#7) @@ -2079,7 +2175,7 @@ void HUD_Score_Rankings(vector pos, vector mySize, entity me) float name_size = mySize_x*0.75; float spacing_size = mySize_x*0.04; - const float highlight_alpha = 0.2; + float highlight_alpha = 0.2; float i = 0, me_printed = 0, first_pl = 0; string s; if (autocvar__hud_configure) @@ -2379,6 +2475,13 @@ void HUD_RaceTimer (void) if(!autocvar_hud_panel_racetimer) return; if(!(gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_CTS)) return; if(spectatee_status == -1) return; + if(getstati(STAT_HEALTH) <= 0) + { + // reset timers if player died (double check in the case that serverside command fails) + race_laptime = 0; + race_checkpointtime = 0; + return; + } } HUD_Panel_UpdateCvars(); @@ -2763,31 +2866,215 @@ void HUD_Mod_CA(vector myPos, vector mySize) } } +// VIP mod icons +float redvip_prevframe, bluevip_prevframe, yellowvip_prevframe, pinkvip_prevframe; // status during previous frame +float redvip_prevstatus, bluevip_prevstatus, yellowvip_prevstatus, pinkvip_prevstatus; // last remembered status +float redvip_statuschange_time, bluevip_statuschange_time, yellowvip_statuschange_time, pinkvip_statuschange_time; // time when the status changed +void HUD_Mod_VIP_Reset(void) +{ + redvip_prevstatus = bluevip_prevstatus = yellowvip_prevstatus = pinkvip_prevstatus = redvip_prevframe = bluevip_prevframe = yellowvip_prevframe = pinkvip_prevframe = redvip_statuschange_time = bluevip_statuschange_time = yellowvip_statuschange_time = pinkvip_statuschange_time = 0; +} +void HUD_Mod_VIP(vector pos, vector mySize) +{ + vector redvip_pos, bluevip_pos, yellowvip_pos, pinkvip_pos; + vector vip_size; + vector e1, e2; + float size1, size2; + float fs, fs2, fs3; + float f; // every function should have that + + float redvip, bluevip, yellowvip, pinkvip, is_vip; // current status + float redvip_statuschange_elapsedtime, bluevip_statuschange_elapsedtime, yellowvip_statuschange_elapsedtime, pinkvip_statuschange_elapsedtime; // time since the status changed + + redvip = (getstatf(STAT_VIP_RED) != 0); + bluevip = (getstatf(STAT_VIP_BLUE) != 0); + yellowvip = (getstatf(STAT_VIP_YELLOW) != 0); + pinkvip = (getstatf(STAT_VIP_PINK) != 0); + is_vip = (getstati(STAT_VIP) != 0); + + if(redvip || bluevip || yellowvip || pinkvip) + mod_active = 1; + else + mod_active = 0; + + // when status CHANGES, set old status into prevstatus and current status into status + if (redvip != redvip_prevframe) + { + redvip_statuschange_time = time; + redvip_prevstatus = redvip_prevframe; + redvip_prevframe = redvip; + } + + if (bluevip != bluevip_prevframe) + { + bluevip_statuschange_time = time; + bluevip_prevstatus = bluevip_prevframe; + bluevip_prevframe = bluevip; + } + + if (yellowvip != yellowvip_prevframe) + { + yellowvip_statuschange_time = time; + yellowvip_prevstatus = yellowvip_prevframe; + yellowvip_prevframe = yellowvip; + } + + if (pinkvip != pinkvip_prevframe) + { + pinkvip_statuschange_time = time; + pinkvip_prevstatus = pinkvip_prevframe; + pinkvip_prevframe = pinkvip; + } + + redvip_statuschange_elapsedtime = time - redvip_statuschange_time; + bluevip_statuschange_elapsedtime = time - bluevip_statuschange_time; + yellowvip_statuschange_elapsedtime = time - yellowvip_statuschange_time; + pinkvip_statuschange_elapsedtime = time - pinkvip_statuschange_time; + + switch(team_count) + { + default: + case 2: fs = 0.5; fs2 = 0.5; fs3 = 0.5; break; + case 3: fs = 1; fs2 = 0.35; fs3 = 0.35; break; + case 4: fs = 0.75; fs2 = 0.25; fs3 = 0.5; break; + } + + if(mySize_x > mySize_y) + { + size1 = mySize_x; + size2 = mySize_y; + e1 = eX; + e2 = eY; + } + else + { + size1 = mySize_y; + size2 = mySize_x; + e1 = eY; + e2 = eX; + } + + switch(myteam) + { + default: + case NUM_TEAM_1: + { + redvip_pos = pos; + bluevip_pos = pos + eX * fs2 * size1; + yellowvip_pos = pos - eX * fs2 * size1; + pinkvip_pos = pos + eX * fs3 * size1; + break; + } + case NUM_TEAM_2: + { + redvip_pos = pos + eX * fs2 * size1; + bluevip_pos = pos; + yellowvip_pos = pos - eX * fs2 * size1; + pinkvip_pos = pos + eX * fs3 * size1; + break; + } + case NUM_TEAM_3: + { + redvip_pos = pos + eX * fs3 * size1; + bluevip_pos = pos - eX * fs2 * size1; + yellowvip_pos = pos; + pinkvip_pos = pos + eX * fs2 * size1; + break; + } + case NUM_TEAM_4: + { + redvip_pos = pos - eX * fs2 * size1; + bluevip_pos = pos + eX * fs3 * size1; + yellowvip_pos = pos + eX * fs2 * size1; + pinkvip_pos = pos; + break; + } + } + vip_size = e1 * fs * size1 + e2 * size2; + + string red_icon = "", red_icon_prevstatus = ""; + string blue_icon = "", blue_icon_prevstatus = ""; + string yellow_icon = "", yellow_icon_prevstatus = ""; + string pink_icon = "", pink_icon_prevstatus = ""; + + if(redvip) { red_icon = "player_red"; } + if(bluevip) { blue_icon = "player_blue"; } + if(yellowvip) { yellow_icon = "player_yellow"; } + if(pinkvip) { pink_icon = "player_pink"; } + + if(redvip_prevframe) { red_icon_prevstatus = "player_red"; } + if(bluevip_prevframe) { blue_icon_prevstatus = "player_blue"; } + if(yellowvip_prevframe) { yellow_icon_prevstatus = "player_yellow"; } + if(pinkvip_prevframe) { pink_icon_prevstatus = "player_pink"; } + + if(is_vip) + switch(myteam) + { + case NUM_TEAM_1: red_icon = "notify_balldropped"; break; + case NUM_TEAM_2: blue_icon = "notify_balldropped"; break; + case NUM_TEAM_3: yellow_icon = "notify_balldropped"; break; + case NUM_TEAM_4: pink_icon = "notify_balldropped"; break; + } + + f = bound(0, redvip_statuschange_elapsedtime*2, 1); + if(red_icon_prevstatus && f < 1) + drawpic_aspect_skin_expanding(redvip_pos, red_icon_prevstatus, vip_size, '1 1 1', panel_fg_alpha * 1, DRAWFLAG_NORMAL, f); + if(red_icon) + drawpic_aspect_skin(redvip_pos, red_icon, vip_size, '1 1 1', panel_fg_alpha * 1 * f, DRAWFLAG_NORMAL); + + f = bound(0, bluevip_statuschange_elapsedtime*2, 1); + if(blue_icon_prevstatus && f < 1) + drawpic_aspect_skin_expanding(bluevip_pos, blue_icon_prevstatus, vip_size, '1 1 1', panel_fg_alpha * 1, DRAWFLAG_NORMAL, f); + if(blue_icon) + drawpic_aspect_skin(bluevip_pos, blue_icon, vip_size, '1 1 1', panel_fg_alpha * 1 * f, DRAWFLAG_NORMAL); + + f = bound(0, yellowvip_statuschange_elapsedtime*2, 1); + if(yellow_icon_prevstatus && f < 1) + drawpic_aspect_skin_expanding(yellowvip_pos, yellow_icon_prevstatus, vip_size, '1 1 1', panel_fg_alpha * 1, DRAWFLAG_NORMAL, f); + if(yellow_icon) + drawpic_aspect_skin(yellowvip_pos, yellow_icon, vip_size, '1 1 1', panel_fg_alpha * 1 * f, DRAWFLAG_NORMAL); + + f = bound(0, pinkvip_statuschange_elapsedtime*2, 1); + if(pink_icon_prevstatus && f < 1) + drawpic_aspect_skin_expanding(pinkvip_pos, pink_icon_prevstatus, vip_size, '1 1 1', panel_fg_alpha * 1, DRAWFLAG_NORMAL, f); + if(pink_icon) + drawpic_aspect_skin(pinkvip_pos, pink_icon, vip_size, '1 1 1', panel_fg_alpha * 1 * f, DRAWFLAG_NORMAL); +} + // CTF HUD modicon section -float redflag_prevframe, blueflag_prevframe; // status during previous frame -float redflag_prevstatus, blueflag_prevstatus; // last remembered status -float redflag_statuschange_time, blueflag_statuschange_time; // time when the status changed +float redflag_prevframe, blueflag_prevframe, yellowflag_prevframe, pinkflag_prevframe, neutralflag_prevframe; // status during previous frame +float redflag_prevstatus, blueflag_prevstatus, yellowflag_prevstatus, pinkflag_prevstatus, neutralflag_prevstatus; // last remembered status +float redflag_statuschange_time, blueflag_statuschange_time, yellowflag_statuschange_time, pinkflag_statuschange_time, neutralflag_statuschange_time; // time when the status changed void HUD_Mod_CTF_Reset(void) { - redflag_prevstatus = blueflag_prevstatus = redflag_prevframe = blueflag_prevframe = redflag_statuschange_time = blueflag_statuschange_time = 0; + redflag_prevstatus = blueflag_prevstatus = yellowflag_prevstatus = pinkflag_prevstatus = neutralflag_prevstatus = 0; + redflag_prevframe = blueflag_prevframe = yellowflag_prevframe = pinkflag_prevframe = neutralflag_prevframe = 0; + redflag_statuschange_time = blueflag_statuschange_time = yellowflag_statuschange_time = pinkflag_statuschange_time = neutralflag_statuschange_time = 0; } void HUD_Mod_CTF(vector pos, vector mySize) { - vector redflag_pos, blueflag_pos; + vector redflag_pos, blueflag_pos, yellowflag_pos, pinkflag_pos, neutralflag_pos; vector flag_size; float f; // every function should have that - float redflag, blueflag; // current status - float redflag_statuschange_elapsedtime, blueflag_statuschange_elapsedtime; // time since the status changed - float stat_items; - - stat_items = getstati(STAT_ITEMS, 0, 24); - redflag = (stat_items/IT_RED_FLAG_TAKEN) & 3; - blueflag = (stat_items/IT_BLUE_FLAG_TAKEN) & 3; + float redflag, blueflag, yellowflag, pinkflag, neutralflag; // current status + float redflag_statuschange_elapsedtime, blueflag_statuschange_elapsedtime, yellowflag_statuschange_elapsedtime, pinkflag_statuschange_elapsedtime, neutralflag_statuschange_elapsedtime; // time since the status changed + float ctf_oneflag; // one-flag CTF mode enabled/disabled + float stat_items = getstati(STAT_CTF_FLAGSTATUS, 0, 24); + float fs, fs2, fs3, size1, size2; + vector e1, e2; + + redflag = (stat_items/CTF_RED_FLAG_TAKEN) & 3; + blueflag = (stat_items/CTF_BLUE_FLAG_TAKEN) & 3; + yellowflag = (stat_items/CTF_YELLOW_FLAG_TAKEN) & 3; + pinkflag = (stat_items/CTF_PINK_FLAG_TAKEN) & 3; + neutralflag = (stat_items/CTF_NEUTRAL_FLAG_TAKEN) & 3; + + ctf_oneflag = (stat_items & CTF_FLAG_NEUTRAL); - if(redflag || blueflag) + if(redflag || blueflag || yellowflag || pinkflag || neutralflag) mod_active = 1; else mod_active = 0; @@ -2796,6 +3083,11 @@ void HUD_Mod_CTF(vector pos, vector mySize) { redflag = 1; blueflag = 2; + if(team_count >= 3) + yellowflag = 2; + if(team_count >= 4) + pinkflag = 3; + ctf_oneflag = neutralflag = 0; // disable neutral flag in hud editor? } // when status CHANGES, set old status into prevstatus and current status into status @@ -2813,8 +3105,32 @@ void HUD_Mod_CTF(vector pos, vector mySize) blueflag_prevframe = blueflag; } + if (yellowflag != yellowflag_prevframe) + { + yellowflag_statuschange_time = time; + yellowflag_prevstatus = yellowflag_prevframe; + yellowflag_prevframe = yellowflag; + } + + if (pinkflag != pinkflag_prevframe) + { + pinkflag_statuschange_time = time; + pinkflag_prevstatus = pinkflag_prevframe; + pinkflag_prevframe = pinkflag; + } + + if (neutralflag != neutralflag_prevframe) + { + neutralflag_statuschange_time = time; + neutralflag_prevstatus = neutralflag_prevframe; + neutralflag_prevframe = neutralflag; + } + redflag_statuschange_elapsedtime = time - redflag_statuschange_time; blueflag_statuschange_elapsedtime = time - blueflag_statuschange_time; + yellowflag_statuschange_elapsedtime = time - yellowflag_statuschange_time; + pinkflag_statuschange_elapsedtime = time - pinkflag_statuschange_time; + neutralflag_statuschange_elapsedtime = time - neutralflag_statuschange_time; float BLINK_FACTOR = 0.15; float BLINK_BASE = 0.85; @@ -2833,7 +3149,7 @@ void HUD_Mod_CTF(vector pos, vector mySize) case 2: red_icon = "flag_red_lost"; break; case 3: red_icon = "flag_red_carrying"; red_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); break; default: - if((stat_items & IT_CTF_SHIELDED) && (myteam == NUM_TEAM_2)) + if((stat_items & CTF_SHIELDED) && (myteam != NUM_TEAM_1)) red_icon = "flag_red_shielded"; else red_icon = string_null; @@ -2846,7 +3162,7 @@ void HUD_Mod_CTF(vector pos, vector mySize) default: if(redflag == 3) red_icon_prevstatus = "flag_red_carrying"; // make it more visible - else if((stat_items & IT_CTF_SHIELDED) && (myteam == NUM_TEAM_2)) + else if((stat_items & CTF_SHIELDED) && (myteam != NUM_TEAM_1)) red_icon_prevstatus = "flag_red_shielded"; else red_icon_prevstatus = string_null; @@ -2861,7 +3177,7 @@ void HUD_Mod_CTF(vector pos, vector mySize) case 2: blue_icon = "flag_blue_lost"; break; case 3: blue_icon = "flag_blue_carrying"; blue_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); break; default: - if((stat_items & IT_CTF_SHIELDED) && (myteam == NUM_TEAM_1)) + if((stat_items & CTF_SHIELDED) && (myteam != NUM_TEAM_2)) blue_icon = "flag_blue_shielded"; else blue_icon = string_null; @@ -2874,60 +3190,213 @@ void HUD_Mod_CTF(vector pos, vector mySize) default: if(blueflag == 3) blue_icon_prevstatus = "flag_blue_carrying"; // make it more visible - else if((stat_items & IT_CTF_SHIELDED) && (myteam == NUM_TEAM_1)) + else if((stat_items & CTF_SHIELDED) && (myteam != NUM_TEAM_2)) blue_icon_prevstatus = "flag_blue_shielded"; else blue_icon_prevstatus = string_null; break; } - if(mySize_x > mySize_y) { - if (myteam == NUM_TEAM_1) { // always draw own flag on left - redflag_pos = pos; - blueflag_pos = pos + eX * 0.5 * mySize_x; - } else { - blueflag_pos = pos; - redflag_pos = pos + eX * 0.5 * mySize_x; - } - flag_size = eX * 0.5 * mySize_x + eY * mySize_y; - } else { - if (myteam == NUM_TEAM_1) { // always draw own flag on left - redflag_pos = pos; - blueflag_pos = pos + eY * 0.5 * mySize_y; - } else { - blueflag_pos = pos; - redflag_pos = pos + eY * 0.5 * mySize_y; - } - flag_size = eY * 0.5 * mySize_y + eX * mySize_x; + string yellow_icon, yellow_icon_prevstatus; + float yellow_alpha, yellow_alpha_prevstatus; + yellow_alpha = yellow_alpha_prevstatus = 1; + switch(yellowflag) { + case 1: yellow_icon = "flag_yellow_taken"; break; + case 2: yellow_icon = "flag_yellow_lost"; break; + case 3: yellow_icon = "flag_yellow_carrying"; yellow_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); break; + default: + if((stat_items & CTF_SHIELDED) && (myteam != NUM_TEAM_3)) + yellow_icon = "flag_yellow_shielded"; + else + yellow_icon = string_null; + break; } + switch(yellowflag_prevstatus) { + case 1: yellow_icon_prevstatus = "flag_yellow_taken"; break; + case 2: yellow_icon_prevstatus = "flag_yellow_lost"; break; + case 3: yellow_icon_prevstatus = "flag_yellow_carrying"; yellow_alpha_prevstatus = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); break; + default: + if(yellowflag == 3) + yellow_icon_prevstatus = "flag_yellow_carrying"; // make it more visible + else if((stat_items & CTF_SHIELDED) && (myteam != NUM_TEAM_3)) + yellow_icon_prevstatus = "flag_yellow_shielded"; + else + yellow_icon_prevstatus = string_null; + break; + } + + string pink_icon, pink_icon_prevstatus; + float pink_alpha, pink_alpha_prevstatus; + pink_alpha = pink_alpha_prevstatus = 1; + switch(pinkflag) { + case 1: pink_icon = "flag_pink_taken"; break; + case 2: pink_icon = "flag_pink_lost"; break; + case 3: pink_icon = "flag_pink_carrying"; pink_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); break; + default: + if((stat_items & CTF_SHIELDED) && (myteam != NUM_TEAM_4)) + pink_icon = "flag_pink_shielded"; + else + pink_icon = string_null; + break; + } + switch(pinkflag_prevstatus) { + case 1: pink_icon_prevstatus = "flag_pink_taken"; break; + case 2: pink_icon_prevstatus = "flag_pink_lost"; break; + case 3: pink_icon_prevstatus = "flag_pink_carrying"; pink_alpha_prevstatus = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); break; + default: + if(pinkflag == 3) + pink_icon_prevstatus = "flag_pink_carrying"; // make it more visible + else if((stat_items & CTF_SHIELDED) && (myteam != NUM_TEAM_4)) + pink_icon_prevstatus = "flag_pink_shielded"; + else + pink_icon_prevstatus = string_null; + break; + } + + string neutral_icon, neutral_icon_prevstatus; + float neutral_alpha, neutral_alpha_prevstatus; + neutral_alpha = neutral_alpha_prevstatus = 1; + switch(neutralflag) { + case 1: neutral_icon = "flag_neutral_taken"; break; + case 2: neutral_icon = "flag_neutral_lost"; break; + case 3: neutral_icon = "flag_neutral_carrying"; neutral_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); break; + default: + if((stat_items & CTF_SHIELDED)) + neutral_icon = "flag_neutral_shielded"; + else + neutral_icon = string_null; + break; + } + switch(neutralflag_prevstatus) { + case 1: neutral_icon_prevstatus = "flag_neutral_taken"; break; + case 2: neutral_icon_prevstatus = "flag_neutral_lost"; break; + case 3: neutral_icon_prevstatus = "flag_neutral_carrying"; neutral_alpha_prevstatus = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); break; + default: + if(neutralflag == 3) + neutral_icon_prevstatus = "flag_neutral_carrying"; // make it more visible + else if((stat_items & CTF_SHIELDED)) + neutral_icon_prevstatus = "flag_neutral_shielded"; + else + neutral_icon_prevstatus = string_null; + break; + } + + if(ctf_oneflag) + { + // hacky, but these aren't needed + red_icon = red_icon_prevstatus = blue_icon = blue_icon_prevstatus = yellow_icon = yellow_icon_prevstatus = pink_icon = pink_icon_prevstatus = string_null; + fs = fs2 = fs3 = 1; + } + else switch(team_count) + { + default: + case 2: fs = 0.5; fs2 = 0.5; fs3 = 0.5; break; + case 3: fs = 1; fs2 = 0.35; fs3 = 0.35; break; + case 4: fs = 0.75; fs2 = 0.25; fs3 = 0.5; break; + } + + if(mySize_x > mySize_y) + { + size1 = mySize_x; + size2 = mySize_y; + e1 = eX; + e2 = eY; + } + else + { + size1 = mySize_y; + size2 = mySize_x; + e1 = eY; + e2 = eX; + } + + switch(myteam) + { + default: + case NUM_TEAM_1: + { + redflag_pos = pos; + blueflag_pos = pos + eX * fs2 * size1; + yellowflag_pos = pos - eX * fs2 * size1; + pinkflag_pos = pos + eX * fs3 * size1; + break; + } + case NUM_TEAM_2: + { + redflag_pos = pos + eX * fs2 * size1; + blueflag_pos = pos; + yellowflag_pos = pos - eX * fs2 * size1; + pinkflag_pos = pos + eX * fs3 * size1; + break; + } + case NUM_TEAM_3: + { + redflag_pos = pos + eX * fs3 * size1; + blueflag_pos = pos - eX * fs2 * size1; + yellowflag_pos = pos; + pinkflag_pos = pos + eX * fs2 * size1; + break; + } + case NUM_TEAM_4: + { + redflag_pos = pos - eX * fs2 * size1; + blueflag_pos = pos + eX * fs3 * size1; + yellowflag_pos = pos + eX * fs2 * size1; + pinkflag_pos = pos; + break; + } + } + neutralflag_pos = pos; + flag_size = e1 * fs * size1 + e2 * size2; + + f = bound(0, redflag_statuschange_elapsedtime*2, 1); + if(red_icon_prevstatus && f < 1) + drawpic_aspect_skin_expanding(redflag_pos, red_icon_prevstatus, flag_size, '1 1 1', panel_fg_alpha * red_alpha_prevstatus, DRAWFLAG_NORMAL, f); + if(red_icon) + drawpic_aspect_skin(redflag_pos, red_icon, flag_size, '1 1 1', panel_fg_alpha * red_alpha * f, DRAWFLAG_NORMAL); + + f = bound(0, blueflag_statuschange_elapsedtime*2, 1); + if(blue_icon_prevstatus && f < 1) + drawpic_aspect_skin_expanding(blueflag_pos, blue_icon_prevstatus, flag_size, '1 1 1', panel_fg_alpha * blue_alpha_prevstatus, DRAWFLAG_NORMAL, f); + if(blue_icon) + drawpic_aspect_skin(blueflag_pos, blue_icon, flag_size, '1 1 1', panel_fg_alpha * blue_alpha * f, DRAWFLAG_NORMAL); + + f = bound(0, yellowflag_statuschange_elapsedtime*2, 1); + if(yellow_icon_prevstatus && f < 1) + drawpic_aspect_skin_expanding(yellowflag_pos, yellow_icon_prevstatus, flag_size, '1 1 1', panel_fg_alpha * yellow_alpha_prevstatus, DRAWFLAG_NORMAL, f); + if(yellow_icon) + drawpic_aspect_skin(yellowflag_pos, yellow_icon, flag_size, '1 1 1', panel_fg_alpha * yellow_alpha * f, DRAWFLAG_NORMAL); + + f = bound(0, pinkflag_statuschange_elapsedtime*2, 1); + if(pink_icon_prevstatus && f < 1) + drawpic_aspect_skin_expanding(pinkflag_pos, pink_icon_prevstatus, flag_size, '1 1 1', panel_fg_alpha * pink_alpha_prevstatus, DRAWFLAG_NORMAL, f); + if(pink_icon) + drawpic_aspect_skin(pinkflag_pos, pink_icon, flag_size, '1 1 1', panel_fg_alpha * pink_alpha * f, DRAWFLAG_NORMAL); + + f = bound(0, neutralflag_statuschange_elapsedtime*2, 1); + if(neutral_icon_prevstatus && f < 1) + drawpic_aspect_skin_expanding(neutralflag_pos, neutral_icon_prevstatus, flag_size, '1 1 1', panel_fg_alpha * neutral_alpha_prevstatus, DRAWFLAG_NORMAL, f); + if(neutral_icon) + drawpic_aspect_skin(neutralflag_pos, neutral_icon, flag_size, '1 1 1', panel_fg_alpha * neutral_alpha * f, DRAWFLAG_NORMAL); +} + +// Keyhunt HUD modicon section +vector KH_SLOTS[4]; - f = bound(0, redflag_statuschange_elapsedtime*2, 1); - if(red_icon_prevstatus && f < 1) - drawpic_aspect_skin_expanding(redflag_pos, red_icon_prevstatus, flag_size, '1 1 1', panel_fg_alpha * red_alpha_prevstatus, DRAWFLAG_NORMAL, f); - if(red_icon) - drawpic_aspect_skin(redflag_pos, red_icon, flag_size, '1 1 1', panel_fg_alpha * red_alpha * f, DRAWFLAG_NORMAL); - - f = bound(0, blueflag_statuschange_elapsedtime*2, 1); - if(blue_icon_prevstatus && f < 1) - drawpic_aspect_skin_expanding(blueflag_pos, blue_icon_prevstatus, flag_size, '1 1 1', panel_fg_alpha * blue_alpha_prevstatus, DRAWFLAG_NORMAL, f); - if(blue_icon) - drawpic_aspect_skin(blueflag_pos, blue_icon, flag_size, '1 1 1', panel_fg_alpha * blue_alpha * f, DRAWFLAG_NORMAL); -} - -// Keyhunt HUD modicon section -vector KH_SLOTS[4]; - -void HUD_Mod_KH(vector pos, vector mySize) -{ - mod_active = 1; // keyhunt should never hide the mod icons panel - - // Read current state - - float state = getstati(STAT_KH_KEYS); +void HUD_Mod_KH(vector pos, vector mySize) +{ + // Read current state + + float state = getstati(STAT_KH_KEYSTATUS); float i, key_state; float all_keys, team1_keys, team2_keys, team3_keys, team4_keys, dropped_keys, carrying_keys; all_keys = team1_keys = team2_keys = team3_keys = team4_keys = dropped_keys = carrying_keys = 0; + if(state) + mod_active = 1; + else + mod_active = 0; + for(i = 0; i < 4; ++i) { key_state = (bitshift(state, i * -5) & 31) - 1; @@ -3374,9 +3843,11 @@ void HUD_ModIcons_SetFunc() case MAPINFO_TYPE_CTS: case MAPINFO_TYPE_RACE: HUD_ModIcons_GameType = HUD_Mod_Race; break; case MAPINFO_TYPE_CA: + case MAPINFO_TYPE_JAILBREAK: case MAPINFO_TYPE_FREEZETAG: HUD_ModIcons_GameType = HUD_Mod_CA; break; case MAPINFO_TYPE_DOMINATION: HUD_ModIcons_GameType = HUD_Mod_Dom; break; case MAPINFO_TYPE_KEEPAWAY: HUD_ModIcons_GameType = HUD_Mod_Keepaway; break; + case MAPINFO_TYPE_VIP: HUD_ModIcons_GameType = HUD_Mod_VIP; break; } } @@ -3423,6 +3894,114 @@ void HUD_ModIcons(void) draw_endBoldFont(); } +void HUD_DiscoMode(void) +{ + // vectors for top right, bottom right, bottom and bottom left corners + + vector topright; + vector bottom; + vector bottomright; + vector bottomleft; + + topright_x = vid_conwidth; + topright_y = 0; + topright_z = 0; + + bottom_x = vid_conwidth/2; + bottom_y = vid_conheight; + bottom_z = 0; + + bottomright_x = vid_conwidth; + bottomright_y = vid_conheight; + bottomright_z = 0; + + bottomleft_x = 0; + bottomleft_y = vid_conheight; + bottomleft_z = 0; + + local vector dmpos; + local float dmalpha; + dmpos = bottomleft; + local vector offset = '0 0 0'; + + dmalpha = 1; + + local vector dmcolor = '0 0 1'; + dmcolor_x = random(); + + drawfill('0 0 0', bottomright, dmcolor, dmalpha * random(), DRAWFLAG_ADDITIVE); + + local string mycolor = ""; + local string mychar; + local float j; + local string dmstring = _("^1D^2i^3s^4c^5o ^6Mode"); + + local float k; + + local float discoball; + local float discoballs = 8; + for ( k = 0; k < discoballs; ++k ) + { + dmpos_y = vid_conheight / 2; + dmpos_x = vid_conwidth / 2; + + //offset_y = 32 * (k+1) * sin(3 * time) * (mod(k, 2)? 1 : -1) * (mod(k, 3)? 1 : -1); + //offset_x = offset_y * (mod(k, 2)? -1 : 1);// * (mod(k, 3)? 0.5 : 1); + offset_x = 48 * (k+1) * sin(4*time+10*k) * cos(6.28*k/discoballs); + offset_y = 48 * (k+1) * sin(4*time+10*k) * sin(6.28*k/discoballs); + + dmcolor_x = 1; + dmcolor_y = cos(16*time+k)/2+0.5; + dmcolor_z = 0; + + discoball = 32 + (discoballs-k)*8 + 16*(cos(5*time+10*k)); + offset_x -= discoball/2; + offset_y -= discoball/2; + + dmalpha = 0.6 + cos(64*time+k)*0.3; + + drawfill(dmpos + offset, discoball*'1 1 0', dmcolor, dmalpha, DRAWFLAG_NORMAL); + + } + + draw_beginBoldFont(); + + for(k = 0; k < 2; ++k) + { + dmpos_y = vid_conheight / 2 - 12; + dmpos_x = 0.5 * (vid_conwidth - 0.6025 * strlennocol(dmstring) * 24); + for(j = 0; j < strlen(dmstring); ++j) + { + mychar = substring(dmstring, j, 1); + + if(mychar == "^") + { + if(substring(dmstring, j+1, 1) == "x") + { + mycolor = substring(dmstring, j, 5); + j += 5; + } + else + { + mycolor = substring(dmstring, j, 2); + ++j; + } + continue; + } + + offset_y = 10 * ((k*10)+1) * cos(dmpos_x + 3 * time) * ((j % 2)? 1 : -1) * (k? 1 : -1); + offset_x = offset_y * ((j % 2)? -1 : 1); + local string resultstr = strcat(mycolor, mychar); + + dummyfunction(0, 0, 0, 0, 0, 0, 0, 0); // work around DP bug (set OFS_PARAM5 to 0) + dmpos_x += stringwidth(resultstr, TRUE, '24 24 0'); + drawcolorcodedstring(dmpos + offset, resultstr, '24 24 0', dmalpha * 0.8, DRAWFLAG_ADDITIVE); + } + } + + draw_endBoldFont(); +} + // Draw pressed keys (#11) // void HUD_PressedKeys(void) @@ -3695,7 +4274,7 @@ void HUD_InfoMessages(void) if(spectatee_status == -1) s = _("^1Observing"); else - s = sprintf(_("^1Spectating: ^7%s"), GetPlayerName(player_localentnum - 1)); + s = sprintf(_("^1Spectating: ^7%s ^1(^3fov: %d^1)"), GetPlayerName(player_localentnum - 1), fovlock); drawInfoMessage(s) if(spectatee_status == -1) @@ -3744,6 +4323,25 @@ void HUD_InfoMessages(void) drawInfoMessage(s) } + if(autocvar_cl_showspectators) + if(num_spectators) + //if(spectatee_status != -1) + { + s = ((spectatee_status) ? _("^1Spectating this player:") : _("^1Spectating you:")); + //drawInfoMessage(s) + float limit = min(num_spectators, MAX_SPECTATORS); + float i; + for(i = 0; i < limit; ++i) + { + float slot = spectatorlist[i]; + if(i == 0) + s = strcat(s, " ^3", GetPlayerName(slot)); + else + s = strcat("^3", GetPlayerName(slot)); + drawInfoMessage(s) + } + } + string blinkcolor; if(time % 1 >= 0.5) blinkcolor = "^1"; @@ -4227,7 +4825,15 @@ void HUD_CenterPrint (void) } HUD_Panel_UpdateCvars(); - if(scoreboard_fade_alpha) + if ( HUD_Radar_Clickable() ) + { + if (hud_panel_radar_bottom >= 0.96 * vid_conheight) + return; + + panel_pos = eY * hud_panel_radar_bottom + eX * 0.5 * (vid_conwidth - panel_size_x); + panel_size_y = min(panel_size_y, vid_conheight - hud_panel_radar_bottom); + } + else if(scoreboard_fade_alpha) { hud_fade_alpha = hud_fade_alpha_save; @@ -4402,6 +5008,595 @@ void HUD_CenterPrint (void) } } +// QuickMenu (#17) +// +// QUICKMENU_MAXLINES must be <= 10 +#define QUICKMENU_MAXLINES 10 +#define QUICKMENU_MAXENTRIES 256 +string QuickMenu_Command[QUICKMENU_MAXLINES]; +string QuickMenu_Description[QUICKMENU_MAXLINES]; +float QuickMenu_CurrentPage; +float QuickMenu_IsLastPage; +// each quickmenu entry (submenu or command) is composed of 2 entries in the buffer +#define QUICKMENU_BUFFER_MAXENTRIES 2*QUICKMENU_MAXENTRIES +var float QuickMenu_Buffer = -1; +float QuickMenu_Buffer_Size; +float QuickMenu_Buffer_Index; +string QuickMenu_CurrentSubMenu; +float QuickMenu_CurrentPage_FirstEntry; +var float QuickMenu_Entries; +float QuickMenu_TimeOut; +void HUD_QuickMenu_load_entry(float i, string s, string s1) +{ + //printf("^xc80 entry %d: %s, %s\n", i, s, s1); + if (QuickMenu_Description[i]) + strunzone(QuickMenu_Description[i]); + QuickMenu_Description[i] = strzone(s); + if (QuickMenu_Command[i]) + strunzone(QuickMenu_Command[i]); + QuickMenu_Command[i] = strzone(s1); +} +void HUD_QuickMenu_clear_entry(float i) +{ + if (QuickMenu_Description[i]) + strunzone(QuickMenu_Description[i]); + QuickMenu_Description[i] = string_null; + if (QuickMenu_Command[i]) + strunzone(QuickMenu_Command[i]); + QuickMenu_Command[i] = string_null; +} + +void HUD_QuickMenu_Load_DefaultEntries(); +float HUD_QuickMenu_Buffer_Init() +{ + float fh = -1; + string s; + if(autocvar_hud_panel_quickmenu_file != "") + if(autocvar_hud_panel_quickmenu_file != "0") + fh = fopen(autocvar_hud_panel_quickmenu_file, FILE_READ); + if(fh < 0) + { + QuickMenu_Buffer = buf_create(); + if(QuickMenu_Buffer < 0) + return false; + HUD_QuickMenu_Load_DefaultEntries(); + QuickMenu_TimeOut = time + autocvar_hud_panel_quickmenu_time; + return true; + } + + QuickMenu_Buffer = buf_create(); + if (QuickMenu_Buffer < 0) + { + fclose(fh); + return false; + } + + QuickMenu_Buffer_Size = 0; + while((s = fgets(fh)) && QuickMenu_Buffer_Size < QUICKMENU_BUFFER_MAXENTRIES) + { + // first skip invalid entries, so we don't check them anymore + float argc; + argc = tokenize_console(s); + if(argc == 0 || argc > 2) + continue; + if(argv(0) == "") + continue; + if(argc == 2 && argv(1) == "") + continue; + + if(argc == 1) + bufstr_set(QuickMenu_Buffer, QuickMenu_Buffer_Size, strcat("S", argv(0))); // Submenu + else + { + bufstr_set(QuickMenu_Buffer, QuickMenu_Buffer_Size, strcat("T", argv(0))); // command Title + ++QuickMenu_Buffer_Size; + bufstr_set(QuickMenu_Buffer, QuickMenu_Buffer_Size, argv(1)); // command + } + ++QuickMenu_Buffer_Size; + QuickMenu_TimeOut = time + autocvar_hud_panel_quickmenu_time; + } + + if (QuickMenu_Buffer_Size <= 0) + { + buf_del(QuickMenu_Buffer); + QuickMenu_Buffer = -1; + } + fclose(fh); + return true; +} + +void HUD_QuickMenu_Buffer_Close() +{ + if (QuickMenu_Buffer >= 0) + { + buf_del(QuickMenu_Buffer); + QuickMenu_Buffer = -1; + QuickMenu_Buffer_Size = 0; + } +} + +void HUD_QuickMenu_Close() +{ + if (QuickMenu_CurrentSubMenu) + strunzone(QuickMenu_CurrentSubMenu); + QuickMenu_CurrentSubMenu = string_null; + float i; + for (i = 0; i < QUICKMENU_MAXLINES; ++i) + HUD_QuickMenu_clear_entry(i); + QuickMenu_Entries = 0; + hud_panel_quickmenu = 0; + mouseClicked = 0; + prevMouseClicked = 0; + HUD_QuickMenu_Buffer_Close(); + + if(autocvar_hud_cursormode) + if(!mv_active) + setcursormode(0); +} + +// It assumes submenu open tag is already detected +void HUD_QuickMenu_skip_submenu(string submenu) +{ + string s, z_submenu; + z_submenu = strzone(submenu); + for(++QuickMenu_Buffer_Index ; QuickMenu_Buffer_Index < QuickMenu_Buffer_Size; ++QuickMenu_Buffer_Index) + { + s = bufstr_get(QuickMenu_Buffer, QuickMenu_Buffer_Index); + if(substring(s, 0, 1) != "S") + continue; + if(substring(s, 1, strlen(s) - 1) == z_submenu) // submenu end + break; + HUD_QuickMenu_skip_submenu(substring(s, 1, strlen(s) - 1)); + } + strunzone(z_submenu); +} + +float HUD_QuickMenu_IsOpened() +{ + return (QuickMenu_Entries > 0); +} + +// new_page 0 means page 0, new_page != 0 means next page +float QuickMenu_Buffer_Index_Prev; +float HUD_QuickMenu_Page(string target_submenu, float new_page) +{ + string s, z_submenu; + + if (new_page == 0) + QuickMenu_CurrentPage = 0; + else + ++QuickMenu_CurrentPage; + QuickMenu_CurrentPage_FirstEntry = QuickMenu_CurrentPage * (QUICKMENU_MAXLINES - 2); + + z_submenu = strzone(target_submenu); + if (QuickMenu_CurrentSubMenu) + strunzone(QuickMenu_CurrentSubMenu); + QuickMenu_CurrentSubMenu = strzone(z_submenu); + + QuickMenu_IsLastPage = TRUE; + QuickMenu_Entries = 0; + + QuickMenu_Buffer_Index = 0; + if (z_submenu != "") + { + // skip everything until the submenu open tag is found + for( ; QuickMenu_Buffer_Index < QuickMenu_Buffer_Size; ++QuickMenu_Buffer_Index) + { + s = bufstr_get(QuickMenu_Buffer, QuickMenu_Buffer_Index); + if(substring(s, 0, 1) == "S" && substring(s, 1, strlen(s) - 1) == z_submenu) + { + // printf("^3 beginning of %s\n", z_submenu); + ++QuickMenu_Buffer_Index; + break; + } + // printf("^1 skipping %s\n", s); + } + } + float total = 0; + for( ; QuickMenu_Buffer_Index < QuickMenu_Buffer_Size; ++QuickMenu_Buffer_Index) + { + s = bufstr_get(QuickMenu_Buffer, QuickMenu_Buffer_Index); + + if(z_submenu != "" && substring(s, 1, strlen(s) - 1) == z_submenu) + { + // printf("^3 end of %s\n", z_submenu); + break; + } + + if (total - QuickMenu_CurrentPage_FirstEntry >= 0) + { + ++QuickMenu_Entries; + if(QuickMenu_Entries == QUICKMENU_MAXLINES - 2) + QuickMenu_Buffer_Index_Prev = QuickMenu_Buffer_Index; + else if(QuickMenu_Entries == QUICKMENU_MAXLINES) + { + HUD_QuickMenu_clear_entry(QUICKMENU_MAXLINES - 1); + QuickMenu_Buffer_Index = QuickMenu_Buffer_Index_Prev; + QuickMenu_IsLastPage = FALSE; + break; + } + } + + // NOTE: entries are loaded starting from 1, not from 0 + if(substring(s, 0, 1) == "S") // submenu + { + if (total - QuickMenu_CurrentPage_FirstEntry >= 0) + HUD_QuickMenu_load_entry(QuickMenu_Entries, substring(s, 1, strlen(s) - 1), ""); + HUD_QuickMenu_skip_submenu(substring(s, 1, strlen(s) - 1)); + } + else if (total - QuickMenu_CurrentPage_FirstEntry >= 0) + { + ++QuickMenu_Buffer_Index; + string cmd = bufstr_get(QuickMenu_Buffer, QuickMenu_Buffer_Index); + HUD_QuickMenu_load_entry(QuickMenu_Entries, substring(s, 1, strlen(s) - 1), cmd); + } + + ++total; + } + strunzone(z_submenu); + if (QuickMenu_Entries == 0) + { + HUD_QuickMenu_Close(); + return 0; + } + QuickMenu_TimeOut = time + autocvar_hud_panel_quickmenu_time; + return 1; +} + +void HUD_QuickMenu_Open() +{ + if(!HUD_QuickMenu_Buffer_Init()) return; + + hud_panel_quickmenu = 1; + if(autocvar_hud_cursormode) + setcursormode(1); + hudShiftState = 0; + + HUD_QuickMenu_Page("", 0); +} + +float HUD_QuickMenu_ActionForNumber(float num) +{ + if (!QuickMenu_IsLastPage) + { + if (num < 0 || num >= QUICKMENU_MAXLINES) + return 0; + if (num == QUICKMENU_MAXLINES - 1) + return 0; + if (num == 0) + { + HUD_QuickMenu_Page(QuickMenu_CurrentSubMenu, +1); + return 0; + } + } else if (num <= 0 || num > QuickMenu_Entries) + return 0; + + if (QuickMenu_Command[num] != "") + { + localcmd(strcat("\n", QuickMenu_Command[num], "\n")); + return 1; + } + if (QuickMenu_Description[num] != "") + HUD_QuickMenu_Page(QuickMenu_Description[num], 0); + return 0; +} + +float HUD_QuickMenu_InputEvent(float bInputType, float nPrimary, float nSecondary) +{ + // we only care for keyboard events + if(bInputType == 2) + return false; + + if(!HUD_QuickMenu_IsOpened() || autocvar__hud_configure || mv_active) + return false; + + if(bInputType == 3) + { + mousepos_x = nPrimary; + mousepos_y = nSecondary; + return true; + } + + // allow console bind to work + string con_keys; + float keys; + con_keys = findkeysforcommand("toggleconsole", 0); + keys = tokenize(con_keys); // findkeysforcommand returns data for this + + float hit_con_bind = 0, i; + for (i = 0; i < keys; ++i) + { + if(nPrimary == stof(argv(i))) + hit_con_bind = 1; + } + + if(bInputType == 0) { + if(nPrimary == K_ALT) hudShiftState |= S_ALT; + if(nPrimary == K_CTRL) hudShiftState |= S_CTRL; + if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT; + } + else if(bInputType == 1) { + if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT); + if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL); + if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT); + } + + if(nPrimary == K_ESCAPE) + { + if (bInputType == 1) + return true; + HUD_QuickMenu_Close(); + } + else if(nPrimary >= '0' && nPrimary <= '9') + { + if (bInputType == 1) + return true; + HUD_QuickMenu_ActionForNumber(stof(chr2str(nPrimary))); + } + if(nPrimary == K_MOUSE1) + { + if(bInputType == 0) // key pressed + mouseClicked |= S_MOUSE1; + else if(bInputType == 1) // key released + mouseClicked -= (mouseClicked & S_MOUSE1); + } + else if(nPrimary == K_MOUSE2) + { + if(bInputType == 0) // key pressed + mouseClicked |= S_MOUSE2; + else if(bInputType == 1) // key released + mouseClicked -= (mouseClicked & S_MOUSE2); + } + else if(hit_con_bind) + return false; + + return true; +} +void HUD_QuickMenu_Mouse() +{ + if(mv_active) return; + + if(!mouseClicked) + if(prevMouseClicked & S_MOUSE2) + { + HUD_QuickMenu_Close(); + return; + } + + if(!autocvar_hud_cursormode) + { + mousepos = mousepos + getmousepos() * autocvar_menu_mouse_speed; + + mousepos_x = bound(0, mousepos_x, vid_conwidth); + mousepos_y = bound(0, mousepos_y, vid_conheight); + } + + HUD_Panel_UpdateCvars() + + if(panel_bg_padding) + { + panel_pos += '1 1 0' * panel_bg_padding; + panel_size -= '2 2 0' * panel_bg_padding; + } + + float first_entry_pos, entries_height; + vector fontsize; + fontsize = '1 1 0' * (panel_size_y / QUICKMENU_MAXLINES); + first_entry_pos = panel_pos_y + ((QUICKMENU_MAXLINES - QuickMenu_Entries) * fontsize_y) / 2; + entries_height = panel_size_y - ((QUICKMENU_MAXLINES - QuickMenu_Entries) * fontsize_y); + + if (mousepos_x >= panel_pos_x && mousepos_y >= first_entry_pos && mousepos_x <= panel_pos_x + panel_size_x && mousepos_y <= first_entry_pos + entries_height) + { + float entry_num; + entry_num = floor((mousepos_y - first_entry_pos) / fontsize_y); + if (QuickMenu_IsLastPage || entry_num != QUICKMENU_MAXLINES - 2) + { + panel_pos_y = first_entry_pos + entry_num * fontsize_y; + vector color; + if(mouseClicked & S_MOUSE1) + color = '0.5 1 0.5'; + else if(hudShiftState & S_CTRL) + color = '1 1 0.3'; + else + color = '1 1 1'; + drawfill(panel_pos, eX * panel_size_x + eY * fontsize_y, color, .2, DRAWFLAG_NORMAL); + + if(!mouseClicked && (prevMouseClicked & S_MOUSE1)) + { + float f; + if (entry_num < QUICKMENU_MAXLINES - 1) + f = HUD_QuickMenu_ActionForNumber(entry_num + 1); + else + f = HUD_QuickMenu_ActionForNumber(0); + if(f && !(hudShiftState & S_CTRL)) + HUD_QuickMenu_Close(); + } + } + } + + vector cursorsize = '32 32 0'; + drawpic(mousepos, strcat("gfx/menu/", autocvar_menu_skin, "/cursor.tga"), cursorsize, '1 1 1', 0.8, DRAWFLAG_NORMAL); + + prevMouseClicked = mouseClicked; +} +void HUD_QuickMenu_DrawEntry(vector pos, string s, vector fontsize) +{ + string entry; + float offset; + entry = textShortenToWidth(s, panel_size_x, fontsize, stringwidth_colors); + if (autocvar_hud_panel_quickmenu_align > 0) + { + offset = (panel_size_x - stringwidth_colors(entry, fontsize)) * min(autocvar_hud_panel_quickmenu_align, 1); + drawcolorcodedstring(pos + eX * offset, entry, fontsize, panel_fg_alpha, DRAWFLAG_ADDITIVE); + } + else + drawcolorcodedstring(pos, entry, fontsize, panel_fg_alpha, DRAWFLAG_ADDITIVE); +} +void HUD_QuickMenu(void) +{ + if(!autocvar__hud_configure) + { + if (hud_configure_prev && hud_configure_prev != -1) + HUD_QuickMenu_Close(); + + if(!hud_draw_maximized) return; + if(mv_active) return; + //if(!autocvar_hud_panel_quickmenu) return; + if(!hud_panel_quickmenu) return; + + if(time > QuickMenu_TimeOut) + { + HUD_QuickMenu_Close(); + return; + } + } + else + { + if(!HUD_QuickMenu_IsOpened()) + { + QuickMenu_Entries = 1; + HUD_QuickMenu_load_entry(QuickMenu_Entries, sprintf(_("Submenu%d"), QuickMenu_Entries), ""); + ++QuickMenu_Entries; + HUD_QuickMenu_load_entry(QuickMenu_Entries, sprintf(_("Submenu%d"), QuickMenu_Entries), ""); + ++QuickMenu_Entries; + // although real command doesn't matter here, it must not be empty + // otherwise the entry is displayed like a submenu + for (; QuickMenu_Entries < QUICKMENU_MAXLINES - 1; ++QuickMenu_Entries) + HUD_QuickMenu_load_entry(QuickMenu_Entries, sprintf(_("Command%d"), QuickMenu_Entries), "-"); + ++QuickMenu_Entries; + HUD_QuickMenu_clear_entry(QuickMenu_Entries); + QuickMenu_IsLastPage = FALSE; + } + } + + HUD_Panel_UpdateCvars(); + + HUD_Panel_DrawBg(1); + + if(panel_bg_padding) + { + panel_pos += '1 1 0' * panel_bg_padding; + panel_size -= '2 2 0' * panel_bg_padding; + } + + float i; + vector fontsize; + string color; + fontsize = '1 1 0' * (panel_size_y / QUICKMENU_MAXLINES); + + if (!QuickMenu_IsLastPage) + { + color = "^5"; + HUD_QuickMenu_DrawEntry(panel_pos + eY * (panel_size_y - fontsize_y), sprintf("%d: %s%s", 0, color, _("Continue...")), fontsize); + } + else + panel_pos_y += ((QUICKMENU_MAXLINES - QuickMenu_Entries) * fontsize_y) / 2; + + for (i = 1; i <= QuickMenu_Entries; ++i) { + if (QuickMenu_Description[i] == "") + break; + if (QuickMenu_Command[i] == "") + color = "^4"; + else + color = "^3"; + HUD_QuickMenu_DrawEntry(panel_pos, sprintf("%d: %s%s", i, color, QuickMenu_Description[i]), fontsize); + panel_pos_y += fontsize_y; + } +} + +#define QUICKMENU_SMENU(submenu) \ + if(QuickMenu_Buffer_Size < QUICKMENU_BUFFER_MAXENTRIES) \ + bufstr_set(QuickMenu_Buffer, QuickMenu_Buffer_Size, strcat("S", submenu)); \ + ++QuickMenu_Buffer_Size; + +#define QUICKMENU_ENTRY(title,command) { \ + if(QuickMenu_Buffer_Size + 1 < QUICKMENU_BUFFER_MAXENTRIES) \ + { \ + bufstr_set(QuickMenu_Buffer, QuickMenu_Buffer_Size, strcat("T", title)); \ + ++QuickMenu_Buffer_Size; \ + bufstr_set(QuickMenu_Buffer, QuickMenu_Buffer_Size, command); \ + } \ + ++QuickMenu_Buffer_Size; \ +} + +// useful to Translate a string inside the Command +#define QUICKMENU_ENTRY_TC(title,command,text,translated_text) \ + if(cvar_string("prvm_language") == "en") \ + QUICKMENU_ENTRY(title, sprintf(command, text)) \ + else if(!autocvar_hud_panel_quickmenu_translatecommands || translated_text == text) \ + QUICKMENU_ENTRY(strcat("(en)", title), sprintf(command, text)) \ + else \ + QUICKMENU_ENTRY(strcat("(", cvar_string("prvm_language"), ")", title), sprintf(command, translated_text)) + +void HUD_QuickMenu_Load_DefaultEntries() +{ +QUICKMENU_SMENU(CTX(_("QMCMD^Chat"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^nice one")), "say %s", ":-) / nice one", CTX(_("QMCMD^:-) / nice one"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^good game")), "say %s", "good game", CTX(_("QMCMD^good game"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^hi / good luck")), "say %s", "hi / good luck and have fun", CTX(_("QMCMD^hi / good luck and have fun"))) +QUICKMENU_SMENU(CTX(_("QMCMD^Chat"))) + +QUICKMENU_SMENU(CTX(_("QMCMD^Team chat"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^quad soon")), "say_team %s", "quad soon", CTX(_("QMCMD^quad soon"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^free item, icon")), "say_team %s; g_waypointsprite_team_here_p", "free item %x^7 (l:%y^7)", CTX(_("QMCMD^free item %x^7 (l:%y^7)"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^took item, icon")), "say_team %s; g_waypointsprite_team_here", "took item (l:%l^7)", CTX(_("QMCMD^took item (l:%l^7)"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^negative")), "say_team %s", "negative", CTX(_("QMCMD^negative"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^positive")), "say_team %s", "positive", CTX(_("QMCMD^positive"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^need help, icon")), "say_team %s; g_waypointsprite_team_helpme; cmd voice needhelp", "need help (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)", CTX(_("QMCMD^need help (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^enemy seen, icon")), "say_team %s; g_waypointsprite_team_danger_p; cmd voice incoming", "enemy seen (l:%y^7)", CTX(_("QMCMD^enemy seen (l:%y^7)"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^flag seen, icon")), "say_team %s; g_waypointsprite_team_here_p; cmd voice seenflag", "flag seen (l:%y^7)", CTX(_("QMCMD^flag seen (l:%y^7)"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^defending, icon")), "say_team %s; g_waypointsprite_team_here", "defending (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)", CTX(_("QMCMD^defending (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^roaming, icon")), "say_team %s; g_waypointsprite_team_here", "roaming (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)", CTX(_("QMCMD^roaming (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^attacking, icon")), "say_team %s; g_waypointsprite_team_here", "attacking (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)", CTX(_("QMCMD^attacking (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^killed flag, icon")), "say_team %s; g_waypointsprite_team_here_p", "killed flagcarrier (l:%y^7)", CTX(_("QMCMD^killed flagcarrier (l:%y^7)"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^dropped flag, icon")), "say_team %s; g_waypointsprite_team_here_d", "dropped flag (l:%d^7)", CTX(_("QMCMD^dropped flag (l:%d^7)"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^drop gun, icon")), "say_team %s; g_waypointsprite_team_here; wait; dropweapon", "dropped gun %w^7 (l:%l^7)", CTX(_("QMCMD^dropped gun %w^7 (l:%l^7)"))) + QUICKMENU_ENTRY_TC(CTX(_("QMCMD^drop flag/key, icon")), "say_team %s; g_waypointsprite_team_here; wait; +use", "dropped flag/key %w^7 (l:%l^7)", CTX(_("QMCMD^dropped flag/key %w^7 (l:%l^7)"))) +QUICKMENU_SMENU(CTX(_("QMCMD^Team chat"))) + +QUICKMENU_SMENU(CTX(_("QMCMD^Settings"))) + QUICKMENU_SMENU(CTX(_("QMCMD^View/HUD settings"))) + QUICKMENU_ENTRY(CTX(_("QMCMD^1st/3rd person view")), "toggle chase_active") + QUICKMENU_ENTRY(CTX(_("QMCMD^Player models like mine on/off")), "toggle cl_forceplayermodels") + QUICKMENU_ENTRY(CTX(_("QMCMD^Names above players on/off")), "toggle hud_shownames") + QUICKMENU_ENTRY(CTX(_("QMCMD^Crosshair per weapon on/off")), "toggle crosshair_per_weapon") + QUICKMENU_ENTRY(CTX(_("QMCMD^FPS on/off")), "toggle hud_panel_engineinfo") + QUICKMENU_ENTRY(CTX(_("QMCMD^Net graph on/off")), "toggle shownetgraph") + QUICKMENU_SMENU(CTX(_("QMCMD^View/HUD settings"))) + + QUICKMENU_SMENU(CTX(_("QMCMD^Sound settings"))) + QUICKMENU_ENTRY(CTX(_("QMCMD^Hit sound on/off")), "toggle cl_hitsound") + QUICKMENU_ENTRY(CTX(_("QMCMD^Chat sound on/off")), "toggle cl_chatsound") + QUICKMENU_SMENU(CTX(_("QMCMD^Sound settings"))) + + QUICKMENU_SMENU(CTX(_("QMCMD^Spectator camera"))) + QUICKMENU_ENTRY(CTX(_("QMCMD^1st person")), "chase_active 0; -use") + QUICKMENU_ENTRY(CTX(_("QMCMD^3rd person free")), "chase_active 1; +use") + QUICKMENU_ENTRY(CTX(_("QMCMD^3rd person behind")), "chase_active 1; -use") + QUICKMENU_SMENU(CTX(_("QMCMD^Spectator camera"))) + + QUICKMENU_SMENU(CTX(_("QMCMD^Observer camera"))) + QUICKMENU_ENTRY(CTX(_("QMCMD^Increase speed")), "weapnext") + QUICKMENU_ENTRY(CTX(_("QMCMD^Decrease speed")), "weapprev") + QUICKMENU_ENTRY(CTX(_("QMCMD^Wall collision off")), "-use") + QUICKMENU_ENTRY(CTX(_("QMCMD^Wall collision on")), "+use") + QUICKMENU_SMENU(CTX(_("QMCMD^Observer camera"))) + + QUICKMENU_ENTRY(CTX(_("QMCMD^toggle fullscreen")), "toggle vid_fullscreen; vid_restart") +QUICKMENU_SMENU(CTX(_("QMCMD^Settings"))) + +QUICKMENU_SMENU(CTX(_("QMCMD^Call a vote"))) + QUICKMENU_ENTRY(CTX(_("QMCMD^Restart the map")), "vcall restart") + QUICKMENU_ENTRY(CTX(_("QMCMD^End match")), "vcall endmatch") + QUICKMENU_ENTRY(CTX(_("QMCMD^Reduce match time")), "vcall reducematchtime") + QUICKMENU_ENTRY(CTX(_("QMCMD^Extend match time")), "vcall extendmatchtime") + QUICKMENU_ENTRY(CTX(_("QMCMD^Shuffle teams")), "vcall shuffleteams") +QUICKMENU_SMENU(CTX(_("QMCMD^Call a vote"))) +} +#undef QUICKMENU_SMENU +#undef QUICKMENU_ENTRY + + // Buffs (#18) // void HUD_Buffs(void) @@ -4462,17 +5657,38 @@ void HUD_Buffs(void) } +// Minigame +// +#include "../common/minigames/cl_minigames_hud.qc" + /* ================== Main HUD system ================== */ +float HUD_Panel_CheckFlags(float showflags) +{ + if ( HUD_Minigame_Showpanels() ) + return showflags & PANEL_SHOW_MINIGAME; + return showflags & PANEL_SHOW_MAINGAME; +} + +void HUD_Panel_Draw(entity panent) +{ + panel = panent; + if ( HUD_Panel_CheckFlags(panel.panel_showflags) ) + panel.panel_draw(); +} + void HUD_Reset (void) { // reset gametype specific icons - if(gametype == MAPINFO_TYPE_CTF) - HUD_Mod_CTF_Reset(); + switch(gametype) + { + case MAPINFO_TYPE_CTF: HUD_Mod_CTF_Reset(); break; + case MAPINFO_TYPE_VIP: HUD_Mod_VIP_Reset(); break; + } } void HUD_Main (void) @@ -4492,16 +5708,24 @@ void HUD_Main (void) if(intermission == 2) // no hud during mapvote hud_fade_alpha = 0; + if ( getstati(STAT_DISCO_MODE) ) + HUD_DiscoMode(); + // panels that we want to be active together with the scoreboard // they must fade only when the menu does if(scoreboard_fade_alpha == 1) { - (panel = HUD_PANEL(CENTERPRINT)).panel_draw(); + HUD_Panel_Draw(HUD_PANEL(CENTERPRINT)); return; } if(!autocvar__hud_configure && !hud_fade_alpha) + { + hud_fade_alpha = 1; + HUD_Panel_Draw(HUD_PANEL(VOTE)); + hud_fade_alpha = 0; return; + } // Drawing stuff if (hud_skin_prev != autocvar_hud_skin) @@ -4601,14 +5825,16 @@ void HUD_Main (void) hud_draw_maximized = 0; // draw panels in order specified by panel_order array for(i = HUD_PANEL_NUM - 1; i >= 0; --i) - (panel = hud_panel[panel_order[i]]).panel_draw(); + HUD_Panel_Draw(hud_panel[panel_order[i]]); hud_draw_maximized = 1; // panels that may be maximized must check this var // draw maximized panels on top if(hud_panel_radar_maximized) - (panel = HUD_PANEL(RADAR)).panel_draw(); + HUD_Panel_Draw(HUD_PANEL(RADAR)); if(autocvar__con_chat_maximized) - (panel = HUD_PANEL(CHAT)).panel_draw(); + HUD_Panel_Draw(HUD_PANEL(CHAT)); + if(hud_panel_quickmenu) + HUD_Panel_Draw(HUD_PANEL(QUICKMENU)); HUD_Configure_PostDraw(); diff --git a/qcsrc/client/hud.qh b/qcsrc/client/hud.qh index d56caf133..fb4816726 100644 --- a/qcsrc/client/hud.qh +++ b/qcsrc/client/hud.qh @@ -9,9 +9,15 @@ string hud_panelorder_prev; float hud_draw_maximized; float hud_panel_radar_maximized; +float hud_panel_radar_mouse; +float hud_panel_radar_bottom; +float hud_panel_radar_temp_hidden; +float hud_panel_quickmenu; float chat_panel_modified; float radar_panel_modified; +void HUD_Radar_Hide_Maximized(); + vector mousepos; vector panel_click_distance; // mouse cursor distance from the top left corner of the panel (saved only upon a click) vector panel_click_resizeorigin; // coordinates for opposite point when resizing @@ -96,28 +102,41 @@ var string panel_bg_padding_str; float current_player; +float mv_active; + +.float panel_showflags; +const float PANEL_SHOW_NEVER = 0x00; +const float PANEL_SHOW_MAINGAME = 0x01; +const float PANEL_SHOW_MINIGAME = 0x02; +const float PANEL_SHOW_ALWAYS = 0xff; +float HUD_Panel_CheckFlags(float showflags); #define HUD_PANELS \ - HUD_PANEL(WEAPONS , HUD_Weapons , weapons) \ - HUD_PANEL(AMMO , HUD_Ammo , ammo) \ - HUD_PANEL(POWERUPS , HUD_Powerups , powerups) \ - HUD_PANEL(HEALTHARMOR , HUD_HealthArmor , healtharmor) \ - HUD_PANEL(NOTIFY , HUD_Notify , notify) \ - HUD_PANEL(TIMER , HUD_Timer , timer) \ - HUD_PANEL(RADAR , HUD_Radar , radar) \ - HUD_PANEL(SCORE , HUD_Score , score) \ - HUD_PANEL(RACETIMER , HUD_RaceTimer , racetimer) \ - HUD_PANEL(VOTE , HUD_Vote , vote) \ - HUD_PANEL(MODICONS , HUD_ModIcons , modicons) \ - HUD_PANEL(PRESSEDKEYS , HUD_PressedKeys , pressedkeys) \ - HUD_PANEL(CHAT , HUD_Chat , chat) \ - HUD_PANEL(ENGINEINFO , HUD_EngineInfo , engineinfo) \ - HUD_PANEL(INFOMESSAGES , HUD_InfoMessages , infomessages) \ - HUD_PANEL(PHYSICS , HUD_Physics , physics) \ - HUD_PANEL(CENTERPRINT , HUD_CenterPrint , centerprint) \ - HUD_PANEL(BUFFS , HUD_Buffs , buffs) - -#define HUD_PANEL(NAME,draw_func,name) \ + HUD_PANEL(WEAPONS , HUD_Weapons , weapons, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(AMMO , HUD_Ammo , ammo, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(POWERUPS , HUD_Powerups , powerups, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(HEALTHARMOR , HUD_HealthArmor , healtharmor, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(NOTIFY , HUD_Notify , notify, PANEL_SHOW_ALWAYS ) \ + HUD_PANEL(TIMER , HUD_Timer , timer, PANEL_SHOW_ALWAYS ) \ + HUD_PANEL(RADAR , HUD_Radar , radar, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(SCORE , HUD_Score , score, PANEL_SHOW_ALWAYS ) \ + HUD_PANEL(RACETIMER , HUD_RaceTimer , racetimer, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(VOTE , HUD_Vote , vote, PANEL_SHOW_ALWAYS ) \ + HUD_PANEL(MODICONS , HUD_ModIcons , modicons, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(PRESSEDKEYS , HUD_PressedKeys , pressedkeys, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(CHAT , HUD_Chat , chat, PANEL_SHOW_ALWAYS ) \ + HUD_PANEL(ENGINEINFO , HUD_EngineInfo , engineinfo, PANEL_SHOW_ALWAYS ) \ + HUD_PANEL(INFOMESSAGES , HUD_InfoMessages , infomessages, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(PHYSICS , HUD_Physics , physics, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(CENTERPRINT , HUD_CenterPrint , centerprint, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(QUICKMENU , HUD_QuickMenu , quickmenu, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(BUFFS , HUD_Buffs , buffs, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(MINIGAME_BOARD, HUD_MinigameBoard ,minigameboard, PANEL_SHOW_MINIGAME ) \ + HUD_PANEL(MINIGAME_STATUS,HUD_MinigameStatus,minigamestatus,PANEL_SHOW_MINIGAME ) \ + HUD_PANEL(MINIGAME_HELP, HUD_MinigameHelp ,minigamehelp, PANEL_SHOW_MINIGAME ) \ + HUD_PANEL(MINIGAME_MENU, HUD_MinigameMenu ,minigamemenu, PANEL_SHOW_ALWAYS ) + +#define HUD_PANEL(NAME,draw_func,name,showflags) \ float HUD_PANEL_##NAME; \ void draw_func(void); \ void RegisterHUD_Panel_##NAME() \ @@ -129,6 +148,7 @@ float current_player; hud_panelent.panel_name = #name; \ hud_panelent.panel_id = HUD_PANEL_##NAME; \ hud_panelent.panel_draw = draw_func; \ + hud_panelent.panel_showflags = showflags; \ ++HUD_PANEL_NUM; \ } \ ACCUMULATE_FUNCTION(RegisterHUD_Panels, RegisterHUD_Panel_##NAME); diff --git a/qcsrc/client/hud_config.qc b/qcsrc/client/hud_config.qc index e19e23787..cfde7c24c 100644 --- a/qcsrc/client/hud_config.qc +++ b/qcsrc/client/hud_config.qc @@ -29,8 +29,6 @@ void HUD_Panel_ExportCfg(string cfgname) HUD_Write("\n"); HUD_Write_Cvar_q("hud_progressbar_alpha"); - HUD_Write_Cvar_q("hud_progressbar_strength_color"); - HUD_Write_Cvar_q("hud_progressbar_shield_color"); HUD_Write_Cvar_q("hud_progressbar_health_color"); HUD_Write_Cvar_q("hud_progressbar_armor_color"); HUD_Write_Cvar_q("hud_progressbar_fuel_color"); @@ -102,8 +100,6 @@ void HUD_Panel_ExportCfg(string cfgname) HUD_Write_PanelCvar_q("_iconalign"); HUD_Write_PanelCvar_q("_baralign"); HUD_Write_PanelCvar_q("_progressbar"); - HUD_Write_PanelCvar_q("_progressbar_strength"); - HUD_Write_PanelCvar_q("_progressbar_shield"); HUD_Write_PanelCvar_q("_text"); break; case HUD_PANEL_HEALTHARMOR: @@ -192,6 +188,9 @@ void HUD_Panel_ExportCfg(string cfgname) HUD_Write_PanelCvar_q("_fade_subsequent_minfontsize"); HUD_Write_PanelCvar_q("_fade_minfontsize"); break; + case HUD_PANEL_QUICKMENU: + HUD_Write_PanelCvar_q("_align"); + break; } HUD_Write("\n"); } @@ -928,6 +927,9 @@ float HUD_Panel_Check_Mouse_Pos(float allow_move) j += 1; panel = hud_panel[i]; + if ( ! HUD_Panel_CheckFlags(panel.panel_showflags) ) + continue; + HUD_Panel_UpdatePosSize() border = max(8, panel_bg_border); // FORCED border so a small border size doesn't mean you can't resize @@ -1009,6 +1011,9 @@ void HUD_Panel_Highlight(float allow_move) j += 1; panel = hud_panel[i]; + if ( ! HUD_Panel_CheckFlags(panel.panel_showflags) ) + continue; + HUD_Panel_UpdatePosSize() border = max(8, panel_bg_border); // FORCED border so a small border size doesn't mean you can't resize diff --git a/qcsrc/client/main.qh b/qcsrc/client/main.qh index c9aa2fb40..1c2ccdf8c 100644 --- a/qcsrc/client/main.qh +++ b/qcsrc/client/main.qh @@ -96,6 +96,7 @@ vector view_origin, view_angles, view_forward, view_right, view_up; float button_zoom; float spectatorbutton_zoom; +float button_attack; float button_attack2; float activeweapon; @@ -148,3 +149,19 @@ entity entcs_receiver[255]; // 255 is the engine limit on maxclients float hud; float view_quality; float framecount; + +float vaporizer_delay; + +float fovlock; + +float cl_fps; +float sv_showfps; + +string sv_announcer; + +float num_spectators; +#define MAX_SPECTATORS 7 +float spectatorlist[MAX_SPECTATORS]; + +float camera_drawviewmodel_locked; +float camera_drawviewmodel_backup; diff --git a/qcsrc/client/mapvoting.qc b/qcsrc/client/mapvoting.qc index 3f30a6a72..302cdc326 100644 --- a/qcsrc/client/mapvoting.qc +++ b/qcsrc/client/mapvoting.qc @@ -1,6 +1,5 @@ float mv_num_maps; -float mv_active; string mv_maps[MAPVOTE_COUNT]; string mv_pics[MAPVOTE_COUNT]; string mv_pk3[MAPVOTE_COUNT]; @@ -192,6 +191,11 @@ void MapVote_DrawMapItem(vector pos, float isize, float tsize, string map, strin pos += autocvar_scoreboard_border_thickness * '1 1 0'; img_size -= (autocvar_scoreboard_border_thickness * 2) * '1 1 0'; + +#ifdef XMAS + string xpic = "gfx/menu/default/xmaspic.tga"; +#endif + if(pic == "") { drawfill(pos, img_size, '.5 .5 .5', .7 * theAlpha, DRAWFLAG_NORMAL); @@ -199,9 +203,21 @@ void MapVote_DrawMapItem(vector pos, float isize, float tsize, string map, strin else { if(drawgetimagesize(pic) == '0 0 0') +#ifdef APRILFOOLS + drawpic_rotated(pos, draw_UseSkinFor("nopreview_map"), img_size, '1 1 1', M_PI, theAlpha, DRAWFLAG_NORMAL); +#else drawpic(pos, draw_UseSkinFor("nopreview_map"), img_size, '1 1 1', theAlpha, DRAWFLAG_NORMAL); +#endif else +#ifdef APRILFOOLS + drawpic_rotated(pos, pic, img_size, '1 1 1', M_PI, theAlpha, DRAWFLAG_NORMAL); +#else drawpic(pos, pic, img_size, '1 1 1', theAlpha, DRAWFLAG_NORMAL); +#endif +#ifdef XMAS + if(drawgetimagesize(xpic) != '0 0 0') + drawpic(pos, xpic, img_size, '1 1 1', theAlpha, DRAWFLAG_NORMAL); +#endif } if(id == mv_ownvote) diff --git a/qcsrc/client/miscfunctions.qc b/qcsrc/client/miscfunctions.qc index 8b674e782..566212dc0 100644 --- a/qcsrc/client/miscfunctions.qc +++ b/qcsrc/client/miscfunctions.qc @@ -382,6 +382,16 @@ void drawcolorcodedstring_aspect_expanding(vector pos, string text, vector sz, f drawcolorcodedstring_expanding(pos, text, '1 1 0' * sz_y, theAlpha, drawflag, fadelerp); } +void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f); + +void drawpic_rotated(vector position, string pic, vector sz, vector rgb, float angle, float theAlpha, float flag) { + drawrotpic(position + sz * 0.5, angle, pic, sz, sz * 0.5, rgb, theAlpha, flag); +} + +void drawpic_rotated_ex(vector position, string pic, vector sz, vector rgb, float angle, vector hotspot, float theAlpha, float flag) { + drawrotpic(position + hotspot, angle, pic, sz, hotspot, rgb, theAlpha, flag); +} + // this draws the triangles of a model DIRECTLY. Don't expect high performance, really... float PolyDrawModelSurface(entity e, float i_s) { diff --git a/qcsrc/client/particles.qc b/qcsrc/client/particles.qc index f16519ab5..1e6d3fac8 100644 --- a/qcsrc/client/particles.qc +++ b/qcsrc/client/particles.qc @@ -239,8 +239,12 @@ void Net_ReadVortexBeamParticle() charge = sqrt(charge); // divide evenly among trail spacing and alpha particles_alphamin = particles_alphamax = particles_fade = charge; - if (autocvar_cl_particles_oldvortexbeam && (getstati(STAT_ALLOW_OLDVORTEXBEAM) || isdemo())) - WarpZone_TrailParticles_WithMultiplier(world, particleeffectnum("TE_TEI_G3"), shotorg, endpos, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE); + string s = ((charge >= 0.95 && particleeffectnum("nex_beam_charged") >= 0) ? "nex_beam_charged" : "nex_beam"); + + if(autocvar_cl_particles_newvortexbeam && (getstati(STAT_ALLOW_OLDVORTEXBEAM) || isdemo())) + WarpZone_TrailParticles_WithMultiplier(world, particleeffectnum("nex_beam_new"), shotorg, endpos, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE); + else if(autocvar_cl_particles_oldvortexbeam && (getstati(STAT_ALLOW_OLDVORTEXBEAM) || isdemo())) + WarpZone_TrailParticles_WithMultiplier(world, particleeffectnum("TE_TEI_G3NEUTRAL"), shotorg, endpos, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE); else - WarpZone_TrailParticles_WithMultiplier(world, particleeffectnum("nex_beam"), shotorg, endpos, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE); + WarpZone_TrailParticles_WithMultiplier(world, particleeffectnum(s), shotorg, endpos, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE); } diff --git a/qcsrc/client/player_skeleton.qc b/qcsrc/client/player_skeleton.qc index ad5e23aa8..b66ebe67c 100644 --- a/qcsrc/client/player_skeleton.qc +++ b/qcsrc/client/player_skeleton.qc @@ -13,6 +13,11 @@ void skeleton_loadinfo(entity e) return; e.bone_upperbody = 0; e.bone_weapon = gettagindex(e, "weapon"); + e.bone_hat = gettagindex(e, "head"); + if(!e.bone_hat) + e.bone_hat = gettagindex(e, "tag_head"); + if(!e.bone_hat) + e.bone_hat = gettagindex(e, "bip01 head"); if(!e.bone_weapon) e.bone_weapon = gettagindex(e, "tag_weapon"); if(!e.bone_weapon) @@ -31,6 +36,8 @@ void skeleton_loadinfo(entity e) e.fixbone = get_model_parameters_fixbone; if(get_model_parameters_bone_weapon) e.bone_weapon = gettagindex(e, get_model_parameters_bone_weapon); + if(get_model_parameters_bone_head) + e.bone_hat = gettagindex(e, get_model_parameters_bone_head); for(i = 0; i < MAX_AIM_BONES; ++i) { if(get_model_parameters_bone_aim[i]) @@ -41,7 +48,7 @@ void skeleton_loadinfo(entity e) } else dprint("No model parameters for ", e.model, "\n"); - dprint(e.model, " uses ", ftos(e.bone_upperbody), " ", ftos(e.fixbone), "\n"); + //dprint(e.model, " uses ", ftos(e.bone_upperbody), " ", ftos(e.fixbone), "\n"); get_model_parameters(string_null, 0); e.skeleton_info_modelindex = e.modelindex; e.skeleton_info_skin = e.skin; @@ -180,5 +187,7 @@ void skeleton_from_frames(entity e, float is_dead) skel_set_boneabs(s, e.(bone_aim[i]), org); } } + + e.angles_x = e.angles_z = 0; // bad hax, fix later } } diff --git a/qcsrc/client/player_skeleton.qh b/qcsrc/client/player_skeleton.qh index d369bac4e..7ee8d3a8b 100644 --- a/qcsrc/client/player_skeleton.qh +++ b/qcsrc/client/player_skeleton.qh @@ -7,3 +7,6 @@ void skeleton_loadinfo(entity e); .float bone_aim[MAX_AIM_BONES]; .float bone_aimweight[MAX_AIM_BONES]; .float fixbone; +.float bone_hat; +.vector hat_height; +.float hat_scale; diff --git a/qcsrc/client/progs.src b/qcsrc/client/progs.src index c21726b4d..87e319e6f 100644 --- a/qcsrc/client/progs.src +++ b/qcsrc/client/progs.src @@ -28,17 +28,26 @@ Defs.qc ../common/command/rpn.qh ../common/command/generic.qh ../common/command/shared_defs.qh +../common/command/script.qh ../common/urllib.qh ../common/animdecide.qh command/cl_cmd.qh -../common/monsters/monsters.qh - autocvars.qh +../common/jeff.qh + ../common/notifications.qh // must be after autocvars ../common/deathtypes.qh // must be after notifications + +../common/effects.qh + +../common/monsters/monsters.qh + +../common/turrets/turrets.qh +../common/turrets/cl_turrets.qh + damage.qh ../csqcmodellib/interpolate.qh @@ -50,11 +59,11 @@ movetypes.qh prandom.qh bgmscript.qh noise.qh -tturrets.qh -../server/tturrets/include/turrets_early.qh ../server/movelib.qc +generator.qh +controlpoint.qh main.qh -vehicles/vehicles.qh +../common/vehicles/vehicles_include.qh ../common/csqcmodel_settings.qh ../csqcmodellib/common.qh ../csqcmodellib/cl_model.qh @@ -62,11 +71,16 @@ vehicles/vehicles.qh weapons/projectile.qh // TODO player_skeleton.qh +../server/mutators/gamemode_ctf.qh // TODO +../server/mutators/gamemode_keyhunt.qh +conquest.qh + sortlist.qc miscfunctions.qc ../server/t_items.qh ../server/t_items.qc +../common/minigames/cl_minigames.qh teamradar.qc hud_config.qc hud.qc @@ -91,11 +105,11 @@ modeleffects.qc tuba.qc target_music.qc -vehicles/vehicles.qc -../server/vehicles/bumblebee.qc +../common/vehicles/vehicles_include.qc shownames.qh shownames.qc +conquest.qc announcer.qc Main.qc View.qc @@ -113,13 +127,19 @@ noise.qc ../common/command/markup.qc ../common/command/rpn.qc ../common/command/generic.qc +../common/command/script.qc ../common/mapinfo.qc ../common/weapons/weapons.qc // TODO ../common/urllib.qc command/cl_cmd.qc +../common/effects.qc + ../common/monsters/monsters.qc +../common/turrets/cl_turrets.qc +../common/turrets/turrets.qc + ../common/nades.qc ../common/buffs.qc @@ -127,7 +147,13 @@ command/cl_cmd.qc ../warpzonelib/mathlib.qc ../warpzonelib/common.qc ../warpzonelib/client.qc -tturrets.qc + +generator.qc +controlpoint.qc player_skeleton.qc ../common/animdecide.qc + + +../common/minigames/minigames.qc +../common/minigames/cl_minigames.qc diff --git a/qcsrc/client/scoreboard.qc b/qcsrc/client/scoreboard.qc index f5b3206ba..92cae0095 100644 --- a/qcsrc/client/scoreboard.qc +++ b/qcsrc/client/scoreboard.qc @@ -25,6 +25,7 @@ string TranslateScoresLabel(string l) case "drops": return CTX(_("SCO^drops")); case "faults": return CTX(_("SCO^faults")); case "fckills": return CTX(_("SCO^fckills")); + case "kckills": return CTX(_("SCO^kckills")); case "goals": return CTX(_("SCO^goals")); case "kckills": return CTX(_("SCO^kckills")); case "kdratio": return CTX(_("SCO^kdratio")); @@ -50,6 +51,8 @@ string TranslateScoresLabel(string l) case "suicides": return CTX(_("SCO^suicides")); case "takes": return CTX(_("SCO^takes")); case "ticks": return CTX(_("SCO^ticks")); + case "liberated": return CTX(_("SCO^liberated")); + case "captured": return CTX(_("SCO^captured")); default: return l; } } @@ -277,6 +280,9 @@ void Cmd_HUD_Help() "or in all but these game types. You can also specify 'all' as a\n" "field to show all fields available for the current game mode.\n\n")); + print(_("You can also put a ~ sign before them to make the field show up\n" + "only if scoreboard_extras is enabled.\n\n")); + print(_("The special game type names 'teams' and 'noteams' can be used to\n" "include/exclude ALL teams/noteams game modes.\n\n")); @@ -288,14 +294,18 @@ void Cmd_HUD_Help() } #define HUD_DefaultColumnLayout() \ -"ping pl name | " \ -"-teams,race,lms/kills +ft,tdm/kills -teams,lms/deaths +ft,tdm/deaths -teams,lms,race,ka/suicides +ft,tdm/suicides -race,dm,tdm,ka,ft/frags " /* tdm already has this in "score" */ \ -"+ctf/caps +ctf/pickups +ctf/fckills +ctf/returns " \ +"ping pl ~fps name | " \ +"-teams,rc,lms/kills +ft,tdm/kills -teams,lms/deaths +ft,tdm/deaths -teams,lms,rc,ka/suicides +ft,tdm/suicides -rc,dm,tdm,ka,ft/frags " /* tdm already has this in "score" */ \ +"~+ctf/deaths ~+ctf/suicides +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns " \ +"+cq/captured +cq/liberated " \ +"+ons/caps +ons/takes " \ +"+inf/survivals " \ "+lms/lives +lms/rank " \ -"+kh/caps +kh/pushes +kh/destroyed " \ -"?+race/laps ?+race/time ?+race/fastest " \ +"~+kh/deaths ~+kh/suicides +kh/caps +kh/pushes +kh/destroyed " \ +"+vip/survivals +vip/vipkills +dom/caps " \ +"?+rc/laps ?+rc/time ?+rc/fastest " \ "+as/objectives +nb/faults +nb/goals +ka/pickups +ka/bckills +ka/bctime +ft/revivals " \ -"-lms,race,nb/score" +"-lms,rc,nb/score" void Cmd_HUD_SetFields(float argc) { @@ -361,6 +371,19 @@ void Cmd_HUD_SetFields(float argc) str = substring(str, 1, strlen(str) - 1); } + if(substring(str, 0, 1) == "~") + { + nocomplain = TRUE; + + if(autocvar_scoreboard_extras) + str = substring(str, 1, strlen(str) - 1); + else + { + str = ""; + continue; + } + } + slash = strstrofs(str, "/", 0); if(slash >= 0) { @@ -380,6 +403,8 @@ void Cmd_HUD_SetFields(float argc) hud_field[hud_num_fields] = SP_PING; } else if(str == "pl") { hud_field[hud_num_fields] = SP_PL; + } else if(str == "fps") { + hud_field[hud_num_fields] = SP_FPS; } else if(str == "kd" || str == "kdr" || str == "kdratio" || str == "k/d") { hud_field[hud_num_fields] = SP_KDRATIO; } else if(str == "sum" || str == "diff" || str == "k-d") { @@ -542,6 +567,17 @@ string HUD_GetField(entity pl, float field) hud_field_rgb = '1 0.5 0.5' - '0 0.5 0.5'*tmp; return str; + case SP_FPS: + if (!pl.gotscores) + return _("N/A"); + + tmp = pl.(scores[field]); + if(tmp == 0) + return ""; + + hud_field_rgb = '1 0 0' + '0 1 1' * (bound(0, tmp, 60) / 60); + return ftos(tmp); + case SP_NAME: if(ready_waiting && pl.ready) { @@ -950,13 +986,17 @@ vector HUD_Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_siz float HUD_WouldDrawScoreboard() { if (autocvar__hud_configure) return 0; + else if (HUD_QuickMenu_IsOpened()) + return 0; + else if (HUD_Radar_Clickable()) + return 0; else if (scoreboard_showscores) return 1; else if (intermission == 1) return 1; else if (intermission == 2) return 0; - else if (spectatee_status != -1 && getstati(STAT_HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS) + else if (spectatee_status != -1 && getstati(STAT_HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame) return 1; else if (scoreboard_showscores_force) return 1; @@ -966,13 +1006,25 @@ float HUD_WouldDrawScoreboard() { float average_accuracy; vector HUD_DrawScoreboardAccuracyStats(vector pos, vector rgb, vector bg_size) { + WepSet weapons_stat = WepSet_GetFromStat(); + WepSet weapons_inmap = WepSet_GetFromStat_InMap(); float i; - float weapon_cnt = WEP_COUNT - 3; // either vaporizer/vortex are hidden, no port-o-launch, no tuba - float rows; - if(autocvar_scoreboard_accuracy_doublerows) - rows = 2; - else - rows = 1; + float weapon_stats; + float disownedcnt = 0; + for(i = WEP_FIRST; i <= WEP_LAST; ++i) + { + self = get_weaponinfo(i); + if(!self.weapon) + continue; + + weapon_stats = weapon_accuracy[i-WEP_FIRST]; + + if(weapon_stats < 0 && !(weapons_stat & WepSet_FromWeapon(i) || weapons_inmap & WepSet_FromWeapon(i))) + ++disownedcnt; + } + + float weapon_cnt = WEP_COUNT - disownedcnt; // either vaporizer/vortex are hidden, no port-o-launch, no tuba + float rows = 1; float height = 40; float fontsize = height * 1/3; float weapon_height = height * 2/3; @@ -1013,7 +1065,6 @@ vector HUD_DrawScoreboardAccuracyStats(vector pos, vector rgb, vector bg_size) if(switchweapon == WEP_VAPORIZER) g_instagib = 1; // TODO: real detection for instagib? - float weapon_stats; if(autocvar_scoreboard_accuracy_nocolors) rgb = '1 1 1'; else @@ -1024,10 +1075,11 @@ vector HUD_DrawScoreboardAccuracyStats(vector pos, vector rgb, vector bg_size) self = get_weaponinfo(i); if (!self.weapon) continue; - if ((i == WEP_VORTEX && g_instagib) || i == WEP_PORTO || (i == WEP_VAPORIZER && !g_instagib) || i == WEP_TUBA) // skip port-o-launch, vortex || vaporizer and tuba - continue; weapon_stats = weapon_accuracy[i-WEP_FIRST]; + if(weapon_stats < 0 && !(weapons_stat & WepSet_FromWeapon(i) || weapons_inmap & WepSet_FromWeapon(i))) + continue; + float weapon_alpha; if(weapon_stats >= 0) weapon_alpha = scoreboard_alpha_fg; diff --git a/qcsrc/client/teamradar.qc b/qcsrc/client/teamradar.qc index 0c0f10204..b12f43571 100644 --- a/qcsrc/client/teamradar.qc +++ b/qcsrc/client/teamradar.qc @@ -46,6 +46,34 @@ vector teamradar_texcoord_to_2dcoord(vector in) return out; } + +vector teamradar_2dcoord_to_texcoord(vector in) +{ + vector out; + out = in; + + out -= teamradar_origin2d; + if(v_flipped) + out_x = -out_x; + out = out / teamradar_size; + + out_y = - out_y; // screen space is reversed + out = rotate(out, -teamradar_angle * DEG2RAD); + + out += teamradar_origin3d_in_texcoord; + + return out; +} + +vector teamradar_texcoord_to_3dcoord(vector in,float z) +{ + vector out; + out_x = in_x * (mi_picmax_x - mi_picmin_x) + mi_picmin_x; + out_y = in_y * (mi_picmax_y - mi_picmin_y) + mi_picmin_y; + out_z = z; + return out; +} + vector yinvert(vector v) { v_y = 1 - v_y; diff --git a/qcsrc/client/waypointsprites.qc b/qcsrc/client/waypointsprites.qc index 2df3dd411..4d1a27066 100644 --- a/qcsrc/client/waypointsprites.qc +++ b/qcsrc/client/waypointsprites.qc @@ -33,7 +33,6 @@ float waypointsprite_alpha; .float maxdistance; .float hideflags; .float spawntime; -.float health; .float build_started; .float build_starthealth; .float build_finished; @@ -199,16 +198,11 @@ float spritelookupblinkvalue(string s) { switch(s) { - case "ons-cp-atck-neut": return 2; - case "ons-cp-atck-red": return 2; - case "ons-cp-atck-blue": return 2; - case "ons-cp-dfnd-red": return 0.5; - case "ons-cp-dfnd-blue": return 0.5; + case "ons-cp-atck": return 2; + case "ons-cp-dfnd": return 0.5; case "item-invis": return 2; case "item-extralife": return 2; case "item-speed": return 2; - case "item-strength": return 2; - case "item-shield": return 2; case "item-fuelregen": return 2; case "item-jetpack": return 2; case "tagged-target": return 2; @@ -241,6 +235,8 @@ string spritelookuptext(string s) case "enemyflagcarrier": return _("Enemy carrier"); case "flagcarrier": return _("Flag carrier"); case "flagdropped": return _("Dropped flag"); + case "keycarrier": return _("Key carrier"); + case "keydropped": return _("Dropped key"); case "helpme": return _("Help me!"); case "here": return _("Here"); case "key-dropped": return _("Dropped key"); @@ -251,18 +247,15 @@ string spritelookuptext(string s) case "keycarrier-red": return _("Key carrier"); case "keycarrier-yellow": return _("Key carrier"); case "redbase": return _("Red base"); + case "yellowbase": return _("Yellow base"); + case "neutralbase": return _("White base"); + case "pinkbase": return _("Pink base"); case "waypoint": return _("Waypoint"); - case "ons-gen-red": return _("Generator"); - case "ons-gen-blue": return _("Generator"); + case "ons-gen": return _("Generator"); case "ons-gen-shielded": return _("Generator"); - case "ons-cp-neut": return _("Control point"); - case "ons-cp-red": return _("Control point"); - case "ons-cp-blue": return _("Control point"); - case "ons-cp-atck-neut": return _("Control point"); - case "ons-cp-atck-red": return _("Control point"); - case "ons-cp-atck-blue": return _("Control point"); - case "ons-cp-dfnd-red": return _("Control point"); - case "ons-cp-dfnd-blue": return _("Control point"); + case "ons-cp": return _("Control point"); + case "ons-cp-atck": return _("Control point"); + case "ons-cp-dfnd": return _("Control point"); case "race-checkpoint": return _("Checkpoint"); case "race-finish": return _("Finish"); case "race-start": return _("Start"); @@ -279,13 +272,16 @@ string spritelookuptext(string s) case "item-invis": return _("Invisibility"); case "item-extralife": return _("Extra life"); case "item-speed": return _("Speed"); - case "item-strength": return _("Strength"); - case "item-shield": return _("Shield"); case "item-fuelregen": return _("Fuel regen"); case "item-jetpack": return _("Jet Pack"); case "frozen": return _("Frozen!"); + case "enemy": return _("Enemy"); case "tagged-target": return _("Tagged"); case "vehicle": return _("Vehicle"); + case "survivals": return _("Survivals"); + case "vipkills": return _("VIP kills"); + case "vip": return _("VIP"); + case "intruder": return _("Intruder!"); default: return s; } } diff --git a/qcsrc/client/waypointsprites.qh b/qcsrc/client/waypointsprites.qh index 948950833..d52a04807 100644 --- a/qcsrc/client/waypointsprites.qh +++ b/qcsrc/client/waypointsprites.qh @@ -1,3 +1,6 @@ // they are drawn using a .draw function void Ent_WaypointSprite(); void Ent_RemoveWaypointSprite(); + + +.float health; diff --git a/qcsrc/client/weapons/projectile.qc b/qcsrc/client/weapons/projectile.qc index e019b6755..e55d739f8 100644 --- a/qcsrc/client/weapons/projectile.qc +++ b/qcsrc/client/weapons/projectile.qc @@ -264,6 +264,9 @@ void Ent_Projectile() if(f & 2) { + string rm_suffix = strcat("rocketminsta_laser_", Static_Team_ColorName_Lower(self.team)); + if(particleeffectnum(rm_suffix) < 0 || Team_TeamToNumber(self.team) == -1) { rm_suffix = "TR_NEXUIZPLASMA"; } + self.cnt = ReadByte(); self.silent = (self.cnt & 0x80); @@ -273,11 +276,13 @@ void Ent_Projectile() self.traileffect = 0; switch(self.cnt) { - case PROJECTILE_ELECTRO: setmodel(self, "models/ebomb.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; + case PROJECTILE_ELECTRO: setmodel(self, "models/ebomb.mdl");self.traileffect = particleeffectnum(rm_suffix); break; case PROJECTILE_ROCKET: setmodel(self, "models/rocket.md3");self.traileffect = particleeffectnum("TR_ROCKET"); self.scale = 2; break; + case PROJECTILE_SUPERROCKET: setmodel(self, "models/rocket.md3");self.traileffect = particleeffectnum("TR_ROCKET"); self.scale = 15; break; + case PROJECTILE_ROCKETMINSTA_LASER: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum(rm_suffix); 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_ELECTRO_BEAM: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum(rm_suffix); 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; @@ -297,6 +302,7 @@ void Ent_Projectile() case PROJECTILE_MAGE_SPIKE: setmodel(self, "models/ebomb.mdl"); self.traileffect = particleeffectnum("TR_VORESPIKE"); break; case PROJECTILE_SHAMBLER_LIGHTNING: setmodel(self, "models/ebomb.mdl"); self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; + case PROJECTILE_SCRAG_SPIKE: setmodel(self, "models/plasmatrail.mdl"); self.traileffect = particleeffectnum("TR_WIZSPIKE"); 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; @@ -310,6 +316,7 @@ void Ent_Projectile() case PROJECTILE_BUMBLE_BEAM: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; case PROJECTILE_RPC: setmodel(self, "models/weapons/ok_rocket.md3");self.traileffect = particleeffectnum("TR_ROCKET"); break; + case PROJECTILE_CANNONBALL: setmodel(self, "models/sphere/sphere.md3");self.traileffect = particleeffectnum("TR_ROCKET"); break; default: if(Nade_IDFromProjectile(self.cnt) != 0) { setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum(Nade_TrailEffect(self.cnt, self.team)); break; } @@ -342,6 +349,18 @@ void Ent_Projectile() self.mins = '-3 -3 -3'; self.maxs = '3 3 3'; break; + case PROJECTILE_CANNONBALL: + self.colormod = '1 1 1'; + self.scale = 0.4; + loopsound(self, CH_SHOTS_SINGLE, "weapons/fireball_fly2.wav", VOL_BASE, ATTEN_NORM); + self.mins = '-3 -3 -3'; + self.maxs = '3 3 3'; + break; + case PROJECTILE_SUPERROCKET: + loopsound(self, CH_SHOTS_SINGLE, "weapons/rocket_fly.wav", VOL_BASE, ATTEN_NORM); + self.mins = '-32 -32 -32'; + self.maxs = '32 32 32'; + break; case PROJECTILE_GRENADE: self.mins = '-3 -3 -3'; self.maxs = '3 3 3'; diff --git a/qcsrc/common/animdecide.qc b/qcsrc/common/animdecide.qc index d2b849f58..fa1f8b9c5 100644 --- a/qcsrc/common/animdecide.qc +++ b/qcsrc/common/animdecide.qc @@ -129,6 +129,8 @@ vector animdecide_getupperanim(entity e) vector animdecide_getloweranim(entity e) { // death etc. + if(e.anim_state & ANIMSTATE_FOLLOW) + return vec3(((e.anim_state & ANIMSTATE_DUCK) ? e.anim_duckidle_x : e.anim_idle_x), e.anim_time, ANIMPRIO_DEAD); // dead priority so it's above all if(e.anim_state & ANIMSTATE_FROZEN) return vec3(e.anim_idle_x, e.anim_time, ANIMPRIO_DEAD); if(e.anim_state & ANIMSTATE_DEAD1) diff --git a/qcsrc/common/animdecide.qh b/qcsrc/common/animdecide.qh index b9d5260e6..2f67a3727 100644 --- a/qcsrc/common/animdecide.qh +++ b/qcsrc/common/animdecide.qh @@ -27,6 +27,7 @@ void animdecide_setstate(entity e, float newstate, float restart); #define ANIMSTATE_DEAD2 2 // base frames: die2 #define ANIMSTATE_DUCK 4 // turns walk into duckwalk, jump into duckjump, etc. #define ANIMSTATE_FROZEN 8 // force idle +#define ANIMSTATE_FOLLOW 16 // also force idle // implicit anim states (inferred from velocity, etc.) #define ANIMIMPLICITSTATE_INAIR 1 diff --git a/qcsrc/common/buffs.qc b/qcsrc/common/buffs.qc index 2f8e0fc23..9a00bcf75 100644 --- a/qcsrc/common/buffs.qc +++ b/qcsrc/common/buffs.qc @@ -43,7 +43,6 @@ float Buff_Type_FromSprite(string buff_sprite) return 0; } - float Buff_Skin(float buff_id) { entity e; diff --git a/qcsrc/common/buffs.qh b/qcsrc/common/buffs.qh index c29dad6dd..1c597b63d 100644 --- a/qcsrc/common/buffs.qh +++ b/qcsrc/common/buffs.qh @@ -1,8 +1,14 @@ +// Welcome to the stuff behind the scenes +// Below, you will find the list of buffs +// Add new buffs here! +// Note: Buffs also need spawnfuncs, which are set below + entity Buff_Type_first; entity Buff_Type_last; .entity enemy; // internal next pointer var float BUFF_LAST = 1; +float BUFF_ALL; .float items; // buff ID .string netname; // buff name @@ -18,6 +24,7 @@ var float BUFF_LAST = 1; { \ BUFF_##NAME = BUFF_LAST * 2; \ BUFF_LAST = BUFF_##NAME; \ + BUFF_ALL |= BUFF_##NAME; \ Buff_Type##sname = spawn(); \ Buff_Type##sname.items = BUFF_##NAME; \ Buff_Type##sname.netname = #sname; \ @@ -82,6 +89,10 @@ BUFF_SPAWNFUNC_Q3TA_COMPAT(ammoregen, BUFF_AMMO) BUFF_SPAWNFUNC_Q3TA_COMPAT(haste, BUFF_SPEED) BUFF_SPAWNFUNC_Q3TA_COMPAT(invis, BUFF_INVISIBLE) BUFF_SPAWNFUNC_Q3TA_COMPAT(medic, BUFF_MEDIC) + +#undef BUFF_SPAWNFUNC +#undef BUFF_SPAWNFUNC_Q3TA_COMPAT +#undef BUFF_SPAWNFUNCS #endif vector Buff_Color(float buff_id); diff --git a/qcsrc/common/command/generic.qc b/qcsrc/common/command/generic.qc index 743793bad..81ab30945 100644 --- a/qcsrc/common/command/generic.qc +++ b/qcsrc/common/command/generic.qc @@ -648,6 +648,7 @@ void GenericCommand_(float request) GENERIC_COMMAND("settemp", GenericCommand_settemp(request, arguments), "Temporarily set a value to a cvar which is restored later") \ GENERIC_COMMAND("settemp_restore", GenericCommand_settemp_restore(request, arguments), "Restore all cvars set by settemp command") \ GENERIC_COMMAND("runtest", GenericCommand_runtest(request, arguments), "Run unit tests") \ + GENERIC_COMMAND("script", GenericCommand_script(request, arguments, command), "Script interpreter") \ /* nothing */ void GenericCommand_macro_help() diff --git a/qcsrc/common/command/script.qc b/qcsrc/common/command/script.qc new file mode 100644 index 000000000..077dbdc5f --- /dev/null +++ b/qcsrc/common/command/script.qc @@ -0,0 +1,63 @@ +.float script_type; +.string script_value; +.entity script_next; + +#include "script/string.qh" +#include "script/ast.qh" +#include "script/lex.qh" +#include "script/parse.qh" + +// ======================================================================== +// Command +// ======================================================================== + +void GenericCommand_script(float request, float argc, string command) +{ + switch(request) + { + case CMD_REQUEST_COMMAND: + { + if ( argc >= 3 && argv(1) == "file" ) + { + float file = fopen(argv(2),FILE_READ); + if ( file < 0 ) + { + dprint("Script: unable to open file ",argv(2),"\n"); + return; + } + dprint("Script: executing ",argv(2),"\n"); + + + string source = ""; + string line; + while (( line = fgets(file) )) + source = strcat(source,line,"\n"); + fclose(file); + entity ast_root = script_compile(source); + + context_push(); + context_define_variable(current_context,"argc",ftos(argc-2)); + float i; + for ( i = 2; i < argc; i++ ) + context_define_variable(current_context,strcat("arg",ftos(i-2)),argv(i)); + script_evaluate(ast_root); + context_pop(); + + script_cleanup(ast_root); + } + else + { + entity ast_root = script_compile(substring(command, 7, strlen(command))); + print(strcat(script_evaluate(ast_root),"\n")); + script_cleanup(ast_root); + } + return; + } + + default: + case CMD_REQUEST_USAGE: + print(strcat("\nUsage:^3 ", GetProgramCommandPrefix(), " script EXPRESSION...\n")); + print(strcat("\nUsage:^3 ", GetProgramCommandPrefix(), " script file filename\n")); + return; + } +} \ No newline at end of file diff --git a/qcsrc/common/command/script.qh b/qcsrc/common/command/script.qh new file mode 100644 index 000000000..a35717016 --- /dev/null +++ b/qcsrc/common/command/script.qh @@ -0,0 +1,11 @@ + +void GenericCommand_script(float request, float argc, string command); + +// Compile the source into an abstract sytax tree +entity script_compile(string source); + +// Recursively evaluate an abstract syntax tree +string script_evaluate(entity ast_root); + +// Recursively remove all the nodes in the abstract syntax tree +void script_cleanup(entity ast_root); diff --git a/qcsrc/common/command/script/ast.qh b/qcsrc/common/command/script/ast.qh new file mode 100644 index 000000000..29939f6f4 --- /dev/null +++ b/qcsrc/common/command/script/ast.qh @@ -0,0 +1,959 @@ + +// ======================================================================== +// Abstract syntax Tree +// ======================================================================== + +.entity ast_operand1; +.entity ast_operand2; +.entity ast_operand3; +.entity() ast_evaluate; +.float ast_lvalue; +.string ast_lvalue_reference; +.float ast_max_iterations; +entity current_context; +.entity context_variables; +.entity context_functions; + +#ifdef MENUQC +.entity owner; +#endif + +const float AST_TYPE_VALUE = 0x00; +const float AST_TYPE_BREAK = 0x01; +const float AST_TYPE_RETURN = 0x02; +const float AST_TYPE_CONTINUE = 0x04; + +//--------------------------------------------------------------------- +// Global function definitions +//--------------------------------------------------------------------- + +#define SCRIPT_FLOAT_FUNCTION(name) \ + SCRIPT_GLOBAL_FUNCTION(name,script_ftos(name(stof(arg))),1 ) + +#define SCRIPT_STRING_FUNCTION(name) \ + SCRIPT_GLOBAL_FUNCTION(name,name(arg),1 ) + +#define SCRIPT_GLOBAL_FUNCTIONS \ + SCRIPT_FLOAT_FUNCTION(sin) \ + SCRIPT_FLOAT_FUNCTION(cos) \ + SCRIPT_FLOAT_FUNCTION(tan) \ + SCRIPT_FLOAT_FUNCTION(asin) \ + SCRIPT_FLOAT_FUNCTION(acos) \ + SCRIPT_FLOAT_FUNCTION(atan) \ + SCRIPT_FLOAT_FUNCTION(floor) \ + SCRIPT_FLOAT_FUNCTION(ceil) \ + SCRIPT_FLOAT_FUNCTION(sqrt) \ + SCRIPT_FLOAT_FUNCTION(log) \ + SCRIPT_STRING_FUNCTION(strtolower) \ + SCRIPT_STRING_FUNCTION(strtoupper) \ + SCRIPT_STRING_FUNCTION(strdecolorize) \ + SCRIPT_GLOBAL_FUNCTION(strlen,script_ftos(strlen(arg)),1 ) \ + SCRIPT_GLOBAL_FUNCTION(print,(dprint(arg,"\n"),""),1) \ + SCRIPT_GLOBAL_FUNCTION(localcmd,(localcmd("\n",arg,"\n"),""),1) \ + SCRIPT_GLOBAL_FUNCTION(random,(arg,script_ftos(random())),0 ) \ + SCRIPT_GLOBAL_FUNCTION(round,script_ftos(rint(stof(arg))),1 ) \ + SCRIPT_GLOBAL_FUNCTION(substring,substring(get_arg(1),stof(get_arg(2)),stof(get_arg(3))),3 ) \ + SCRIPT_GLOBAL_FUNCTION(spawn,script_ftos(num_for_edict(spawn())),0) \ + SCRIPT_GLOBAL_FUNCTION(remove,(remove(string_to_entity(arg)),""), 1) \ + SCRIPT_GLOBAL_FUNCTION(edict_get,entity_get(string_to_entity(get_arg(1)),get_arg(2)), 2 ) \ + SCRIPT_GLOBAL_FUNCTION(edict_set, \ + script_ftos(entity_set(string_to_entity(get_arg(1)),get_arg(2),get_arg(3))), 3 ) \ + SCRIPT_GLOBAL_FUNCTION(get,context_get_variable(current_context,arg),1) \ + SCRIPT_GLOBAL_FUNCTION(rgb_to_hexcolor,rgb_to_hexcolor(stov(arg)),1) \ + SCRIPT_GLOBAL_FUNCTION(vector,strcat("'",\ + script_ftos(stof(get_arg(1)))," ",\ + script_ftos(stof(get_arg(2)))," ",\ + script_ftos(stof(get_arg(3))),"'" ), 3 ) \ + + +#define SCRIPT_GLOBAL_FUNCTION(name,code,nargs) \ + entity script_global_function_##name () { \ + return ast_tempvalue(code); \ + } +#define arg \ + context_get_variable(current_context,"arg") +#define get_arg(n) \ + context_get_variable(current_context,strcat("arg",#n)) +string context_get_variable(entity context,string varname); +entity ast_tempvalue(string value); +SCRIPT_GLOBAL_FUNCTIONS +#undef get_arg +#undef arg +#undef SCRIPT_GLOBAL_FUNCTION + + +entity context_define_function(entity context, string funcname, entity executor, entity param_chain); +void script_global_functions_init(entity context) +{ + entity next_func; + entity param; + #define SCRIPT_GLOBAL_FUNCTION(name,code,nargs) \ + next_func = spawn(); \ + next_func.ast_evaluate = script_global_function_##name; \ + if ( nargs == 1 ) { \ + param = spawn(); \ + param.script_value = "arg"; \ + } else { \ + float i; \ + entity nextparam; \ + param = world; \ + for ( i = nargs; i > 0; i-- ) { \ + nextparam = spawn(); \ + nextparam.script_value = strcat("arg",ftos(i));\ + nextparam.script_next = param; \ + param = nextparam; \ + } \ + } \ + context_define_function(context,#name,next_func,param).classname = "script_global_function"; + SCRIPT_GLOBAL_FUNCTIONS + #undef SCRIPT_GLOBAL_FUNCTION +} + +//--------------------------------------------------------------------- +// Context stack operations +//--------------------------------------------------------------------- + +string context_get_variable(entity context,string varname) +{ + if ( strlen(varname) > 5 ) + if ( substring(varname,0,5) == "cvar_" ) + { + varname = substring(varname,5,strlen(varname)-5); + if ( cvar_type(varname) & CVAR_TYPEFLAG_EXISTS ) + return cvar_string(varname); + return ""; + } + entity currentvar = context.context_variables; + while ( currentvar ) + { + if ( currentvar.netname == varname ) + return currentvar.script_value; + currentvar = currentvar.script_next; + } + if ( context.owner ) + return context_get_variable(context.owner,varname); + return ""; +} +float context_set_variable(entity context,string varname, string newvalue) +{ + if ( strlen(varname) > 5 ) + if ( substring(varname,0,5) == "cvar_" ) + { + varname = substring(varname,5,strlen(varname)-5); +#ifdef MENUQC + registercvar(varname, "", 0); +#else + registercvar(varname, ""); +#endif + cvar_set(varname,newvalue); + return 1; + } + entity currentvar = context.context_variables; + while ( currentvar ) + { + if ( currentvar.netname == varname ) + { + currentvar.script_value = newvalue; + return 1; + } + currentvar = currentvar.script_next; + } + if ( context.owner ) + if ( context_set_variable(context.owner,varname,newvalue) ) + return 1; + return 0; +} +void context_define_variable(entity context,string varname, string newvalue) +{ + entity newvar = spawn(); + newvar.script_value = newvalue; + newvar.netname = varname; + newvar.classname = "script_variable"; + newvar.script_next = context.context_variables; + context.context_variables = newvar; +} +entity context_get_function(entity context, string funcname) +{ + entity currentfunc = context.context_functions; + while ( currentfunc ) + { + if ( currentfunc.netname == funcname ) + { + return currentfunc; + } + currentfunc = currentfunc.script_next; + } + if ( context.owner ) + return context_get_function(context.owner,funcname); + dprint("Script: called undefined function: ",funcname,"\n"); + return world; +} +entity context_define_function(entity context, string funcname, entity executor, entity param_chain) +{ + entity function = spawn(); + function.netname = funcname; + function.ast_operand1 = param_chain; + function.ast_operand2 = executor; + function.script_next = context.context_functions; + function.classname = "script_function"; + context.context_functions = function; + return function; +} +void context_push() +{ + entity new_context = spawn(); + new_context.classname = "script_context"; + new_context.owner = current_context; + if ( current_context ) + { + new_context.ast_max_iterations = current_context.ast_max_iterations; + } + else + { + new_context.ast_max_iterations = 100; // max recursion depth TODO: cvar + script_global_functions_init(new_context); + } + current_context = new_context; +} +void context_pop() +{ + if ( current_context ) + { + entity old_context = current_context; + current_context = current_context.owner; + script_cleanup(old_context.context_variables); + entity func = old_context.context_functions; + entity func_old; + while(func) + { + func_old = func; + func = func.script_next; + if ( func_old.classname == "script_global_function" ) + { + func_old.script_next = world; + script_cleanup(func_old.ast_operand1); + remove(func_old.ast_operand2); + } + remove(func_old); + } + old_context.context_functions = world; + remove(old_context); + } +} + +//--------------------------------------------------------------------- +// Recursive operations +//--------------------------------------------------------------------- + +entity ast_tempnull(); + +// Evaluate a tree and return the resulting entity +// Use this when you need to forward the result +entity script_evaluate_entity(entity ast_root) +{ + if ( !ast_root ) + return ast_tempnull(); + entity oldself = self; + self = ast_root; + entity result = self.ast_evaluate(); + self = oldself; + return result; +} + +// Evaluate a tree and return the resulting value +// Use this when you will use just the value for further operations +string script_evaluate(entity ast_root) +{ + entity e = script_evaluate_entity(ast_root); + string result = e.script_value; + if ( e.classname == "ast_tempvalue" ) + remove(e); + return result; +} + +void script_cleanup(entity ast_root) +{ + if ( !ast_root || wasfreed(ast_root) ) + return; + if ( ast_root.ast_operand1 ) + script_cleanup(ast_root.ast_operand1); + if ( ast_root.ast_operand2 ) + script_cleanup(ast_root.ast_operand2); + if ( ast_root.ast_operand3 ) + script_cleanup(ast_root.ast_operand3); + if ( ast_root.script_next ) + script_cleanup(ast_root.script_next); + remove(ast_root); +} + +void script_debug_ast_recursive(entity ast_root,string prefix) +{ + dprint(prefix,"^3",ast_root.classname,"^7: ",ast_root.script_value,"\n"); + if ( ast_root.ast_operand1 ) + script_debug_ast_recursive(ast_root.ast_operand1,strcat(prefix," op1 ")); + if ( ast_root.ast_operand2 ) + script_debug_ast_recursive(ast_root.ast_operand2,strcat(prefix," op2 ")); + if ( ast_root.ast_operand3 ) + script_debug_ast_recursive(ast_root.ast_operand3,strcat(prefix," op3 ")); + + if ( ast_root.script_next ) + script_debug_ast_recursive(ast_root.script_next,prefix); +} +void script_debug_ast(entity ast_root) +{ + if ( cvar("developer") ) + { + if ( !ast_root ) + dprint("Empty tree\n"); + else + { + dprint("(ast begin)\n"); + script_debug_ast_recursive(ast_root,"^8"); + dprint("(ast end)\n"); + } + } +} + +//--------------------------------------------------------------------- +// Tree structure +//--------------------------------------------------------------------- + +// simple string value +entity ast_tempvalue_evaluate() +{ + return self; +} +entity ast_tempvalue(string newvalue) +{ + entity ast_temp = spawn(); + ast_temp.script_value = newvalue; + ast_temp.ast_evaluate = ast_tempvalue_evaluate; + ast_temp.classname="ast_tempvalue"; + return ast_temp; +} +entity ast_tempnull() +{ + return ast_tempvalue(""); +} + +entity ast_simplevalue_evaluate() +{ + return ast_tempvalue(self.script_value); +} +entity ast_simplevalue(string newvalue) +{ + entity ast_temp = spawn(); + ast_temp.script_value = newvalue; + ast_temp.ast_evaluate = ast_simplevalue_evaluate; + ast_temp.classname="ast_simplevalue"; + return ast_temp; +} + +// variable reference +entity ast_variable_evaluate() +{ + entity val = ast_tempvalue(context_get_variable(current_context,self.script_value)); + val.ast_lvalue = self.ast_lvalue; + val.ast_lvalue_reference = self.ast_lvalue_reference; + return val; +} +entity ast_variable(string var_name) +{ + entity ast_temp = spawn(); + ast_temp.script_value = var_name; + ast_temp.ast_evaluate = ast_variable_evaluate; + ast_temp.classname="ast_variable"; + ast_temp.ast_lvalue = -1; + ast_temp.ast_lvalue_reference = var_name; + return ast_temp; +} + +entity ast_field_evaluate() +{ + string entname = script_evaluate(self.ast_operand1); + entity ent = string_to_entity(entname); + if ( ent ) + { + entity val = ast_tempvalue(entity_get(ent,self.ast_lvalue_reference)); + val.ast_lvalue = stof(entname); + val.ast_lvalue_reference = self.ast_lvalue_reference; + return val; + } + + dprint("Access to invalid entity field: #",entname,".",self.ast_lvalue_reference,"\n"); + return ast_tempnull(); +} + +// entity field access +entity ast_field(entity operand,string field_name) +{ + entity ast_temp = spawn(); + ast_temp.script_value = field_name; + ast_temp.ast_operand1 = operand; + ast_temp.ast_evaluate = ast_field_evaluate; + ast_temp.classname="ast_field"; + ast_temp.ast_lvalue = -1; + ast_temp.ast_lvalue_reference = field_name; + return ast_temp; +} + +// variable assignment +entity ast_assign_evaluate() +{ + entity lval = script_evaluate_entity(self.ast_operand1); + entity rval = script_evaluate_entity(self.ast_operand2); + + if ( lval.ast_lvalue < 0 ) + { + if ( !context_set_variable(current_context,lval.ast_lvalue_reference,rval.script_value) ) + context_define_variable(current_context,lval.ast_lvalue_reference,rval.script_value); + } + else if ( lval.ast_lvalue > 0 ) + { + if ( !entity_set(float_to_entity(lval.ast_lvalue),lval.ast_lvalue_reference,rval.script_value) ) + dprint("Assignment to invalid entity field: #", + ftos(lval.ast_lvalue),".",lval.ast_lvalue_reference,"\n"); + } + else + dprint("Assignment ro rvalue: ",lval.script_value," = ",rval.script_value,"\n"); + rval.ast_lvalue = lval.ast_lvalue; + rval.ast_lvalue_reference = lval.ast_lvalue_reference; + remove(lval); + return rval; +} +entity ast_assign(entity lvalue,entity rvalue) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = lvalue; + ast_temp.ast_operand2 = rvalue; + ast_temp.ast_evaluate = ast_assign_evaluate; + ast_temp.ast_lvalue = -1; + ast_temp.classname="ast_assign"; + return ast_temp; +} +entity ast_assign_operator_evaluate() +{ + entity lval_tree = self.ast_operand1; + self.ast_operand1 = script_evaluate_entity(lval_tree); + self.ast_operand1.classname = "ast_assign_operator_lval"; + self.ast_operand2.ast_operand1 = self.ast_operand1; + entity result = ast_assign_evaluate(); + self.ast_operand2.ast_operand1 = world; + self.ast_operand1 = lval_tree; + return result; +} +entity ast_assign_operator(entity lvalue,entity rvalue,entity(entity,entity) operator) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = lvalue; + ast_temp.ast_operand2 = operator(world,rvalue); + ast_temp.ast_evaluate = ast_assign_operator_evaluate; + ast_temp.ast_lvalue = -1; + ast_temp.classname="ast_assign_operator"; + return ast_temp; +} + +// function call +entity ast_function_evaluate() +{ + entity function = context_get_function(current_context,self.script_value); + entity next_arg = self.ast_operand1; + entity next_param = function.ast_operand1; + if ( function && current_context.ast_max_iterations > 0 ) + { + context_push(); + current_context.ast_max_iterations -= 1; + + while ( next_arg && next_param) + { + context_define_variable(current_context,next_param.script_value,script_evaluate(next_arg)); + next_arg = next_arg.script_next; + next_param = next_param.script_next; + } + while ( next_param ) + { + context_define_variable(current_context,next_param.script_value, + script_evaluate(next_param.ast_operand1) ); + next_param = next_param.script_next; + } + entity newvalue = script_evaluate_entity(function.ast_operand2); + context_pop(); + newvalue.script_type &= ~AST_TYPE_RETURN; + return newvalue; + } + return ast_tempnull(); +} +entity ast_function(string newname) +{ + entity ast_temp = spawn(); + ast_temp.script_value = newname; + ast_temp.ast_evaluate = ast_function_evaluate; + ast_temp.classname="ast_function"; + return ast_temp; +} + +entity ast_function_declaration_evaluate() +{ + context_define_function(current_context,self.script_value,self.ast_operand2,self.ast_operand1); + return ast_tempnull(); +} +entity ast_function_declaration(string newname) +{ + entity ast_temp = spawn(); + ast_temp.script_value = newname; + ast_temp.ast_evaluate = ast_function_declaration_evaluate; + ast_temp.classname="ast_function_declaration"; + return ast_temp; +} + +// control structure +entity ast_if_evaluate() +{ + if ( stof(script_evaluate(self.ast_operand1)) ) + return script_evaluate_entity(self.ast_operand2); + return script_evaluate_entity(self.ast_operand3); +} +entity ast_if(entity condition, entity branch_true, entity branch_false) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = condition; + ast_temp.ast_operand2 = branch_true; + ast_temp.ast_operand3 = branch_false; + ast_temp.ast_evaluate = ast_if_evaluate; + ast_temp.classname="ast_if"; + return ast_temp; +} + +entity ast_while_evaluate() +{ + entity val = world; + float it = 0; + while ( stof(script_evaluate(self.ast_operand1)) && + it < self.ast_max_iterations ) + { + if ( val ) + remove(val); + val = script_evaluate_entity(self.ast_operand2); + if ( val.script_type & (AST_TYPE_BREAK|AST_TYPE_RETURN) ) + { + val.script_type &= ~ AST_TYPE_BREAK; + break; + } + it++; + } + return val; +} +entity ast_while(entity condition, entity body) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = condition; + ast_temp.ast_operand2 = body; + ast_temp.ast_evaluate = ast_while_evaluate; + ast_temp.ast_max_iterations = 1000; // TODO: cvar + ast_temp.classname="ast_while"; + return ast_temp; +} + +entity ast_block_evaluate() +{ + entity val = world; + entity next = self.ast_operand1; + context_push(); + while ( next ) + { + if ( val ) + remove(val); + val = script_evaluate_entity(next); + if ( val.script_type & (AST_TYPE_BREAK|AST_TYPE_RETURN|AST_TYPE_CONTINUE) ) + { + val.script_type &= ~AST_TYPE_CONTINUE; + break; + } + next = next.script_next; + } + context_pop(); + return val; +} +entity ast_block() +{ + entity ast_temp = spawn(); + ast_temp.ast_evaluate = ast_block_evaluate; + ast_temp.classname="ast_block"; + return ast_temp; +} + +entity ast_break_evaluate() +{ + entity val = ast_tempvalue(self.script_value); + val.script_type = self.script_type; + return val; +} +entity ast_break() +{ + entity ast_temp = spawn(); + ast_temp.script_type = AST_TYPE_BREAK; + ast_temp.classname="ast_break"; + ast_temp.ast_evaluate = ast_break_evaluate; + return ast_temp; +} +entity ast_continue() +{ + entity ast_temp = spawn(); + ast_temp.script_type = AST_TYPE_CONTINUE; + ast_temp.classname="ast_continue"; + ast_temp.ast_evaluate = ast_break_evaluate; + return ast_temp; +} + +entity ast_return_evaluate() +{ + entity val = script_evaluate_entity(self.ast_operand1); + val.script_type = self.script_type; + return val; +} +entity ast_return(entity newvalue) +{ + entity ast_temp = spawn(); + ast_temp.script_type = AST_TYPE_RETURN; + ast_temp.classname="ast_return"; + ast_temp.ast_evaluate = ast_return_evaluate; + ast_temp.ast_operand1 = newvalue; + return ast_temp; +} + + +// Simple unary oprators +entity ast_unaryminus_evaluate() +{ + return ast_tempvalue(script_ftos(-stof( script_evaluate(self.ast_operand1) ))); +} +entity ast_unaryminus(entity operand) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand; + ast_temp.ast_evaluate = ast_unaryminus_evaluate; + ast_temp.classname="ast_unaryminus"; + return ast_temp; +} +entity ast_bitnot_evaluate() +{ + return ast_tempvalue(script_ftos( ~ stof( + script_evaluate(self.ast_operand1) ))); +} +entity ast_bitnot(entity operand) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand; + ast_temp.ast_evaluate = ast_bitnot_evaluate; + ast_temp.classname="ast_bitnot"; + return ast_temp; +} +entity ast_not_evaluate() +{ + return ast_tempvalue(script_ftos( ! stof( + script_evaluate(self.ast_operand1) ))); +} +entity ast_not(entity operand) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand; + ast_temp.ast_evaluate = ast_not_evaluate; + ast_temp.classname="ast_not"; + return ast_temp; +} + +// Simple binary operators +entity ast_mul_evaluate() +{ + return ast_tempvalue(script_ftos( + stof(script_evaluate(self.ast_operand1)) * + stof(script_evaluate(self.ast_operand2)) + )); +} +entity ast_mul(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_mul_evaluate; + ast_temp.classname="ast_mul"; + return ast_temp; +} +entity ast_div_evaluate() +{ + return ast_tempvalue(script_ftos( + stof(script_evaluate(self.ast_operand1)) / + stof(script_evaluate(self.ast_operand2)) + )); +} +entity ast_div(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_div_evaluate; + ast_temp.classname="ast_div"; + return ast_temp; +} +entity ast_mod_evaluate() +{ + return ast_tempvalue(script_ftos( + stof(script_evaluate(self.ast_operand1)) % + stof(script_evaluate(self.ast_operand2)) + )); +} +entity ast_mod(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_mod_evaluate; + ast_temp.classname="ast_mod"; + return ast_temp; +} +entity ast_sum_evaluate() +{ + string op1 = script_evaluate(self.ast_operand1); + string op2 = script_evaluate(self.ast_operand2); + if ( is_numeric(op1) && is_numeric(op2) ) + return ast_tempvalue(script_ftos(stof(op1)+stof(op2))); + return ast_tempvalue(strcat(op1,op2)); +} +entity ast_sum(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_sum_evaluate; + ast_temp.classname="ast_sum"; + return ast_temp; +} +entity ast_sub_evaluate() +{ + return ast_tempvalue(script_ftos( + stof(script_evaluate(self.ast_operand1)) - + stof(script_evaluate(self.ast_operand2)) + )); +} +entity ast_sub(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_sub_evaluate; + ast_temp.classname="ast_sub"; + return ast_temp; +} +entity ast_bitand_evaluate() +{ + return ast_tempvalue(script_ftos( + stof(script_evaluate(self.ast_operand1)) & + stof(script_evaluate(self.ast_operand2)) + )); +} +entity ast_bitand(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_bitand_evaluate; + ast_temp.classname="ast_bitand"; + return ast_temp; +} +entity ast_bitor_evaluate() +{ + return ast_tempvalue(script_ftos( + stof(script_evaluate(self.ast_operand1)) | + stof(script_evaluate(self.ast_operand2)) + )); +} +entity ast_bitor(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_bitor_evaluate; + ast_temp.classname="ast_bitor"; + return ast_temp; +} +entity ast_bitxor_evaluate() +{ + return ast_tempvalue(script_ftos( + stof(script_evaluate(self.ast_operand1)) ^ + stof(script_evaluate(self.ast_operand2)) + )); +} +entity ast_bitxor(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_bitxor_evaluate; + ast_temp.classname="ast_bitxor"; + return ast_temp; +} +entity ast_bitlshift_evaluate() +{ + return ast_tempvalue(script_ftos( + stof(script_evaluate(self.ast_operand1)) << + stof(script_evaluate(self.ast_operand2)) + )); +} +entity ast_bitlshift(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_bitlshift_evaluate; + ast_temp.classname="ast_bitlshift"; + return ast_temp; +} +entity ast_bitrshift_evaluate() +{ + return ast_tempvalue(script_ftos( + stof(script_evaluate(self.ast_operand1)) >> + stof(script_evaluate(self.ast_operand2)) + )); +} +entity ast_bitrshift(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_bitrshift_evaluate; + ast_temp.classname="ast_bitrshift"; + return ast_temp; +} +entity ast_eq_evaluate() +{ + return ast_tempvalue(script_ftos( + script_evaluate(self.ast_operand1) == + script_evaluate(self.ast_operand2) + )); +} +entity ast_eq(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_eq_evaluate; + ast_temp.classname="ast_eq"; + return ast_temp; +} +entity ast_ne_evaluate() +{ + string op1 = script_evaluate(self.ast_operand1); + string op2 = script_evaluate(self.ast_operand2); + if ( is_numeric(op1) && is_numeric(op2) ) + return ast_tempvalue(script_ftos(stof(op1) != stof(op2))); + return ast_tempvalue(script_ftos(strcmp(op1,op2)!=0)); +} +entity ast_ne(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_ne_evaluate; + ast_temp.classname="ast_ne"; + return ast_temp; +} +entity ast_lt_evaluate() +{ + string op1 = script_evaluate(self.ast_operand1); + string op2 = script_evaluate(self.ast_operand2); + if ( is_numeric(op1) && is_numeric(op2) ) + return ast_tempvalue(script_ftos(stof(op1)stof(op2))); + return ast_tempvalue(script_ftos(strcmp(op1,op2)>0)); +} +entity ast_gt(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_gt_evaluate; + ast_temp.classname="ast_gt"; + return ast_temp; +} +entity ast_ge_evaluate() +{ + string op1 = script_evaluate(self.ast_operand1); + string op2 = script_evaluate(self.ast_operand2); + if ( is_numeric(op1) && is_numeric(op2) ) + return ast_tempvalue(script_ftos(stof(op1)>=stof(op2))); + return ast_tempvalue(script_ftos(strcmp(op1,op2)>=0)); +} +entity ast_ge(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_ge_evaluate; + ast_temp.classname="ast_ge"; + return ast_temp; +} +entity ast_and_evaluate() +{ + return ast_tempvalue(script_ftos( + stof(script_evaluate(self.ast_operand1)) && + stof(script_evaluate(self.ast_operand2)) + )); +} +entity ast_and(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_and_evaluate; + ast_temp.classname="ast_and"; + return ast_temp; +} +entity ast_or_evaluate() +{ + return ast_tempvalue(script_ftos( + stof(script_evaluate(self.ast_operand1)) || + stof(script_evaluate(self.ast_operand2)) + )); +} +entity ast_or(entity operand1, entity operand2) +{ + entity ast_temp = spawn(); + ast_temp.ast_operand1 = operand1; + ast_temp.ast_operand2 = operand2; + ast_temp.ast_evaluate = ast_or_evaluate; + ast_temp.classname="ast_or"; + return ast_temp; +} \ No newline at end of file diff --git a/qcsrc/common/command/script/lex.qh b/qcsrc/common/command/script/lex.qh new file mode 100644 index 000000000..814c4dd34 --- /dev/null +++ b/qcsrc/common/command/script/lex.qh @@ -0,0 +1,213 @@ + +// ======================================================================== +// Lexer +// ======================================================================== + +const float LEX_UNKNOWN = 0; +const float LEX_VALUE_NUMBER = 1; +const float LEX_VALUE_STRING = 2; +const float LEX_IDENTIFIER = 3; +const float LEX_KEYWORD = 4; +const float LEX_EOF = 5; +const float LEX_OPERATOR_OTHER = 100; +const float LEX_OPERATOR_ARITH = 101; +const float LEX_OPERATOR_MULT = 102; +const float LEX_OPERATOR_ASSIGN = 103; +const float LEX_OPERATOR_BIT = 104; +const float LEX_OPERATOR_LOGICAL = 105; +const float LEX_OPERATOR_COMPARE = 106; +const float LEX_OPERATOR_COMMA = 107; +const float LEX_OPERATOR_INCREMENT=108; + +float lex_string_index; +float lex_string_end; +string lex_string; +entity lex_lookahead; + +entity lex_move_lookahead() +{ + entity tmp = lex_lookahead; + lex_lookahead = world; + return tmp; +} +void lex_token(float type, string lexeme) +{ + if ( lex_lookahead ) + remove(lex_lookahead); + lex_lookahead = spawn(); + lex_lookahead.script_value = lexeme; + lex_lookahead.script_type = type; + lex_lookahead.classname = "lex_token"; +} + +void lex_skipws() +{ + while ( lex_string_index < lex_string_end && + ctype_space(substring(lex_string,lex_string_index,1)) ) + { + lex_string_index++; + } +} + +string lex_identifier() +{ + float start_index = lex_string_index; + while( lex_string_index < lex_string_end && + ctype_identifier(substring(lex_string,lex_string_index,1)) ) + { + lex_string_index++; + } + return substring(lex_string,start_index,lex_string_index-start_index); +} + +// TODO: allow exponent 1.2e-3 +string lex_number() +{ + string nextch; + float start_index = lex_string_index; + float candot = 1; + while( lex_string_index < lex_string_end ) + { + nextch = substring(lex_string,lex_string_index,1); + if ( ctype_digit(nextch) ) + { + lex_string_index++; + } + else if ( candot && nextch == "." ) + { + candot = 0; + lex_string_index++; + } + else + { + break; + } + } + // ensure that trailing fractional zeros are truncated + return script_ftos(stof(substring(lex_string,start_index,lex_string_index-start_index))); +} + +string lex_string_literal() +{ + string nextch; + lex_string_index++; // skip " + string lexeme = ""; + while( lex_string_index < lex_string_end ) + { + nextch = substring(lex_string,lex_string_index,1); + + lex_string_index++; + if ( nextch == "\"" ) + break; + + if ( nextch == "\\" && lex_string_index < lex_string_end ) + { + nextch = substring(lex_string,lex_string_index,1); + if ( nextch == "\\" || nextch == "\"" ) + lex_string_index++; + else if ( nextch == "n" ) + { + nextch = "\n"; + lex_string_index++; + } + else + nextch = "\\"; + } + + lexeme = strcat(lexeme,nextch); + } + return lexeme; +} + +void lex_get_token() +{ + lex_skipws(); + if ( lex_string_index >= lex_string_end ) + return lex_token(LEX_EOF,""); + + string nextch = substring(lex_string,lex_string_index,1); + + if ( ctype_digit(nextch) || + ( nextch == "." && ctype_digit(substring(lex_string,lex_string_index+1,1)) ) ) + { + return lex_token(LEX_VALUE_NUMBER,lex_number()); + } + + if ( ctype_identifier(nextch) ) + { + string id = lex_identifier(); + if ( id == "if" || id == "else" || id == "while" || id == "for" || + id == "function" || id == "return" || id == "break" || id == "continue" ) + return lex_token(LEX_KEYWORD,id); + return lex_token(LEX_IDENTIFIER,id); + } + + if ( nextch == "\"" ) + { + return lex_token(LEX_VALUE_STRING,lex_string_literal()); + } + + lex_string_index++; + + if ( nextch == "(" || nextch == "{" ) + return lex_token(LEX_OPERATOR_OTHER,"("); + if ( nextch == ")" || nextch == "}" ) + return lex_token(LEX_OPERATOR_OTHER,")"); + if ( nextch == "?" || nextch == ":" || nextch == "." ) + return lex_token(LEX_OPERATOR_OTHER,nextch); + if ( nextch == "," || nextch == ";" ) + return lex_token(LEX_OPERATOR_COMMA,nextch); + + if ( lex_string_index < lex_string_end && substring(lex_string,lex_string_index,1) == "=" ) + { + lex_string_index++; + + if ( nextch == "=" || nextch == "!" || nextch == "<" || nextch == ">" ) + return lex_token(LEX_OPERATOR_COMPARE,strcat(nextch,"=")); + + if ( nextch == "&" || nextch == "^" || nextch == "|" || + nextch == "+" || nextch == "-" || nextch == "*" || nextch == "/" || nextch == "%" ) + return lex_token(LEX_OPERATOR_ASSIGN,strcat(nextch,"=")); + + lex_string_index--; + } + + if ( nextch == "+" || nextch == "-" ) + { + if ( substring(lex_string,lex_string_index,1) == nextch ) + { + lex_string_index++; + return lex_token(LEX_OPERATOR_INCREMENT,strcat(nextch,nextch)); + } + return lex_token(LEX_OPERATOR_ARITH,nextch); + } + if ( nextch == "*" || nextch == "/" || nextch == "%" ) + return lex_token(LEX_OPERATOR_MULT,nextch); + if ( nextch == "^" || nextch == "~" ) + return lex_token(LEX_OPERATOR_BIT,nextch); + if ( nextch == "|" || nextch == "&" ) + { + if ( substring(lex_string,lex_string_index,1) == nextch ) + { + lex_string_index++; + return lex_token(LEX_OPERATOR_LOGICAL,strcat(nextch,nextch)); + } + return lex_token(LEX_OPERATOR_BIT,nextch); + } + if ( nextch == "<" || nextch == ">" ) + { + if ( substring(lex_string,lex_string_index,1) == nextch ) + { + lex_string_index++; + return lex_token(LEX_OPERATOR_BIT,strcat(nextch,nextch)); + } + return lex_token(LEX_OPERATOR_COMPARE,nextch); + } + if ( nextch == "!" ) + return lex_token(LEX_OPERATOR_LOGICAL,nextch); + if ( nextch == "=" ) + return lex_token(LEX_OPERATOR_ASSIGN,nextch); + + return lex_token(LEX_UNKNOWN,substring(lex_string,lex_string_index-1,1)); +} + diff --git a/qcsrc/common/command/script/parse.qh b/qcsrc/common/command/script/parse.qh new file mode 100644 index 000000000..715d0d605 --- /dev/null +++ b/qcsrc/common/command/script/parse.qh @@ -0,0 +1,445 @@ + +// ======================================================================== +// Parser +// ======================================================================== +entity parse_block(); +entity parse_expression(); + +entity parse_identifier(string id) +{ + if ( lex_lookahead.script_type == LEX_OPERATOR_OTHER && + lex_lookahead.script_value == "(" ) + { + entity function_call = ast_function(id); + lex_get_token(); + if ( lex_lookahead.script_type == LEX_OPERATOR_OTHER && lex_lookahead.script_value == ")" ) + lex_get_token(); + else + { + entity param = world; + entity nextparam; + while ( lex_lookahead.script_type != LEX_EOF ) + { + nextparam = parse_expression(); + if ( !nextparam ) + break; + if ( !param ) + function_call.ast_operand1 = nextparam; + else + param.script_next = nextparam; + param = nextparam; + + if ( lex_lookahead.script_type == LEX_OPERATOR_OTHER && + lex_lookahead.script_value == ")" ) + { + lex_get_token(); + break; + } + + lex_get_token(); + } + } + return function_call; + } + return ast_variable(id); +} + +entity parse_primary() +{ + entity lex_prev = lex_move_lookahead(); + lex_get_token(); + entity result = world; + switch(lex_prev.script_type) + { + case LEX_IDENTIFIER: + result = parse_identifier(lex_prev.script_value); + break; + case LEX_VALUE_NUMBER: + case LEX_VALUE_STRING: + result = ast_simplevalue(lex_prev.script_value); + break; + case LEX_OPERATOR_ARITH: + result = parse_primary(); + if ( lex_prev.script_value == "-" ) + result = ast_unaryminus(result); + break; + case LEX_OPERATOR_OTHER: + if ( lex_prev.script_value == "(" ) + { + result = parse_block(); + lex_get_token(); // remove ) + } + break; + case LEX_OPERATOR_BIT: + if ( lex_prev.script_value == "~" ) + { + result = parse_primary(); + result = ast_bitnot(result); + } + break; + case LEX_OPERATOR_LOGICAL: + if ( lex_prev.script_value == "!" ) + { + result = parse_primary(); + result = ast_not(result); + } + break; + case LEX_OPERATOR_INCREMENT: + if ( lex_lookahead.script_type == LEX_IDENTIFIER ) + { + entity (entity, entity) operator = ast_sum; + if ( lex_prev.script_value == "--" ) + operator = ast_sub; + result = ast_assign_operator(ast_variable(lex_lookahead.script_value), + ast_simplevalue("1"),operator); + lex_get_token(); + } + break; + case LEX_KEYWORD: + if ( lex_prev.script_value == "return" ) + { + if ( lex_lookahead.script_type == LEX_OPERATOR_COMMA ) + lex_get_token(); // remove ,; + else + result = parse_expression(); + result = ast_return(result); + } + else if ( lex_prev.script_value == "break" ) + { + result = ast_break(); + } + else if ( lex_prev.script_value == "continue" ) + { + result = ast_continue(); + } + break; + } + remove(lex_prev); + return result; +} + +entity parse_field() +{ + entity val = parse_primary(); + while(lex_lookahead.script_type == LEX_OPERATOR_OTHER && + lex_lookahead.script_value == "." && val.ast_lvalue ) + { + lex_get_token(); + val = ast_field(val,lex_lookahead.script_value); + lex_get_token(); + } + return val; +} + +entity parse_multiplication() +{ + entity val = parse_field(); + entity lex_prev; + while(lex_lookahead.script_type == LEX_OPERATOR_MULT) + { + lex_prev = lex_move_lookahead(); + lex_get_token(); + if ( lex_prev.script_value == "/" ) + val = ast_div(val,parse_field()); + else if ( lex_prev.script_value == "%" ) + val = ast_mod(val,parse_field()); + else + val = ast_mul(val,parse_field()); + remove(lex_prev); + } + return val; +} + +entity parse_arithmetic() +{ + entity val = parse_multiplication(); + entity lex_prev; + while(lex_lookahead.script_type == LEX_OPERATOR_ARITH) + { + lex_prev = lex_move_lookahead(); + lex_get_token(); + if ( lex_prev.script_value == "-" ) + val = ast_sub(val,parse_multiplication()); + else + val = ast_sum(val,parse_multiplication()); + remove(lex_prev); + } + return val; +} + +entity parse_bitwise() +{ + entity val = parse_arithmetic(); + entity lex_prev; + while(lex_lookahead.script_type == LEX_OPERATOR_BIT) + { + lex_prev = lex_move_lookahead(); + lex_get_token(); + if ( lex_prev.script_value == "<<" ) + val = ast_bitlshift(val,parse_arithmetic()); + else if ( lex_prev.script_value == ">>" ) + val = ast_bitrshift(val,parse_arithmetic()); + else if ( lex_prev.script_value == "&" ) + val = ast_bitand(val,parse_arithmetic()); + else if ( lex_prev.script_value == "^" ) + val = ast_bitxor(val,parse_arithmetic()); + else + val = ast_bitor(val,parse_arithmetic()); + remove(lex_prev); + } + return val; +} + +entity parse_comparison() +{ + entity val = parse_bitwise(); + entity lex_prev; + while(lex_lookahead.script_type == LEX_OPERATOR_COMPARE) + { + lex_prev = lex_move_lookahead(); + lex_get_token(); + if ( lex_prev.script_value == "<" ) + val = ast_lt(val,parse_bitwise()); + else if ( lex_prev.script_value == "<=" ) + val = ast_le(val,parse_bitwise()); + else if ( lex_prev.script_value == "!=" ) + val = ast_ne(val,parse_bitwise()); + else if ( lex_prev.script_value == ">=" ) + val = ast_ge(val,parse_bitwise()); + else if ( lex_prev.script_value == ">" ) + val = ast_gt(val,parse_bitwise()); + else + val = ast_eq(val,parse_bitwise()); + remove(lex_prev); + } + return val; +} + +entity parse_boolean() +{ + entity val = parse_comparison(); + entity lex_prev; + while(lex_lookahead.script_type == LEX_OPERATOR_LOGICAL) + { + lex_prev = lex_move_lookahead(); + lex_get_token(); + if ( lex_prev.script_value == "||" ) + val = ast_or(val,parse_comparison()); + else + val = ast_and(val,parse_comparison()); + remove(lex_prev); + } + return val; +} + +entity parse_expression() +{ + entity val = parse_boolean(); + + if ( lex_lookahead.script_type == LEX_OPERATOR_OTHER && + lex_lookahead.script_value == "?" ) + { + lex_get_token(); + entity branch_true = parse_boolean(); + entity branch_false; + if ( lex_lookahead.script_type == LEX_OPERATOR_OTHER && + lex_lookahead.script_value == ":" ) + { + lex_get_token(); + branch_false = parse_boolean(); + } + else + branch_false = ast_simplevalue(""); + + return ast_if(val,branch_true,branch_false); + } + + return val; +} + +entity parse_assignment() +{ + entity lval = parse_expression(); + if ( lex_lookahead.script_type == LEX_OPERATOR_ASSIGN && lval.ast_lvalue ) + { + string oper = substring(lex_lookahead.script_value,0,1); + lex_get_token(); + entity rval = parse_expression(); + entity(entity,entity) operator; + if ( oper == "+" ) + operator = ast_sum; + else if ( oper == "-" ) + operator = ast_sub; + else if ( oper == "*" ) + operator = ast_mul; + else if ( oper == "/" ) + operator = ast_div; + else if ( oper == "%" ) + operator = ast_mod; + else if ( oper == "^" ) + operator = ast_bitxor; + else if ( oper == "&" ) + operator = ast_bitand; + else if ( oper == "|" ) + operator = ast_bitor; + else + return ast_assign(lval,rval); + return ast_assign_operator(lval,rval,operator); + } + else if ( lex_lookahead.script_type == LEX_OPERATOR_INCREMENT && lval.ast_lvalue ) + { + entity (entity, entity) operator = ast_sum; + if ( lex_lookahead.script_value == "--" ) + operator = ast_sub; + lex_get_token(); + return ast_assign_operator(lval,ast_simplevalue("1"),operator); + } + + return lval; +} + +entity parse_statement() +{ + if ( lex_lookahead.script_type == LEX_KEYWORD ) + { + if ( lex_lookahead.script_value == "if" ) + { + lex_get_token(); // remove if + lex_get_token(); // remove ( + entity condition = parse_assignment(); + lex_get_token(); // remove ) + entity branch_true = parse_statement(); + entity branch_false; + if ( lex_lookahead.script_type == LEX_KEYWORD && lex_lookahead.script_value == "else" ) + { + lex_get_token(); // remove else + branch_false = parse_statement(); + } + else + branch_false = ast_simplevalue(""); + + return ast_if(condition,branch_true,branch_false); + } + else if ( lex_lookahead.script_value == "while" ) + { + lex_get_token(); // remove while + lex_get_token(); // remove ( + entity condition = parse_assignment(); + lex_get_token(); // remove ) + + return ast_while(condition,parse_statement()); + } + else if ( lex_lookahead.script_value == "for" ) + { + lex_get_token(); // remove for + lex_get_token(); // remove ( + entity start = parse_assignment(); + lex_get_token(); // remove ; + entity condition = parse_assignment(); + lex_get_token(); // remove ; + entity increment = parse_assignment(); + lex_get_token(); // remove ) + + // { block; increment } + entity for_inner_block = ast_block(); + for_inner_block.ast_operand1 = parse_statement(); + if ( for_inner_block.ast_operand1 ) + for_inner_block.ast_operand1.script_next = increment; + else + for_inner_block.ast_operand1 = increment; + + // { start; while ( condition ) inner_block } + entity for_outer_block = ast_block(); + for_outer_block.ast_operand1 = start; + start.script_next = ast_while(condition,for_inner_block); + + return for_outer_block; + } + else if ( lex_lookahead.script_value == "function" ) + { + lex_get_token(); // remove function + string id = lex_lookahead.script_value; + lex_get_token(); // remove id + lex_get_token(); // remove ( + entity func_declaration = ast_function_declaration(id); + + if ( lex_lookahead.script_type == LEX_OPERATOR_OTHER && lex_lookahead.script_value == ")" ) + lex_get_token(); + else + { + entity param = world; + entity nextparam; + while ( lex_lookahead.script_type != LEX_EOF ) + { + nextparam = spawn(); + nextparam.script_value = lex_lookahead.script_value; + lex_get_token(); // remove param name + if ( lex_lookahead.script_type == LEX_OPERATOR_ASSIGN && + lex_lookahead.script_value == "=" ) + { + lex_get_token(); // remove = + nextparam.ast_operand1 = parse_expression(); + } + if ( !param ) + func_declaration.ast_operand1 = nextparam; + else + param.script_next = nextparam; + param = nextparam; + + if ( lex_lookahead.script_type == LEX_OPERATOR_OTHER && + lex_lookahead.script_value == ")" ) + { + lex_get_token(); + break; + } + + lex_get_token(); + + } + } + func_declaration.ast_operand2 = parse_statement(); + + return func_declaration; + } + } + + + entity val = parse_assignment(); + if ( lex_lookahead.script_type == LEX_OPERATOR_COMMA ) + lex_get_token(); // skip ; and , + return val; +} + +entity parse_block() +{ + entity val = ast_block(); + entity prev = world; + entity next; + while ( lex_lookahead.script_type != LEX_EOF && + !( lex_lookahead.script_type == LEX_OPERATOR_OTHER && lex_lookahead.script_value == ")" ) ) + { + next = parse_statement(); + if ( !next ) + break; + if ( prev ) + prev.script_next = next; + else + val.ast_operand1 = next; + prev = next; + } + return val; +} + +entity script_compile(string source) +{ + lex_string_index = 0; + lex_string_end = strlen(source); + lex_string = source; + lex_get_token(); + entity ast = parse_block(); + remove(lex_lookahead); + lex_lookahead = world; + //script_debug_ast(ast); + return ast; +} + diff --git a/qcsrc/common/command/script/string.qh b/qcsrc/common/command/script/string.qh new file mode 100644 index 000000000..87d747ad7 --- /dev/null +++ b/qcsrc/common/command/script/string.qh @@ -0,0 +1,131 @@ + +// ======================================================================== +// String functions +// ======================================================================== + +float ctype_space(string char) +{ + return char == " " || char == "\n" || char == "\t"; +} + +float ctype_upper(string char) +{ + return char == "A" || char == "B" || char == "C" || char == "D" || + char == "E" || char == "F" || char == "G" || char == "H" || + char == "I" || char == "J" || char == "K" || char == "L" || + char == "M" || char == "N" || char == "O" || char == "P" || + char == "Q" || char == "R" || char == "S" || char == "T" || + char == "U" || char == "V" || char == "W" || char == "X" || + char == "Y" || char == "Z"; +} + +float ctype_lower(string char) +{ + return char == "a" || char == "b" || char == "c" || char == "d" || + char == "e" || char == "f" || char == "g" || char == "h" || + char == "i" || char == "j" || char == "k" || char == "l" || + char == "m" || char == "n" || char == "o" || char == "p" || + char == "q" || char == "r" || char == "s" || char == "t" || + char == "u" || char == "v" || char == "w" || char == "x" || + char == "y" || char == "z"; +} + +float ctype_alpha(string char) +{ + return ctype_upper(char) || ctype_lower(char); +} + +float ctype_digit(string char) +{ + return char == "0" || char == "1" || char == "2" || char == "3" || + char == "4" || char == "5" || char == "6" || char == "7" || + char == "8" || char == "9"; +} + +float ctype_alnum(string char) +{ + return ctype_alpha(char) || ctype_digit(char); +} + +float ctype_identifier(string char) +{ + return ctype_alnum(char) || char == "_"; +} + +string script_ftos(float f) +{ + return sprintf("%.9g", f); +} + +float is_numeric(string s) +{ + float candot = 1; + float canexp = 1; + float i; + string c; + for ( i = 0; i < strlen(s); i++ ) + { + c = substring(s,i,1); + if ( !ctype_digit(c) ) + { + if ( candot && c == "." ) + candot = 0; + else if ( canexp == 1 && (c == "e" || c == "E") && i+1 < strlen(s) ) + { + canexp = candot = 0; + c = substring(s,i+1,1); + if ( c == "+" || c == "-" ) + i++; + } + else + return 0; + } + } + return 1; +} + +entity float_to_entity(float n) +{ +#ifdef SVQC + if ( n ) return edict_num(n); +#elif defined(CSQC) + if ( n ) return entitybyindex(n); +#elif defined(MENUQC) + if ( n ) return ftoe(n); +#endif + return world; +} +entity string_to_entity(string s) +{ + return float_to_entity(stof(s)); +} +string entity_get(entity e, string newfield) +{ + float n = numentityfields(); + float i; + for ( i = 0; i < n; i++ ) + { + if ( entityfieldname(i) == newfield ) + { + return getentityfieldstring(i,e); + } + } + return ""; +} + +float entity_set(entity e, string newfield, string newvalue) +{ + if ( !e ) + return 0; + float n = numentityfields(); + float i; + for ( i = 0; i < n; i++ ) + { + if ( entityfieldname(i) == newfield ) + { + return putentityfieldstring(i,e,newvalue); + } + } + return 0; +} + diff --git a/qcsrc/common/constants.qh b/qcsrc/common/constants.qh index 8586cffa9..d5fe35966 100644 --- a/qcsrc/common/constants.qh +++ b/qcsrc/common/constants.qh @@ -21,7 +21,8 @@ // Revision 20: naggers // Revision 21: entcs for players optimized (position data down from 12 to 7 bytes); waypointsprites in csqc for team radar // Revision 22: hook shot origin -#define CSQC_REVISION 22 +// Revision 23: variable hit sound +#define CSQC_REVISION 23 const float AS_STRING = 1; const float AS_INT = 2; @@ -42,6 +43,8 @@ const float TE_CSQC_HAGAR_MAXROCKETS = 111; const float TE_CSQC_VEHICLESETUP = 112; const float TE_CSQC_SVNOTICE = 113; const float TE_CSQC_SHOCKWAVEPARTICLE = 114; +const float TE_CSQC_SPECINFO = 115; +const float TE_CSQC_SUPERBLASTPARTICLE = 116; const float RACE_NET_CHECKPOINT_HIT_QUALIFYING = 0; // byte checkpoint, short time, short recordtime, string recordholder const float RACE_NET_CHECKPOINT_CLEAR = 1; @@ -100,9 +103,16 @@ const float ENT_CLIENT_ELIMINATEDPLAYERS = 39; const float ENT_CLIENT_TURRET = 40; const float ENT_CLIENT_AUXILIARYXHAIR = 50; const float ENT_CLIENT_VEHICLE = 60; +const float ENT_CLIENT_GENERATOR = 70; +const float ENT_CLIENT_CONTROLPOINT_ICON = 71; +const float ENT_CLIENT_JAILCAMERA = 72; +const float ENT_CLIENT_EFFECT = 73; +const float ENT_CLIENT_CONQUEST_CONTROLPOINT = 74; const float ENT_CLIENT_HEALING_ORB = 80; +const float ENT_CLIENT_MINIGAME = 81; + const float SPRITERULE_DEFAULT = 0; const float SPRITERULE_TEAMPLAY = 1; @@ -141,18 +151,8 @@ const float CVAR_READONLY = 4; /////////////////////////// // csqc communication stuff -const float CTF_STATE_ATTACK = 1; -const float CTF_STATE_DEFEND = 2; -const float CTF_STATE_COMMANDER = 3; - const float HUD_NORMAL = 0; -const float HUD_VEHICLE_FIRST = 10; -const float HUD_SPIDERBOT = 10; -const float HUD_WAKIZASHI = 11; -const float HUD_RAPTOR = 12; -const float HUD_BUMBLEBEE = 13; -const float HUD_BUMBLEBEE_GUN = 14; -const float HUD_VEHICLE_LAST = 14; +const float HUD_BUMBLEBEE_GUN = 25; const vector eX = '1 0 0'; const vector eY = '0 1 0'; @@ -200,7 +200,7 @@ const vector eZ = '0 0 1'; /** * Score indices */ -#define MAX_SCORE 10 +#define MAX_SCORE 11 #define MAX_TEAMSCORE 2 #define ST_SCORE 0 @@ -208,6 +208,7 @@ const vector eZ = '0 0 1'; #define SP_DEATHS 1 #define SP_SUICIDES 2 #define SP_SCORE 3 +#define SP_FPS 10 // game mode specific indices are not in common/, but in server/scores_rules.qc! const float CH_INFO = 0; @@ -272,8 +273,13 @@ const float PROJECTILE_BUMBLE_BEAM = 31; const float PROJECTILE_MAGE_SPIKE = 32; const float PROJECTILE_SHAMBLER_LIGHTNING = 33; +const float PROJECTILE_SCRAG_SPIKE = 34; + +const float PROJECTILE_ROCKETMINSTA_LASER = 35; +const float PROJECTILE_SUPERROCKET = 36; -const float PROJECTILE_RPC = 60; +const float PROJECTILE_RPC = 37; +const float PROJECTILE_CANNONBALL = 38; const float SPECIES_HUMAN = 0; const float SPECIES_ROBOT_SOLID = 1; diff --git a/qcsrc/common/csqcmodel_settings.qh b/qcsrc/common/csqcmodel_settings.qh index f521d7de9..56f94dfde 100644 --- a/qcsrc/common/csqcmodel_settings.qh +++ b/qcsrc/common/csqcmodel_settings.qh @@ -5,7 +5,7 @@ //#define CSQCMODEL_SUPPORT_GETTAGINFO_BEFORE_DRAW // server decides crouching, this lags, but so be it -#define CSQCMODEL_SERVERSIDE_CROUCH +//#define CSQCMODEL_SERVERSIDE_CROUCH // a hack for Xonotic #ifdef CSQC @@ -31,12 +31,12 @@ CSQCMODEL_PROPERTY(64, float, ReadByte, WriteByte, solid) \ CSQCMODEL_IF(!isplayer) \ CSQCMODEL_PROPERTY(128, TAG_ENTITY_TYPE, ReadShort, WriteEntity, TAG_ENTITY_NAME) \ - CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, glowmod_x, 255, 0, 255) \ - CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, glowmod_y, 255, 0, 255) \ - CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, glowmod_z, 255, 0, 255) \ - CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, colormod_x, 255, 0, 255) \ - CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, colormod_y, 255, 0, 255) \ - CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, colormod_z, 255, 0, 255) \ + CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, glowmod_x, 254, -1, 254) \ + CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, glowmod_y, 254, -1, 254) \ + CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, glowmod_z, 254, -1, 254) \ + CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, colormod_x, 254, -1, 254) \ + CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, colormod_y, 254, -1, 254) \ + CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, colormod_z, 254, -1, 254) \ CSQCMODEL_ENDIF \ CSQCMODEL_IF(isplayer) \ CSQCMODEL_PROPERTY(128, float, ReadByte, WriteByte, anim_state) \ diff --git a/qcsrc/common/deathtypes.qh b/qcsrc/common/deathtypes.qh index c8512cdcf..4f99fefdf 100644 --- a/qcsrc/common/deathtypes.qh +++ b/qcsrc/common/deathtypes.qh @@ -4,9 +4,10 @@ #define DEATHTYPES \ DEATHTYPE(DEATH_AUTOTEAMCHANGE, DEATH_SELF_AUTOTEAMCHANGE, NO_MSG, DEATH_SPECIAL_START) \ - DEATHTYPE(DEATH_BUFF_VENGEANCE, NO_MSG, DEATH_MURDER_VENGEANCE, NORMAL_POS) \ + DEATHTYPE(DEATH_BUFF, NO_MSG, DEATH_MURDER_BUFF, NORMAL_POS) \ DEATHTYPE(DEATH_CAMP, DEATH_SELF_CAMP, NO_MSG, NORMAL_POS) \ DEATHTYPE(DEATH_CHEAT, DEATH_SELF_CHEAT, DEATH_MURDER_CHEAT, NORMAL_POS) \ + DEATHTYPE(DEATH_CRUSH, NO_MSG, DEATH_MURDER_CRUSH, NORMAL_POS) \ DEATHTYPE(DEATH_CUSTOM, DEATH_SELF_CUSTOM, NO_MSG, NORMAL_POS) \ DEATHTYPE(DEATH_DROWN, DEATH_SELF_DROWN, DEATH_MURDER_DROWN, NORMAL_POS) \ DEATHTYPE(DEATH_FALL, DEATH_SELF_FALL, DEATH_MURDER_FALL, NORMAL_POS) \ @@ -16,14 +17,26 @@ DEATHTYPE(DEATH_KILL, DEATH_SELF_SUICIDE, NO_MSG, NORMAL_POS) \ DEATHTYPE(DEATH_LAVA, DEATH_SELF_LAVA, DEATH_MURDER_LAVA, NORMAL_POS) \ DEATHTYPE(DEATH_MIRRORDAMAGE, DEATH_SELF_BETRAYAL, NO_MSG, NORMAL_POS) \ - DEATHTYPE(DEATH_MONSTER_MAGE, DEATH_SELF_MON_MAGE, DEATH_MURDER_MONSTER, DEATH_MONSTER_FIRST) \ - DEATHTYPE(DEATH_MONSTER_SHAMBLER_CLAW, DEATH_SELF_MON_SHAMBLER_CLAW, DEATH_MURDER_MONSTER, NORMAL_POS) \ - DEATHTYPE(DEATH_MONSTER_SHAMBLER_SMASH, DEATH_SELF_MON_SHAMBLER_SMASH, DEATH_MURDER_MONSTER, NORMAL_POS) \ - DEATHTYPE(DEATH_MONSTER_SHAMBLER_ZAP, DEATH_SELF_MON_SHAMBLER_ZAP, DEATH_MURDER_MONSTER, NORMAL_POS) \ - DEATHTYPE(DEATH_MONSTER_SPIDER, DEATH_SELF_MON_SPIDER, DEATH_MURDER_MONSTER, NORMAL_POS) \ - DEATHTYPE(DEATH_MONSTER_WYVERN, DEATH_SELF_MON_WYVERN, DEATH_MURDER_MONSTER, NORMAL_POS) \ - DEATHTYPE(DEATH_MONSTER_ZOMBIE_JUMP, DEATH_SELF_MON_ZOMBIE_JUMP, DEATH_MURDER_MONSTER, NORMAL_POS) \ - DEATHTYPE(DEATH_MONSTER_ZOMBIE_MELEE, DEATH_SELF_MON_ZOMBIE_MELEE, DEATH_MURDER_MONSTER, DEATH_MONSTER_LAST) \ + DEATHTYPE(DEATH_MONSTER_AFRIT, DEATH_SELF_MON_AFRIT, DEATH_MURDER_MONSTER, DEATH_MONSTER_FIRST) \ + DEATHTYPE(DEATH_MONSTER_CREEPER, DEATH_SELF_MON_CREEPER, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_DEMON_JUMP, DEATH_SELF_MON_DEMON_JUMP, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_DEMON_MELEE, DEATH_SELF_MON_DEMON_MELEE, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_MAGE, DEATH_SELF_MON_MAGE, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_OGRE_GRENADE, DEATH_SELF_MON_OGRE_GRENADE, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_OGRE_MACHINEGUN,DEATH_SELF_MON_OGRE_MACHINEGUN, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_OGRE_MELEE, DEATH_SELF_MON_OGRE_MELEE, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_ROTFISH_MELEE, DEATH_SELF_MON_ROTFISH, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_ROTTWEILER, DEATH_SELF_MON_ROTTWEILER, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_SCRAG, DEATH_SELF_MON_SCRAG, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_SHAMBLER_CLAW, DEATH_SELF_MON_SHAMBLER_CLAW, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_SHAMBLER_SMASH, DEATH_SELF_MON_SHAMBLER_SMASH, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_SHAMBLER_ZAP, DEATH_SELF_MON_SHAMBLER_ZAP, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_SPIDER, DEATH_SELF_MON_SPIDER, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_VORE, DEATH_SELF_MON_VORE, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_VORE_MELEE, DEATH_SELF_MON_VORE_MELEE, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_WYVERN, DEATH_SELF_MON_WYVERN, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_ZOMBIE_JUMP, DEATH_SELF_MON_ZOMBIE_JUMP, DEATH_MURDER_MONSTER, NORMAL_POS) \ + DEATHTYPE(DEATH_MONSTER_ZOMBIE_MELEE, DEATH_SELF_MON_ZOMBIE_MELEE, DEATH_MURDER_MONSTER, DEATH_MONSTER_LAST) \ DEATHTYPE(DEATH_NADE, DEATH_SELF_NADE, DEATH_MURDER_NADE, NORMAL_POS) \ DEATHTYPE(DEATH_NADE_NAPALM, DEATH_SELF_NADE_NAPALM, DEATH_MURDER_NADE_NAPALM, NORMAL_POS) \ DEATHTYPE(DEATH_NADE_ICE, DEATH_SELF_NADE_ICE, DEATH_MURDER_NADE_ICE, NORMAL_POS) \ @@ -48,7 +61,7 @@ DEATHTYPE(DEATH_TURRET_PLASMA, DEATH_SELF_TURRET_PLASMA, NO_MSG, NORMAL_POS) \ DEATHTYPE(DEATH_TURRET_TESLA, DEATH_SELF_TURRET_TESLA, NO_MSG, NORMAL_POS) \ DEATHTYPE(DEATH_TURRET_WALK_GUN, DEATH_SELF_TURRET_WALK_GUN, NO_MSG, NORMAL_POS) \ - DEATHTYPE(DEATH_TURRET_WALK_MEELE, DEATH_SELF_TURRET_WALK_MEELE, NO_MSG, NORMAL_POS) \ + DEATHTYPE(DEATH_TURRET_WALK_MELEE, DEATH_SELF_TURRET_WALK_MELEE, NO_MSG, NORMAL_POS) \ DEATHTYPE(DEATH_TURRET_WALK_ROCKET, DEATH_SELF_TURRET_WALK_ROCKET, NO_MSG, DEATH_TURRET_LAST) \ DEATHTYPE(DEATH_VH_BUMB_DEATH, DEATH_SELF_VH_BUMB_DEATH, DEATH_MURDER_VH_BUMB_DEATH, DEATH_VHFIRST) \ DEATHTYPE(DEATH_VH_BUMB_GUN, NO_MSG, DEATH_MURDER_VH_BUMB_GUN, NORMAL_POS) \ @@ -60,6 +73,8 @@ DEATHTYPE(DEATH_VH_SPID_DEATH, DEATH_SELF_VH_SPID_DEATH, DEATH_MURDER_VH_SPID_DEATH, NORMAL_POS) \ DEATHTYPE(DEATH_VH_SPID_MINIGUN, NO_MSG, DEATH_MURDER_VH_SPID_MINIGUN, NORMAL_POS) \ DEATHTYPE(DEATH_VH_SPID_ROCKET, DEATH_SELF_VH_SPID_ROCKET, DEATH_MURDER_VH_SPID_ROCKET, NORMAL_POS) \ + DEATHTYPE(DEATH_VH_TANK_DEATH, DEATH_SELF_VH_TANK_DEATH, DEATH_MURDER_VH_TANK_DEATH, NORMAL_POS) \ + DEATHTYPE(DEATH_VH_TANKLL48, NO_MSG, DEATH_MURDER_VH_TANKLL48, NORMAL_POS) \ DEATHTYPE(DEATH_VH_WAKI_DEATH, DEATH_SELF_VH_WAKI_DEATH, DEATH_MURDER_VH_WAKI_DEATH, NORMAL_POS) \ DEATHTYPE(DEATH_VH_WAKI_GUN, NO_MSG, DEATH_MURDER_VH_WAKI_GUN, NORMAL_POS) \ DEATHTYPE(DEATH_VH_WAKI_ROCKET, DEATH_SELF_VH_WAKI_ROCKET, DEATH_MURDER_VH_WAKI_ROCKET, DEATH_VHLAST) \ @@ -121,5 +136,5 @@ const float DEATH_HITTYPEMASK = 0x1F00; // which is WAY below 10000 used for nor const float HITTYPE_SECONDARY = 0x100; const float HITTYPE_SPLASH = 0x200; // automatically set by RadiusDamage const float HITTYPE_BOUNCE = 0x400; -const float HITTYPE_RESERVED2 = 0x800; +const float HITTYPE_HEADSHOT = 0x800; // automatically set by Damage (if headshotbonus is set) const float HITTYPE_RESERVED = 0x1000; // unused yet diff --git a/qcsrc/common/effects.qc b/qcsrc/common/effects.qc new file mode 100644 index 000000000..50b5d1386 --- /dev/null +++ b/qcsrc/common/effects.qc @@ -0,0 +1,109 @@ +void Create_Effect_Entity(float eff_name, string eff_string, float eff_trail) +{ + entity eff; + effects_ent[eff_name - 1] = eff = spawn(); + + eff.classname = "effect_entity"; + eff.eent_net_name = eff_name; + eff.eent_eff_name = eff_string; + eff.eent_eff_trail = eff_trail; +} + +#ifdef CSQC +void Read_Effect(float is_new) +{ +#if EFFECTS_COUNT >= 255 + float net_name = ReadShort(); +#else + float net_name = ReadByte(); +#endif + + entity eff = effects_ent[net_name - 1]; + + vector v, vel = '0 0 0'; + float eff_cnt = 1; + float eff_trail = eff.eent_eff_trail; + v_x = ReadCoord(); + v_y = ReadCoord(); + v_z = ReadCoord(); + + float use_vel = ReadByte(); + if(use_vel) + { + vel_x = ReadCoord(); + vel_y = ReadCoord(); + vel_z = ReadCoord(); + } + + if(!eff_trail) + eff_cnt = ReadByte(); + + if(is_new) + if(eff_trail) + WarpZone_TrailParticles(world, particleeffectnum(eff.eent_eff_name), v, vel); + else + pointparticles(particleeffectnum(eff.eent_eff_name), v, vel, eff_cnt); +} +#endif + +#ifdef SVQC +float Net_Write_Effect(entity client, float sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_EFFECT); +#if EFFECTS_COUNT >= 255 + WriteShort(MSG_ENTITY, self.eent_net_name); +#else + WriteByte(MSG_ENTITY, self.eent_net_name); +#endif + WriteCoord(MSG_ENTITY, self.eent_net_location_x); + WriteCoord(MSG_ENTITY, self.eent_net_location_y); + WriteCoord(MSG_ENTITY, self.eent_net_location_z); + + // attempt to save a tiny bit more bandwidth by not sending velocity if it isn't set + if(self.eent_net_velocity) + { + WriteByte(MSG_ENTITY, TRUE); + WriteCoord(MSG_ENTITY, self.eent_net_velocity_x); + WriteCoord(MSG_ENTITY, self.eent_net_velocity_y); + WriteCoord(MSG_ENTITY, self.eent_net_velocity_z); + } + else { WriteByte(MSG_ENTITY, FALSE); } + + if(!self.eent_eff_trail) { WriteByte(MSG_ENTITY, self.eent_net_count); } + return TRUE; +} + +// problem with this is, we might not have all the available effects for it +/*float Effect_NameToID(string eff_name) +{ + float i; + for(i = EFFECT_FIRST; i < EFFECT_MAX; ++i) + { + if((effects_ent[i - 1]).eent_eff_name == eff_name) + return (effects_ent[i - 1]).eent_net_name; + } + + return 0; +} */ + +void Send_Effect(float eff_name, vector eff_loc, vector eff_vel, float eff_cnt) +{ + entity eff = effects_ent[eff_name - 1]; + if(!eff) { return; } + if(!eff.eent_eff_trail && !eff_cnt) { return; } // effect has no count! + entity net_eff = spawn(); + net_eff.owner = eff; + net_eff.classname = "net_effect"; + //net_eff.eent_broadcast = broadcast; + net_eff.eent_net_name = eff_name; + net_eff.eent_net_velocity = eff_vel; + net_eff.eent_net_location = eff_loc; + net_eff.eent_net_count = eff_cnt; + net_eff.eent_eff_trail = eff.eent_eff_trail; + + net_eff.think = SUB_Remove; + net_eff.nextthink = time + 0.2; // don't need to keep this long + + Net_LinkEntity(net_eff, FALSE, 0, Net_Write_Effect); +} +#endif \ No newline at end of file diff --git a/qcsrc/common/effects.qh b/qcsrc/common/effects.qh new file mode 100644 index 000000000..9d7b05d38 --- /dev/null +++ b/qcsrc/common/effects.qh @@ -0,0 +1,155 @@ +// Global list of effects, networked to CSQC by ID to save bandwidth and to use client particle numbers (allows mismatching effectinfos to some degree) +// Not too concerned about the order of this list, just keep the weapon effects together! + +// EFFECT(istrail, EFFECT_NAME, "effectinfo_string") +#define EFFECTS \ + EFFECT(0, EFFECT_EXPLOSION_SMALL, "explosion_small") \ + EFFECT(0, EFFECT_EXPLOSION_MEDIUM, "explosion_medium") \ + EFFECT(0, EFFECT_EXPLOSION_BIG, "explosion_big") \ + EFFECT(1, EFFECT_VAPORIZER_RED, "TE_TEI_G3RED") \ + EFFECT(1, EFFECT_VAPORIZER_RED_HIT, "TE_TEI_G3RED_HIT") \ + EFFECT(1, EFFECT_VAPORIZER_BLUE, "TE_TEI_G3BLUE") \ + EFFECT(1, EFFECT_VAPORIZER_BLUE_HIT, "TE_TEI_G3BLUE_HIT") \ + EFFECT(1, EFFECT_VAPORIZER_YELLOW, "TE_TEI_G3YELLOW") \ + EFFECT(1, EFFECT_VAPORIZER_YELLOW_HIT, "TE_TEI_G3YELLOW_HIT") \ + EFFECT(1, EFFECT_VAPORIZER_PINK, "TE_TEI_G3PINK") \ + EFFECT(1, EFFECT_VAPORIZER_PINK_HIT, "TE_TEI_G3PINK_HIT") \ + EFFECT(1, EFFECT_VAPORIZER_NEUTRAL, "TE_TEI_G3NEUTRAL") \ + EFFECT(1, EFFECT_VAPORIZER_NEUTRAL_HIT, "TE_TEI_G3NEUTRAL_HIT") \ + EFFECT(0, EFFECT_ELECTRO_COMBO, "electro_combo") \ + EFFECT(0, EFFECT_ELECTRO_IMPACT, "electro_impact") \ + EFFECT(0, EFFECT_ELECTRO_MUZZLEFLASH, "electro_muzzleflash") \ + EFFECT(0, EFFECT_HAGAR_BOUNCE, "hagar_bounce") \ + EFFECT(0, EFFECT_HAGAR_MUZZLEFLASH, "hagar_muzzleflash") \ + EFFECT(0, EFFECT_LASER_MUZZLEFLASH, "laser_muzzleflash") \ + EFFECT(0, EFFECT_MACHINEGUN_MUZZLEFLASH, "uzi_muzzleflash") \ + EFFECT(0, EFFECT_RIFLE_MUZZLEFLASH, "rifle_muzzleflash") \ + EFFECT(1, EFFECT_RIFLE, "tr_rifle") \ + EFFECT(1, EFFECT_RIFLE_WEAK, "tr_rifle_weak") \ + EFFECT(0, EFFECT_SEEKER_MUZZLEFLASH, "seeker_muzzleflash") \ + EFFECT(0, EFFECT_SHOTGUN_MUZZLEFLASH, "shotgun_muzzleflash") \ + EFFECT(0, EFFECT_GRENADE_MUZZLEFLASH, "grenadelauncher_muzzleflash") \ + EFFECT(0, EFFECT_GRENADE_EXPLODE, "grenade_explode") \ + EFFECT(0, EFFECT_CRYLINK_JOINEXPLODE, "crylink_joinexplode") \ + EFFECT(0, EFFECT_CRYLINK_MUZZLEFLASH, "crylink_muzzleflash") \ + EFFECT(0, EFFECT_VORTEX_MUZZLEFLASH, "nex_muzzleflash") \ + EFFECT(0, EFFECT_HOOK_MUZZLEFLASH, "grapple_muzzleflash") \ + EFFECT(0, EFFECT_HOOK_IMPACT, "grapple_impact") \ + EFFECT(0, EFFECT_ROCKET_EXPLODE, "rocket_explode") \ + EFFECT(0, EFFECT_ROCKET_GUIDE, "rocket_guide") \ + EFFECT(0, EFFECT_ROCKET_MUZZLEFLASH, "rocketlauncher_muzzleflash") \ + EFFECT(0, EFFECT_FIREBALL_LASER, "fireball_laser") \ + EFFECT(0, EFFECT_FIREBALL_EXPLODE, "fireball_explode") \ + EFFECT(0, EFFECT_FIREBALL_BFGDAMAGE, "fireball_bfgdamage") \ + EFFECT(0, EFFECT_FIREBALL_MUZZLEFLASH, "fireball_muzzleflash") \ + EFFECT(0, EFFECT_FIREBALL_PRE_MUZZLEFLASH, "fireball_preattack_muzzleflash") \ + EFFECT(0, EFFECT_TELEPORT, "teleport") \ + EFFECT(0, EFFECT_SPAWN_RED, "spawn_event_red") \ + EFFECT(0, EFFECT_SPAWN_BLUE, "spawn_event_blue") \ + EFFECT(0, EFFECT_SPAWN_YELLOW, "spawn_event_yellow") \ + EFFECT(0, EFFECT_SPAWN_PINK, "spawn_event_pink") \ + EFFECT(0, EFFECT_SPAWN_NEUTRAL, "spawn_event_neutral") \ + EFFECT(0, EFFECT_NADE_RED_EXPLODE, "nade_red_explode") \ + EFFECT(0, EFFECT_NADE_BLUE_EXPLODE, "nade_blue_explode") \ + EFFECT(0, EFFECT_NADE_YELLOW_EXPLODE, "nade_yellow_explode") \ + EFFECT(0, EFFECT_NADE_PINK_EXPLODE, "nade_pink_explode") \ + EFFECT(0, EFFECT_NADE_NEUTRAL_EXPLODE, "nade_neutral_explode") \ + EFFECT(0, EFFECT_ICEORGLASS, "iceorglass") \ + EFFECT(0, EFFECT_ICEFIELD, "icefield") \ + EFFECT(0, EFFECT_FIREFIELD, "firefield") \ + EFFECT(0, EFFECT_HEALING, "healing_fx") \ + EFFECT(1, EFFECT_LASER_BEAM_FAST, "nex242_misc_laser_beam_fast") \ + EFFECT(0, EFFECT_RESPAWN_GHOST, "respawn_ghost") \ + EFFECT(0, EFFECT_FLAG_RED_TOUCH, "redflag_touch") \ + EFFECT(0, EFFECT_FLAG_BLUE_TOUCH, "blueflag_touch") \ + EFFECT(0, EFFECT_FLAG_YELLOW_TOUCH, "yellowflag_touch") \ + EFFECT(0, EFFECT_FLAG_PINK_TOUCH, "pinkflag_touch") \ + EFFECT(0, EFFECT_FLAG_NEUTRAL_TOUCH, "neutralflag_touch") \ + EFFECT(1, EFFECT_RED_PASS, "red_pass") \ + EFFECT(1, EFFECT_BLUE_PASS, "blue_pass") \ + EFFECT(1, EFFECT_YELLOW_PASS, "yellow_pass") \ + EFFECT(1, EFFECT_PINK_PASS, "pink_pass") \ + EFFECT(1, EFFECT_NEUTRAL_PASS, "neutral_pass") \ + EFFECT(0, EFFECT_RED_CAP, "red_cap") \ + EFFECT(0, EFFECT_BLUE_CAP, "blue_cap") \ + EFFECT(0, EFFECT_YELLOW_CAP, "yellow_cap") \ + EFFECT(0, EFFECT_PINK_CAP, "pink_cap") \ + EFFECT(0, EFFECT_BALL_SPARKS, "kaball_sparks") \ + EFFECT(0, EFFECT_ELECTRIC_SPARKS, "electricity_sparks") \ + EFFECT(0, EFFECT_SPARKS, "sparks") \ + EFFECT(0, EFFECT_RAGE, "rage") \ + EFFECT(0, EFFECT_SMOKING, "smoking") \ + EFFECT(0, EFFECT_SMOKE_RING, "smoke_ring") \ + EFFECT(0, EFFECT_ITEM_PICKUP, "item_pickup") \ + EFFECT(0, EFFECT_ITEM_RESPAWN, "item_respawn") \ + EFFECT(0, EFFECT_JUMPPAD, "jumppad_activate") \ + EFFECT(1, EFFECT_BULLET, "tr_bullet") + + + + +// -------------------- +// -------------------------- +// ----------------------------------- +// ------------------------------------------| +// some stuff you don't need to care about...| +// ------------------------------------------| +// ----------------------------------- +// -------------------------- +// -------------------- + +.float eent_net_name; // id +.vector eent_net_location; +.vector eent_net_velocity; +.float eent_eff_trail; +.string eent_eff_name; +.float eent_net_count; + +#ifdef CSQC +void Read_Effect(float is_new); +#endif + +#ifdef SVQC +void Send_Effect(float eff_name, vector eff_loc, vector eff_vel, float eff_cnt); +#endif + +#define EFFECT_FIRST 1 +float EFFECT_COUNT; + +#define MAX_EFFECTS 512 +entity effects_ent[MAX_EFFECTS]; + +void Create_Effect_Entity(float eff_name, string eff_string, float eff_trail); + +#define EFFECT(istrail,name,realname) \ + float name; \ + void RegisterEffect_##name() \ + { \ + SET_FIELD_COUNT(name, EFFECT_FIRST, EFFECT_COUNT) \ + CHECK_MAX_COUNT(name, MAX_EFFECTS, EFFECT_COUNT, "EFFECT") \ + Create_Effect_Entity(name, realname, istrail); \ + } \ + ACCUMULATE_FUNCTION(RegisterEffects, RegisterEffect_##name); + +void RegisterEffects_First() +{ + #ifdef SVQC + #define dedi (server_is_dedicated ? "a dedicated " : "") + #else + #define dedi "" + #endif + + printf("Beginning effect initialization on %s%s program...\n", dedi, PROGNAME); + #undef dedi +} + +void RegisterEffects_Done() +{ + print("Effects initialization successful!\n"); +} + +// NOW we actually activate the declarations +ACCUMULATE_FUNCTION(RegisterEffects, RegisterEffects_First); +EFFECTS +ACCUMULATE_FUNCTION(RegisterEffects, RegisterEffects_Done); +#undef EFFECT diff --git a/qcsrc/common/jeff.qh b/qcsrc/common/jeff.qh new file mode 100644 index 000000000..f3da8ccb4 --- /dev/null +++ b/qcsrc/common/jeff.qh @@ -0,0 +1,49 @@ +// sadly, we can't put this stuff inside an #ifdef, as it is always called + +#define JEFF_ANCE_NOTIF \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_BIOHAZARD, CH_INFO, "biohazard", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_BLAZEOFGLORY, CH_INFO, "blazeofglory", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_BLUESTREAK, CH_INFO, "bluestreak", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_BOTTOMFEEDER, CH_INFO, "bottomfeeder", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_BULLSEYE, CH_INFO, "bullseye", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_COMBOKING, CH_INFO, "comboking", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_DENIED, CH_INFO, "denied", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_DOUBLEKILL, CH_INFO, "doublekill", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_EAGLEEYE, CH_INFO, "eagleeye", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_FIRSTBLOOD, CH_INFO, "firstblood", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_FLAKMASTER, CH_INFO, "flakmaster", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_GAMEOVER, CH_INFO, "gameover", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_GODLIKE, CH_INFO, "godlike", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_GUNSLINGER, CH_INFO, "gunslinger", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_HATTRICK, CH_INFO, "hattrick", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_HIJACKED, CH_INFO, "hijacked", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_HITANDRUN, CH_INFO, "hitandrun", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_HOLY, CH_INFO, "holyshit", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_HUMILIATION, CH_INFO, "humiliation", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_JACKHAMMER, CH_INFO, "jackhammer", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_JUGGERNAUT, CH_INFO, "juggernaut", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_LUDICROUSKILL, CH_INFO, "ludicrouskill", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_MANSLAUGHTER, CH_INFO, "vehicularmanslaughter", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_MEGAKILL, CH_INFO, "megakill", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_MONSTERKILL, CH_INFO, "monsterkill", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_MULTIKILL, CH_INFO, "multikill", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_MUTDESTRUCT, CH_INFO, "mutualdestruction", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_NODEBUSTER, CH_INFO, "nodebuster", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_OUTSTANDING, CH_INFO, "outstanding", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_NUKEMHOLY, CH_INFO, "nukemholy", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_PANCAKE, CH_INFO, "pancake", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_PAYBACK, CH_INFO, "payback", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_REJECTED, CH_INFO, "rejected", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_RETRIBUTION, CH_INFO, "retribution", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_ROCKETSCIENTIST, CH_INFO, "rocketscientist", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_ROADKILL, CH_INFO, "roadkill", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_ROADRAGE, CH_INFO, "roadrage", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_ROADRAMPAGE, CH_INFO, "roadrampage", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_SHAFTMASTER, CH_INFO, "shaftmaster", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_SLACKER, CH_INFO, "slacker", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_SPEED, CH_INFO, "speed", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_TEAMKILLER, CH_INFO, "teamkiller", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_TOPGUN, CH_INFO, "topgun", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_ULTRAKILL, CH_INFO, "ultrakill", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_VENGEANCE, CH_INFO, "vengeance", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_JEFF_WICKEDSICK, CH_INFO, "wickedsick", VOL_BASEVOICE, ATTEN_NONE) diff --git a/qcsrc/common/mapinfo.qc b/qcsrc/common/mapinfo.qc index ccf02e71d..1d13569a3 100644 --- a/qcsrc/common/mapinfo.qc +++ b/qcsrc/common/mapinfo.qc @@ -310,13 +310,17 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp if(v == "dom_controlpoint") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DOMINATION; else if(v == "item_flag_team2") - MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF; + MapInfo_Map_supportedGametypes |= (MAPINFO_TYPE_CTF | MAPINFO_TYPE_VIP); else if(v == "team_CTF_blueflag") - MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF; + MapInfo_Map_supportedGametypes |= (MAPINFO_TYPE_CTF | MAPINFO_TYPE_VIP); + else if(v == "conquest_controlpoint") + MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CONQUEST; else if(v == "invasion_spawnpoint") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_INVASION; else if(v == "target_assault_roundend") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ASSAULT; + else if(startsWith(v, "jailbreak_")) + MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_JAILBREAK; else if(v == "onslaught_generator") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ONSLAUGHT; else if(substring(v, 0, 8) == "nexball_" || substring(v, 0, 4) == "ball") @@ -357,7 +361,7 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp } diameter = vlen(mapMaxs - mapMins); - twoBaseModes = MapInfo_Map_supportedGametypes & (MAPINFO_TYPE_CTF | MAPINFO_TYPE_ASSAULT | MAPINFO_TYPE_RACE | MAPINFO_TYPE_NEXBALL); + twoBaseModes = MapInfo_Map_supportedGametypes & (MAPINFO_TYPE_CTF | MAPINFO_TYPE_ASSAULT | MAPINFO_TYPE_RACE | MAPINFO_TYPE_NEXBALL | MAPINFO_TYPE_VIP); if(twoBaseModes && (MapInfo_Map_supportedGametypes == twoBaseModes)) { // we have a CTF-only or Assault-only map. Don't add other modes then, @@ -373,6 +377,7 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TEAM_DEATHMATCH; MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_FREEZETAG; MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CA; + MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_VIP; } if(spawnpoints >= 12 && diameter > 5120) MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_KEYHUNT; @@ -501,6 +506,30 @@ void _MapInfo_Map_ApplyGametype(string s, float pWantedType, float pThisType, fl s = cdr(s); } + if(pWantedType == MAPINFO_TYPE_VIP) + { + sa = car(s); + if(sa != "") + cvar_set("fraglimit", sa); + s = cdr(s); + } + + if(pWantedType == MAPINFO_TYPE_CONQUEST) + { + sa = car(s); + if(sa != "") + cvar_set("g_conquest_teams", sa); + s = cdr(s); + } + + if(pWantedType == MAPINFO_TYPE_JAILBREAK) + { + sa = car(s); + if(sa != "") + cvar_set("g_jailbreak_teams", sa); + s = cdr(s); + } + /* keepaway wuz here if(pWantedType == MAPINFO_TYPE_KEEPAWAY) { @@ -594,6 +623,10 @@ void _MapInfo_Map_ApplyGametypeEx(string s, float pWantedType, float pThisType) cvar_set("g_tdm_teams", cvar_defstring("g_tdm_teams")); cvar_set("g_ca_teams", cvar_defstring("g_ca_teams")); cvar_set("g_freezetag_teams", cvar_defstring("g_freezetag_teams")); + cvar_set("g_conquest_teams", cvar_defstring("g_conquest_teams")); + cvar_set("g_jailbreak_teams", cvar_defstring("g_jailbreak_teams")); + cvar_set("g_infection_teams", cvar_defstring("g_infection_teams")); + cvar_set("g_invasion_teams", cvar_defstring("g_invasion_teams")); cvar_set("g_keyhunt_teams", cvar_defstring("g_keyhunt_teams")); cvar_set("g_domination_default_teams", cvar_defstring("g_domination_default_teams")); cvar_set("g_race_qualifying_timelimit", cvar_defstring("g_race_qualifying_timelimit")); @@ -643,6 +676,9 @@ void _MapInfo_Map_ApplyGametypeEx(string s, float pWantedType, float pThisType) cvar_set("g_keyhunt_teams", v); cvar_set("g_domination_default_teams", v); cvar_set("g_invasion_teams", v); + cvar_set("g_conquest_teams", v); + cvar_set("g_jailbreak_teams", v); + cvar_set("g_infection_teams", v); } else if(k == "qualifying_timelimit") { @@ -1127,9 +1163,16 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, float pAllowGenerate, flo if (!cvar_value_issafe(t)) print("Map ", pFilename, " contains a potentially harmful cdtrack, ignored\n"); else - MapInfo_Map_clientstuff = strcat( - MapInfo_Map_clientstuff, "cd loop \"", t, "\"\n" - ); + { +#ifdef XMAS + if(fexists("tis_xmas")) + MapInfo_Map_clientstuff = sprintf("%s%s%d\n", MapInfo_Map_clientstuff, "cd loop xmas", floor(random() * 5)); + else +#endif + MapInfo_Map_clientstuff = strcat( + MapInfo_Map_clientstuff, "cd loop \"", t, "\"\n" + ); + } } } else diff --git a/qcsrc/common/mapinfo.qh b/qcsrc/common/mapinfo.qh index bbcc267b2..c431bf53b 100644 --- a/qcsrc/common/mapinfo.qh +++ b/qcsrc/common/mapinfo.qh @@ -61,13 +61,13 @@ REGISTER_GAMETYPE(_("Clan Arena"),ca,g_ca,CA,TRUE,"timelimit=20 pointlimit=10 te REGISTER_GAMETYPE(_("Domination"),dom,g_domination,DOMINATION,TRUE,"timelimit=20 pointlimit=200 teams=2 leadlimit=0",_("Capture all the control points to win")); #define g_domination IS_GAMETYPE(DOMINATION) -REGISTER_GAMETYPE(_("Key Hunt"),kh,g_keyhunt,KEYHUNT,TRUE,"timelimit=20 pointlimit=1000 teams=3 leadlimit=0",_("Gather all the keys to win the round")); +REGISTER_GAMETYPE(_("Key Hunt"),kh,g_keyhunt,KEYHUNT,TRUE,"timelimit=20 pointlimit=10 teams=3 leadlimit=0",_("Gather all the keys to win the round")); #define g_keyhunt IS_GAMETYPE(KEYHUNT) REGISTER_GAMETYPE(_("Assault"),as,g_assault,ASSAULT,TRUE,"timelimit=20",_("Destroy obstacles to find and destroy the enemy power core before time runs out")); #define g_assault IS_GAMETYPE(ASSAULT) -REGISTER_GAMETYPE(_("Onslaught"),ons,g_onslaught,ONSLAUGHT,TRUE,"timelimit=20",_("Capture control points to reach and destroy the enemy generator")); +REGISTER_GAMETYPE(_("Onslaught"),ons,g_onslaught,ONSLAUGHT,TRUE,"pointlimit=1 timelimit=30",_("Capture control points to reach and destroy the enemy generator")); #define g_onslaught IS_GAMETYPE(ONSLAUGHT) REGISTER_GAMETYPE(_("Nexball"),nb,g_nexball,NEXBALL,TRUE,"timelimit=20 pointlimit=5 leadlimit=0",_("XonSports")); @@ -79,9 +79,21 @@ REGISTER_GAMETYPE(_("Freeze Tag"),ft,g_freezetag,FREEZETAG,TRUE,"timelimit=20 po REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,TRUE,"timelimit=20 pointlimit=30",_("Hold the ball to get points for kills")); #define g_keepaway IS_GAMETYPE(KEEPAWAY) -REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,FALSE,"pointlimit=50 teams=0",_("Survive against waves of monsters")); +REGISTER_GAMETYPE(_("VIP"),vip,g_vip,VIP,TRUE,"timelimit=20 pointlimit=10 leadlimit=0",_("A VIP is chosen on each team, when a VIP dies, the round is over")); +#define g_vip IS_GAMETYPE(VIP) + +REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,TRUE,"pointlimit=50 teams=0",_("Survive against waves of monsters")); #define g_invasion IS_GAMETYPE(INVASION) +REGISTER_GAMETYPE(_("Conquest"),cq,g_conquest,CONQUEST,TRUE,"timelimit=20 pointlimit=1 teams=2",_("Capture all the spawnpoint control points to win")) +#define g_conquest IS_GAMETYPE(CONQUEST) + +REGISTER_GAMETYPE(_("Infection"),inf,g_infection,INFECTION,TRUE,"timelimit=20 pointlimit=10 leadlimit=0",_("Kill enemies to 'infect' them with your team color")); +#define g_infection IS_GAMETYPE(INFECTION) + +REGISTER_GAMETYPE(_("Jailbreak"),jb,g_jailbreak,JAILBREAK,TRUE,"teams=2 timelimit=20 pointlimit=5 leadlimit=0",_("Kill enemies to send them to jail, capture control points to release teammates")); +#define g_jailbreak IS_GAMETYPE(JAILBREAK) + const float MAPINFO_FEATURE_WEAPONS = 1; // not defined for instagib-only maps const float MAPINFO_FEATURE_VEHICLES = 2; const float MAPINFO_FEATURE_TURRETS = 4; diff --git a/qcsrc/common/minigames/cl_minigames.qc b/qcsrc/common/minigames/cl_minigames.qc new file mode 100644 index 000000000..cb7345260 --- /dev/null +++ b/qcsrc/common/minigames/cl_minigames.qc @@ -0,0 +1,399 @@ +// Draw a square in the center of the avaliable area +void minigame_hud_simpleboard(vector pos, vector mySize, string board_texture) +{ + if(panel.current_panel_bg != "0" && panel.current_panel_bg != "") + draw_BorderPicture(pos - '1 1 0' * panel_bg_border, + panel.current_panel_bg, + mySize + '1 1 0' * 2 * panel_bg_border, + panel_bg_color, panel_bg_alpha, + '1 1 0' * (panel_bg_border/BORDER_MULTIPLIER)); + drawpic(pos, board_texture, mySize, '1 1 1', panel_bg_alpha, DRAWFLAG_NORMAL); +} + +// De-normalize (2D vector) v from relative coordinate inside pos mySize +vector minigame_hud_denormalize(vector v, vector pos, vector mySize) +{ + v_x = pos_x + v_x * mySize_x; + v_y = pos_y + v_y * mySize_y; + return v; +} +// De-normalize (2D vector) v from relative size inside pos mySize +vector minigame_hud_denormalize_size(vector v, vector pos, vector mySize) +{ + v_x = v_x * mySize_x; + v_y = v_y * mySize_y; + return v; +} + +// Normalize (2D vector) v to relative coordinate inside pos mySize +vector minigame_hud_normalize(vector v, vector pos, vector mySize) +{ + v_x = ( v_x - pos_x ) / mySize_x; + v_y = ( v_y - pos_y ) / mySize_y; + return v; +} + +// Check if the mouse is inside the given area +float minigame_hud_mouse_in(vector pos, vector sz) +{ + return mousepos_x >= pos_x && mousepos_x < pos_x + sz_x && + mousepos_y >= pos_y && mousepos_y < pos_y + sz_y ; +} + +void initialize_minigames() +{ + entity last_minig = world; + entity minig; + #define MINIGAME(name,nicename) \ + minig = spawn(); \ + minig.classname = "minigame_descriptor"; \ + minig.netname = strzone(strtolower(#name)); \ + minig.message = nicename; \ + minig.minigame_hud_board = minigame_hud_board_##name; \ + minig.minigame_hud_status = minigame_hud_status_##name; \ + minig.minigame_event = minigame_event_##name; \ + if ( !last_minig ) minigame_descriptors = minig; \ + else last_minig.list_next = minig; \ + last_minig = minig; + + REGISTERED_MINIGAMES + + #undef MINIGAME +} + +string minigame_texture_skin(string skinname, string name) +{ + return sprintf("gfx/hud/%s/minigames/%s", skinname, name); +} +string minigame_texture(string name) +{ + string path = minigame_texture_skin(autocvar_menu_skin,name); + if ( precache_pic(path) == "" ) + path = minigame_texture_skin("default", name); + return path; +} + +#define FIELD(Flags, Type, Name) MSLE_CLEAN_##Type(self.Name) +#define MSLE_CLEAN_String(x) strunzone(x); +#define MSLE_CLEAN_Byte(x) +#define MSLE_CLEAN_Char(x) +#define MSLE_CLEAN_Short(x) +#define MSLE_CLEAN_Coord(x) +#define MSLE_CLEAN_Angle(x) +#define MSLE_CLEAN_Float(x) +#define MSLE_CLEAN_Vector(x) +#define MSLE_CLEAN_Vector2D(x) + +#define MSLE(Name,Fields) \ + void msle_entremove_##Name() { strunzone(self.netname); Fields } +MINIGAME_SIMPLELINKED_ENTITIES +#undef MSLE +#undef FIELD + +void minigame_autoclean_entity(entity e) +{ + dprint("CL Auto-cleaned: ",ftos(num_for_edict(e)), " (",e.classname,")\n"); + remove(e); +} + +void HUD_MinigameMenu_CurrentButton(); +float auto_close_minigamemenu; +void deactivate_minigame() +{ + if ( !active_minigame ) + return; + + active_minigame.minigame_event(active_minigame,"deactivate"); + entity e = world; + while( (e = findentity(e, owner, self)) ) + if ( e.minigame_autoclean ) + { + minigame_autoclean_entity(e); + } + + minigame_self = world; + active_minigame = world; + + if ( auto_close_minigamemenu ) + { + HUD_MinigameMenu_Close(); + auto_close_minigamemenu = 0; + } + else + HUD_MinigameMenu_CurrentButton(); +} + +void activate_minigame(entity minigame) +{ + if ( !minigame ) + { + deactivate_minigame(); + return; + } + + if ( !minigame.descriptor || minigame.classname != "minigame" ) + { + dprint("Trying to activate unregistered minigame ",minigame.netname," in client\n"); + return; + } + + if ( minigame == active_minigame ) + return; + + if ( active_minigame ) + { + entity olds = minigame_self; + deactivate_minigame(); + minigame_self = olds; + } + + if ( minigame_self.owner != minigame ) + minigame_self = world; + active_minigame = minigame; + active_minigame.minigame_event(active_minigame,"activate"); + + if ( HUD_MinigameMenu_IsOpened() ) + HUD_MinigameMenu_CurrentButton(); + else + { + auto_close_minigamemenu = 1; + HUD_MinigameMenu_Open(); + } +} + +void minigame_player_entremove() +{ + if ( self.owner == active_minigame && self.minigame_playerslot == player_localentnum ) + deactivate_minigame(); +} + +vector ReadVector2D() { vector v; v_x = ReadCoord(); v_y = ReadCoord(); v_z = 0; return v; } +vector ReadVector() { vector v; v_x = ReadCoord(); v_y = ReadCoord(); v_z = ReadCoord(); return v; } +string() ReadString_Raw = #366; +string ReadString_Zoned() { return strzone(ReadString_Raw()); } +#define ReadFloat ReadCoord +#define ReadString ReadString_Zoned +#define FIELD(Flags, Type,Name) if ( sf & (Flags) ) self.Name = Read##Type(); +#define MSLE(Name,Fields) \ + else if ( self.classname == #Name ) { \ + if ( sf & MINIG_SF_CREATE ) { \ + minigame_read_owner(); \ + self.entremove = msle_entremove_##Name; \ + } \ + minigame_ent = self.owner; \ + Fields \ + } +void minigame_read_owner() +{ + string owner_name = ReadString_Raw(); + self.owner = world; + do + self.owner = find(self.owner,netname,owner_name); + while ( self.owner && self.owner.classname != "minigame" ); + if ( !self.owner ) + dprint("Got a minigame entity without a minigame!\n"); +} +void ent_read_minigame() +{ + float sf = ReadByte(); + if ( sf & MINIG_SF_CREATE ) + { + self.classname = msle_classname(ReadShort()); + self.netname = ReadString_Zoned(); + } + + entity minigame_ent = world; + + if ( self.classname == "minigame" ) + { + minigame_ent = self; + + if ( sf & MINIG_SF_CREATE ) + { + self.entremove = deactivate_minigame; + self.descriptor = minigame_get_descriptor(ReadString_Raw()); + if ( !self.descriptor ) + dprint("Got a minigame without a client-side descriptor!\n"); + else + self.minigame_event = self.descriptor.minigame_event; + } + if ( sf & MINIG_SF_UPDATE ) + self.minigame_flags = ReadLong(); + } + else if ( self.classname == "minigame_player" ) + { + float activate = 0; + if ( sf & MINIG_SF_CREATE ) + { + self.entremove = minigame_player_entremove; + minigame_read_owner(); + float ent = ReadLong(); + self.minigame_playerslot = ent; + dprint("Player: ",GetPlayerName(ent-1),"\n"); + + activate = (ent == player_localnum+1 && self.owner && self.owner != active_minigame); + + } + minigame_ent = self.owner; + + if ( sf & MINIG_SF_UPDATE ) + self.team = ReadByte(); + + if ( activate ) + { + minigame_self = self; + activate_minigame(self.owner); + } + } + MINIGAME_SIMPLELINKED_ENTITIES + + if ( minigame_ent ) + minigame_ent.minigame_event(minigame_ent,"network_receive",self,sf); + + dprint("CL Reading entity: ",ftos(num_for_edict(self)), + " classname:",self.classname," enttype:",ftos(self.enttype) ); + dprint(" sf:",ftos(sf)," netname:",self.netname,"\n\n"); +} +#undef ReadFloat +#undef ReadString +#undef FIELD +#undef MSLE + +string minigame_getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw) +{ + float last_word; + string s; + float take_until; + float skip = 0; + + s = getWrappedLine_remaining; + + if(w <= 0) + { + getWrappedLine_remaining = string_null; + return s; // the line has no size ANYWAY, nothing would be displayed. + } + + take_until = textLengthUpToWidth(s, w, theFontSize, tw); + + if ( take_until > strlen(s) ) + take_until = strlen(s); + + float i; + for ( i = 0; i < take_until; i++ ) + if ( substring(s,i,1) == "\n" ) + { + take_until = i; + skip = 1; + break; + } + + if ( take_until > 0 || skip > 0 ) + { + if ( skip == 0 && take_until < strlen(s) ) + { + last_word = take_until; + while(last_word > 0 && substring(s, last_word, 1) != " ") + --last_word; + + if ( last_word != 0 ) + { + take_until = last_word; + skip = 1; + } + } + + getWrappedLine_remaining = substring(s, take_until+skip, strlen(s) - (take_until+skip)); + if(getWrappedLine_remaining == "") + getWrappedLine_remaining = string_null; + else if (tw("^7", theFontSize) == 0) + getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take_until)), getWrappedLine_remaining); + return substring(s, 0, take_until); + } + else + { + getWrappedLine_remaining = string_null; + return s; + } +} + +vector minigame_drawstring_wrapped( float maxwidth, vector pos, string text, + vector fontsize, vector color, float theAlpha, float drawflags, float align ) +{ + getWrappedLine_remaining = text; + vector mypos = pos; + while ( getWrappedLine_remaining ) + { + string line = minigame_getWrappedLine(maxwidth,fontsize,stringwidth_nocolors); + if ( line == "" ) + break; + mypos_x = pos_x + (maxwidth - stringwidth_nocolors(line, fontsize)) * align; + drawstring(mypos, line, fontsize, color, theAlpha, drawflags); + mypos_y += fontsize_y; + } + mypos_x = maxwidth; + mypos_y -= pos_y; + return mypos; +} + +vector minigame_drawcolorcodedstring_wrapped( float maxwidth, vector pos, + string text, vector fontsize, float theAlpha, float drawflags, float align ) +{ + getWrappedLine_remaining = text; + vector mypos = pos; + while ( getWrappedLine_remaining ) + { + string line = minigame_getWrappedLine(maxwidth,fontsize,stringwidth_colors); + if ( line == "" ) + break; + mypos_x = pos_x + (maxwidth - stringwidth_colors(line, fontsize)) * align; + drawcolorcodedstring(mypos, line, fontsize, theAlpha, drawflags); + mypos_y += fontsize_y; + } + mypos_x = maxwidth; + mypos_y -= pos_y; + return mypos; +} + +void minigame_drawstring_trunc(float maxwidth, vector pos, string text, + vector fontsize, vector color, float theAlpha, float drawflags ) +{ + string line = textShortenToWidth(text,maxwidth,fontsize,stringwidth_nocolors); + drawstring(pos, line, fontsize, color, theAlpha, drawflags); +} + +void minigame_drawcolorcodedstring_trunc(float maxwidth, vector pos, string text, + vector fontsize, float theAlpha, float drawflags ) +{ + string line = textShortenToWidth(text,maxwidth,fontsize,stringwidth_colors); + drawcolorcodedstring(pos, line, fontsize, theAlpha, drawflags); +} + +void minigame_drawpic_centered( vector pos, string texture, vector sz, + vector color, float thealpha, float drawflags ) +{ + drawpic( pos-sz/2, texture, sz, color, thealpha, drawflags ); +} + +// Workaround because otherwise variadic arguments won't work properly +// It could be a bug in the compiler or in darkplaces +void minigame_cmd_workaround(float dummy, string...cmdargc) +{ + string cmd; + cmd = "cmd minigame "; + float i; + for ( i = 0; i < cmdargc; i++ ) + cmd = strcat(cmd,...(i,string)); + localcmd(strcat(cmd,"\n")); +} + +// Prompt the player to play in the current minigame +// (ie: it's their turn and they should get back to the minigame) +void minigame_prompt() +{ + if ( active_minigame && ! HUD_MinigameMenu_IsOpened() ) + { + HUD_Notify_Push(sprintf("minigames/%s/icon_notif",active_minigame.descriptor.netname), + _("It's your turn"), ""); + } +} \ No newline at end of file diff --git a/qcsrc/common/minigames/cl_minigames.qh b/qcsrc/common/minigames/cl_minigames.qh new file mode 100644 index 000000000..201b6ca6b --- /dev/null +++ b/qcsrc/common/minigames/cl_minigames.qh @@ -0,0 +1,115 @@ +// Get a square in the center of the avaliable area +// \note macro to pass by reference pos and mySize +#define minigame_hud_fitsqare(pos, mySize) \ + if ( mySize##_x > mySize##_y ) \ + { \ + pos##_x += (mySize##_x-mySize##_y)/2; \ + mySize##_x = mySize##_y; \ + } \ + else \ + { \ + pos##_y += (mySize##_y-mySize##_x)/2; \ + mySize##_x = mySize##_x; \ + } \ + if(panel_bg_padding) \ + { \ + pos += '1 1 0' * panel_bg_padding; \ + mySize -= '2 2 0' * panel_bg_padding; \ + } + +// Get position and size of a panel +// \note macro to pass by reference pos and mySize +#define minigame_hud_panelarea(pos, mySize, panelID) \ + pos = stov(cvar_string(strcat("hud_panel_", HUD_PANEL(panelID).panel_name, "_pos"))); \ + mySize = stov(cvar_string(strcat("hud_panel_", HUD_PANEL(panelID).panel_name, "_size"))); \ + pos##_x *= vid_conwidth; pos##_y *= vid_conheight; \ + mySize##_x *= vid_conwidth; mySize##_y *= vid_conheight; + +// draw a panel border and the given texture +void minigame_hud_simpleboard(vector pos, vector mySize, string board_texture); + +// Normalize (2D vector) v to relative coordinate inside pos mySize +vector minigame_hud_normalize(vector v, vector pos, vector mySize); + +// De-normalize (2D vector) v from relative coordinate inside pos mySize +vector minigame_hud_denormalize(vector v, vector pos, vector mySize); + +// De-normalize (2D vector) v from relative size inside pos mySize +vector minigame_hud_denormalize_size(vector v, vector pos, vector mySize); + +// Check if the mouse is inside the given area +float minigame_hud_mouse_in(vector pos, vector sz); + +// Like drawstring, but wrapping words to fit maxwidth +// returns the size of the drawn area +// align selects the string alignment (0 = left, 0.5 = center, 1 = right) +vector minigame_drawstring_wrapped( float maxwidth, vector pos, string text, + vector fontsize, vector color, float theAlpha, float drawflags, float align ); + +// Like drawcolorcodedstring, but wrapping words to fit maxwidth +// returns the size of the drawn area +// align selects the string alignment (0 = left, 0.5 = center, 1 = right) +vector minigame_drawcolorcodedstring_wrapped( float maxwidth, vector pos, + string text, vector fontsize, float theAlpha, float drawflags, float align ); + +// Like drawstring but truncates the text to fit maxwidth +void minigame_drawstring_trunc(float maxwidth, vector pos, string text, + vector fontsize, vector color, float theAlpha, float drawflags ); + +// Like drawcolorcodedstring but truncates the text to fit maxwidth +void minigame_drawcolorcodedstring_trunc(float maxwidth, vector pos, string text, + vector fontsize, float theAlpha, float drawflags ); + +// like drawpic but pos represent the center rather than the topleft corner +void minigame_drawpic_centered( vector pos, string texture, vector sz, + vector color, float thealpha, float drawflags ); + +// Get full path of a minigame texture +string minigame_texture(string name); + +// For minigame descriptors: hud function for the game board +.void(vector pos, vector size) minigame_hud_board; +// For minigame descriptors: hud function for the game status +.void(vector pos, vector size) minigame_hud_status; +// For minigame_player: player server slot, don't use for anything else +.float minigame_playerslot; + +// register all minigames +void initialize_minigames(); + +// client-side minigame session cleanup +void deactivate_minigame(); + +// Currently active minigame session +entity active_minigame; +// minigame_player representing this client +entity minigame_self; + +// Whethere there's an active minigame +float minigame_isactive() +{ + return active_minigame != world; +} + +// Execute a minigame command +#define minigame_cmd(...) minigame_cmd_workaround(0,__VA_ARGS__) +void minigame_cmd_workaround(float dummy, string...cmdargc); + +// Read a minigame entity from the server +void ent_read_minigame(); + +// Prompt the player to play in the current minigame +// (ie: it's their turn and they should get back to the minigame) +void minigame_prompt(); + +float HUD_MinigameMenu_IsOpened(); +void HUD_MinigameMenu_Close(); +float HUD_Minigame_Showpanels(); +// Adds a game-specific entry to the menu +void HUD_MinigameMenu_CustomEntry(entity parent, string message, string event_arg); + + +#define FOREACH_MINIGAME_ENTITY(entityvar) \ + entityvar=world; \ + while( (entityvar = findentity(entityvar,owner,active_minigame)) ) + diff --git a/qcsrc/common/minigames/cl_minigames_hud.qc b/qcsrc/common/minigames/cl_minigames_hud.qc new file mode 100644 index 000000000..0c4008511 --- /dev/null +++ b/qcsrc/common/minigames/cl_minigames_hud.qc @@ -0,0 +1,698 @@ +#include "minigames.qh" + +float HUD_mouse_over(entity somepanel) +{ + vector pos = stov(cvar_string(strcat("hud_panel_", somepanel.panel_name, "_pos"))); + vector sz = stov(cvar_string(strcat("hud_panel_", somepanel.panel_name, "_size"))); + return mousepos_x >= pos_x*vid_conwidth && mousepos_x <= (pos_x+sz_x)*vid_conwidth && + mousepos_y >= pos_y*vid_conheight && mousepos_y <= (pos_y+sz_y)*vid_conheight ; +} + +// ==================================================================== +// Minigame Board +// ==================================================================== + +void HUD_MinigameBoard () +{ + entity hud_minigame = world; + + if(!autocvar__hud_configure) + hud_minigame = active_minigame.descriptor; + else + hud_minigame = minigame_get_descriptor("nmm"); + + if ( !hud_minigame ) + return; + + HUD_Panel_UpdateCvars(); + + + vector pos, mySize; + pos = panel_pos; + mySize = panel_size; + + hud_minigame.minigame_hud_board(pos,mySize); +} + +// ==================================================================== +// Minigame Status +// ==================================================================== +void HUD_MinigameStatus () +{ + entity hud_minigame = world; + + if(!autocvar__hud_configure) + hud_minigame = active_minigame.descriptor; + else + hud_minigame = minigame_get_descriptor("nmm"); + + if ( !hud_minigame ) + return; + + HUD_Panel_UpdateCvars(); + + + vector pos, mySize; + pos = panel_pos; + mySize = panel_size; + + if(panel_bg_padding) + { + pos += '1 1 0' * panel_bg_padding; + mySize -= '2 2 0' * panel_bg_padding; + } + + hud_minigame.minigame_hud_status(pos,mySize); +} + +// ==================================================================== +// Minigame Menu +// ==================================================================== + +// Minigame menu options: list head +entity HUD_MinigameMenu_entries; +// Minigame menu options: list tail +entity HUD_MinigameMenu_last_entry; + +// Minigame menu options: insert entry after the given location +void HUD_MinigameMenu_InsertEntry(entity new, entity prev) +{ + if ( !HUD_MinigameMenu_entries ) + { + HUD_MinigameMenu_entries = new; + HUD_MinigameMenu_last_entry = new; + return; + } + + new.list_prev = prev; + new.list_next = prev.list_next; + if ( prev.list_next ) + prev.list_next.list_prev = new; + else + HUD_MinigameMenu_last_entry = new; + prev.list_next = new; + +} + + +// minigame menu item uder the mouse +entity HUD_MinigameMenu_activeitem; + +// Click the given item +void HUD_MinigameMenu_Click(entity menuitem) +{ + if ( menuitem ) + { + entity e = self; + self = menuitem; + menuitem.use(); + self = e; + } +} + +// Minigame menu options: Remove the given entry +// Precondition: the given entry is actually in the list +void HUD_MinigameMenu_EraseEntry ( entity e ) +{ + // remove child items (if any) + if ( e.flags & 2 ) + { + HUD_MinigameMenu_Click(e); + } + + if ( e.list_prev ) + e.list_prev.list_next = e.list_next; + else + HUD_MinigameMenu_entries = e.list_next; + + if ( e.list_next ) + e.list_next.list_prev = e.list_prev; + else + HUD_MinigameMenu_last_entry = e.list_prev; + + if ( HUD_MinigameMenu_activeitem == e ) + HUD_MinigameMenu_activeitem = world; + + remove(e); +} + +// Minigame menu options: create entry +entity HUD_MinigameMenu_SpawnEntry(string s, vector offset, vector fontsize, vector color,void() click) +{ + entity entry = spawn(); + entry.message = s; + entry.origin = offset; + entry.size = fontsize; + entry.colormod = color; + entry.flags = 0; + entry.use = click; + panel_pos_y += fontsize_y; + return entry; +} + +// Spawn a child entry of a collapsable entry +entity HUD_MinigameMenu_SpawnSubEntry(string s, void() click, entity parent) +{ + vector item_fontsize = hud_fontsize*1.25; + vector item_offset = '1 0 0' * item_fontsize_x; + entity item = HUD_MinigameMenu_SpawnEntry( + s,item_offset,item_fontsize,'0.8 0.8 0.8', click ); + item.owner = parent; + return item; +} + +// Click action for Create sub-entries +void HUD_MinigameMenu_ClickCreate_Entry() +{ + minigame_cmd("create ",self.netname); +} + +// Helper click action for collapsible entries +// returns true when you have to create the sub-entries +float HUD_MinigameMenu_Click_ExpandCollapse() +{ + entity e; + if ( self.flags & 2 ) + { + if ( HUD_MinigameMenu_activeitem && + HUD_MinigameMenu_activeitem.owner == self ) + HUD_MinigameMenu_activeitem = world; + self.flags &= ~2; + for ( e = self.list_next; e != world && e.owner == self; e = self.list_next ) + { + if ( e.flags & 2 ) + HUD_MinigameMenu_Click(e); + self.list_next = e.list_next; + remove(e); + } + if ( self.list_next ) + self.list_next.list_prev = self; + } + else + { + for ( e = HUD_MinigameMenu_entries; e != world; e = e.list_next ) + { + if ( e.flags & 2 && e.origin_x == self.origin_x) + HUD_MinigameMenu_Click(e); + } + + self.flags |= 2; + + return true; + } + return false; +} + +// Click action for the Create menu +void HUD_MinigameMenu_ClickCreate() +{ + if ( HUD_MinigameMenu_Click_ExpandCollapse() ) + { + entity e; + entity curr; + entity prev = self; + for ( e = minigame_descriptors; e != world; e = e.list_next ) + { + curr = HUD_MinigameMenu_SpawnSubEntry( + e.message, HUD_MinigameMenu_ClickCreate_Entry, self ); + curr.netname = e.netname; + curr.model = strzone(minigame_texture(strcat(e.netname,"/icon"))); + HUD_MinigameMenu_InsertEntry( curr, prev ); + prev = curr; + } + } +} + +// Click action for Join sub-entries +void HUD_MinigameMenu_ClickJoin_Entry() +{ + minigame_cmd("join ",self.netname); + HUD_MinigameMenu_EraseEntry(self); +} + +// Click action for the Join menu +void HUD_MinigameMenu_ClickJoin() +{ + if ( HUD_MinigameMenu_Click_ExpandCollapse() ) + { + entity e = world; + entity curr; + entity prev = self; + while( (e = find(e,classname,"minigame")) ) + { + if ( e != active_minigame ) + { + curr = HUD_MinigameMenu_SpawnSubEntry( + e.netname, HUD_MinigameMenu_ClickJoin_Entry, self ); + curr.netname = e.netname; + curr.model = strzone(minigame_texture(strcat(e.descriptor.netname,"/icon"))); + HUD_MinigameMenu_InsertEntry( curr, prev ); + prev = curr; + } + } + } +} + +/*// Temporary placeholder for un-implemented Click actions +void HUD_MinigameMenu_ClickNoop() +{ + dprint("Placeholder for ",self.message,"\n"); +}*/ + +// Click action for Quit +void HUD_MinigameMenu_ClickQuit() +{ + minigame_cmd("end"); +} + +// Click action for Invite sub-entries +void HUD_MinigameMenu_ClickInvite_Entry() +{ + minigame_cmd("invite #",self.netname); +} + +// Click action for the Invite menu +void HUD_MinigameMenu_ClickInvite() +{ + if ( HUD_MinigameMenu_Click_ExpandCollapse() ) + { + float i; + entity e; + entity prev = self; + for(i = 0; i < maxclients; ++i) + { + if ( player_localnum != i && playerslots[i] && GetPlayerName(i) != "" && + !findfloat(world,minigame_playerslot,i+1) && playerslots[i].ping ) + { + e = HUD_MinigameMenu_SpawnSubEntry( + strzone(GetPlayerName(i)), HUD_MinigameMenu_ClickInvite_Entry, + self ); + e.flags |= 1; + e.netname = strzone(ftos(i+1)); + e.origin_x *= 2; + HUD_MinigameMenu_InsertEntry(e,prev); + prev = e; + } + } + } +} + +void HUD_MinigameMenu_ClickCustomEntry() +{ + if ( active_minigame ) + active_minigame.minigame_event(active_minigame,"menu_click",self.netname); +} + +// Adds a game-specific entry to the menu +void HUD_MinigameMenu_CustomEntry(entity parent, string menumessage, string event_arg) +{ + entity e = HUD_MinigameMenu_SpawnSubEntry( + menumessage, HUD_MinigameMenu_ClickCustomEntry, parent ); + e.netname = event_arg; + HUD_MinigameMenu_InsertEntry(e, parent); + dprint("CustomEntry ",ftos(num_for_edict(parent))," ",menumessage," ",event_arg,"\n"); +} + +// Click action for the Current Game menu +void HUD_MinigameMenu_ClickCurrentGame() +{ + if ( HUD_MinigameMenu_Click_ExpandCollapse() ) + { + HUD_MinigameMenu_InsertEntry( HUD_MinigameMenu_SpawnSubEntry( + _("Quit"), HUD_MinigameMenu_ClickQuit, self ), self); + + active_minigame.minigame_event(active_minigame,"menu_show",self); + + HUD_MinigameMenu_InsertEntry( HUD_MinigameMenu_SpawnSubEntry( + _("Invite"), HUD_MinigameMenu_ClickInvite, self), self); + } +} +// Whether the minigame menu panel is open +float HUD_MinigameMenu_IsOpened() +{ + return !!HUD_MinigameMenu_entries; +} + +// Close the minigame menu panel +void HUD_MinigameMenu_Close() +{ + if ( HUD_MinigameMenu_IsOpened() ) + { + entity e, p; + for ( e = HUD_MinigameMenu_entries; e != world; e = p ) + { + p = e.list_next; + remove(e); + } + HUD_MinigameMenu_entries = world; + HUD_MinigameMenu_last_entry = world; + HUD_MinigameMenu_activeitem = world; + if(autocvar_hud_cursormode) + if ( !autocvar__hud_configure ) + setcursormode(0); + } +} + +// toggle a button to manage the current game +void HUD_MinigameMenu_CurrentButton() +{ + entity e; + if ( active_minigame ) + { + for ( e = HUD_MinigameMenu_last_entry; e != world; e = e.list_prev ) + if ( e.classname == "hud_minigamemenu_exit" ) + { + HUD_MinigameMenu_EraseEntry(e); + break; + } + entity currb = HUD_MinigameMenu_SpawnEntry( + _("Current Game"), '0 0 0', hud_fontsize*1.5,'0.7 0.84 1', HUD_MinigameMenu_ClickCurrentGame ); + currb.classname = "hud_minigamemenu_current"; + currb.model = strzone(minigame_texture(strcat(active_minigame.descriptor.netname,"/icon"))); + HUD_MinigameMenu_InsertEntry(currb,HUD_MinigameMenu_last_entry); + HUD_MinigameMenu_Click(currb); + } + else + { + entity p; + for ( e = HUD_MinigameMenu_last_entry; e != world; e = p.list_prev ) + { + p = e; + if ( e.classname == "hud_minigamemenu_current" ) + { + p = e.list_next; + if ( !p ) + p = HUD_MinigameMenu_last_entry; + HUD_MinigameMenu_EraseEntry(e); + break; + } + } + for ( e = HUD_MinigameMenu_last_entry; e != world; e = e.list_prev ) + if ( e.classname == "hud_minigamemenu_exit" ) + return; + entity exit = HUD_MinigameMenu_SpawnEntry( + _("Exit Menu"),'0 0 0',hud_fontsize*1.5,'0.7 0.84 1', HUD_MinigameMenu_Close); + exit.classname = "hud_minigamemenu_exit"; + HUD_MinigameMenu_InsertEntry ( exit, HUD_MinigameMenu_last_entry ); + } +} + +// Open the minigame menu panel +void HUD_MinigameMenu_Open() +{ + if ( !HUD_MinigameMenu_IsOpened() ) + { + HUD_MinigameMenu_InsertEntry( HUD_MinigameMenu_SpawnEntry( + _("Create"), '0 0 0', hud_fontsize*1.5,'0.7 0.84 1', HUD_MinigameMenu_ClickCreate), + HUD_MinigameMenu_last_entry ); + HUD_MinigameMenu_InsertEntry ( HUD_MinigameMenu_SpawnEntry( + _("Join"),'0 0 0',hud_fontsize*1.5,'0.7 0.84 1', HUD_MinigameMenu_ClickJoin), + HUD_MinigameMenu_last_entry ); + HUD_MinigameMenu_CurrentButton(); + HUD_MinigameMenu_activeitem = world; + if(autocvar_hud_cursormode) + setcursormode(1); + } +} + +// Handles mouse input on to minigame menu panel +void HUD_MinigameMenu_MouseInput() +{ + panel = HUD_PANEL(MINIGAME_MENU); + + HUD_Panel_UpdateCvars() + + if(panel_bg_padding) + { + panel_pos += '1 1 0' * panel_bg_padding; + panel_size -= '2 2 0' * panel_bg_padding; + } + + entity e; + + panel_pos_y += hud_fontsize_y*2; + + HUD_MinigameMenu_activeitem = world; + vector sz; + for ( e = HUD_MinigameMenu_entries; e != world; e = e.list_next ) + { + sz = eX*panel_size_x + eY*e.size_y; + if ( e.model ) + sz_y = 22; + if ( !HUD_MinigameMenu_activeitem && mousepos_y >= panel_pos_y && mousepos_y <= panel_pos_y + sz_y ) + { + HUD_MinigameMenu_activeitem = e; + } + panel_pos_y += sz_y; + } +} + +// Draw a menu entry +void HUD_MinigameMenu_DrawEntry(vector pos, string s, vector fontsize, vector color) +{ + minigame_drawstring_trunc(panel_size_x-pos_x+panel_pos_x, pos, s, + fontsize, color, panel_fg_alpha, DRAWFLAG_NORMAL); +} +// Draw a color-coded menu +void HUD_MinigameMenu_DrawColoredEntry(vector pos, string s, vector fontsize) +{ + minigame_drawcolorcodedstring_trunc(panel_size_x-pos_x+panel_pos_x, pos, s, + fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); +} + +// minigame menu panel UI +void HUD_MinigameMenu () +{ + if ( !HUD_MinigameMenu_IsOpened() ) + return; + + HUD_Panel_UpdateCvars(); + + HUD_Panel_DrawBg(1); + + if(panel_bg_padding) + { + panel_pos += '1 1 0' * panel_bg_padding; + panel_size -= '2 2 0' * panel_bg_padding; + } + + HUD_MinigameMenu_DrawEntry(panel_pos,_("Minigames"),hud_fontsize*2,'0.25 0.47 0.72'); + panel_pos_y += hud_fontsize_y*2; + + entity e; + vector color; + vector offset; + float itemh; + vector imgsz = '22 22 0'; // NOTE: if changed, edit where HUD_MinigameMenu_activeitem is selected + for ( e = HUD_MinigameMenu_entries; e != world; e = e.list_next ) + { + color = e.colormod; + + offset = e.origin; + itemh = e.size_y; + + if ( e.model ) + itemh = imgsz_y; + + if ( e.flags & 2 ) + { + drawfill(panel_pos, eX*panel_size_x + eY*itemh, e.colormod, + panel_fg_alpha, DRAWFLAG_NORMAL); + color = '0 0 0'; + } + + if ( e.model ) + { + drawpic( panel_pos+offset, e.model, imgsz, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL ); + offset_x += imgsz_x; + offset_y = (imgsz_y-e.size_y) / 2; + } + + if ( e.flags & 1 ) + HUD_MinigameMenu_DrawColoredEntry(panel_pos+offset,e.message,e.size); + else + HUD_MinigameMenu_DrawEntry(panel_pos+offset,e.message,e.size,color); + + if ( e == HUD_MinigameMenu_activeitem ) + drawfill(panel_pos, eX*panel_size_x + eY*itemh,'1 1 1', 0.25, DRAWFLAG_ADDITIVE); + + panel_pos_y += itemh; + } +} + +// ==================================================================== +// Minigame Help Panel +// ==================================================================== + +void HUD_MinigameHelp() +{ + string help_message; + + if(!autocvar__hud_configure) + help_message = active_minigame.message; + else + help_message = "Minigame message"; + + if ( !help_message ) + return; + + HUD_Panel_UpdateCvars(); + + + vector pos, mySize; + pos = panel_pos; + mySize = panel_size; + + if(panel_bg_padding) + { + pos += '1 1 0' * panel_bg_padding; + mySize -= '2 2 0' * panel_bg_padding; + } + + minigame_drawcolorcodedstring_wrapped( mySize_x, pos, help_message, + hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL, 0.5 ); +} + +// ==================================================================== +// Minigame Panel Input +// ==================================================================== +float HUD_Minigame_InputEvent(float bInputType, float nPrimary, float nSecondary) +{ + + if( !HUD_MinigameMenu_IsOpened() || autocvar__hud_configure ) + return false; + + if(bInputType == 3) + { + mousepos_x = nPrimary; + mousepos_y = nSecondary; + if ( minigame_isactive() && HUD_mouse_over(HUD_PANEL(MINIGAME_BOARD)) ) + active_minigame.minigame_event(active_minigame,"mouse_moved",mousepos); + return true; + + } + else + { + + if(bInputType == 0) { + if(nPrimary == K_ALT) hudShiftState |= S_ALT; + if(nPrimary == K_CTRL) hudShiftState |= S_CTRL; + if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT; + if(nPrimary == K_MOUSE1) mouseClicked |= S_MOUSE1; + if(nPrimary == K_MOUSE2) mouseClicked |= S_MOUSE2; + } + else if(bInputType == 1) { + if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT); + if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL); + if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT); + if(nPrimary == K_MOUSE1) mouseClicked -= (mouseClicked & S_MOUSE1); + if(nPrimary == K_MOUSE2) mouseClicked -= (mouseClicked & S_MOUSE2); + } + + // allow some binds + string con_keys; + float keys; + float i; + con_keys = findkeysforcommand("toggleconsole", 0); + keys = tokenize(con_keys); // findkeysforcommand returns data for this + for (i = 0; i < keys; ++i) + { + if(nPrimary == stof(argv(i))) + return false; + } + + if ( minigame_isactive() && ( bInputType == 0 || bInputType == 1 ) ) + { + string device = ""; + string action = bInputType == 0 ? "pressed" : "released"; + if ( nPrimary >= K_MOUSE1 && nPrimary <= K_MOUSE16 ) + { + if ( HUD_mouse_over(HUD_PANEL(MINIGAME_BOARD)) ) + device = "mouse"; + } + else + device = "key"; + + if ( device && active_minigame.minigame_event( + active_minigame,strcat(device,"_",action),nPrimary) ) + return true; + + /// TODO: bInputType == 2? + } + + if ( bInputType == 0 ) + { + if ( nPrimary == K_MOUSE1 && HUD_MinigameMenu_activeitem && + HUD_mouse_over(HUD_PANEL(MINIGAME_MENU)) ) + { + HUD_MinigameMenu_Click(HUD_MinigameMenu_activeitem); + return true; + } + if ( nPrimary == K_UPARROW || nPrimary == K_KP_UPARROW ) + { + if ( HUD_MinigameMenu_activeitem && HUD_MinigameMenu_activeitem.list_prev ) + HUD_MinigameMenu_activeitem = HUD_MinigameMenu_activeitem.list_prev; + else + HUD_MinigameMenu_activeitem = HUD_MinigameMenu_last_entry; + return true; + } + else if ( nPrimary == K_DOWNARROW || nPrimary == K_KP_DOWNARROW ) + { + if ( HUD_MinigameMenu_activeitem && HUD_MinigameMenu_activeitem.list_next ) + HUD_MinigameMenu_activeitem = HUD_MinigameMenu_activeitem.list_next; + else + HUD_MinigameMenu_activeitem = HUD_MinigameMenu_entries; + return true; + } + else if ( nPrimary == K_HOME || nPrimary == K_KP_HOME ) + { + HUD_MinigameMenu_activeitem = HUD_MinigameMenu_entries; + return true; + } + else if ( nPrimary == K_END || nPrimary == K_KP_END ) + { + HUD_MinigameMenu_activeitem = HUD_MinigameMenu_entries; + return true; + } + else if ( nPrimary == K_KP_ENTER || nPrimary == K_ENTER || nPrimary == K_SPACE ) + { + HUD_MinigameMenu_Click(HUD_MinigameMenu_activeitem); + return true; + } + else if ( nPrimary == K_ESCAPE ) + { + HUD_MinigameMenu_Close(); + return true; + } + } + } + + return false; + +} + +void HUD_Minigame_Mouse() +{ + if( !HUD_MinigameMenu_IsOpened() || autocvar__hud_configure || mv_active ) + return; + + if(!autocvar_hud_cursormode) + { + mousepos = mousepos + getmousepos() * autocvar_menu_mouse_speed; + + mousepos_x = bound(0, mousepos_x, vid_conwidth); + mousepos_y = bound(0, mousepos_y, vid_conheight); + } + + if ( HUD_MinigameMenu_IsOpened() && HUD_mouse_over(HUD_PANEL(MINIGAME_MENU)) ) + HUD_MinigameMenu_MouseInput(); + + vector cursorsize = '32 32 0'; + drawpic(mousepos-'8 4 0', strcat("gfx/menu/", autocvar_menu_skin, "/cursor.tga"), + cursorsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); +} + +float HUD_Minigame_Showpanels() +{ + return HUD_MinigameMenu_IsOpened() && ( autocvar__hud_configure || minigame_isactive() ); +} diff --git a/qcsrc/common/minigames/minigame/all.qh b/qcsrc/common/minigames/minigame/all.qh new file mode 100644 index 000000000..1330648cc --- /dev/null +++ b/qcsrc/common/minigames/minigame/all.qh @@ -0,0 +1,99 @@ +/** + +How to create a minigame +======================== + +Create a file for your minigame in this directory and #include it here. +(ttt.qc implements tic tac toe and can be used as an example) +and add your minigame to REGISTERED_MINIGAMES (see below) + +Required functions +------------------ + +SVQC: + float minigame_event_(entity minigame, string event, ...count) + see ../minigames.qh for a detailed explanation +CSQC: + void minigame_hud_board_(vector pos, vector mySize) + draws the main game board inside the rectangle defined by pos and mySize + (That rectangle is expressed in window coordinates) + void minigame_hud_status_(vector pos, vector mySize) + draws the game status panel inside the rectangle defined by pos and mySize + (That rectangle is expressed in window coordinates) + This panel shows eg scores, captured pieces and so on + float minigame_event_(entity minigame, string event, ...count) + see ../minigames.qh for a detailed explanation + +Managing entities +----------------- + +You can link entities without having to worry about them if their classname +has been defined in MINIGAME_SIMPLELINKED_ENTITIES (see below) +Such entities can be spawned with msle_spawn and the system +will handle networking and cleanup automatically. +You'll still need to set .SendFlags according to what you specified in FIELD +in order for them to be sent, ../minigames.qh defines some constants to be +used as send flags for minigame entities: + +* MINIG_SF_CREATE + Used when creating a new object, you can use this to define fields that don't change +* MINIG_SF_UPDATE + A miscellaneous update, can be safely used if the entity has just a few fields +* MINIG_SF_CUSTOM + Starting value for custom flags, since there are bit-wise flags, + the following values shall be MINIG_SF_CUSTOM*2, MINIG_SF_CUSTOM*4 and MINIG_SF_CUSTOM*8. +* MINIG_SF_MAX + Maximum flag value that will be networked +* MINIG_SF_ALL + Mask matching all possible flags + +Note: As of now, flags are sent as a single byte + +Even for non-networked entities, the system provides a system to remove +automatically unneeded entities when the minigame is over, the requirement is +that .owner is set to the minigame session entity and .minigame_autoclean is true. +*/ + +#include "nmm.qc" +#include "ttt.qc" + +/** + * Registration: + * MINIGAME(id,"Name") + * id (QuakeC symbol) Game identifier, used to find the functions explained above + * "Name"(String) Human readable name for the game, shown in the UI + */ +#define REGISTERED_MINIGAMES \ + MINIGAME(nmm, "Nine Men's Morris") \ + MINIGAME(ttt, "Tic Tac Toe") \ + /*empty line*/ + +/** + * Set up automatic entity read/write functionality + * To ensure that everything is handled automatically, spawn on the server using msle_spawn + * Syntax: + * MSLE(classname,Field...) \ + * classname: Identifier used to recognize the type of the entity + * (must be set as .classname on the sent entities) + * Field... : List of FIELD calls + * FIELD(sendflags, Type, field) + * sendflags: Send flags that signal when this field has to be sent + * Type : Type of the entity field. Used to determine WriteX/ReadX functions. + * Follows a list of accepted values + * Byte + * Char + * Short + * Long + * Coord + * Angle + * String Note: strzoned on client + * Float Note: implemented as Write/Read Coord + * Vector Note: implemented as Write/Read Coord on _x _y _z + * Vector2D Note: implemented as Write/Read Coord on _x _y + * Note: + * classname and netname are always sent + * MSLE stands for Minigame Simple Linked Entity + */ +#define MINIGAME_SIMPLELINKED_ENTITIES \ + MSLE(minigame_board_piece,FIELD(MINIG_SF_CREATE,Byte,team) FIELD(MINIG_SF_UPDATE, Short, minigame_flags) FIELD(MINIG_SF_UPDATE, Vector2D,origin)) \ + /*empty line*/ diff --git a/qcsrc/common/minigames/minigame/nmm.qc b/qcsrc/common/minigames/minigame/nmm.qc new file mode 100644 index 000000000..aaebe690e --- /dev/null +++ b/qcsrc/common/minigames/minigame/nmm.qc @@ -0,0 +1,762 @@ +const float NMM_TURN_PLACE = 0x0100; // player has to place a piece on the board +const float NMM_TURN_MOVE = 0x0200; // player has to move a piece by one tile +const float NMM_TURN_FLY = 0x0400; // player has to move a piece anywhere +const float NMM_TURN_TAKE = 0x0800; // player has to take a non-mill piece +const float NMM_TURN_TAKEANY=0x1000; // combine with NMM_TURN_TAKE, can take mill pieces +const float NMM_TURN_WIN = 0x2000; // player has won +const float NMM_TURN_TYPE = 0xff00; +const float NMM_TURN_TEAM1 = 0x0001; +const float NMM_TURN_TEAM2 = 0x0002; +const float NMM_TURN_TEAM = 0x00ff; + +const float NMM_PIECE_DEAD = 0x0; // captured by the enemy +const float NMM_PIECE_HOME = 0x1; // not yet placed +const float NMM_PIECE_BOARD = 0x2; // placed on the board + +.float nmm_tile_distance; +.entity nmm_tile_piece; +.string nmm_tile_hmill; +.string nmm_tile_vmill; + +// build a string containing the indices of the tile to check for a horizontal mill +string nmm_tile_build_hmill(entity tile) +{ + float number = minigame_tile_number(tile.netname); + float letter = minigame_tile_letter(tile.netname); + if ( number == letter || number+letter == 6 ) + { + float add = letter < 3 ? 1 : -1; + return strcat(tile.netname," ", + minigame_tile_buildname(letter+add*tile.nmm_tile_distance,number)," ", + minigame_tile_buildname(letter+add*2*tile.nmm_tile_distance,number) ); + } + else if ( letter == 3 ) + return strcat(minigame_tile_buildname(letter-tile.nmm_tile_distance,number)," ", + tile.netname," ", + minigame_tile_buildname(letter+tile.nmm_tile_distance,number) ); + else if ( letter < 3 ) + return strcat(minigame_tile_buildname(0,number)," ", + minigame_tile_buildname(1,number)," ", + minigame_tile_buildname(2,number) ); + else + return strcat(minigame_tile_buildname(4,number)," ", + minigame_tile_buildname(5,number)," ", + minigame_tile_buildname(6,number) ); +} + +// build a string containing the indices of the tile to check for a vertical mill +string nmm_tile_build_vmill(entity tile) +{ + float letter = minigame_tile_letter(tile.netname); + float number = minigame_tile_number(tile.netname); + if ( letter == number || letter+number == 6 ) + { + float add = number < 3 ? 1 : -1; + return strcat(tile.netname," ", + minigame_tile_buildname(letter,number+add*tile.nmm_tile_distance)," ", + minigame_tile_buildname(letter,number+add*2*tile.nmm_tile_distance) ); + } + else if ( number == 3 ) + return strcat(minigame_tile_buildname(letter,number-tile.nmm_tile_distance)," ", + tile.netname," ", + minigame_tile_buildname(letter,number+tile.nmm_tile_distance) ); + else if ( number < 3 ) + return strcat(minigame_tile_buildname(letter,0)," ", + minigame_tile_buildname(letter,1)," ", + minigame_tile_buildname(letter,2) ); + else + return strcat(minigame_tile_buildname(letter,4)," ", + minigame_tile_buildname(letter,5)," ", + minigame_tile_buildname(letter,6) ); +} + +// Create an new tile +// \param id Tile index (eg: a1) +// \param minig Owner minigame instance +// \param distance Distance from adjacent tiles +void nmm_spawn_tile(string id, entity minig, float distance) +{ + // TODO global variable + list_next for simpler tile loops + entity e = spawn(); + e.origin = minigame_tile_pos(id,7,7); + e.classname = "minigame_nmm_tile"; + e.netname = id; + e.owner = minig; + e.team = 0; + e.nmm_tile_distance = distance; + e.nmm_tile_hmill = strzone(nmm_tile_build_hmill(e)); + e.nmm_tile_vmill = strzone(nmm_tile_build_vmill(e)); +} + +// Create a tile square and recursively create inner squares +// \param minig Owner minigame instance +// \param offset Index offset (eg: 1 to start the square at b2, 0 at a1 etc.) +// \param skip Number of indices to skip between tiles (eg 1: a1, a3) +void nmm_spawn_tile_square( entity minig, float offset, float skip ) +{ + float letter = offset; + float number = offset; + float i, j; + for ( i = 0; i < 3; i++ ) + { + number = offset; + for ( j = 0; j < 3; j++ ) + { + if ( i != 1 || j != 1 ) + nmm_spawn_tile(strzone(minigame_tile_buildname(letter,number)),minig, skip+1); + number += skip+1; + } + letter += skip+1; + } + + if ( skip > 0 ) + nmm_spawn_tile_square(minig,offset+1,skip-1); +} + +// Remove tiles of a NMM minigame +void nmm_kill_tiles(entity minig) +{ + entity e = world; + while ( ( e = findentity(e,owner,minig) ) ) + if ( e.classname == "minigame_nmm_tile" ) + { + strunzone(e.netname); + strunzone(e.nmm_tile_hmill); + strunzone(e.nmm_tile_vmill); + remove(e); + } +} + +// Create the tiles of a NMM minigame +void nmm_init_tiles(entity minig) +{ + nmm_spawn_tile_square(minig,0,2); +} + +// Find a tile by its id +entity nmm_find_tile(entity minig, string id) +{ + entity e = world; + while ( ( e = findentity(e,owner,minig) ) ) + if ( e.classname == "minigame_nmm_tile" && e.netname == id ) + return e; + return world; +} + +// Check whether two tiles are adjacent +float nmm_tile_adjacent(entity tile1, entity tile2) +{ + + float dnumber = fabs ( minigame_tile_number(tile1.netname) - minigame_tile_number(tile2.netname) ); + float dletter = fabs ( minigame_tile_letter(tile1.netname) - minigame_tile_letter(tile2.netname) ); + + return ( dnumber == 0 && ( dletter == 1 || dletter == tile1.nmm_tile_distance ) ) || + ( dletter == 0 && ( dnumber == 1 || dnumber == tile1.nmm_tile_distance ) ); +} + +// Returns 1 if there is at least 1 free adjacent tile +float nmm_tile_canmove(entity tile) +{ + entity e = world; + while ( ( e = findentity(e,owner,tile.owner) ) ) + if ( e.classname == "minigame_nmm_tile" && !e.nmm_tile_piece + && nmm_tile_adjacent(e,tile) ) + { + return 1; + } + return 0; +} + +// Check if the given tile id appears in the string +float nmm_in_mill_string(entity tile, string s) +{ + float argc = tokenize(s); + float i; + for ( i = 0; i < argc; i++ ) + { + entity e = nmm_find_tile(tile.owner,argv(i)); + if ( !e || !e.nmm_tile_piece || e.nmm_tile_piece.team != tile.nmm_tile_piece.team ) + return 0; + } + return 1; +} + +// Check if a tile is in a mill +float nmm_in_mill(entity tile) +{ + return tile.nmm_tile_piece && ( + nmm_in_mill_string(tile,tile.nmm_tile_hmill) || + nmm_in_mill_string(tile,tile.nmm_tile_vmill) ); +} + + +#ifdef SVQC +// Find a NMM piece matching some of the given flags and team number +entity nmm_find_piece(entity start, entity minigame, float teamn, float pieceflags) +{ + entity e = start; + while ( ( e = findentity(e,owner,minigame) ) ) + if ( e.classname == "minigame_board_piece" && + (e.minigame_flags & pieceflags) && e.team == teamn ) + return e; + return world; +} + +// Count NMM pieces matching flags and team number +float nmm_count_pieces(entity minigame, float teamn, float pieceflags) +{ + float n = 0; + entity e = world; + while (( e = nmm_find_piece(e,minigame, teamn, pieceflags) )) + n++; + return n; +} + +// required function, handle server side events +float minigame_event_nmm(entity minigame, string event, ...) +{ + if ( event == "start" ) + { + minigame.minigame_flags = NMM_TURN_PLACE|NMM_TURN_TEAM1; + nmm_init_tiles(minigame); + float i; + entity e; + for ( i = 0; i < 7; i++ ) + { + e = msle_spawn(minigame,"minigame_board_piece"); + e.team = 1; + e.minigame_flags = NMM_PIECE_HOME; + e = msle_spawn(minigame,"minigame_board_piece"); + e.team = 2; + e.minigame_flags = NMM_PIECE_HOME; + } + + return 1; + } + else if ( event == "end" ) + { + nmm_kill_tiles(minigame); + } + else if ( event == "join" ) + { + float n = 0; + entity e; + for ( e = minigame.minigame_players; e; e = e.list_next ) + n++; + if ( n >= 2 ) + return 0; + if ( minigame.minigame_players && minigame.minigame_players.team == 1 ) + return 2; + return 1; + } + else if ( event == "cmd" ) + { + entity e = ...(0,entity); + float argc = ...(1,float); + entity tile = world; + entity piece = world; + float move_ok = 0; + + if ( e && argc >= 2 && argv(0) == "move" && + ( minigame.minigame_flags & NMM_TURN_TEAM ) == e.team ) + { + tile = nmm_find_tile(minigame,argv(1)); + if ( !tile ) + { + move_ok = 0; + } + else if ( minigame.minigame_flags & NMM_TURN_PLACE ) + { + piece = nmm_find_piece(world,minigame,e.team,NMM_PIECE_HOME); + if ( !tile.nmm_tile_piece && piece ) + { + tile.nmm_tile_piece = piece; + piece.minigame_flags = NMM_PIECE_BOARD; + piece.origin = tile.origin; + piece.SendFlags |= MINIG_SF_UPDATE; + move_ok = 1; + } + } + else if ( minigame.minigame_flags & NMM_TURN_MOVE ) + { + if ( tile.nmm_tile_piece && tile.nmm_tile_piece.team == e.team ) + { + piece = tile.nmm_tile_piece; + entity tile2 = nmm_find_tile(minigame,argv(2)); + if ( tile2 && nmm_tile_adjacent(tile,tile2) && !tile2.nmm_tile_piece ) + { + tile.nmm_tile_piece = world; + tile2.nmm_tile_piece = piece; + piece.origin = tile2.origin; + piece.SendFlags |= MINIG_SF_UPDATE; + tile = tile2; + move_ok = 1; + } + } + + } + else if ( minigame.minigame_flags & NMM_TURN_FLY ) + { + if ( tile.nmm_tile_piece && tile.nmm_tile_piece.team == e.team ) + { + piece = tile.nmm_tile_piece; + entity tile2 = nmm_find_tile(minigame,argv(2)); + if ( tile2 && !tile2.nmm_tile_piece ) + { + tile.nmm_tile_piece = world; + tile2.nmm_tile_piece = piece; + piece.origin = tile2.origin; + piece.SendFlags |= MINIG_SF_UPDATE; + tile = tile2; + move_ok = 1; + } + } + + } + else if ( minigame.minigame_flags & NMM_TURN_TAKE ) + { + piece = tile.nmm_tile_piece; + if ( piece && piece.nmm_tile_piece.team != e.team ) + { + tile.nmm_tile_piece = world; + piece.minigame_flags = NMM_PIECE_DEAD; + piece.SendFlags |= MINIG_SF_UPDATE; + move_ok = 1; + } + } + + float nextteam = e.team % 2 + 1; + float npieces = nmm_count_pieces(minigame,nextteam,NMM_PIECE_HOME|NMM_PIECE_BOARD); + + if ( npieces < 3 ) + { + minigame.minigame_flags = NMM_TURN_WIN | e.team; + minigame.SendFlags |= MINIG_SF_UPDATE; + } + else if ( move_ok) + { + if ( !(minigame.minigame_flags & NMM_TURN_TAKE) && nmm_in_mill(tile) ) + { + minigame.minigame_flags = NMM_TURN_TAKE|e.team; + float takemill = NMM_TURN_TAKEANY; + entity f = world; + while ( ( f = findentity(f,owner,minigame) ) ) + if ( f.classname == "minigame_nmm_tile" && f.nmm_tile_piece && + f.nmm_tile_piece.team == nextteam && !nmm_in_mill(f) ) + { + takemill = 0; + break; + } + minigame.minigame_flags |= takemill; + } + else + { + if ( nmm_find_piece(world,minigame,nextteam,NMM_PIECE_HOME) ) + minigame.minigame_flags = NMM_TURN_PLACE|nextteam; + else if ( npieces == 3 ) + minigame.minigame_flags = NMM_TURN_FLY|nextteam; + else + { + minigame.minigame_flags = NMM_TURN_WIN|e.team; + entity f = world; + while ( ( f = findentity(f,owner,minigame) ) ) + if ( f.classname == "minigame_nmm_tile" && f.nmm_tile_piece && + f.nmm_tile_piece.team == nextteam && nmm_tile_canmove(f) ) + { + minigame.minigame_flags = NMM_TURN_MOVE|nextteam; + break; + } + } + } + minigame.SendFlags |= MINIG_SF_UPDATE; + } + else + dprint("Invalid move: ",...(2,string),"\n"); + return 1; + } + } + return 0; +} + +#elif defined(CSQC) + +entity nmm_currtile; +entity nmm_fromtile; + +vector nmm_boardpos; +vector nmm_boardsize; + +// whether the given tile is a valid selection +float nmm_valid_selection(entity tile) +{ + if ( ( tile.owner.minigame_flags & NMM_TURN_TEAM ) != minigame_self.team ) + return 0; // not our turn + if ( tile.owner.minigame_flags & NMM_TURN_PLACE ) + return !tile.nmm_tile_piece; // need to put a piece on an empty spot + if ( tile.owner.minigame_flags & NMM_TURN_MOVE ) + { + if ( tile.nmm_tile_piece && tile.nmm_tile_piece.team == minigame_self.team && + nmm_tile_canmove(tile) ) + return 1; // movable tile + if ( nmm_fromtile ) // valid destination + return !tile.nmm_tile_piece && nmm_tile_adjacent(nmm_fromtile,tile); + return 0; + } + if ( tile.owner.minigame_flags & NMM_TURN_FLY ) + { + if ( nmm_fromtile ) + return !tile.nmm_tile_piece; + else + return tile.nmm_tile_piece && tile.nmm_tile_piece.team == minigame_self.team; + } + if ( tile.owner.minigame_flags & NMM_TURN_TAKE ) + return tile.nmm_tile_piece && tile.nmm_tile_piece.team != minigame_self.team && + ( (tile.owner.minigame_flags & NMM_TURN_TAKEANY) || !nmm_in_mill(tile) ); + return 0; +} + +// whether it should highlight valid tile selections +float nmm_draw_avaliable(entity tile) +{ + if ( ( tile.owner.minigame_flags & NMM_TURN_TEAM ) != minigame_self.team ) + return 0; + if ( (tile.owner.minigame_flags & NMM_TURN_TAKE) ) + return 1; + if ( (tile.owner.minigame_flags & (NMM_TURN_FLY|NMM_TURN_MOVE)) && nmm_fromtile ) + return !tile.nmm_tile_piece; + return 0; +} + +// Required function, draw the game board +void minigame_hud_board_nmm(vector pos, vector mySize) +{ + minigame_hud_fitsqare(pos, mySize); + nmm_boardpos = pos; + nmm_boardsize = mySize; + minigame_hud_simpleboard(pos,mySize,minigame_texture("nmm/board")); + + vector tile_size = minigame_hud_denormalize_size('1 1 0'/7,pos,mySize); + vector tile_pos; + entity e; + FOREACH_MINIGAME_ENTITY(e) + { + if ( e.classname == "minigame_nmm_tile" ) + { + tile_pos = minigame_hud_denormalize(e.origin,pos,mySize); + + if ( e == nmm_fromtile ) + { + minigame_drawpic_centered( tile_pos, minigame_texture("nmm/tile_active"), + tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL ); + } + else if ( nmm_draw_avaliable(e) && nmm_valid_selection(e) ) + { + minigame_drawpic_centered( tile_pos, minigame_texture("nmm/tile_available"), + tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL ); + } + + if ( e == nmm_currtile ) + { + minigame_drawpic_centered( tile_pos, minigame_texture("nmm/tile_selected"), + tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_ADDITIVE ); + } + + if ( e.nmm_tile_piece ) + { + minigame_drawpic_centered( tile_pos, + minigame_texture(strcat("nmm/piece",ftos(e.nmm_tile_piece.team))), + tile_size*0.8, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL ); + } + + //drawstring(tile_pos, e.netname, hud_fontsize, '1 0 0', 1, DRAWFLAG_NORMAL); + } + } + + if ( active_minigame.minigame_flags & NMM_TURN_WIN ) + { + vector winfs = hud_fontsize*2; + string playername = ""; + FOREACH_MINIGAME_ENTITY(e) + if ( e.classname == "minigame_player" && + e.team == (active_minigame.minigame_flags & NMM_TURN_TEAM) ) + playername = GetPlayerName(e.minigame_playerslot-1); + + vector win_pos = pos+eY*(mySize_y-winfs_y)/2; + vector win_sz; + win_sz = minigame_drawcolorcodedstring_wrapped(mySize_x,win_pos, + sprintf("%s^7 won the game!",playername), + winfs, 0, DRAWFLAG_NORMAL, 0.5); + + drawfill(win_pos-eY*hud_fontsize_y,win_sz+2*eY*hud_fontsize_y,'1 1 1',0.5,DRAWFLAG_ADDITIVE); + + minigame_drawcolorcodedstring_wrapped(mySize_x,win_pos, + sprintf("%s^7 won the game!",playername), + winfs, panel_fg_alpha, DRAWFLAG_NORMAL, 0.5); + } +} + +// Required function, draw the game status panel +void minigame_hud_status_nmm(vector pos, vector mySize) +{ + HUD_Panel_DrawBg(1); + vector ts; + + ts = minigame_drawstring_wrapped(mySize_x,pos,active_minigame.descriptor.message, + hud_fontsize * 2, '0.25 0.47 0.72', panel_fg_alpha, DRAWFLAG_NORMAL,0.5); + pos_y += ts_y; + mySize_y -= ts_y; + + vector player_fontsize = hud_fontsize * 1.75; + ts_y = ( mySize_y - 2*player_fontsize_y ) / 2; + ts_x = mySize_x; + + float player1x = 0; + float player2x = 0; + vector piece_sz = '48 48 0'; + float piece_space = piece_sz_x + ( ts_x - 7 * piece_sz_x ) / 6; + vector mypos; + float piece_light = 1; + entity e = world; + + mypos = pos; + if ( (active_minigame.minigame_flags&NMM_TURN_TEAM) == 2 ) + mypos_y += player_fontsize_y + ts_y; + drawfill(mypos,eX*mySize_x+eY*player_fontsize_y,'1 1 1',0.5,DRAWFLAG_ADDITIVE); + mypos_y += player_fontsize_y; + drawfill(mypos,eX*mySize_x+eY*piece_sz_y,'1 1 1',0.25,DRAWFLAG_ADDITIVE); + + FOREACH_MINIGAME_ENTITY(e) + { + if ( e.classname == "minigame_player" ) + { + mypos = pos; + if ( e.team == 2 ) + mypos_y += player_fontsize_y + ts_y; + minigame_drawcolorcodedstring_trunc(mySize_x,mypos, + GetPlayerName(e.minigame_playerslot-1), + player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); + } + else if ( e.classname == "minigame_board_piece" ) + { + mypos = pos; + mypos_y += player_fontsize_y; + if ( e.team == 2 ) + { + mypos_x += player2x; + player2x += piece_space; + mypos_y += player_fontsize_y + ts_y; + } + else + { + mypos_x += player1x; + player1x += piece_space; + } + if ( e.minigame_flags == NMM_PIECE_HOME ) + piece_light = 0.5; + else if ( e.minigame_flags == NMM_PIECE_BOARD ) + piece_light = 1; + else + piece_light = 0.15; + + drawpic(mypos, minigame_texture(strcat("nmm/piece",ftos(e.team))), piece_sz, + '1 1 1'*piece_light, panel_fg_alpha, DRAWFLAG_NORMAL ); + } + } +} + +// Make the correct move +void nmm_make_move(entity minigame) +{ + if ( nmm_currtile ) + { + if ( minigame.minigame_flags & (NMM_TURN_PLACE|NMM_TURN_TAKE) ) + { + minigame_cmd("move ",nmm_currtile.netname); + nmm_fromtile = world; + } + else if ( (minigame.minigame_flags & (NMM_TURN_MOVE|NMM_TURN_FLY)) ) + { + if ( nmm_fromtile == nmm_currtile ) + { + nmm_fromtile = world; + } + else if ( nmm_currtile.nmm_tile_piece && nmm_currtile.nmm_tile_piece.team == minigame_self.team ) + { + nmm_fromtile = nmm_currtile; + } + else if ( nmm_fromtile ) + { + minigame_cmd("move ",nmm_fromtile.netname," ",nmm_currtile.netname); + nmm_fromtile = world; + } + } + } + else + nmm_fromtile = world; +} + +string nmm_turn_to_string(float turnflags) +{ + if ( turnflags & NMM_TURN_WIN ) + { + if ( (turnflags&NMM_TURN_TEAM) != minigame_self.team ) + return _("You lost the game!"); + return _("You win!"); + } + + if ( (turnflags&NMM_TURN_TEAM) != minigame_self.team ) + return _("Wait for your opponent to make their move"); + if ( turnflags & NMM_TURN_PLACE ) + return _("Click on the game board to place your piece"); + if ( turnflags & NMM_TURN_MOVE ) + return _("You can select one of your pieces to move it in one of the surrounding places"); + if ( turnflags & NMM_TURN_FLY ) + return _("You can select one of your pieces to move it anywhere on the board"); + if ( turnflags & NMM_TURN_TAKE ) + return _("You can take one of the opponent's pieces"); + + return ""; +} + +// Required function, handle client events +float minigame_event_nmm(entity minigame, string event, ...) +{ + if ( event == "activate" ) + { + nmm_fromtile = world; + nmm_init_tiles(minigame); + minigame.message = nmm_turn_to_string(minigame.minigame_flags); + } + else if ( event == "deactivate" ) + { + nmm_fromtile = world; + nmm_kill_tiles(minigame); + } + else if ( event == "key_pressed" && (minigame.minigame_flags&NMM_TURN_TEAM) == minigame_self.team ) + { + switch ( ...(0,float) ) + { + case K_RIGHTARROW: + case K_KP_RIGHTARROW: + if ( ! nmm_currtile ) + nmm_currtile = nmm_find_tile(active_minigame,"a7"); + else + { + string tileid = nmm_currtile.netname; + nmm_currtile = world; + while ( !nmm_currtile ) + { + tileid = minigame_relative_tile(tileid,1,0,7,7); + nmm_currtile = nmm_find_tile(active_minigame,tileid); + } + } + return 1; + case K_LEFTARROW: + case K_KP_LEFTARROW: + if ( ! nmm_currtile ) + nmm_currtile = nmm_find_tile(active_minigame,"g7"); + else + { + string tileid = nmm_currtile.netname; + nmm_currtile = world; + while ( !nmm_currtile ) + { + tileid = minigame_relative_tile(tileid,-1,0,7,7); + nmm_currtile = nmm_find_tile(active_minigame,tileid); + } + } + return 1; + case K_UPARROW: + case K_KP_UPARROW: + if ( ! nmm_currtile ) + nmm_currtile = nmm_find_tile(active_minigame,"a1"); + else + { + string tileid = nmm_currtile.netname; + nmm_currtile = world; + while ( !nmm_currtile ) + { + tileid = minigame_relative_tile(tileid,0,1,7,7); + nmm_currtile = nmm_find_tile(active_minigame,tileid); + } + } + return 1; + case K_DOWNARROW: + case K_KP_DOWNARROW: + if ( ! nmm_currtile ) + nmm_currtile = nmm_find_tile(active_minigame,"a7"); + else + { + string tileid = nmm_currtile.netname; + nmm_currtile = world; + while ( !nmm_currtile ) + { + tileid = minigame_relative_tile(tileid,0,-1,7,7); + nmm_currtile = nmm_find_tile(active_minigame,tileid); + } + } + return 1; + case K_ENTER: + case K_KP_ENTER: + case K_SPACE: + nmm_make_move(minigame); + return 1; + } + return 0; + } + else if ( event == "mouse_pressed" && ...(0,float) == K_MOUSE1 ) + { + nmm_make_move(minigame); + return 1; + } + else if ( event == "mouse_moved" ) + { + nmm_currtile = world; + vector tile_pos; + vector tile_size = minigame_hud_denormalize_size('1 1 0'/7,nmm_boardpos,nmm_boardsize); + entity e; + FOREACH_MINIGAME_ENTITY(e) + { + if ( e.classname == "minigame_nmm_tile" ) + { + tile_pos = minigame_hud_denormalize(e.origin,nmm_boardpos,nmm_boardsize)-tile_size/2; + if ( minigame_hud_mouse_in(tile_pos, tile_size) && nmm_valid_selection(e) ) + { + nmm_currtile = e; + break; + } + } + } + return 1; + } + else if ( event == "network_receive" ) + { + if ( self.classname == "minigame_board_piece" && ( ...(1,float) & MINIG_SF_UPDATE ) ) + { + entity e; + string tileid = ""; + if ( self.minigame_flags & NMM_PIECE_BOARD ) + tileid = minigame_tile_name(self.origin,7,7); + FOREACH_MINIGAME_ENTITY(e) + { + if ( e.classname == "minigame_nmm_tile" ) + { + if ( e.nmm_tile_piece == self ) + e.nmm_tile_piece = world; + if ( e.netname == tileid ) + e.nmm_tile_piece = self; + } + } + } + else if ( self.classname == "minigame" && ( ...(1,float) & MINIG_SF_UPDATE ) ) + { + self.message = nmm_turn_to_string(self.minigame_flags); + if ( self.minigame_flags & minigame_self.team ) + minigame_prompt(); + } + } + + return 0; +} + +#endif diff --git a/qcsrc/common/minigames/minigame/ttt.qc b/qcsrc/common/minigames/minigame/ttt.qc new file mode 100644 index 000000000..85b97276a --- /dev/null +++ b/qcsrc/common/minigames/minigame/ttt.qc @@ -0,0 +1,688 @@ +const float TTT_TURN_PLACE = 0x0100; // player has to place a piece on the board +const float TTT_TURN_WIN = 0x0200; // player has won +const float TTT_TURN_DRAW = 0x0400; // no moves are possible +const float TTT_TURN_NEXT = 0x0800; // a player wants to start a new match +const float TTT_TURN_TYPE = 0x0f00; // turn type mask + +const float TTT_TURN_TEAM1 = 0x0001; +const float TTT_TURN_TEAM2 = 0x0002; +const float TTT_TURN_TEAM = 0x000f; // turn team mask + +// send flags +const float TTT_SF_PLAYERSCORE = MINIG_SF_CUSTOM; // send minigame_player scores (won matches) +const float TTT_SF_SINGLEPLAYER = MINIG_SF_CUSTOM<<1;// send minigame.ttt_ai + +.float ttt_npieces; // (minigame) number of pieces on the board (simplifies checking a draw) +.float ttt_nexteam; // (minigame) next team (used to change the starting team on following matches) +.float ttt_ai; // (minigame) when non-zero, singleplayer vs AI + +// find tic tac toe piece given its tile name +entity ttt_find_piece(entity minig, string tile) +{ + entity e = world; + while ( ( e = findentity(e,owner,minig) ) ) + if ( e.classname == "minigame_board_piece" && e.netname == tile ) + return e; + return world; +} + +// Checks if the given piece completes a row +float ttt_winning_piece(entity piece) +{ + float number = minigame_tile_number(piece.netname); + float letter = minigame_tile_letter(piece.netname); + + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(0,number)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(1,number)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(2,number)).team == piece.team ) + return 1; + + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(letter,0)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(letter,1)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(letter,2)).team == piece.team ) + return 1; + + if ( number == letter ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(0,0)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(1,1)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(2,2)).team == piece.team ) + return 1; + + if ( number == 2-letter ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(0,2)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(1,1)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(2,0)).team == piece.team ) + return 1; + + return 0; +} + +// check if the tile name is valid (3x3 grid) +float ttt_valid_tile(string tile) +{ + if ( !tile ) + return 0; + float number = minigame_tile_number(tile); + float letter = minigame_tile_letter(tile); + return 0 <= number && number < 3 && 0 <= letter && letter < 3; +} + +// make a move +void ttt_move(entity minigame, entity player, string pos ) +{ + if ( minigame.minigame_flags & TTT_TURN_PLACE ) + if ( pos && player.team == (minigame.minigame_flags & TTT_TURN_TEAM) ) + { + if ( ttt_valid_tile(pos) ) + if ( !ttt_find_piece(minigame,pos) ) + { + entity piece = msle_spawn(minigame,"minigame_board_piece"); + piece.team = player.team; + piece.netname = strzone(pos); + minigame_server_sendflags(piece,MINIG_SF_ALL); + minigame_server_sendflags(minigame,MINIG_SF_UPDATE); + minigame.ttt_npieces++; + minigame.ttt_nexteam = minigame_next_team(player.team,2); + if ( ttt_winning_piece(piece) ) + { + player.minigame_flags++; + minigame_server_sendflags(player, TTT_SF_PLAYERSCORE); + minigame.minigame_flags = TTT_TURN_WIN | player.team; + } + else if ( minigame.ttt_npieces >= 9 ) + minigame.minigame_flags = TTT_TURN_DRAW; + else + minigame.minigame_flags = TTT_TURN_PLACE | minigame.ttt_nexteam; + } + } +} + +// request a new match +void ttt_next_match(entity minigame, entity player) +{ +#ifdef SVQC + // on multiplayer matches, wait for both players to agree + if ( minigame.minigame_flags & (TTT_TURN_WIN|TTT_TURN_DRAW) ) + { + minigame.minigame_flags = TTT_TURN_NEXT | player.team; + minigame.SendFlags |= MINIG_SF_UPDATE; + } + else if ( (minigame.minigame_flags & TTT_TURN_NEXT) && + !( minigame.minigame_flags & player.team ) ) +#endif + { + minigame.minigame_flags = TTT_TURN_PLACE | minigame.ttt_nexteam; + minigame_server_sendflags(minigame,MINIG_SF_UPDATE); + minigame.ttt_npieces = 0; + entity e = world; + while ( ( e = findentity(e,owner,minigame) ) ) + if ( e.classname == "minigame_board_piece" ) + remove(e); + } +} + +#ifdef SVQC + + +// required function, handle server side events +float minigame_event_ttt(entity minigame, string event, ...) +{ + switch(event) + { + case "start": + { + minigame.minigame_flags = (TTT_TURN_PLACE | TTT_TURN_TEAM1); + return TRUE; + } + case "end": + { + entity e = world; + while( (e = findentity(e, owner, minigame)) ) + if(e.classname == "minigame_board_piece") + { + if(e.netname) { strunzone(e.netname); } + remove(e); + } + return FALSE; + } + case "join": + { + float pl_num = minigame_count_players(minigame); + + // Don't allow joining a single player match + if ( (minigame.ttt_ai) && pl_num > 0 ) + return FALSE; + + // Don't allow more than 2 players + if(pl_num >= 2) { return FALSE; } + + // Get the right team + if(minigame.minigame_players) + return minigame_next_team(minigame.minigame_players.team, 2); + + // Team 1 by default + return 1; + } + case "cmd": + { + switch(argv(0)) + { + case "move": + ttt_move(minigame, ...(0,entity), ...(1,float) == 2 ? argv(1) : string_null ); + return TRUE; + case "next": + ttt_next_match(minigame,...(0,entity)); + return TRUE; + case "singleplayer": + if ( minigame_count_players(minigame) == 1 ) + { + minigame.ttt_ai = minigame_next_team(minigame.minigame_players.team, 2); + minigame.SendFlags = TTT_SF_SINGLEPLAYER; + } + return TRUE; + } + + return FALSE; + } + case "network_send": + { + entity sent = ...(0,entity); + float sf = ...(1,float); + if ( sent.classname == "minigame_player" && (sf & TTT_SF_PLAYERSCORE ) ) + { + WriteByte(MSG_ENTITY,sent.minigame_flags); + } + else if ( sent.classname == "minigame" && (sf & TTT_SF_SINGLEPLAYER) ) + { + WriteByte(MSG_ENTITY,sent.ttt_ai); + } + return FALSE; + } + } + + return FALSE; +} + + +#elif defined(CSQC) + +string ttt_curr_pos; // identifier of the tile under the mouse +vector ttt_boardpos; // HUD board position +vector ttt_boardsize;// HUD board size +.float ttt_checkwin; // Used to optimize checks to display a win + +// Required function, draw the game board +void minigame_hud_board_ttt(vector pos, vector mySize) +{ + minigame_hud_fitsqare(pos, mySize); + ttt_boardpos = pos; + ttt_boardsize = mySize; + + minigame_hud_simpleboard(pos,mySize,minigame_texture("ttt/board")); + + vector tile_size = minigame_hud_denormalize_size('1 1 0'/3,pos,mySize); + vector tile_pos; + + if ( (active_minigame.minigame_flags & TTT_TURN_TEAM) == minigame_self.team ) + if ( ttt_valid_tile(ttt_curr_pos) ) + { + tile_pos = minigame_tile_pos(ttt_curr_pos,3,3); + tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize); + minigame_drawpic_centered( tile_pos, + minigame_texture(strcat("ttt/piece",ftos(minigame_self.team))), + tile_size, '1 1 1', panel_fg_alpha/2, DRAWFLAG_NORMAL ); + } + + entity e; + FOREACH_MINIGAME_ENTITY(e) + { + if ( e.classname == "minigame_board_piece" ) + { + tile_pos = minigame_tile_pos(e.netname,3,3); + tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize); + + if ( active_minigame.minigame_flags & TTT_TURN_WIN ) + if ( !e.ttt_checkwin ) + e.ttt_checkwin = ttt_winning_piece(e) ? 1 : -1; + + float icon_color = 1; + if ( e.ttt_checkwin == -1 ) + icon_color = 0.4; + else if ( e.ttt_checkwin == 1 ) + { + icon_color = 2; + minigame_drawpic_centered( tile_pos, minigame_texture("ttt/winglow"), + tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_ADDITIVE ); + } + + minigame_drawpic_centered( tile_pos, + minigame_texture(strcat("ttt/piece",ftos(e.team))), + tile_size, '1 1 1'*icon_color, panel_fg_alpha, DRAWFLAG_NORMAL ); + } + } +} + + +// Required function, draw the game status panel +void minigame_hud_status_ttt(vector pos, vector mySize) +{ + HUD_Panel_DrawBg(1); + vector ts; + ts = minigame_drawstring_wrapped(mySize_x,pos,active_minigame.descriptor.message, + hud_fontsize * 2, '0.25 0.47 0.72', panel_fg_alpha, DRAWFLAG_NORMAL,0.5); + + pos_y += ts_y; + mySize_y -= ts_y; + + vector player_fontsize = hud_fontsize * 1.75; + ts_y = ( mySize_y - 2*player_fontsize_y ) / 2; + ts_x = mySize_x; + vector mypos; + vector tile_size = '48 48 0'; + + entity e; + FOREACH_MINIGAME_ENTITY(e) + { + if ( e.classname == "minigame_player" ) + { + mypos = pos; + if ( e.team == 2 ) + mypos_y += player_fontsize_y + ts_y; + minigame_drawcolorcodedstring_trunc(mySize_x,mypos, + (e.minigame_playerslot ? GetPlayerName(e.minigame_playerslot-1) : _("AI")), + player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); + + mypos_y += player_fontsize_y; + drawpic( mypos, + minigame_texture(strcat("ttt/piece",ftos(e.team))), + tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL ); + + mypos_x += tile_size_x; + + drawstring(mypos,ftos(e.minigame_flags),tile_size, + '0.7 0.84 1', panel_fg_alpha, DRAWFLAG_NORMAL); + } + } +} + +// Turn a set of flags into a help message +string ttt_turn_to_string(float turnflags) +{ + if ( turnflags & TTT_TURN_DRAW ) + return _("Draw"); + + if ( turnflags & TTT_TURN_WIN ) + { + if ( (turnflags&TTT_TURN_TEAM) != minigame_self.team ) + return _("You lost the game!\nSelect \"^1Next Match^7\" on the menu for a rematch!"); + return _("You win!\nSelect \"^1Next Match^7\" on the menu to start a new match!"); + } + + if ( turnflags & TTT_TURN_NEXT ) + { + if ( (turnflags&TTT_TURN_TEAM) != minigame_self.team ) + return _("Select \"^1Next Match^7\" on the menu to start a new match!"); + return _("Wait for your opponent to confirm the rematch"); + } + + if ( (turnflags & TTT_TURN_TEAM) != minigame_self.team ) + return _("Wait for your opponent to make their move"); + + if ( turnflags & TTT_TURN_PLACE ) + return _("Click on the game board to place your piece"); + + return ""; +} + +const float TTT_AI_POSFLAG_A1 = 0x0001; +const float TTT_AI_POSFLAG_A2 = 0x0002; +const float TTT_AI_POSFLAG_A3 = 0x0004; +const float TTT_AI_POSFLAG_B1 = 0x0008; +const float TTT_AI_POSFLAG_B2 = 0x0010; +const float TTT_AI_POSFLAG_B3 = 0x0020; +const float TTT_AI_POSFLAG_C1 = 0x0040; +const float TTT_AI_POSFLAG_C2 = 0x0080; +const float TTT_AI_POSFLAG_C3 = 0x0100; + +// convert a flag to a position +string ttt_ai_piece_flag2pos(float pieceflag) +{ + switch(pieceflag) + { + case TTT_AI_POSFLAG_A1: + return "a1"; + case TTT_AI_POSFLAG_A2: + return "a2"; + case TTT_AI_POSFLAG_A3: + return "a3"; + + case TTT_AI_POSFLAG_B1: + return "b1"; + case TTT_AI_POSFLAG_B2: + return "b2"; + case TTT_AI_POSFLAG_B3: + return "b3"; + + case TTT_AI_POSFLAG_C1: + return "c1"; + case TTT_AI_POSFLAG_C2: + return "c2"; + case TTT_AI_POSFLAG_C3: + return "c3"; + + default: + return string_null; + } +} + +float ttt_ai_checkmask(float piecemask, float checkflags) +{ + return checkflags && (piecemask & checkflags) == checkflags; +} + +// get the third flag if the mask matches two of them +float ttt_ai_1of3(float piecemask, float flag1, float flag2, float flag3) +{ + if ( ttt_ai_checkmask(piecemask,flag1|flag2|flag3) ) + return 0; + + if ( ttt_ai_checkmask(piecemask,flag1|flag2) ) + return flag3; + + if ( ttt_ai_checkmask(piecemask,flag3|flag2) ) + return flag1; + + if ( ttt_ai_checkmask(piecemask,flag3|flag1) ) + return flag2; + + return 0; +} + +// Select a random flag in the mask +float ttt_ai_random(float piecemask) +{ + if ( !piecemask ) + return 0; + + float i; + float f = 1; + + RandomSelection_Init(); + + for ( i = 0; i < 9; i++ ) + { + if ( piecemask & f ) + RandomSelection_Add(world, f, string_null, 1, 1); + f <<= 1; + } + + dprint(sprintf("TTT AI: selected %x from %x\n", + RandomSelection_chosen_float, piecemask) ); + return RandomSelection_chosen_float; +} + +// Block/complete a 3 i na row +float ttt_ai_block3 ( float piecemask, float piecemask_free ) +{ + float r = 0; + + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A1,TTT_AI_POSFLAG_A2,TTT_AI_POSFLAG_A3); + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_B1,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_B3); + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_C1,TTT_AI_POSFLAG_C2,TTT_AI_POSFLAG_C3); + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A1,TTT_AI_POSFLAG_B1,TTT_AI_POSFLAG_C1); + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A2,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_C2); + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A3,TTT_AI_POSFLAG_B3,TTT_AI_POSFLAG_C3); + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A1,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_C3); + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A3,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_C1); + dprint(sprintf("TTT AI: possible 3 in a rows in %x: %x (%x)\n",piecemask,r, r&piecemask_free)); + r &= piecemask_free; + return ttt_ai_random(r); +} + +// Simple AI +// 1) tries to win the game if possible +// 2) tries to block the opponent if they have 2 in a row +// 3) places a piece randomly +string ttt_ai_choose_simple(float piecemask_self, float piecemask_opponent, float piecemask_free ) +{ + float move = 0; + + dprint("TTT AI: checking winning move\n"); + if (( move = ttt_ai_block3(piecemask_self,piecemask_free) )) + return ttt_ai_piece_flag2pos(move); // place winning move + + dprint("TTT AI: checking opponent's winning move\n"); + if (( move = ttt_ai_block3(piecemask_opponent,piecemask_free) )) + return ttt_ai_piece_flag2pos(move); // block opponent + + dprint("TTT AI: random move\n"); + return ttt_ai_piece_flag2pos(ttt_ai_random(piecemask_free)); +} + +// AI move (if it's AI's turn) +void ttt_aimove(entity minigame) +{ + if ( minigame.minigame_flags == (TTT_TURN_PLACE|minigame.ttt_ai) ) + { + entity aiplayer = world; + while ( ( aiplayer = findentity(aiplayer,owner,minigame) ) ) + if ( aiplayer.classname == "minigame_player" && !aiplayer.minigame_playerslot ) + break; + + /* + * Build bit masks for the board pieces + * .---.---.---. + * | 4 | 32|256| 3 + * |---+---+---| + * | 2 | 16|128| 2 + * |---+---+---| + * | 1 | 8 | 64| 1 + * '---'---'---' + * A B C + */ + float piecemask_self = 0; + float piecemask_opponent = 0; + float piecemask_free = 0; + float pieceflag = 1; + string pos; + + float i,j; + for ( i = 0; i < 3; i++ ) + for ( j = 0; j < 3; j++ ) + { + pos = minigame_tile_buildname(i,j); + entity piece = ttt_find_piece(minigame,pos); + if ( piece ) + { + if ( piece.team == aiplayer.team ) + piecemask_self |= pieceflag; + else + piecemask_opponent |= pieceflag; + } + else + piecemask_free |= pieceflag; + pieceflag <<= 1; + } + + // TODO multiple AI difficulties + dprint(sprintf("TTT AI: self: %x opponent: %x free: %x\n", + piecemask_self, piecemask_opponent, piecemask_free)); + pos = ttt_ai_choose_simple(piecemask_self, piecemask_opponent, piecemask_free); + dprint("TTT AI: chosen move: ",pos,"\n\n"); + if ( !pos ) + dprint("Tic Tac Toe AI has derped!\n"); + else + ttt_move(minigame,aiplayer,pos); + } + minigame.message = ttt_turn_to_string(minigame.minigame_flags); +} + +// Make the correct move +void ttt_make_move(entity minigame) +{ + if ( minigame.minigame_flags == (TTT_TURN_PLACE|minigame_self.team) ) + { + if ( minigame.ttt_ai ) + { + ttt_move(minigame, minigame_self, ttt_curr_pos ); + ttt_aimove(minigame); + } + else + minigame_cmd("move ",ttt_curr_pos); + } +} + +void ttt_set_curr_pos(string s) +{ + if ( ttt_curr_pos ) + strunzone(ttt_curr_pos); + if ( s ) + s = strzone(s); + ttt_curr_pos = s; +} + +// Required function, handle client events +float minigame_event_ttt(entity minigame, string event, ...) +{ + switch(event) + { + case "activate": + { + ttt_set_curr_pos(""); + minigame.message = ttt_turn_to_string(minigame.minigame_flags); + return FALSE; + } + case "key_pressed": + { + if((minigame.minigame_flags & TTT_TURN_TEAM) == minigame_self.team) + { + switch ( ...(0,float) ) + { + case K_RIGHTARROW: + case K_KP_RIGHTARROW: + if ( ! ttt_curr_pos ) + ttt_set_curr_pos("a3"); + else + ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,1,0,3,3)); + return TRUE; + case K_LEFTARROW: + case K_KP_LEFTARROW: + if ( ! ttt_curr_pos ) + ttt_set_curr_pos("c3"); + else + ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,-1,0,3,3)); + return TRUE; + case K_UPARROW: + case K_KP_UPARROW: + if ( ! ttt_curr_pos ) + ttt_set_curr_pos("a1"); + else + ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,0,1,3,3)); + return TRUE; + case K_DOWNARROW: + case K_KP_DOWNARROW: + if ( ! ttt_curr_pos ) + ttt_set_curr_pos("a3"); + else + ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,0,-1,3,3)); + return TRUE; + case K_ENTER: + case K_KP_ENTER: + case K_SPACE: + ttt_make_move(minigame); + return TRUE; + } + } + + return FALSE; + } + case "mouse_pressed": + { + if(...(0,float) == K_MOUSE1) + { + ttt_make_move(minigame); + return TRUE; + } + + return FALSE; + } + case "mouse_moved": + { + vector mouse_pos = minigame_hud_normalize(mousepos,ttt_boardpos,ttt_boardsize); + if ( minigame.minigame_flags == (TTT_TURN_PLACE|minigame_self.team) ) + ttt_set_curr_pos(minigame_tile_name(mouse_pos,3,3)); + if ( ! ttt_valid_tile(ttt_curr_pos) ) + ttt_set_curr_pos(""); + + return TRUE; + } + case "network_receive": + { + entity sent = ...(0,entity); + float sf = ...(1,float); + if ( sent.classname == "minigame" ) + { + if ( sf & MINIG_SF_UPDATE ) + { + sent.message = ttt_turn_to_string(sent.minigame_flags); + if ( sent.minigame_flags & minigame_self.team ) + minigame_prompt(); + } + + if ( (sf & TTT_SF_SINGLEPLAYER) ) + { + float ai = ReadByte(); + float spawnai = ai && !sent.ttt_ai; + sent.ttt_ai = ai; + + if ( spawnai ) + { + entity aiplayer = spawn(); + aiplayer.classname = "minigame_player"; + aiplayer.owner = minigame; + aiplayer.team = ai; + aiplayer.minigame_playerslot = 0; + aiplayer.minigame_autoclean = 1; + ttt_aimove(minigame); + } + + } + } + else if ( sent.classname == "minigame_player" && (sf & TTT_SF_PLAYERSCORE ) ) + { + sent.minigame_flags = ReadByte(); + } + + return FALSE; + } + case "menu_show": + { + HUD_MinigameMenu_CustomEntry(...(0,entity),_("Next Match"),"next"); + HUD_MinigameMenu_CustomEntry(...(0,entity),_("Single Player"),"singleplayer"); + return FALSE; + } + case "menu_click": + { + if(...(0,string) == "next") + { + if ( minigame.ttt_ai ) + { + ttt_next_match(minigame,minigame_self); + ttt_aimove(minigame); + } + else + minigame_cmd("next"); + } + else if ( ...(0,string) == "singleplayer" && !minigame.ttt_ai ) + { + if ( minigame_count_players(minigame) == 1 ) + minigame_cmd("singleplayer"); + } + return FALSE; + } + } + + return FALSE; +} + +#endif \ No newline at end of file diff --git a/qcsrc/common/minigames/minigames.qc b/qcsrc/common/minigames/minigames.qc new file mode 100644 index 000000000..6b8dbf355 --- /dev/null +++ b/qcsrc/common/minigames/minigames.qc @@ -0,0 +1,129 @@ +entity minigame_get_descriptor(string id) +{ + entity e; + for ( e = minigame_descriptors; e != world; e = e.list_next ) + if ( e.netname == id ) + return e; + return world; +} + +// Get letter index of a tile name +float minigame_tile_letter(string id) +{ + return str2chr(substring(id,0,1),0)-'a'; +} + +// Get number index of a tile name +// Note: this is 0 based, useful for mathematical operations +// Note: Since the tile notation starts from the bottom left, +// you may want to do number_of_rows - what_this_function_returns or something +float minigame_tile_number(string id) +{ + return stof(substring(id,1,-1)) -1 ; +} + +// Get relative position of the center of a given tile +vector minigame_tile_pos(string id, float rows, float columns) +{ + return eX*(minigame_tile_letter(id)+0.5)/columns + + eY - eY*(minigame_tile_number(id)+0.5)/rows; +} + +// Get a tile name from indices +string minigame_tile_buildname(float letter, float number) +{ + return strcat(chr2str('a'+letter),ftos(number+1)); +} + +// Get the id of a tile relative to the given one +string minigame_relative_tile(string start_id, float dx, float dy, float rows, float columns) +{ + float letter = minigame_tile_letter(start_id); + float number = minigame_tile_number(start_id); + letter = (letter+dx) % columns; + number = (number+dy) % rows; + if ( letter < 0 ) + letter = columns + letter; + if ( number < 0 ) + number = rows + number; + return minigame_tile_buildname(letter, number); +} + +// Get tile name from a relative position (matches the tile covering a square area) +string minigame_tile_name(vector pos, float rows, float columns) +{ + if ( pos_x < 0 || pos_x > 1 || pos_y < 0 || pos_y > 1 ) + return ""; // no tile + + float letter = floor(pos_x * columns); + float number = floor((1-pos_y) * rows); + return minigame_tile_buildname(letter, number); +} + +// Get the next team number (note: team numbers are between 1 and n_teams, inclusive) +float minigame_next_team(float curr_team, float n_teams) +{ + return curr_team % n_teams + 1; +} + +// set send flags only when on server +// (for example in game logic which can be used both in client and server +void minigame_server_sendflags(entity ent, float mgflags) +{ + #ifdef SVQC + ent.SendFlags |= mgflags; + #endif +} + +// Spawn linked entity on the server or local entity on the client +// This entity will be removed automatically when the minigame ends +entity msle_spawn(entity minigame_session, string class_name) +{ + entity e = spawn(); + e.classname = class_name; + e.owner = minigame_session; + e.minigame_autoclean = 1; + #ifdef SVQC + e.customizeentityforclient = minigame_CheckSend; + Net_LinkEntity(e, FALSE, 0, minigame_SendEntity); + #endif + return e; +} + +const float msle_base_id = 2; +float msle_id(string class_name) +{ + if ( class_name == "minigame" ) return 1; + if ( class_name == "minigame_player" ) return 2; + float i = msle_base_id; +#define MSLE(Name, Fields) i++; if ( class_name == #Name ) return i; + MINIGAME_SIMPLELINKED_ENTITIES +#undef MSLE + return 0; +} + +string msle_classname(float id) +{ + if ( id == 1 ) return "minigame"; + if ( id == 2 ) return "minigame_player"; + float i = msle_base_id; +#define MSLE(Name, Fields) i++; if ( id == i ) return #Name; + MINIGAME_SIMPLELINKED_ENTITIES +#undef MSLE + return ""; +} + +float minigame_count_players(entity minigame) +{ + float pl_num = 0; + entity e; +#ifdef SVQC + for(e = minigame.minigame_players; e; e = e.list_next) +#elif defined(CSQC) + e = world; + while( (e = findentity(e,owner,minigame)) ) + if ( e.classname == "minigame_player" ) +#endif + pl_num++; + return pl_num; +} \ No newline at end of file diff --git a/qcsrc/common/minigames/minigames.qh b/qcsrc/common/minigames/minigames.qh new file mode 100644 index 000000000..a9aa92272 --- /dev/null +++ b/qcsrc/common/minigames/minigames.qh @@ -0,0 +1,119 @@ +entity minigame_descriptors; + +// previous node in a doubly linked list +.entity list_prev; +// next node in a linked list +.entity list_next; + +entity minigame_get_descriptor(string id); + +// Get letter index of a tile name +float minigame_tile_letter(string id); + +// Get number index of a tile name +// Note: this is 0 based, useful for mathematical operations +// Note: Since the tile notation starts from the bottom left, +// you may want to do number_of_rows - what_this_function_returns or something +float minigame_tile_number(string id); + +// Get relative position of the center of a given tile +vector minigame_tile_pos(string id, float rows, float columns); + +// Get a tile name from indices +string minigame_tile_buildname(float letter, float number); + +// Get the id of a tile relative to the given one +string minigame_relative_tile(string start_id, float dx, float dy, float rows, float columns); + +// Get tile name from a relative position (matches the tile covering a square area) +string minigame_tile_name(vector pos, float rows, float columns); + +// Get the next team number (note: team numbers are between 1 and n_teams, inclusive) +float minigame_next_team(float curr_team, float n_teams); + +// set send flags only when on server +// (for example in game logic which can be used both in client and server +void minigame_server_sendflags(entity ent, float mgflags); + +// count the number of players in a minigame session +float minigame_count_players(entity minigame); + +/// For minigame sessions: minigame descriptor object +.entity descriptor; + +/// For minigame sessions/descriptors: execute the given event +/// Client events: +/// mouse_moved(vector mouse_pos) +/// return 1 to handle input, 0 to discard +/// mouse_pressed/released(float K_Keycode) +/// return 1 to handle input, 0 to discard +/// note: see dpdefs/keycodes.qc for values +/// key_pressed/released(float K_Keycode) +/// return 1 to handle input, 0 to discard +/// note: see dpdefs/keycodes.qc for values +/// activate() +/// executed when the minigame is activated for the current client +/// deactivate() +/// executed when the minigame is deactivated for the current client +/// network_receive(entity received,float flags) +/// executed each time a networked entity is received +/// note: when this is called self == ...(0,entity) +/// You can use the MINIG_SF_ constants to check the send flags +/// IMPORTANT: always read in client everything you send from the server! +/// menu_show(entity parent_menu_item) +/// executed when the Current Game menu is shown, used to add custom entries +/// Call HUD_MinigameMenu_CustomEntry to do so (pass ...(0,entity) as first argument) +/// menu_click(string arg) +/// executed when a custom menu entry is clicked +/// Server events: +/// start() +/// executed when the minigame session is starting +/// end() +/// executed when the minigame session is shutting down +/// join(entity player) +/// executed when a player wants to join the session +/// return the player team number to accept the new player, 0 to discard +/// part(entity player) +/// executed when a player is going to leave the session +/// network_send(entity sent,float flags) +/// executed each time a networked entity is sent +/// note: when this is called self == ...(0,entity) +/// You can use the MINIG_SF_ constants to check the send flags +/// IMPORTANT: always read in client everything you send from the server! +/// cmd(entity minigame_player, float argc, string command) +/// self = client entity triggering this +/// argv(n) = console token +/// argc: number of console tokens +/// command: full command string +/// triggered when a player does "cmd minigame ..." with some unrecognized command +/// return 1 if the minigame has handled the command +/// impulse(entity minigame_player,float impulse) +/// self = client entity triggering this +/// triggered when a player does "impulse ..." +/// return 1 if the minigame has handled the impulse +.float(entity,string,...) minigame_event; + +// For run-time gameplay entities: Whether to be removed when the game is deactivated +.float minigame_autoclean; + +// For run-time gameplay entities: some place to store flags safely +.float minigame_flags; + +// Send flags, set to .SendFlags on networked entities to send entity information +// Flag values for customized events must be powers of 2 in the range +// [MINIG_SF_CUSTOM, MINIG_SF_MAX] (inclusive) +const float MINIG_SF_CREATE = 0x01; // Create a new object +const float MINIG_SF_UPDATE = 0x02; // miscellaneous entity update +const float MINIG_SF_CUSTOM = 0x10; // a customized networked event +const float MINIG_SF_MAX = 0x80; // maximum flag value sent over the network +const float MINIG_SF_ALL = 0xff; // use to resend everything + + +// Spawn linked entity on the server or local entity on the client +// This entity will be removed automatically when the minigame ends +entity msle_spawn(entity minigame_session, string class_name); + +#include "minigame/all.qh" + +float msle_id(string class_name); +string msle_classname(float id); \ No newline at end of file diff --git a/qcsrc/common/minigames/sv_minigames.qc b/qcsrc/common/minigames/sv_minigames.qc new file mode 100644 index 000000000..0683f6aa6 --- /dev/null +++ b/qcsrc/common/minigames/sv_minigames.qc @@ -0,0 +1,428 @@ +void player_clear_minigame(entity player) +{ + player.active_minigame = world; + if ( IS_PLAYER(player) ) + player.movetype = MOVETYPE_WALK; + else + player.movetype = MOVETYPE_FLY_WORLDONLY; + player.team_forced = 0; +} + +void minigame_rmplayer(entity minigame_session, entity player) +{ + entity e; + entity p = minigame_session.minigame_players; + + if ( p.minigame_players == player ) + { + if ( p.list_next == world ) + { + end_minigame(minigame_session); + return; + } + minigame_session.minigame_event(minigame_session,"part",player); + GameLogEcho(strcat(":minigame:part:",minigame_session.netname,":", + ftos(num_for_edict(player)),":",player.netname)); + minigame_session.minigame_players = p.list_next; + remove ( p ); + player_clear_minigame(player); + } + else + { + for ( e = p.list_next; e != world; e = e.list_next ) + { + if ( e.minigame_players == player ) + { + minigame_session.minigame_event(minigame_session,"part",player); + GameLogEcho(strcat(":minigame:part:",minigame_session.netname,":", + ftos(num_for_edict(player)),":",player.netname)); + p.list_next = e.list_next; + remove(e); + player_clear_minigame(player); + return; + } + p = e; + } + } +} + + +#define FIELD(Flags, Type,Name) if ( sf & (Flags) ) Write##Type(MSG_ENTITY, self.Name); +#define WriteVector(to,Name) WriteCoord(to,Name##_x); WriteCoord(to,Name##_y); WriteCoord(to,Name##_z) +#define WriteVector2D(to,Name) WriteCoord(to,Name##_x); WriteCoord(to,Name##_y) +#define WriteFloat WriteCoord +#define MSLE(Name,Fields) \ + else if ( self.classname == #Name ) { \ + if ( sf & MINIG_SF_CREATE ) WriteString(MSG_ENTITY,self.owner.netname); \ + Fields } + +// Send an entity to a client +// only use on minigame entities or entities with a minigame owner +float minigame_SendEntity(entity to, float sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_MINIGAME); + WriteByte(MSG_ENTITY, sf); + + if ( sf & MINIG_SF_CREATE ) + { + WriteShort(MSG_ENTITY,msle_id(self.classname)); + WriteString(MSG_ENTITY,self.netname); + } + + entity minigame_ent = self.owner; + + if ( self.classname == "minigame" ) + { + minigame_ent = self; + + if ( sf & MINIG_SF_CREATE ) + WriteString(MSG_ENTITY,self.descriptor.netname); + + if ( sf & MINIG_SF_UPDATE ) + WriteLong(MSG_ENTITY,self.minigame_flags); + } + else if ( self.classname == "minigame_player" ) + { + if ( sf & MINIG_SF_CREATE ) + { + WriteString(MSG_ENTITY,self.owner.netname); + WriteLong(MSG_ENTITY,num_for_edict(self.minigame_players)); + } + if ( sf & MINIG_SF_UPDATE ) + WriteByte(MSG_ENTITY,self.team); + } + MINIGAME_SIMPLELINKED_ENTITIES + + minigame_ent.minigame_event(minigame_ent,"network_send",self,sf); + + return 1; + +} +#undef FIELD +#undef MSLE +#undef WriteFloat + +// Force resend all minigame entities +void minigame_resend(entity minigame) +{ + minigame.SendFlags = MINIG_SF_ALL; + entity e = world; + while (( e = findentity(e,owner,minigame) )) + { + e.SendFlags = MINIG_SF_ALL; + } +} + +float minigame_CheckSend() +{ + entity e; + for ( e = self.owner.minigame_players; e != world; e = e.list_next ) + if ( e.minigame_players == other ) + return 1; + return 0; +} + +float minigame_addplayer(entity minigame_session, entity player) +{ + if ( player.active_minigame ) + { + if ( player.active_minigame == minigame_session ) + return 0; + minigame_rmplayer(player.active_minigame,player); + } + + float mgteam = minigame_session.minigame_event(minigame_session,"join",player); + + if ( mgteam ) + { + entity player_pointer = spawn(); + player_pointer.classname = "minigame_player"; + player_pointer.owner = minigame_session; + player_pointer.minigame_players = player; + player_pointer.team = mgteam; + player_pointer.list_next = minigame_session.minigame_players; + minigame_session.minigame_players = player_pointer; + player.active_minigame = minigame_session; + player_pointer.customizeentityforclient = minigame_CheckSend; + Net_LinkEntity(player_pointer, FALSE, 0, minigame_SendEntity); + + if ( !IS_OBSERVER(player) && autocvar_sv_minigames_observer ) + { + entity e = self; + self = player; + PutObserverInServer(); + self = e; + } + if ( autocvar_sv_minigames_observer == 2 ) + player.team_forced = -1; + + minigame_resend(minigame_session); + } + GameLogEcho(strcat(":minigame:join",(mgteam?"":"fail"),":",minigame_session.netname,":", + ftos(num_for_edict(player)),":",player.netname)); + + return mgteam; +} + +entity start_minigame(entity player, string minigame ) +{ + if ( !autocvar_sv_minigames || !IS_REAL_CLIENT(player) ) + return world; + + entity e = minigame_get_descriptor(minigame); + if ( e ) + { + entity minig = spawn(); + minig.classname = "minigame"; + minig.netname = strzone(strcat(e.netname,"_",ftos(num_for_edict(minig)))); + minig.descriptor = e; + minig.minigame_event = e.minigame_event; + minig.minigame_event(minig,"start"); + GameLogEcho(strcat(":minigame:start:",minig.netname)); + if ( ! minigame_addplayer(minig,player) ) + { + dprint("Minigame ",minig.netname," rejected the first player join!\n"); + end_minigame(minig); + return world; + } + Net_LinkEntity(minig, FALSE, 0, minigame_SendEntity); + + if ( !minigame_sessions ) + minigame_sessions = minig; + else + { + minigame_sessions.owner = minig; + minig.list_next = minigame_sessions; + minigame_sessions = minig; + } + return minig; + } + + return world; +} + +entity join_minigame(entity player, string game_id ) +{ + if ( !autocvar_sv_minigames || !IS_REAL_CLIENT(player) ) + return world; + + entity minig; + for ( minig = minigame_sessions; minig != world; minig = minig.list_next ) + { + if ( minig.netname == game_id ) + if ( minigame_addplayer(minig,player) ) + return minig; + } + + return world; +} + +void part_minigame(entity player ) +{ + entity minig = player.active_minigame; + + if ( minig && minig.classname == "minigame" ) + minigame_rmplayer(minig,player); +} + +void end_minigame(entity minigame_session) +{ + if ( minigame_session.owner ) + minigame_session.owner.list_next = minigame_session.list_next; + else + minigame_sessions = minigame_session.list_next; + + minigame_session.minigame_event(minigame_session,"end"); + GameLogEcho(strcat(":minigame:end:",minigame_session.netname)); + + + entity e = world; + while( (e = findentity(e, owner, minigame_session)) ) + if ( e.minigame_autoclean ) + { + dprint("SV Auto-cleaned: ",ftos(num_for_edict(e)), " (",e.classname,")\n"); + remove(e); + } + + entity p; + for ( e = minigame_session.minigame_players; e != world; e = p ) + { + p = e.list_next; + player_clear_minigame(e.minigame_players); + remove(e); + } + + strunzone(minigame_session.netname); + remove(minigame_session); +} + +void end_minigames() +{ + while ( minigame_sessions ) + { + end_minigame(minigame_sessions); + } +} + +void initialize_minigames() +{ + entity last_minig = world; + entity minig; + #define MINIGAME(name,nicename) \ + minig = spawn(); \ + minig.classname = "minigame_descriptor"; \ + minig.netname = #name; \ + minig.message = nicename; \ + minig.minigame_event = minigame_event_##name; \ + if ( !last_minig ) minigame_descriptors = minig; \ + else last_minig.list_next = minig; \ + last_minig = minig; + + REGISTERED_MINIGAMES + + #undef MINIGAME +} + +string invite_minigame(entity inviter, entity player) +{ + if ( !inviter || !inviter.active_minigame ) + return "Invalid minigame"; + if ( !VerifyClientEntity(player, TRUE, FALSE) ) + return "Invalid player"; + if ( inviter == player ) + return "You can't invite yourself"; + if ( player.active_minigame == inviter.active_minigame ) + return strcat(player.netname," is already playing"); + + Send_Notification(NOTIF_ONE, player, MSG_INFO, INFO_MINIGAME_INVITE, + inviter.active_minigame.netname, inviter.netname ); + + GameLogEcho(strcat(":minigame:invite:",inviter.active_minigame.netname,":", + ftos(num_for_edict(player)),":",player.netname)); + + return ""; +} + +entity minigame_find_player(entity client) +{ + if ( ! client.active_minigame ) + return world; + entity e; + for ( e = client.active_minigame.minigame_players; e; e = e.list_next ) + if ( e.minigame_players == client ) + return e; + return world; +} + +float MinigameImpulse(float imp) +{ + entity e = minigame_find_player(self); + if ( imp && self.active_minigame && e ) + { + return self.active_minigame.minigame_event(self.active_minigame,"impulse",e,imp); + } + return 0; +} + + + +void ClientCommand_minigame(float request, float argc, string command) +{ + if ( !autocvar_sv_minigames ) + { + sprint(self,"Minigames are not enabled!\n"); + return; + } + + if (request == CMD_REQUEST_COMMAND ) + { + string minig_cmd = argv(1); + if ( minig_cmd == "create" && argc > 2 ) + { + entity minig = start_minigame(self, argv(2)); + if ( minig ) + sprint(self,"Created minigame session: ",minig.netname,"\n"); + else + sprint(self,"Cannot start minigame session!\n"); + return; + } + else if ( minig_cmd == "join" && argc > 2 ) + { + entity minig = join_minigame(self, argv(2)); + if ( minig ) + sprint(self,"Joined: ",minig.netname,"\n"); + else + { + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_JOIN_PREVENT_MINIGAME); + sprint(self,"Cannot join given minigame session!\n"); + } + return; + } + else if ( minig_cmd == "list" ) + { + entity e; + for ( e = minigame_descriptors; e != world; e = e.list_next ) + sprint(self,e.netname," (",e.message,") ","\n"); + return; + } + else if ( minig_cmd == "list-sessions" ) + { + entity e; + for ( e = minigame_sessions; e != world; e = e.list_next ) + sprint(self,e.netname,"\n"); + return; + } + else if ( minig_cmd == "end" || minig_cmd == "part" ) + { + if ( self.active_minigame ) + { + part_minigame(self); + sprint(self,"Left minigame session\n"); + } + else + sprint(self,"You aren't playing any minigame...\n"); + return; + } + else if ( minig_cmd == "invite" && argc > 2 ) + { + if ( self.active_minigame ) + { + entity client = GetIndexedEntity(argc, 2); + string error = invite_minigame(self,client); + if ( error == "" ) + { + sprint(self,"You have invited ",client.netname, + " to join your game of ", self.active_minigame.descriptor.message, "\n"); + } + else + sprint(self,"Could not invite: ", error, ".\n"); + } + else + sprint(self,"You aren't playing any minigame...\n"); + return; + } + else if ( self.active_minigame ) + { + entity e = minigame_find_player(self); + string subcommand = substring(command,argv_end_index(0),-1); + float arg_c = tokenize_console(subcommand); + if ( self.active_minigame.minigame_event(self.active_minigame,"cmd",e,arg_c,subcommand) ) + return; + + } + else sprint(self,strcat("Wrong command:^1 ",command,"\n")); + } + + sprint(self, "\nUsage:^3 cmd minigame create \n"); + sprint(self, " Start a new minigame session\n"); + sprint(self, "Usage:^3 cmd minigame join \n"); + sprint(self, " Join an exising minigame session\n"); + sprint(self, "Usage:^3 cmd minigame list\n"); + sprint(self, " List available minigames\n"); + sprint(self, "Usage:^3 cmd minigame list-sessions\n"); + sprint(self, " List available minigames sessions\n"); + sprint(self, "Usage:^3 cmd minigame part|end\n"); + sprint(self, " Leave the current minigame\n"); + sprint(self, "Usage:^3 cmd minigame invite \n"); + sprint(self, " Invite the given player to join you in a minigame\n"); +} \ No newline at end of file diff --git a/qcsrc/common/minigames/sv_minigames.qh b/qcsrc/common/minigames/sv_minigames.qh new file mode 100644 index 000000000..a8d7d6f7a --- /dev/null +++ b/qcsrc/common/minigames/sv_minigames.qh @@ -0,0 +1,51 @@ + + +/// Initialize the minigame system +void initialize_minigames(); + +/// Create a new minigame session +/// \return minigame session entity +entity start_minigame(entity player, string minigame ); + +/// Join an existing minigame session +/// \return minigame session entity +entity join_minigame(entity player, string game_id ); + +/// Invite a player to join in a minigame +/// \return Error string +string invite_minigame(entity inviter, entity player); + +// Part minigame session +void part_minigame(entity player); + +// Ends a minigame session +void end_minigame(entity minigame_session); + +// Ends all minigame sessions +void end_minigames(); + +// Only sends entities to players who joined the minigame +// Use on customizeentityforclient for gameplay entities +float minigame_CheckSend(); + +// Check for minigame impulses +float MinigameImpulse(float imp); + +// Parse a client command ( cmd minigame ... ) +void ClientCommand_minigame(float request, float argc, string command); + +// Find the minigame_player entity for the given client entity +entity minigame_find_player(entity client); + +/// For players: Minigame being played +.entity active_minigame; + +/// For minigame sessions: list of players +/// For minigame_player: client entity +.entity minigame_players; + +entity minigame_sessions; + +float minigame_SendEntity(entity to, float sf); + +var void remove(entity e); diff --git a/qcsrc/common/monsters/all.qh b/qcsrc/common/monsters/all.qh index d30f29894..d827d45d8 100644 --- a/qcsrc/common/monsters/all.qh +++ b/qcsrc/common/monsters/all.qh @@ -1,5 +1,19 @@ #include "monster/zombie.qc" #include "monster/spider.qc" #include "monster/mage.qc" +#ifndef MONSTERS_EXTRA #include "monster/wyvern.qc" +#endif #include "monster/shambler.qc" +#include "monster/creeper.qc" +#ifdef MONSTERS_EXTRA +#include "monster/demon.qc" +#include "monster/rotfish.qc" +#include "monster/ogre.qc" +#include "monster/spawn.qc" +#include "monster/scrag.qc" +#include "monster/rottweiler.qc" +#include "monster/vore.qc" +#include "monster/afrit.qc" +#include "monster/enforcer.qc" +#endif diff --git a/qcsrc/common/monsters/monster/afrit.qc b/qcsrc/common/monsters/monster/afrit.qc new file mode 100644 index 000000000..5d9c01d91 --- /dev/null +++ b/qcsrc/common/monsters/monster/afrit.qc @@ -0,0 +1,192 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ AFRIT, +/* functions */ M_Afrit, M_Afrit_Attack, +/* spawnflags */ MONSTER_SIZE_BROKEN | MON_FLAG_RANGED | MONSTER_TYPE_FLY, +/* mins,maxs */ '-16 -16 -24', '16 16 24', +/* model */ "afrit.mdl", +/* netname */ "afrit", +/* fullname */ _("Afrit") +); + +#else +#ifdef SVQC +float autocvar_g_monster_afrit_health; +var float autocvar_g_monster_afrit_damageforcescale = 0.3; +float autocvar_g_monster_afrit_attack_fireball_damage; +float autocvar_g_monster_afrit_attack_fireball_edgedamage; +float autocvar_g_monster_afrit_attack_fireball_damagetime; +float autocvar_g_monster_afrit_attack_fireball_force; +float autocvar_g_monster_afrit_attack_fireball_radius; +float autocvar_g_monster_afrit_attack_fireball_speed; +float autocvar_g_monster_afrit_speed_stop; +float autocvar_g_monster_afrit_speed_run; +float autocvar_g_monster_afrit_speed_walk; + +const float afrit_anim_sleep = 0; +const float afrit_anim_getup = 1; +const float afrit_anim_fly = 2; +const float afrit_anim_pain = 3; +const float afrit_anim_attack = 4; +const float afrit_anim_die1 = 5; +const float afrit_anim_die2 = 6; + +.float afrit_targetrange_backup; +.float afrit_targetcheck_delay; + +void M_Afrit_Attack_Fireball_Explode() +{ + entity e; + if(self) + { + pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1); + + RadiusDamage(self, self.realowner, (autocvar_g_monster_afrit_attack_fireball_damage), (autocvar_g_monster_afrit_attack_fireball_edgedamage), + (autocvar_g_monster_afrit_attack_fireball_force), world, world, (autocvar_g_monster_afrit_attack_fireball_radius), self.projectiledeathtype, world); + + for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= (autocvar_g_monster_afrit_attack_fireball_radius)) + Fire_AddDamage(e, self, 5 * MONSTER_SKILLMOD(self), (autocvar_g_monster_afrit_attack_fireball_damagetime), self.projectiledeathtype); + + remove(self); + } +} + +void M_Afrit_Attack_Fireball_Touch() +{ + PROJECTILE_TOUCH; + + M_Afrit_Attack_Fireball_Explode(); +} + +void M_Afrit_Attack_Fireball() +{ + entity missile = spawn(); + vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + + monster_makevectors(self.enemy); + + missile.owner = missile.realowner = self; + missile.solid = SOLID_TRIGGER; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.projectiledeathtype = DEATH_MONSTER_AFRIT; + setsize(missile, '-6 -6 -6', '6 6 6'); + setorigin(missile, self.origin + '0 0 13' + v_forward * 10); + missile.flags = FL_PROJECTILE; + missile.velocity = dir * (autocvar_g_monster_afrit_attack_fireball_speed); + missile.avelocity = '300 300 300'; + missile.nextthink = time + 5; + missile.think = M_Afrit_Attack_Fireball_Explode; + missile.enemy = self.enemy; + missile.touch = M_Afrit_Attack_Fireball_Touch; + CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE); +} + +float M_Afrit_Attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + case MONSTER_ATTACK_RANGED: + { + self.attack_finished_single = time + 2; + self.anim_finished = time + 1.3; + self.frame = afrit_anim_attack; + + Monster_Delay(2, 0.3, 0.7, M_Afrit_Attack_Fireball); + + return TRUE; + } + } + + return FALSE; +} + +void spawnfunc_monster_afrit() { Monster_Spawn(MON_AFRIT); } + +float M_Afrit(float req) +{ + switch(req) + { + case MR_THINK: + { + if(self.target_range < 0) + { + if(time >= self.afrit_targetcheck_delay) + { + self.target_range = self.afrit_targetrange_backup; + if(Monster_FindTarget(self) != world) + { + self.frame = afrit_anim_getup; + self.anim_finished = time + 2.3; + } + else + { + self.target_range = -1; + self.afrit_targetcheck_delay = time + 1; + } + } + } + self.fire_endtime = 0; // never burns + + if(self.flags & FL_ONGROUND) + self.m_anim_idle = afrit_anim_sleep; + else + self.m_anim_idle = afrit_anim_fly; + + if(self.frame == afrit_anim_sleep) + self.armorvalue = 0.95; // almost invincible + else + self.armorvalue = autocvar_g_monsters_armor_blockpercent; + return TRUE; + } + case MR_PAIN: + { + if(frag_deathtype == DEATH_FIRE || frag_deathtype == DEATH_LAVA) + frag_damage = 0; // afrit doesn't burn + else + { + self.pain_finished = time + 0.6; + self.frame = afrit_anim_pain; + } + return TRUE; + } + case MR_DEATH: + { + self.frame = (random() >= 0.5) ? afrit_anim_die1 : afrit_anim_die2; + self.superweapons_finished = time + 20; + return TRUE; + } + case MR_SETUP: + { + if(!self.health) self.health = (autocvar_g_monster_afrit_health); + if(!self.speed) { self.speed = (autocvar_g_monster_afrit_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_afrit_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_afrit_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_afrit_damageforcescale); } + + self.m_anim_walk = afrit_anim_fly; + self.m_anim_run = afrit_anim_fly; + self.m_anim_idle = afrit_anim_fly; + + if(!self.target_range) { self.afrit_targetrange_backup = autocvar_g_monsters_target_range; } + else { self.afrit_targetrange_backup = self.target_range; } + self.target_range = -1; // special handler + + self.weapon = WEP_FIREBALL; // hehehe + self.effects |= EF_FLAME; + + self.noalign = FALSE; // starts on ground + + return TRUE; + } + case MR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/creeper.qc b/qcsrc/common/monsters/monster/creeper.qc new file mode 100644 index 000000000..1db708ce0 --- /dev/null +++ b/qcsrc/common/monsters/monster/creeper.qc @@ -0,0 +1,125 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ CREEPER, +/* functions */ M_Creeper, M_Creeper_Attack, +/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RIDE, +/* mins,maxs */ '-18 -18 -25', '18 18 40', +/* model */ "creeper.dpm", +/* netname */ "creeper", +/* fullname */ _("Creeper") +); + +#else +#ifdef SVQC +float autocvar_g_monster_creeper_health; +var float autocvar_g_monster_creeper_damageforcescale = 0.0001; // hehehe +float autocvar_g_monster_creeper_attack_explode_damage; +float autocvar_g_monster_creeper_attack_explode_edgedamage; +float autocvar_g_monster_creeper_attack_explode_radius; +float autocvar_g_monster_creeper_attack_explode_force; +float autocvar_g_monster_creeper_attack_explode_prime_delay; +float autocvar_g_monster_creeper_speed_stop; +float autocvar_g_monster_creeper_speed_walk; +float autocvar_g_monster_creeper_speed_run; + +const float creeper_anim_idle = 0; +const float creeper_anim_walk = 1; +const float creeper_anim_die = 2; + +.float creeper_primed; + +float M_Creeper_Attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + if(self.creeper_primed) + { + pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1); + sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); + + RadiusDamage (self, self, (autocvar_g_monster_creeper_attack_explode_damage), (autocvar_g_monster_creeper_attack_explode_edgedamage), (autocvar_g_monster_creeper_attack_explode_radius), world, world, (autocvar_g_monster_creeper_attack_explode_force), DEATH_MONSTER_CREEPER, other); + + //Monster_Sound(monstersound_attack, 0, FALSE, CH_SHOTS); + Damage (self, world, world, self.health + self.max_health + 200, DEATH_KILL, self.origin, '0 0 0'); // killing monster should be reliable enough + self.event_damage = func_null; + self.frame = creeper_anim_idle; + } + else + { + Monster_Sound(monstersound_ranged, 0, FALSE, CH_VOICE); + self.creeper_primed = TRUE; + + self.colormod = '1 0 0'; + self.frame = creeper_anim_idle; + self.velocity_x = 0; + self.velocity_y = 0; + self.state = MONSTER_ATTACK_MELEE; + self.attack_finished_single = time + (autocvar_g_monster_creeper_attack_explode_prime_delay); + } + return TRUE; + } + case MONSTER_ATTACK_RANGED: + { + // creeper has no ranged attacks + return FALSE; + } + } + + return FALSE; +} + +void spawnfunc_monster_creeper() { Monster_Spawn(MON_CREEPER); } + +float M_Creeper(float req) +{ + switch(req) + { + case MR_THINK: + { + if(self.creeper_primed) + if(vlen(self.origin - self.enemy.origin) >= self.attack_range * 2) + self.creeper_primed = FALSE; + if(self.health > 0 && time > self.attack_finished_single) { self.colormod = '1 1 1'; } + return TRUE; + } + case MR_PAIN: + { + return TRUE; + } + case MR_DEATH: + { + self.frame = creeper_anim_die; + return TRUE; + } + case MR_SETUP: + { + if(!self.health) self.health = (autocvar_g_monster_creeper_health); + if(!self.wander_delay) self.wander_delay = 10; + if(!self.wander_distance) self.wander_distance = 200; + if(!self.speed) { self.speed = (autocvar_g_monster_creeper_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_creeper_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_creeper_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_creeper_damageforcescale); } + + self.m_anim_walk = creeper_anim_walk; + self.m_anim_run = creeper_anim_walk; + self.m_anim_idle = creeper_anim_idle; + self.monster_loot = spawnfunc_ammo_rockets; + self.frame = creeper_anim_idle; + self.colormod = '1 1 1'; + + return TRUE; + } + case MR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/demon.qc b/qcsrc/common/monsters/monster/demon.qc new file mode 100644 index 000000000..19fb151aa --- /dev/null +++ b/qcsrc/common/monsters/monster/demon.qc @@ -0,0 +1,127 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ DEMON, +/* functions */ M_Demon, M_Demon_Attack, +/* spawnflags */ MON_FLAG_MELEE | MONSTER_SIZE_BROKEN | MON_FLAG_RIDE, +/* mins,maxs */ '-32 -32 -24', '32 32 24', +/* model */ "demon.mdl", +/* netname */ "demon", +/* fullname */ _("Demon") +); + +#else +#ifdef SVQC +float autocvar_g_monster_demon_health; +var float autocvar_g_monster_demon_damageforcescale = 0.35; +float autocvar_g_monster_demon_attack_melee_damage; +float autocvar_g_monster_demon_attack_melee_delay; +float autocvar_g_monster_demon_attack_leap_damage; +float autocvar_g_monster_demon_attack_leap_force; +float autocvar_g_monster_demon_attack_leap_speed; +float autocvar_g_monster_demon_attack_leap_delay; +float autocvar_g_monster_demon_attack_leap_mindist; +float autocvar_g_monster_demon_speed_stop; +float autocvar_g_monster_demon_speed_run; +float autocvar_g_monster_demon_speed_walk; + +const float demon_anim_stand = 0; +const float demon_anim_walk = 1; +const float demon_anim_run = 2; +const float demon_anim_leap = 3; +const float demon_anim_pain = 4; +const float demon_anim_death = 5; +const float demon_anim_attack = 6; + +void M_Demon_Attack_Leap_Touch() +{ + if (self.health <= 0) + return; + + vector angles_face; + + if(other.takedamage) + { + angles_face = vectoangles(self.moveto - self.origin); + angles_face = normalize(angles_face) * (autocvar_g_monster_demon_attack_leap_force); + Damage(other, self, self, (autocvar_g_monster_demon_attack_leap_damage) * MONSTER_SKILLMOD(self), DEATH_MONSTER_DEMON_JUMP, other.origin, angles_face); + self.touch = Monster_Touch; // instantly turn it off to stop damage spam + self.state = 0; + } + + if (trace_dphitcontents) + { + self.state = 0; + self.touch = Monster_Touch; + } +} + +float M_Demon_Attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + return Monster_Attack_Melee(self.enemy, (autocvar_g_monster_demon_attack_melee_damage), demon_anim_attack, self.attack_range, (autocvar_g_monster_demon_attack_melee_delay), DEATH_MONSTER_DEMON_MELEE, TRUE); + } + case MONSTER_ATTACK_RANGED: + { + if(vlen(self.enemy.origin - self.origin) <= autocvar_g_monster_demon_attack_leap_mindist) { return FALSE; } + makevectors(self.angles); + return Monster_Attack_Leap(demon_anim_leap, M_Demon_Attack_Leap_Touch, v_forward * (autocvar_g_monster_demon_attack_leap_speed) + '0 0 200', (autocvar_g_monster_demon_attack_leap_delay)); + } + } + + return FALSE; +} + +void spawnfunc_monster_demon() { Monster_Spawn(MON_DEMON); } +void spawnfunc_monster_demon1() { spawnfunc_monster_demon(); } +void spawnfunc_monster_animus() { spawnfunc_monster_demon(); } + +float M_Demon(float req) +{ + switch(req) + { + case MR_THINK: + { + return TRUE; + } + case MR_PAIN: + { + self.pain_finished = time + 0.5; + self.frame = demon_anim_pain; + return TRUE; + } + case MR_DEATH: + { + self.frame = demon_anim_death; + return TRUE; + } + case MR_SETUP: + { + if(!self.health) self.health = (autocvar_g_monster_demon_health); + if(!self.speed) { self.speed = (autocvar_g_monster_demon_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_demon_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_demon_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_demon_damageforcescale); } + + self.m_anim_walk = demon_anim_walk; + self.m_anim_run = demon_anim_run; + self.m_anim_idle = demon_anim_stand; + + self.monster_loot = spawnfunc_item_health_large; + self.frame = demon_anim_stand; + + return TRUE; + } + case MR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/enforcer.qc b/qcsrc/common/monsters/monster/enforcer.qc new file mode 100644 index 000000000..3143238c4 --- /dev/null +++ b/qcsrc/common/monsters/monster/enforcer.qc @@ -0,0 +1,178 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ ENFORCER, +/* functions */ M_Enforcer, M_Enforcer_Attack, +/* spawnflags */ MON_FLAG_MELEE | MONSTER_SIZE_BROKEN, +/* mins,maxs */ '-16 -16 -24', '16 16 24', +/* model */ "enforcer.mdl", +/* netname */ "enforcer", +/* fullname */ _("Enforcer") +); + +#else +#ifdef SVQC +float autocvar_g_monster_enforcer_health; +var float autocvar_g_monster_enforcer_damageforcescale = 0.7; +float autocvar_g_monster_enforcer_attack_plasma_damage; +float autocvar_g_monster_enforcer_attack_plasma_edgedamage; +float autocvar_g_monster_enforcer_attack_plasma_force; +float autocvar_g_monster_enforcer_attack_plasma_radius; +float autocvar_g_monster_enforcer_attack_plasma_spread; +float autocvar_g_monster_enforcer_attack_plasma_speed; +float autocvar_g_monster_enforcer_attack_plasma_lifetime; +float autocvar_g_monster_enforcer_attack_plasma_shots; +float autocvar_g_monster_enforcer_attack_plasma_delay; +float autocvar_g_monster_enforcer_speed_stop; +float autocvar_g_monster_enforcer_speed_run; +float autocvar_g_monster_enforcer_speed_walk; + +const float enforcer_anim_stand = 0; +const float enforcer_anim_walk = 1; +const float enforcer_anim_run = 2; +const float enforcer_anim_attack = 3; +const float enforcer_anim_death1 = 4; +const float enforcer_anim_death2 = 5; +const float enforcer_anim_pain1 = 6; +const float enforcer_anim_pain2 = 7; +const float enforcer_anim_pain3 = 8; +const float enforcer_anim_pain4 = 9; + +void M_Enforcer_Attack_Plasma_Explode() +{ + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + if(self.movetype == MOVETYPE_NONE) + self.velocity = self.oldvelocity; + + sound (self, CH_WEAPON_A, W_Sound("tag_impact"), VOL_BASE, ATTEN_NORM); + pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1); + RadiusDamage (self, self.realowner, autocvar_g_monster_enforcer_attack_plasma_damage, autocvar_g_monster_enforcer_attack_plasma_edgedamage, + autocvar_g_monster_enforcer_attack_plasma_radius, world, world, autocvar_g_monster_enforcer_attack_plasma_force, self.projectiledeathtype, other); + + remove (self); +} + +void M_Enforcer_Attack_Plasma_Touch() +{ + PROJECTILE_TOUCH; + M_Enforcer_Attack_Plasma_Explode(); +} + +void M_Enforcer_Attack_Plasma() +{ + entity gren; + + sound (self, CH_WEAPON_A, W_Sound("lasergun_fire"), VOL_BASE, ATTEN_NORM); + + vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + vector org = self.origin + v_forward * 14 + '0 0 5' + v_right * -14; + + makevectors(self.angles); + + pointparticles(particleeffectnum("electro_muzzleflash"), org, dir * 1000, 1); + + gren = spawn (); + gren.owner = gren.realowner = self; + gren.classname = "grenade"; + gren.bot_dodge = TRUE; + gren.bot_dodgerating = autocvar_g_monster_enforcer_attack_plasma_damage; + gren.movetype = MOVETYPE_FLY; + PROJECTILE_MAKETRIGGER(gren); + gren.projectiledeathtype = DEATH_MONSTER_OGRE_GRENADE; + setorigin(gren, org); + setsize(gren, '-3 -3 -3', '3 3 3'); + + gren.nextthink = time + autocvar_g_monster_enforcer_attack_plasma_lifetime; + gren.think = adaptor_think2use_hittype_splash; + gren.use = M_Enforcer_Attack_Plasma_Explode; + gren.touch = M_Enforcer_Attack_Plasma_Touch; + + gren.missile_flags = MIF_SPLASH; + W_SetupProjVelocity_Explicit(gren, dir, v_up, autocvar_g_monster_enforcer_attack_plasma_speed, 0, 0, autocvar_g_monster_enforcer_attack_plasma_spread, FALSE); + + gren.flags = FL_PROJECTILE; + + CSQCProjectile(gren, TRUE, PROJECTILE_ELECTRO, TRUE); + + other = gren; MUTATOR_CALLHOOK(EditProjectile); + + self.attack_finished_single = time + autocvar_g_monster_enforcer_attack_plasma_delay; +} + +float M_Enforcer_Attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + case MONSTER_ATTACK_RANGED: + { + self.frame = enforcer_anim_attack; + self.attack_finished_single = time + 0.7; + self.anim_finished = time + 0.7 * autocvar_g_monster_enforcer_attack_plasma_shots; + self.state = MONSTER_ATTACK_RANGED; + Monster_Delay(autocvar_g_monster_enforcer_attack_plasma_shots - 1, 0.3, 0.4, M_Enforcer_Attack_Plasma); + return TRUE; + } + } + + return FALSE; +} + +void spawnfunc_monster_enforcer() { Monster_Spawn(MON_ENFORCER); } + +float M_Enforcer(float req) +{ + switch(req) + { + case MR_THINK: + { + return TRUE; + } + case MR_PAIN: + { + switch(floor(random() * 5)) + { + default: + case 1: self.frame = enforcer_anim_pain1; self.pain_finished = time + 0.3; break; + case 2: self.frame = enforcer_anim_pain2; self.pain_finished = time + 0.4; break; + case 3: self.frame = enforcer_anim_pain3; self.pain_finished = time + 0.7; break; + case 4: self.frame = enforcer_anim_pain4; self.pain_finished = time + 1; break; + } + return TRUE; + } + case MR_DEATH: + { + self.frame = ((random() > 0.5) ? enforcer_anim_death1 : enforcer_anim_death2); + return TRUE; + } + case MR_SETUP: + { + if(!self.health) self.health = (autocvar_g_monster_enforcer_health); + if(!self.speed) { self.speed = (autocvar_g_monster_enforcer_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_enforcer_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_enforcer_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_enforcer_damageforcescale); } + + self.m_anim_walk = enforcer_anim_walk; + self.m_anim_run = enforcer_anim_run; + self.m_anim_idle = enforcer_anim_stand; + + self.monster_loot = spawnfunc_item_cells; + self.weapon = WEP_CRYLINK; + + return TRUE; + } + case MR_PRECACHE: + { + precache_sound(W_Sound("tag_impact")); + precache_sound(W_Sound("lasergun_fire")); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/mage.qc b/qcsrc/common/monsters/monster/mage.qc index 3adc59a30..907bb5101 100644 --- a/qcsrc/common/monsters/monster/mage.qc +++ b/qcsrc/common/monsters/monster/mage.qc @@ -1,7 +1,7 @@ #ifdef REGISTER_MONSTER REGISTER_MONSTER( /* MON_##id */ MAGE, -/* function */ m_mage, +/* functions */ M_Mage, M_Mage_Attack, /* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED, /* mins,maxs */ '-36 -36 -24', '36 36 50', /* model */ "mage.dpm", @@ -12,6 +12,7 @@ REGISTER_MONSTER( #else #ifdef SVQC float autocvar_g_monster_mage_health; +var float autocvar_g_monster_mage_damageforcescale = 0.5; float autocvar_g_monster_mage_attack_spike_damage; float autocvar_g_monster_mage_attack_spike_radius; float autocvar_g_monster_mage_attack_spike_delay; @@ -46,26 +47,25 @@ const float mage_anim_pain = 3; const float mage_anim_death = 4; const float mage_anim_run = 5; -void() mage_heal; -void() mage_shield; +void() M_Mage_Defend_Heal; +void() M_Mage_Defend_Shield; .entity mage_spike; -.float shield_ltime; +.float mage_shield_delay; +.float mage_shield_time; -float friend_needshelp(entity e) +float M_Mage_Defend_Heal_Check(entity e) { if(e == world) return FALSE; if(e.health <= 0) return FALSE; - if(DIFF_TEAM(e, self) && e != self.monster_owner) + if(DIFF_TEAM(e, self) && e != self.monster_follow) return FALSE; if(e.frozen) return FALSE; if(!IS_PLAYER(e)) - return ((e.flags & FL_MONSTER) && e.health < e.max_health); - if(e.items & IT_INVINCIBLE) - return FALSE; + return (IS_MONSTER(e) && e.health < e.max_health); switch(self.skin) { @@ -78,29 +78,29 @@ float friend_needshelp(entity e) return FALSE; } -void mage_spike_explode() +void M_Mage_Attack_Spike_Explode() { self.event_damage = func_null; - sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM); + sound(self, CH_SHOTS, W_Sound("grenade_impact"), VOL_BASE, ATTEN_NORM); self.realowner.mage_spike = world; - pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1); + Send_Effect(EFFECT_EXPLOSION_SMALL, self.origin, '0 0 0', 1); RadiusDamage (self, self.realowner, (autocvar_g_monster_mage_attack_spike_damage), (autocvar_g_monster_mage_attack_spike_damage) * 0.5, (autocvar_g_monster_mage_attack_spike_radius), world, world, 0, DEATH_MONSTER_MAGE, other); remove (self); } -void mage_spike_touch() +void M_Mage_Attack_Spike_Touch() { PROJECTILE_TOUCH; - mage_spike_explode(); + M_Mage_Attack_Spike_Explode(); } // copied from W_Seeker_Think -void mage_spike_think() +void M_Mage_Attack_Spike_Think() { entity e; vector desireddir, olddir, newdir, eorg; @@ -111,7 +111,7 @@ void mage_spike_think() if (time > self.ltime || self.enemy.health <= 0 || self.owner.health <= 0) { self.projectiledeathtype |= HITTYPE_SPLASH; - mage_spike_explode(); + M_Mage_Attack_Spike_Explode(); } spd = vlen(self.velocity); @@ -163,7 +163,7 @@ void mage_spike_think() UpdateCSQCProjectile(self); } -void mage_attack_spike() +void M_Mage_Attack_Spike() { entity missile; vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); @@ -172,7 +172,7 @@ void mage_attack_spike() missile = spawn (); missile.owner = missile.realowner = self; - missile.think = mage_spike_think; + missile.think = M_Mage_Attack_Spike_Think; missile.ltime = time + 7; missile.nextthink = time; missile.solid = SOLID_BBOX; @@ -183,19 +183,19 @@ void mage_attack_spike() missile.velocity = dir * 400; missile.avelocity = '300 300 300'; missile.enemy = self.enemy; - missile.touch = mage_spike_touch; + missile.touch = M_Mage_Attack_Spike_Touch; self.mage_spike = missile; CSQCProjectile(missile, TRUE, PROJECTILE_MAGE_SPIKE, TRUE); } -void mage_heal() +void M_Mage_Defend_Heal() { entity head; float washealed = FALSE; - for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain) if(friend_needshelp(head)) + for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain) if(M_Mage_Defend_Heal_Check(head)) { washealed = TRUE; string fx = ""; @@ -234,7 +234,7 @@ void mage_heal() { pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1); head.health = bound(0, head.health + (autocvar_g_monster_mage_heal_allies), head.max_health); - if(!(head.spawnflags & MONSTERFLAG_INVINCIBLE)) + if(!(head.spawnflags & MONSTERFLAG_INVINCIBLE) && head.sprite) WaypointSprite_UpdateHealth(head.sprite, head.health); } } @@ -243,12 +243,13 @@ void mage_heal() { self.frame = mage_anim_attack; self.attack_finished_single = time + (autocvar_g_monster_mage_heal_delay); + self.anim_finished = time + 1.5; } } -void mage_push() +void M_Mage_Attack_Push() { - sound(self, CH_SHOTS, "weapons/tagexp1.wav", 1, ATTEN_NORM); + sound(self, CH_SHOTS, W_Sound("tagexp1"), 1, ATTEN_NORM); RadiusDamage (self, self, (autocvar_g_monster_mage_attack_push_damage), (autocvar_g_monster_mage_attack_push_damage), (autocvar_g_monster_mage_attack_push_radius), world, world, (autocvar_g_monster_mage_attack_push_force), DEATH_MONSTER_MAGE, self.enemy); pointparticles(particleeffectnum("TE_EXPLOSION"), self.origin, '0 0 0', 1); @@ -256,7 +257,7 @@ void mage_push() self.attack_finished_single = time + (autocvar_g_monster_mage_attack_push_delay); } -void mage_teleport() +void M_Mage_Attack_Teleport() { if(vlen(self.enemy.origin - self.origin) >= 500) return; @@ -273,25 +274,24 @@ void mage_teleport() self.attack_finished_single = time + 0.2; } -void mage_shield_remove() +void M_Mage_Defend_Shield_Remove() { self.effects &= ~(EF_ADDITIVE | EF_BLUE); - self.armorvalue = 0; - self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent; + self.armorvalue = autocvar_g_monsters_armor_blockpercent; } -void mage_shield() +void M_Mage_Defend_Shield() { self.effects |= (EF_ADDITIVE | EF_BLUE); - self.lastshielded = time + (autocvar_g_monster_mage_shield_delay); - self.m_armor_blockpercent = (autocvar_g_monster_mage_shield_blockpercent); - self.armorvalue = self.health; - self.shield_ltime = time + (autocvar_g_monster_mage_shield_time); + self.mage_shield_delay = time + (autocvar_g_monster_mage_shield_delay); + self.armorvalue = (autocvar_g_monster_mage_shield_blockpercent); + self.mage_shield_time = time + (autocvar_g_monster_mage_shield_time); self.frame = mage_anim_attack; self.attack_finished_single = time + 1; + self.anim_finished = time + 1; } -float mage_attack(float attack_type) +float M_Mage_Attack(float attack_type) { switch(attack_type) { @@ -299,7 +299,7 @@ float mage_attack(float attack_type) { if(random() <= 0.7) { - mage_push(); + M_Mage_Attack_Push(); return TRUE; } @@ -311,14 +311,15 @@ float mage_attack(float attack_type) { if(random() <= 0.4) { - mage_teleport(); + M_Mage_Attack_Teleport(); return TRUE; } else { self.frame = mage_anim_attack; self.attack_finished_single = time + (autocvar_g_monster_mage_attack_spike_delay); - defer(0.2, mage_attack_spike); + self.anim_finished = time + 1; + Monster_Delay(1, 0, 0.2, M_Mage_Attack_Spike); return TRUE; } } @@ -333,17 +334,15 @@ float mage_attack(float attack_type) return FALSE; } -void spawnfunc_monster_mage() -{ - self.classname = "monster_mage"; - - if(!monster_initialize(MON_MAGE)) { remove(self); return; } -} +void spawnfunc_monster_mage() { Monster_Spawn(MON_MAGE); } +// extra monsters contains original shalrath +#ifndef MONSTERS_EXTRA // compatibility with old spawns void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); } +#endif -float m_mage(float req) +float M_Mage(float req) { switch(req) { @@ -352,9 +351,10 @@ float m_mage(float req) entity head; float need_help = FALSE; - for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain) + for(head = world; (head = findfloat(head, iscreature, TRUE)); ) if(head != self) - if(friend_needshelp(head)) + if(vlen(head.origin - self.origin) <= (autocvar_g_monster_mage_heal_range)) + if(M_Mage_Defend_Heal_Check(head)) { need_help = TRUE; break; @@ -363,18 +363,21 @@ float m_mage(float req) if(self.health < (autocvar_g_monster_mage_heal_minhealth) || need_help) if(time >= self.attack_finished_single) if(random() < 0.5) - mage_heal(); + M_Mage_Defend_Heal(); - if(time >= self.shield_ltime && self.armorvalue) - mage_shield_remove(); + if(time >= self.mage_shield_time && self.armorvalue) + M_Mage_Defend_Shield_Remove(); if(self.enemy) if(self.health < self.max_health) - if(time >= self.lastshielded) + if(time >= self.mage_shield_delay) if(random() < 0.5) - mage_shield(); + M_Mage_Defend_Shield(); - monster_move((autocvar_g_monster_mage_speed_run), (autocvar_g_monster_mage_speed_walk), (autocvar_g_monster_mage_speed_stop), mage_anim_walk, mage_anim_run, mage_anim_idle); + return TRUE; + } + case MR_PAIN: + { return TRUE; } case MR_DEATH: @@ -385,18 +388,24 @@ float m_mage(float req) case MR_SETUP: { if(!self.health) self.health = (autocvar_g_monster_mage_health); + if(!self.speed) { self.speed = (autocvar_g_monster_mage_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_mage_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_mage_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_mage_damageforcescale); } + + self.m_anim_walk = mage_anim_walk; + self.m_anim_run = mage_anim_run; + self.m_anim_idle = mage_anim_idle; self.monster_loot = spawnfunc_item_health_large; - self.monster_attackfunc = mage_attack; self.frame = mage_anim_walk; return TRUE; } case MR_PRECACHE: { - precache_model("models/monsters/mage.dpm"); - precache_sound ("weapons/grenade_impact.wav"); - precache_sound ("weapons/tagexp1.wav"); + precache_sound (W_Sound("grenade_impact")); + precache_sound (W_Sound("tagexp1")); return TRUE; } } @@ -405,19 +414,4 @@ float m_mage(float req) } #endif // SVQC -#ifdef CSQC -float m_mage(float req) -{ - switch(req) - { - case MR_PRECACHE: - { - return TRUE; - } - } - - return TRUE; -} - -#endif // CSQC #endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/ogre.qc b/qcsrc/common/monsters/monster/ogre.qc new file mode 100644 index 000000000..fae371c42 --- /dev/null +++ b/qcsrc/common/monsters/monster/ogre.qc @@ -0,0 +1,370 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ OGRE, +/* functions */ M_Ogre, M_Ogre_Attack, +/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED | MONSTER_SIZE_BROKEN, +/* mins,maxs */ '-36 -36 -20', '36 36 50', +/* model */ "ogre.mdl", +/* netname */ "ogre", +/* fullname */ _("Ogre") +); + +#else +#ifdef SVQC +float autocvar_g_monster_ogre_health; +var float autocvar_g_monster_ogre_damageforcescale = 0.4; +float autocvar_g_monster_ogre_attack_machinegun_spread; +float autocvar_g_monster_ogre_attack_machinegun_solidpenetration; +float autocvar_g_monster_ogre_attack_machinegun_damage; +float autocvar_g_monster_ogre_attack_machinegun_force; +float autocvar_g_monster_ogre_attack_grenade_damage; +float autocvar_g_monster_ogre_attack_grenade_edgedamage; +float autocvar_g_monster_ogre_attack_grenade_radius; +float autocvar_g_monster_ogre_attack_grenade_speed; +float autocvar_g_monster_ogre_attack_grenade_speed_up; +float autocvar_g_monster_ogre_attack_grenade_speed_z; +float autocvar_g_monster_ogre_attack_grenade_spread; +float autocvar_g_monster_ogre_attack_grenade_force; +float autocvar_g_monster_ogre_attack_grenade_bouncefactor; +float autocvar_g_monster_ogre_attack_grenade_bouncestop; +float autocvar_g_monster_ogre_attack_grenade_lifetime; +float autocvar_g_monster_ogre_attack_grenade_health; +float autocvar_g_monster_ogre_attack_grenade_damageforcescale; +float autocvar_g_monster_ogre_attack_melee_damage; +float autocvar_g_monster_ogre_attack_melee_nonplayerdamage; +float autocvar_g_monster_ogre_attack_melee_delay; +float autocvar_g_monster_ogre_attack_melee_time; +float autocvar_g_monster_ogre_attack_melee_range; +float autocvar_g_monster_ogre_attack_melee_traces; +float autocvar_g_monster_ogre_attack_melee_swing_up; +float autocvar_g_monster_ogre_attack_melee_swing_side; +float autocvar_g_monster_ogre_speed_stop; +float autocvar_g_monster_ogre_speed_run; +float autocvar_g_monster_ogre_speed_walk; + +const float ogre_anim_idle = 0; +const float ogre_anim_walk = 1; +const float ogre_anim_run = 2; +const float ogre_anim_swing = 3; +const float ogre_anim_smash = 4; +const float ogre_anim_shoot = 5; +const float ogre_anim_pain1 = 6; +const float ogre_anim_pain2 = 7; +const float ogre_anim_pain3 = 8; +const float ogre_anim_pain4 = 9; +const float ogre_anim_pain5 = 10; +const float ogre_anim_death1 = 11; +const float ogre_anim_death2 = 12; +const float ogre_anim_pull = 13; + +void M_Ogre_Attack_MachineGun() +{ + vector dir = normalize(self.enemy.origin - self.origin); + vector org = self.origin + self.view_ofs + v_forward * 14; + sound (self, CH_WEAPON_A, W_Sound("uzi_fire"), VOL_BASE, ATTEN_NORM); + + fireBullet(org, dir, autocvar_g_monster_ogre_attack_machinegun_spread, autocvar_g_monster_ogre_attack_machinegun_solidpenetration, autocvar_g_monster_ogre_attack_machinegun_damage, autocvar_g_monster_ogre_attack_machinegun_force, DEATH_MONSTER_OGRE_MACHINEGUN, 0); + + Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, org, dir * 1000, 1); + + // casing code + if (autocvar_g_casings >= 2) + SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); +} + +void M_Ogre_Attack_Grenade_Explode() +{ + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + if(self.movetype == MOVETYPE_NONE) + self.velocity = self.oldvelocity; + + sound (self, CH_WEAPON_A, W_Sound("grenade_impact"), VOL_BASE, ATTEN_NORM); + pointparticles(particleeffectnum("grenade_explode"), self.origin, '0 0 0', 1); + RadiusDamage (self, self.realowner, autocvar_g_monster_ogre_attack_grenade_damage, autocvar_g_monster_ogre_attack_grenade_edgedamage, autocvar_g_monster_ogre_attack_grenade_radius, world, world, autocvar_g_monster_ogre_attack_grenade_force, self.projectiledeathtype, other); + + remove (self); +} + +void M_Ogre_Attack_Grenade_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 -= damage; + + if (self.health <= 0) + W_PrepareExplosionByDamage(attacker, self.use); +} + +void M_Ogre_Attack_Grenade_Touch() +{ + PROJECTILE_TOUCH; + + if (other.takedamage == DAMAGE_AIM) + { + self.use(); + return; + } + + float r; + r = random() * 6; + if(r < 1) + spamsound (self, CH_SHOTS, W_Sound("grenade_bounce1"), VOL_BASE, ATTEN_NORM); + else if(r < 2) + spamsound (self, CH_SHOTS, W_Sound("grenade_bounce2"), VOL_BASE, ATTEN_NORM); + else if(r < 3) + spamsound (self, CH_SHOTS, W_Sound("grenade_bounce3"), VOL_BASE, ATTEN_NORM); + else if(r < 4) + spamsound (self, CH_SHOTS, W_Sound("grenade_bounce4"), VOL_BASE, ATTEN_NORM); + else if(r < 5) + spamsound (self, CH_SHOTS, W_Sound("grenade_bounce5"), VOL_BASE, ATTEN_NORM); + else + spamsound (self, CH_SHOTS, W_Sound("grenade_bounce6"), VOL_BASE, ATTEN_NORM); + self.projectiledeathtype |= HITTYPE_BOUNCE; +} + +void M_Ogre_Attack_Grenade() +{ + entity gren; + + sound (self, CH_WEAPON_A, W_Sound("grenade_fire"), VOL_BASE, ATTEN_NORM); + + vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + vector org = self.origin + v_forward * 14 + '0 0 30' + v_right * -14; + + makevectors(self.angles); + + Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, org, dir * 1000, 1); + + gren = spawn (); + gren.owner = gren.realowner = self; + gren.classname = "grenade"; + gren.bot_dodge = TRUE; + gren.bot_dodgerating = autocvar_g_monster_ogre_attack_grenade_damage; + gren.movetype = MOVETYPE_BOUNCE; + gren.bouncefactor = autocvar_g_monster_ogre_attack_grenade_bouncefactor; + gren.bouncestop = autocvar_g_monster_ogre_attack_grenade_bouncestop; + PROJECTILE_MAKETRIGGER(gren); + gren.projectiledeathtype = DEATH_MONSTER_OGRE_GRENADE; + setorigin(gren, org); + setsize(gren, '-3 -3 -3', '3 3 3'); + + gren.nextthink = time + autocvar_g_monster_ogre_attack_grenade_lifetime; + gren.think = adaptor_think2use_hittype_splash; + gren.use = M_Ogre_Attack_Grenade_Explode; + gren.touch = M_Ogre_Attack_Grenade_Touch; + + gren.takedamage = DAMAGE_YES; + gren.health = autocvar_g_monster_ogre_attack_grenade_health; + gren.damageforcescale = autocvar_g_monster_ogre_attack_grenade_damageforcescale; + gren.event_damage = M_Ogre_Attack_Grenade_Damage; + gren.damagedbycontents = TRUE; + gren.missile_flags = MIF_SPLASH | MIF_ARC; + W_SetupProjVelocity_Explicit(gren, dir, v_up, autocvar_g_monster_ogre_attack_grenade_speed, autocvar_g_monster_ogre_attack_grenade_speed_up, autocvar_g_monster_ogre_attack_grenade_speed_z, autocvar_g_monster_ogre_attack_grenade_spread, FALSE); + + gren.angles = vectoangles (gren.velocity); + gren.flags = FL_PROJECTILE; + + CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE); + + other = gren; MUTATOR_CALLHOOK(EditProjectile); + + self.attack_finished_single = time + 0.7; + self.state = 0; +} + +.float ogre_swing_prev; +.entity ogre_swing_alreadyhit; +void M_Ogre_Attack_Chainsaw() +{ + // declarations + float i, f, swing, swing_factor, swing_damage, meleetime, is_player, is_monster; + entity target_victim; + vector targpos; + + if(!self.cnt) // set start time of melee + { + self.cnt = time; + } + + makevectors(self.realowner.angles); // update values for v_* vectors + + // calculate swing percentage based on time + meleetime = autocvar_g_monster_ogre_attack_melee_time; + swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); + f = ((1 - swing) * autocvar_g_monster_ogre_attack_melee_traces); + + // check to see if we can still continue, otherwise give up now + if(self.realowner.deadflag != DEAD_NO) + { + remove(self); + return; + } + + // if okay, perform the traces needed for this frame + for(i=self.ogre_swing_prev; i < f; ++i) + { + swing_factor = ((1 - (i / autocvar_g_monster_ogre_attack_melee_traces)) * 2 - 1); + + targpos = (self.realowner.origin + self.realowner.view_ofs + + (v_forward * autocvar_g_monster_ogre_attack_melee_range) + + (v_up * swing_factor * autocvar_g_monster_ogre_attack_melee_swing_up) + + (v_right * swing_factor * autocvar_g_monster_ogre_attack_melee_swing_side)); + + WarpZone_traceline_antilag(self, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self, 0); + + // 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"); + is_monster = IS_MONSTER(trace_ent); + + if((trace_fraction < 1) // if trace is good, apply the damage and remove self + && (trace_ent.takedamage == DAMAGE_AIM) + && (trace_ent != self.ogre_swing_alreadyhit) + && ((is_player || is_monster) || autocvar_g_monster_ogre_attack_melee_nonplayerdamage)) + { + target_victim = trace_ent; // so it persists through other calls + + if(is_player || is_monster) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught. + swing_damage = (autocvar_g_monster_ogre_attack_melee_damage * min(1, swing_factor + 1)); + else + swing_damage = (autocvar_g_monster_ogre_attack_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, DEATH_MONSTER_OGRE_MELEE, + self.realowner.origin + self.realowner.view_ofs, + v_forward * 1); + + // draw large red flash for debugging + //te_customflash(targpos, 200, 2, '15 0 0'); + + self.ogre_swing_alreadyhit = target_victim; + continue; // move along to next trace + } + } + + if(time >= self.cnt + meleetime) + { + // melee is finished + self.realowner.frame = ogre_anim_idle; + remove(self); + return; + } + else + { + // set up next frame + self.ogre_swing_prev = i; + self.nextthink = time; + } +} + +float M_Ogre_Attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + vector vdir = normalize(self.enemy.origin - self.origin); + + if(vdir_z > 0.7) + { + self.attack_finished_single = time + 1.2; + self.frame = ogre_anim_shoot; + self.state = MONSTER_ATTACK_RANGED; + Monster_Delay(2, 0.1, 0.4, M_Ogre_Attack_MachineGun); + return 2; + } + entity meleetemp; + meleetemp = spawn(); + meleetemp.realowner = self; + meleetemp.think = M_Ogre_Attack_Chainsaw; + meleetemp.nextthink = time + autocvar_g_monster_ogre_attack_melee_delay; + self.attack_finished_single = time + autocvar_g_monster_ogre_attack_melee_time + autocvar_g_monster_ogre_attack_melee_delay + 0.7; + self.anim_finished = self.attack_finished_single; + self.state = MONSTER_ATTACK_MELEE; + self.frame = ogre_anim_swing; + + return TRUE; + } + case MONSTER_ATTACK_RANGED: + { + Monster_Delay(0, 0, 0.5, M_Ogre_Attack_Grenade); + self.state = MONSTER_ATTACK_RANGED; + self.attack_finished_single = time + 1; + self.anim_finished = time + 0.5; + self.frame = ogre_anim_shoot; + return TRUE; + } + } + + return FALSE; +} + +void spawnfunc_monster_ogre() { Monster_Spawn(MON_OGRE); } + +float M_Ogre(float req) +{ + switch(req) + { + case MR_THINK: + { + return TRUE; + } + case MR_PAIN: + { + switch(floor(random() * 6)) + { + default: + case 1: self.frame = ogre_anim_pain1; self.anim_finished = time + 0.4; break; + case 2: self.frame = ogre_anim_pain2; self.anim_finished = time + 0.2; break; + case 3: self.frame = ogre_anim_pain3; self.anim_finished = time + 0.5; break; + case 4: self.frame = ogre_anim_pain4; self.anim_finished = time + 1.5; break; + case 5: self.frame = ogre_anim_pain5; self.anim_finished = time + 1.4; break; + } + return TRUE; + } + case MR_DEATH: + { + self.frame = ((random() >= 0.5) ? ogre_anim_death1 : ogre_anim_death2); + return TRUE; + } + case MR_SETUP: + { + if(!self.health) self.health = (autocvar_g_monster_ogre_health); + if(!self.speed) { self.speed = (autocvar_g_monster_ogre_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_ogre_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_ogre_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_ogre_damageforcescale); } + + self.m_anim_walk = ogre_anim_walk; + self.m_anim_run = ogre_anim_run; + self.m_anim_idle = ogre_anim_idle; + + self.monster_loot = spawnfunc_item_rockets; + self.weapon = WEP_MACHINEGUN; + self.frame = ogre_anim_pull; + self.spawn_time = time + 1; + self.spawnshieldtime = self.spawn_time; + + return TRUE; + } + case MR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/rotfish.qc b/qcsrc/common/monsters/monster/rotfish.qc new file mode 100644 index 000000000..8d2a3953a --- /dev/null +++ b/qcsrc/common/monsters/monster/rotfish.qc @@ -0,0 +1,97 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ ROTFISH, +/* functions */ M_Rotfish, M_Rotfish_Attack, +/* spawnflags */ MON_FLAG_MELEE | MONSTER_SIZE_BROKEN | MONSTER_TYPE_SWIM, +/* mins,maxs */ '-20 -20 -31', '20 20 20', +/* model */ "fish.mdl", +/* netname */ "rotfish", +/* fullname */ _("Rotfish") +); + +#else +#ifdef SVQC +float autocvar_g_monster_rotfish_health; +var float autocvar_g_monster_rotfish_damageforcescale = 0.8; +float autocvar_g_monster_rotfish_attack_range; +float autocvar_g_monster_rotfish_attack_melee_damage; +float autocvar_g_monster_rotfish_attack_melee_delay; +float autocvar_g_monster_rotfish_speed_stop; +float autocvar_g_monster_rotfish_speed_run; +float autocvar_g_monster_rotfish_speed_walk; + +const float rotfish_anim_attack = 0; +const float rotfish_anim_death = 1; +const float rotfish_anim_swim = 2; +const float rotfish_anim_pain = 3; + + +float M_Rotfish_Attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + return Monster_Attack_Melee(self.enemy, (autocvar_g_monster_rotfish_attack_melee_damage), rotfish_anim_attack, self.attack_range, (autocvar_g_monster_rotfish_attack_melee_delay), DEATH_MONSTER_ROTFISH_MELEE, TRUE); + } + case MONSTER_ATTACK_RANGED: + { + // rotfish has no ranged attack yet + return FALSE; + } + } + + return FALSE; +} + +void spawnfunc_monster_rotfish() { Monster_Spawn(MON_ROTFISH); } +void spawnfunc_monster_fish() { spawnfunc_monster_rotfish(); } + +float M_Rotfish(float req) +{ + switch(req) + { + case MR_THINK: + { + return TRUE; + } + case MR_PAIN: + { + self.pain_finished = 0.8; + self.frame = rotfish_anim_pain; + return TRUE; + } + case MR_DEATH: + { + self.frame = rotfish_anim_death; + return TRUE; + } + case MR_SETUP: + { + if(!self.health) self.health = (autocvar_g_monster_rotfish_health); + if(!self.speed) { self.speed = (autocvar_g_monster_rotfish_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_rotfish_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_rotfish_speed_stop); } + if(!self.attack_range) { self.attack_range = autocvar_g_monster_rotfish_attack_range; } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_rotfish_damageforcescale); } + + self.m_anim_walk = rotfish_anim_swim; + self.m_anim_run = rotfish_anim_swim; + self.m_anim_idle = rotfish_anim_swim; + + self.monster_loot = spawnfunc_item_armor_small; + self.frame = rotfish_anim_swim; + + return TRUE; + } + case MR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/rottweiler.qc b/qcsrc/common/monsters/monster/rottweiler.qc new file mode 100644 index 000000000..c4ed126c1 --- /dev/null +++ b/qcsrc/common/monsters/monster/rottweiler.qc @@ -0,0 +1,101 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ ROTTWEILER, +/* functions */ M_Rottweiler, M_Rottweiler_Attack, +/* spawnflags */ MON_FLAG_MELEE | MONSTER_SIZE_BROKEN | MON_FLAG_RIDE, +/* mins,maxs */ '-16 -16 -24', '16 16 12', +/* model */ "dog.mdl", +/* netname */ "rottweiler", +/* fullname */ _("Rottweiler") +); + +#else +#ifdef SVQC +float autocvar_g_monster_rottweiler_health; +var float autocvar_g_monster_rottweiler_damageforcescale = 0.7; +float autocvar_g_monster_rottweiler_attack_melee_damage; +float autocvar_g_monster_rottweiler_attack_melee_delay; +float autocvar_g_monster_rottweiler_speed_stop; +float autocvar_g_monster_rottweiler_speed_run; +float autocvar_g_monster_rottweiler_speed_walk; + +const float rottweiler_anim_attack1 = 0; +const float rottweiler_anim_death1 = 1; +const float rottweiler_anim_death2 = 2; +const float rottweiler_anim_attack2 = 3; +const float rottweiler_anim_pain = 4; +const float rottweiler_anim_run = 5; +const float rottweiler_anim_leap = 6; +const float rottweiler_anim_idle = 7; +const float rottweiler_anim_walk = 8; + +float M_Rottweiler_Attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + return Monster_Attack_Melee(self.enemy, (autocvar_g_monster_rottweiler_attack_melee_damage), ((random() >= 0.5) ? rottweiler_anim_attack1 : rottweiler_anim_attack2), self.attack_range, (autocvar_g_monster_rottweiler_attack_melee_delay), DEATH_MONSTER_ROTTWEILER, TRUE); + } + case MONSTER_ATTACK_RANGED: + { + // rottweiler has no ranged attack yet! + return FALSE; + } + } + + return FALSE; +} + +void spawnfunc_monster_rottweiler() { Monster_Spawn(MON_ROTTWEILER); } + +float M_Rottweiler(float req) +{ + switch(req) + { + case MR_THINK: + { + return TRUE; + } + case MR_PAIN: + { + if(random() <= 0.3) + { + self.pain_finished = time + 1.5; + self.frame = rottweiler_anim_pain; + } + return TRUE; + } + case MR_DEATH: + { + self.frame = (random() >= 0.5) ? rottweiler_anim_death1 : rottweiler_anim_death2; + return TRUE; + } + case MR_SETUP: + { + if(!self.health) self.health = (autocvar_g_monster_rottweiler_health); + if(!self.speed) { self.speed = (autocvar_g_monster_rottweiler_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_rottweiler_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_rottweiler_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_rottweiler_damageforcescale); } + + self.m_anim_walk = rottweiler_anim_walk; + self.m_anim_run = rottweiler_anim_run; + self.m_anim_idle = rottweiler_anim_idle; + + self.monster_loot = spawnfunc_item_health_small; + self.frame = rottweiler_anim_idle; + + return TRUE; + } + case MR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/scrag.qc b/qcsrc/common/monsters/monster/scrag.qc new file mode 100644 index 000000000..32ceb6ff8 --- /dev/null +++ b/qcsrc/common/monsters/monster/scrag.qc @@ -0,0 +1,161 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ SCRAG, +/* functions */ M_Scrag, M_Scrag_Attack, +/* spawnflags */ MONSTER_TYPE_FLY | MONSTER_SIZE_BROKEN | MON_FLAG_RANGED, +/* mins,maxs */ '-20 -20 -58', '20 20 20', +/* model */ "scrag.mdl", +/* netname */ "scrag", +/* fullname */ _("Scrag") +); + +#else +#ifdef SVQC +float autocvar_g_monster_scrag_health; +var float autocvar_g_monster_scrag_damageforcescale = 0.55; +float autocvar_g_monster_scrag_attack_spike_damage; +float autocvar_g_monster_scrag_attack_spike_edgedamage; +float autocvar_g_monster_scrag_attack_spike_force; +float autocvar_g_monster_scrag_attack_spike_radius; +float autocvar_g_monster_scrag_attack_spike_speed; +float autocvar_g_monster_scrag_speed_stop; +float autocvar_g_monster_scrag_speed_run; +float autocvar_g_monster_scrag_speed_walk; + +const float scrag_anim_hover = 0; +const float scrag_anim_fly = 1; +const float scrag_anim_magic = 2; +const float scrag_anim_pain = 3; +const float scrag_anim_death = 4; + +void M_Scrag_Attack_Spike_Explode() +{ + if(self) + { + pointparticles(particleeffectnum("TE_WIZSPIKE"), self.origin, '0 0 0', 1); + + RadiusDamage(self, self.realowner, (autocvar_g_monster_scrag_attack_spike_damage), (autocvar_g_monster_scrag_attack_spike_edgedamage), (autocvar_g_monster_scrag_attack_spike_force), + self.owner, world, (autocvar_g_monster_scrag_attack_spike_radius), self.projectiledeathtype, world); + + remove(self); + } +} + +void M_Scrag_Attack_Spike_Touch() +{ + PROJECTILE_TOUCH; + if(other == self.owner) { return; } + + M_Scrag_Attack_Spike_Explode(); +} + +void M_Scrag_Attack_Spike() +{ + entity missile = spawn(); + vector viewofs = self.view_ofs; + vector dir = normalize(self.enemy.origin - self.origin); + + monster_makevectors(self.enemy); + + missile.owner = missile.realowner = self; + missile.solid = SOLID_TRIGGER; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.projectiledeathtype = DEATH_MONSTER_SCRAG; + setsize(missile, '-6 -6 -6', '6 6 6'); + setorigin(missile, self.origin + viewofs + v_forward * 14); + missile.flags = FL_PROJECTILE; + missile.velocity = dir * (autocvar_g_monster_scrag_attack_spike_speed); + missile.avelocity = '300 300 300'; + missile.nextthink = time + 5; + missile.think = M_Scrag_Attack_Spike_Explode; + missile.enemy = self.enemy; + missile.touch = M_Scrag_Attack_Spike_Touch; + CSQCProjectile(missile, TRUE, PROJECTILE_SCRAG_SPIKE, TRUE); +} + +float M_Scrag_Attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + case MONSTER_ATTACK_RANGED: + { + self.attack_finished_single = time + ((random() >= 0.8) ? 1.3 : 0.15); + self.anim_finished = self.attack_finished_single; + + self.frame = scrag_anim_magic; + + M_Scrag_Attack_Spike(); + + if(random() <= 0.1 || self.monster_moveto) + { + makevectors(self.angles); + self.monster_moveto = self.origin + '1 1 0' * (random() >= 0.5) ? (v_right * 300) : (v_right * -300); + self.monster_face = self.enemy.origin; // but still look at enemy + if(random() <= 0.4) + self.monster_moveto = self.monster_face = '0 0 0'; + } + + return TRUE; + } + } + + return FALSE; +} + +void spawnfunc_monster_scrag() { Monster_Spawn(MON_SCRAG); } + +// compatibility with old spawns +void spawnfunc_monster_wizard() { spawnfunc_monster_scrag(); } +void spawnfunc_monster_wyvern() { spawnfunc_monster_scrag(); } + +float M_Scrag(float req) +{ + switch(req) + { + case MR_THINK: + { + return TRUE; + } + case MR_PAIN: + { + self.pain_finished = time + 0.3; + self.frame = scrag_anim_pain; + return TRUE; + } + case MR_DEATH: + { + self.frame = scrag_anim_death; + self.velocity_x = -200 + 400 * random(); + self.velocity_y = -200 + 400 * random(); + self.velocity_z = 100 + 100 * random(); + return TRUE; + } + case MR_SETUP: + { + if(!self.health) self.health = (autocvar_g_monster_scrag_health); + if(!self.speed) { self.speed = (autocvar_g_monster_scrag_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_scrag_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_scrag_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_scrag_damageforcescale); } + + self.m_anim_walk = scrag_anim_hover; + self.m_anim_run = scrag_anim_fly; + self.m_anim_idle = scrag_anim_hover; + + self.monster_loot = spawnfunc_item_shells; + self.frame = scrag_anim_hover; + + return TRUE; + } + case MR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/shambler.qc b/qcsrc/common/monsters/monster/shambler.qc index 7c46a1da1..1383463dd 100644 --- a/qcsrc/common/monsters/monster/shambler.qc +++ b/qcsrc/common/monsters/monster/shambler.qc @@ -1,7 +1,7 @@ #ifdef REGISTER_MONSTER REGISTER_MONSTER( /* MON_##id */ SHAMBLER, -/* function */ m_shambler, +/* functions */ M_Shambler, M_Shambler_Attack, /* spawnflags */ MONSTER_SIZE_BROKEN | MON_FLAG_SUPERMONSTER | MON_FLAG_MELEE | MON_FLAG_RANGED, /* mins,maxs */ '-41 -41 -31', '41 41 65', /* model */ "shambler.mdl", @@ -12,7 +12,9 @@ REGISTER_MONSTER( #else #ifdef SVQC float autocvar_g_monster_shambler_health; +var float autocvar_g_monster_shambler_damageforcescale = 0.1; float autocvar_g_monster_shambler_attack_smash_damage; +float autocvar_g_monster_shambler_attack_smash_range; float autocvar_g_monster_shambler_attack_claw_damage; float autocvar_g_monster_shambler_attack_lightning_damage; float autocvar_g_monster_shambler_attack_lightning_force; @@ -36,35 +38,38 @@ const float shambler_anim_death = 8; .float shambler_lastattack; // delay attacks separately -void shambler_smash() +void M_Shambler_Attack_Smash() { makevectors(self.angles); - pointparticles(particleeffectnum("explosion_medium"), (self.origin + (v_forward * 150)) - ('0 0 1' * self.maxs_z), '0 0 0', 1); - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_EXPLOSION_MEDIUM, (self.origin + (v_forward * 150)) - ('0 0 1' * self.maxs_z), '0 0 0', 1); + sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); - tracebox(self.origin + v_forward * 50, self.mins * 0.5, self.maxs * 0.5, self.origin + v_forward * 500, MOVE_NORMAL, self); + // RadiusDamage does NOT support custom starting location, which means we must use this hack... + + tracebox(self.origin + v_forward * 50, self.mins * 0.5, self.maxs * 0.5, self.origin + v_forward * autocvar_g_monster_shambler_attack_smash_range, MOVE_NORMAL, self); if(trace_ent.takedamage) - Damage(trace_ent, self, self, (autocvar_g_monster_shambler_attack_smash_damage) * Monster_SkillModifier(), DEATH_MONSTER_SHAMBLER_SMASH, trace_ent.origin, normalize(trace_ent.origin - self.origin)); + Damage(trace_ent, self, self, (autocvar_g_monster_shambler_attack_smash_damage) * MONSTER_SKILLMOD(self), DEATH_MONSTER_SHAMBLER_SMASH, trace_ent.origin, normalize(trace_ent.origin - self.origin)); } -void shambler_swing() +void M_Shambler_Attack_Swing() { float r = (random() < 0.5); - monster_melee(self.enemy, (autocvar_g_monster_shambler_attack_claw_damage), ((r) ? shambler_anim_swingr : shambler_anim_swingl), self.attack_range, 0.8, DEATH_MONSTER_SHAMBLER_CLAW, TRUE); + Monster_Attack_Melee(self.enemy, (autocvar_g_monster_shambler_attack_claw_damage), ((r) ? shambler_anim_swingr : shambler_anim_swingl), self.attack_range, 0.8, DEATH_MONSTER_SHAMBLER_CLAW, TRUE); if(r) { - defer(0.5, shambler_swing); + Monster_Delay(1, 0, 0.5, M_Shambler_Attack_Swing); self.attack_finished_single += 0.5; + self.anim_finished = self.attack_finished_single; } } -void shambler_lightning_explode() +void M_Shambler_Attack_Lightning_Explode() { entity head; - sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTEN_NORM); - pointparticles(particleeffectnum("electro_impact"), '0 0 0', '0 0 0', 1); + sound(self, CH_SHOTS, W_Sound("electro_impact"), VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_ELECTRO_IMPACT, self.origin, '0 0 0', 1); self.event_damage = func_null; self.takedamage = DAMAGE_NO; @@ -79,14 +84,14 @@ void shambler_lightning_explode() for(head = findradius(self.origin, (autocvar_g_monster_shambler_attack_lightning_radius_zap)); head; head = head.chain) if(head != self.realowner) if(head.takedamage) { te_csqc_lightningarc(self.origin, head.origin); - Damage(head, self, self.realowner, (autocvar_g_monster_shambler_attack_lightning_damage) * Monster_SkillModifier(), DEATH_MONSTER_SHAMBLER_ZAP, head.origin, '0 0 0'); + Damage(head, self, self.realowner, (autocvar_g_monster_shambler_attack_lightning_damage) * MONSTER_SKILLMOD(self), DEATH_MONSTER_SHAMBLER_ZAP, head.origin, '0 0 0'); } self.think = SUB_Remove; self.nextthink = time + 0.2; } -void shambler_lightning_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +void M_Shambler_Attack_Lightning_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) { if (self.health <= 0) return; @@ -100,25 +105,25 @@ void shambler_lightning_damage(entity inflictor, entity attacker, float damage, W_PrepareExplosionByDamage(attacker, self.use); } -void shambler_lightning_touch() +void M_Shambler_Attack_Lightning_Touch() { PROJECTILE_TOUCH; self.use (); } -void shambler_lightning_think() +void M_Shambler_Attack_Lightning_Think() { self.nextthink = time; if (time > self.cnt) { other = world; - shambler_lightning_explode(); + M_Shambler_Attack_Lightning_Explode(); return; } } -void shambler_lightning() +void M_Shambler_Attack_Lightning() { entity gren; @@ -138,14 +143,14 @@ void shambler_lightning() gren.cnt = time + 5; gren.nextthink = time; - gren.think = shambler_lightning_think; - gren.use = shambler_lightning_explode; - gren.touch = shambler_lightning_touch; + gren.think = M_Shambler_Attack_Lightning_Think; + gren.use = M_Shambler_Attack_Lightning_Explode; + gren.touch = M_Shambler_Attack_Lightning_Touch; gren.takedamage = DAMAGE_YES; gren.health = 50; gren.damageforcescale = 0; - gren.event_damage = shambler_lightning_damage; + gren.event_damage = M_Shambler_Attack_Lightning_Damage; gren.damagedbycontents = TRUE; gren.missile_flags = MIF_SPLASH | MIF_ARC; W_SetupProjVelocity_Explicit(gren, v_forward, v_up, (autocvar_g_monster_shambler_attack_lightning_speed), (autocvar_g_monster_shambler_attack_lightning_speed_up), 0, 0, FALSE); @@ -156,33 +161,39 @@ void shambler_lightning() CSQCProjectile(gren, TRUE, PROJECTILE_SHAMBLER_LIGHTNING, TRUE); } -float shambler_attack(float attack_type) +float M_Shambler_Attack(float attack_type) { switch(attack_type) { case MONSTER_ATTACK_MELEE: { - shambler_swing(); + M_Shambler_Attack_Swing(); return TRUE; } case MONSTER_ATTACK_RANGED: { + float randomness = random(), enemy_len = vlen(self.enemy.origin - self.origin); + if(time >= self.shambler_lastattack) // shambler doesn't attack much if(self.flags & FL_ONGROUND) - if(random() <= 0.5 && vlen(self.enemy.origin - self.origin) <= 500) + if(randomness <= 0.5 && enemy_len <= autocvar_g_monster_shambler_attack_smash_range) { self.frame = shambler_anim_smash; - defer(0.7, shambler_smash); + Monster_Delay(1, 0, 0.7, M_Shambler_Attack_Smash); self.attack_finished_single = time + 1.1; - self.shambler_lastattack = time + 3; + self.anim_finished = time + 1.1; + self.state = MONSTER_ATTACK_MELEE; // kinda a melee attack + self.shambler_lastattack = time + 3 + random() * 1.5; return TRUE; } - else if(random() <= 0.1) // small chance, don't want this spammed + else if(randomness <= 0.1 && enemy_len >= autocvar_g_monster_shambler_attack_smash_range * 1.5) // small chance, don't want this spammed { self.frame = shambler_anim_magic; + self.state = MONSTER_ATTACK_MELEE; // maybe we should rename this to something more general self.attack_finished_single = time + 1.1; - self.shambler_lastattack = time + 3; - defer(0.6, shambler_lightning); + self.anim_finished = 1.1; + self.shambler_lastattack = time + 3 + random() * 1.5; + Monster_Delay(1, 0, 0.6, M_Shambler_Attack_Lightning); return TRUE; } @@ -193,20 +204,20 @@ float shambler_attack(float attack_type) return FALSE; } -void spawnfunc_monster_shambler() -{ - self.classname = "monster_shambler"; - - if(!monster_initialize(MON_SHAMBLER)) { remove(self); return; } -} +void spawnfunc_monster_shambler() { Monster_Spawn(MON_SHAMBLER); } -float m_shambler(float req) +float M_Shambler(float req) { switch(req) { case MR_THINK: { - monster_move((autocvar_g_monster_shambler_speed_run), (autocvar_g_monster_shambler_speed_walk), (autocvar_g_monster_shambler_speed_stop), shambler_anim_run, shambler_anim_walk, shambler_anim_stand); + return TRUE; + } + case MR_PAIN: + { + self.pain_finished = time + 0.5; + self.frame = shambler_anim_pain; return TRUE; } case MR_DEATH: @@ -218,17 +229,27 @@ float m_shambler(float req) { if(!self.health) self.health = (autocvar_g_monster_shambler_health); if(!self.attack_range) self.attack_range = 150; + if(!self.speed) { self.speed = (autocvar_g_monster_shambler_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_shambler_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_shambler_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_shambler_damageforcescale); } + + self.m_anim_walk = shambler_anim_walk; + self.m_anim_run = shambler_anim_run; + self.m_anim_idle = shambler_anim_stand; self.monster_loot = spawnfunc_item_health_mega; - self.monster_attackfunc = shambler_attack; self.frame = shambler_anim_stand; - self.weapon = WEP_VORTEX; + self.weapon = WEP_ELECTRO; // matches attacks better than WEP_VORTEX + + self.frame = shambler_anim_magic; + self.spawn_time = time + 1.1; + self.spawnshieldtime = self.spawn_time; return TRUE; } case MR_PRECACHE: { - precache_model("models/monsters/shambler.mdl"); return TRUE; } } @@ -237,19 +258,4 @@ float m_shambler(float req) } #endif // SVQC -#ifdef CSQC -float m_shambler(float req) -{ - switch(req) - { - case MR_PRECACHE: - { - return TRUE; - } - } - - return TRUE; -} - -#endif // CSQC #endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/spawn.qc b/qcsrc/common/monsters/monster/spawn.qc new file mode 100644 index 000000000..7040f3c88 --- /dev/null +++ b/qcsrc/common/monsters/monster/spawn.qc @@ -0,0 +1,136 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ SPAWN, +/* functions */ M_Spawn, M_Spawn_Attack, +/* spawnflags */ MON_FLAG_MELEE | MONSTER_SIZE_BROKEN, +/* mins,maxs */ '-16 -16 -24', '16 16 16', +/* model */ "tarbaby.mdl", +/* netname */ "spawn", +/* fullname */ _("Spawn") +); + +#else +#ifdef SVQC +float autocvar_g_monster_spawn_health; +var float autocvar_g_monster_spawn_damageforcescale = 0.6; +float autocvar_g_monster_spawn_attack_explode_damage; +float autocvar_g_monster_spawn_attack_explode_edgedamage; +float autocvar_g_monster_spawn_attack_explode_radius; +float autocvar_g_monster_spawn_attack_explode_force; +float autocvar_g_monster_spawn_attack_leap_speed; +float autocvar_g_monster_spawn_attack_leap_delay; +float autocvar_g_monster_spawn_speed_stop; +float autocvar_g_monster_spawn_speed_run; +float autocvar_g_monster_spawn_speed_walk; + +const float spawn_anim_walk = 0; +const float spawn_anim_run = 1; +const float spawn_anim_jump = 2; +const float spawn_anim_fly = 3; +const float spawn_anim_explode = 4; + +void M_Spawn_Attack_Explode() +{ + Send_Effect(EFFECT_GRENADE_EXPLODE, self.origin, '0 0 0', 1); + sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); + + RadiusDamage (self, self, (autocvar_g_monster_spawn_attack_explode_damage), (autocvar_g_monster_spawn_attack_explode_edgedamage), (autocvar_g_monster_spawn_attack_explode_radius), world, world, (autocvar_g_monster_spawn_attack_explode_force), DEATH_MONSTER_CREEPER, other); + + self.think = SUB_Remove; + self.nextthink = time + 0.1; +} + +void M_Spawn_Attack_Leap_Touch() +{ + if(self.health <= 0) { return; } + + if(other.takedamage) + if(vlen(self.velocity) > 200) + { + Damage (self, world, world, self.health + self.max_health + 200, DEATH_KILL, self.origin, '0 0 0'); + self.touch = Monster_Touch; // instantly turn it off to stop damage spam + } + + if (trace_dphitcontents) + { + self.movetype = MOVETYPE_WALK; + self.touch = Monster_Touch; + } +} + +float M_Spawn_Attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + case MONSTER_ATTACK_RANGED: + { + makevectors(self.angles); + float leap_success = Monster_Attack_Leap(spawn_anim_jump, M_Spawn_Attack_Leap_Touch, v_forward * (autocvar_g_monster_spawn_attack_leap_speed) + '0 0 200', (autocvar_g_monster_spawn_attack_leap_delay)); + if(leap_success) + { + self.movetype = MOVETYPE_BOUNCE; + self.velocity_z += random() * 150; + } + return leap_success; + } + } + + return FALSE; +} + +void spawnfunc_monster_spawn() { Monster_Spawn(MON_SPAWN); } + +float M_Spawn(float req) +{ + switch(req) + { + case MR_THINK: + { + // prevent standard code from breaking everything + if(self.flags & FL_ONGROUND) + if(self.movetype == MOVETYPE_BOUNCE) + if(time >= self.attack_finished_single) + self.movetype = MOVETYPE_WALK; + return TRUE; + } + case MR_PAIN: + { + return TRUE; + } + case MR_DEATH: + { + // KABOOM! + self.frame = 0; + defer(0.05, M_Spawn_Attack_Explode); // simply defer to prevent recursion + return TRUE; + } + case MR_SETUP: + { + if(!self.health) self.health = (autocvar_g_monster_spawn_health); + if(!self.speed) { self.speed = (autocvar_g_monster_spawn_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_spawn_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_spawn_speed_stop); } + if(!self.attack_range) { self.attack_range = autocvar_g_monsters_target_range * 0.8; } // bounce from almost any distance + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_spawn_damageforcescale); } + + self.m_anim_walk = spawn_anim_walk; + self.m_anim_run = spawn_anim_run; + self.m_anim_idle = spawn_anim_walk; + + self.monster_loot = spawnfunc_item_rockets; + self.frame = spawn_anim_walk; + + return TRUE; + } + case MR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/spider.qc b/qcsrc/common/monsters/monster/spider.qc index c6c454547..5ef819fd2 100644 --- a/qcsrc/common/monsters/monster/spider.qc +++ b/qcsrc/common/monsters/monster/spider.qc @@ -1,8 +1,8 @@ #ifdef REGISTER_MONSTER REGISTER_MONSTER( /* MON_##id */ SPIDER, -/* function */ m_spider, -/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED, +/* functions */ M_Spider, M_Spider_Attack, +/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED | MON_FLAG_RIDE, /* mins,maxs */ '-18 -18 -25', '18 18 30', /* model */ "spider.dpm", /* netname */ "spider", @@ -12,6 +12,7 @@ REGISTER_MONSTER( #else #ifdef SVQC float autocvar_g_monster_spider_health; +var float autocvar_g_monster_spider_damageforcescale = 0.6; float autocvar_g_monster_spider_attack_bite_damage; float autocvar_g_monster_spider_attack_bite_delay; float autocvar_g_monster_spider_attack_web_damagetime; @@ -29,38 +30,38 @@ const float spider_anim_attack2 = 3; .float spider_web_delay; -void spider_web_explode() +void M_Spider_Attack_Web_Explode() { entity e; if(self) { - pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_IMPACT, self.origin, '0 0 0', 1); RadiusDamage(self, self.realowner, 0, 0, 25, world, world, 25, self.projectiledeathtype, world); - for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0) if(e.monsterid != MON_SPIDER) + for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0) if(e.monsterid != self.realowner.monsterid) e.spider_slowness = time + (autocvar_g_monster_spider_attack_web_damagetime); remove(self); } } -void spider_web_touch() +void M_Spider_Attack_Web_Touch() { PROJECTILE_TOUCH; - spider_web_explode(); + M_Spider_Attack_Web_Explode(); } -void spider_shootweb() +void M_Spider_Attack_Web() { monster_makevectors(self.enemy); - sound(self, CH_SHOTS, "weapons/electro_fire2.wav", VOL_BASE, ATTEN_NORM); + sound(self, CH_SHOTS, W_Sound("electro_fire2"), VOL_BASE, ATTEN_NORM); entity proj = spawn (); proj.classname = "plasma"; proj.owner = proj.realowner = self; - proj.use = spider_web_touch; + proj.use = M_Spider_Attack_Web_Explode; proj.think = adaptor_think2use_hittype_splash; proj.bot_dodge = TRUE; proj.bot_dodgerating = 0; @@ -73,7 +74,7 @@ void spider_shootweb() //proj.glow_color = 45; proj.movetype = MOVETYPE_BOUNCE; W_SetupProjVelocity_Explicit(proj, v_forward, v_up, (autocvar_g_monster_spider_attack_web_speed), (autocvar_g_monster_spider_attack_web_speed_up), 0, 0, FALSE); - proj.touch = spider_web_touch; + proj.touch = M_Spider_Attack_Web_Touch; setsize(proj, '-4 -4 -4', '4 4 4'); proj.takedamage = DAMAGE_NO; proj.damageforcescale = 0; @@ -89,13 +90,13 @@ void spider_shootweb() CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO, TRUE); } -float spider_attack(float attack_type) +float M_Spider_Attack(float attack_type) { switch(attack_type) { case MONSTER_ATTACK_MELEE: { - return monster_melee(self.enemy, (autocvar_g_monster_spider_attack_bite_damage), ((random() > 0.5) ? spider_anim_attack : spider_anim_attack2), self.attack_range, (autocvar_g_monster_spider_attack_bite_delay), DEATH_MONSTER_SPIDER, TRUE); + return Monster_Attack_Melee(self.enemy, (autocvar_g_monster_spider_attack_bite_damage), ((random() > 0.5) ? spider_anim_attack : spider_anim_attack2), self.attack_range, (autocvar_g_monster_spider_attack_bite_delay), DEATH_MONSTER_SPIDER, TRUE); } case MONSTER_ATTACK_RANGED: { @@ -103,7 +104,8 @@ float spider_attack(float attack_type) { self.frame = spider_anim_attack2; self.attack_finished_single = time + (autocvar_g_monster_spider_attack_web_delay); - spider_shootweb(); + self.anim_finished = time + 1; + M_Spider_Attack_Web(); self.spider_web_delay = time + 3; return TRUE; } @@ -115,20 +117,18 @@ float spider_attack(float attack_type) return FALSE; } -void spawnfunc_monster_spider() -{ - self.classname = "monster_spider"; - - if(!monster_initialize(MON_SPIDER)) { remove(self); return; } -} +void spawnfunc_monster_spider() { Monster_Spawn(MON_SPIDER); } -float m_spider(float req) +float M_Spider(float req) { switch(req) { case MR_THINK: { - monster_move((autocvar_g_monster_spider_speed_run), (autocvar_g_monster_spider_speed_walk), (autocvar_g_monster_spider_speed_stop), spider_anim_walk, spider_anim_walk, spider_anim_idle); + return TRUE; + } + case MR_PAIN: + { return TRUE; } case MR_DEATH: @@ -140,17 +140,23 @@ float m_spider(float req) case MR_SETUP: { if(!self.health) self.health = (autocvar_g_monster_spider_health); + if(!self.speed) { self.speed = (autocvar_g_monster_spider_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_spider_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_spider_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_spider_damageforcescale); } + + self.m_anim_walk = spider_anim_walk; + self.m_anim_run = spider_anim_walk; + self.m_anim_idle = spider_anim_idle; self.monster_loot = spawnfunc_item_health_medium; - self.monster_attackfunc = spider_attack; self.frame = spider_anim_idle; return TRUE; } case MR_PRECACHE: { - precache_model("models/monsters/spider.dpm"); - precache_sound ("weapons/electro_fire2.wav"); + precache_sound (W_Sound("electro_fire2")); return TRUE; } } @@ -159,19 +165,4 @@ float m_spider(float req) } #endif // SVQC -#ifdef CSQC -float m_spider(float req) -{ - switch(req) - { - case MR_PRECACHE: - { - return TRUE; - } - } - - return TRUE; -} - -#endif // CSQC #endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/vore.qc b/qcsrc/common/monsters/monster/vore.qc new file mode 100644 index 000000000..768826fd2 --- /dev/null +++ b/qcsrc/common/monsters/monster/vore.qc @@ -0,0 +1,232 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ VORE, +/* functions */ M_Vore, M_Vore_Attack, +/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED, +/* mins,maxs */ '-32 -32 -24', '32 32 32', +/* model */ "shalrath.mdl", +/* netname */ "vore", +/* fullname */ _("Vore") +); + +#else +#ifdef SVQC +float autocvar_g_monster_vore_health; +var float autocvar_g_monster_vore_damageforcescale = 0.4; +float autocvar_g_monster_vore_attack_spike_damage; +float autocvar_g_monster_vore_attack_spike_radius; +float autocvar_g_monster_vore_attack_spike_delay; +float autocvar_g_monster_vore_attack_spike_accel; +float autocvar_g_monster_vore_attack_spike_decel; +float autocvar_g_monster_vore_attack_spike_turnrate; +float autocvar_g_monster_vore_attack_spike_speed_max; +float autocvar_g_monster_vore_attack_spike_smart; +float autocvar_g_monster_vore_attack_spike_smart_trace_min; +float autocvar_g_monster_vore_attack_spike_smart_trace_max; +float autocvar_g_monster_vore_attack_spike_smart_mindist; +float autocvar_g_monster_vore_attack_melee_damage; +float autocvar_g_monster_vore_attack_melee_delay; +float autocvar_g_monster_vore_speed_stop; +float autocvar_g_monster_vore_speed_run; +float autocvar_g_monster_vore_speed_walk; + +const float vore_anim_attack = 0; +const float vore_anim_pain = 1; +const float vore_anim_death = 2; +const float vore_anim_walk = 3; + +.entity vore_spike; + +void M_Vore_Attack_Spike_Explode() +{ + self.event_damage = func_null; + + sound(self, CH_SHOTS, W_Sound("grenade_impact"), VOL_BASE, ATTEN_NORM); + + self.realowner.vore_spike = world; + + Send_Effect(EFFECT_EXPLOSION_SMALL, self.origin, '0 0 0', 1); + RadiusDamage (self, self.realowner, (autocvar_g_monster_vore_attack_spike_damage), (autocvar_g_monster_vore_attack_spike_damage) * 0.5, (autocvar_g_monster_vore_attack_spike_radius), world, world, 0, DEATH_MONSTER_VORE, other); + + remove (self); +} + +void M_Vore_Attack_Spike_Touch() +{ + PROJECTILE_TOUCH; + + M_Vore_Attack_Spike_Explode(); +} + +// copied from W_Seeker_Think +void M_Vore_Attack_Spike_Think() +{ + entity e; + vector desireddir, olddir, newdir, eorg; + float turnrate; + float dist; + float spd; + + if (time > self.ltime || self.enemy.health <= 0 || self.owner.health <= 0) + { + self.projectiledeathtype |= HITTYPE_SPLASH; + M_Vore_Attack_Spike_Explode(); + } + + spd = vlen(self.velocity); + spd = bound( + spd - (autocvar_g_monster_vore_attack_spike_decel) * frametime, + (autocvar_g_monster_vore_attack_spike_speed_max), + spd + (autocvar_g_monster_vore_attack_spike_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 = (autocvar_g_monster_vore_attack_spike_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 ((autocvar_g_monster_vore_attack_spike_smart) && (dist > (autocvar_g_monster_vore_attack_spike_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((autocvar_g_monster_vore_attack_spike_smart_trace_min), vlen(self.origin - trace_endpos), self.wait = (autocvar_g_monster_vore_attack_spike_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; + + /////////////// + + //self.angles = vectoangles(self.velocity); // turn model in the new flight direction + self.nextthink = time;// + 0.05; // csqc projectiles + UpdateCSQCProjectile(self); +} + +void M_Vore_Attack_Spike() +{ + entity missile; + vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + + makevectors(self.angles); + + missile = spawn (); + missile.owner = missile.realowner = self; + missile.think = M_Vore_Attack_Spike_Think; + missile.ltime = time + 7; + missile.nextthink = time; + missile.solid = SOLID_BBOX; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.flags = FL_PROJECTILE; + setorigin(missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14); + setsize (missile, '0 0 0', '0 0 0'); + missile.velocity = dir * 400; + missile.avelocity = '300 300 300'; + missile.enemy = self.enemy; + missile.touch = M_Vore_Attack_Spike_Touch; + + self.vore_spike = missile; + + CSQCProjectile(missile, TRUE, PROJECTILE_MAGE_SPIKE, TRUE); +} + +float M_Vore_Attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + return Monster_Attack_Melee(self.enemy, (autocvar_g_monster_vore_attack_melee_damage), vore_anim_attack, self.attack_range, (autocvar_g_monster_vore_attack_melee_delay), DEATH_MONSTER_VORE_MELEE, TRUE); + } + case MONSTER_ATTACK_RANGED: + { + if(!self.vore_spike) + { + self.frame = vore_anim_attack; + self.attack_finished_single = time + (autocvar_g_monster_vore_attack_spike_delay); + self.anim_finished = time + 1; + Monster_Delay(1, 0, 0.2, M_Vore_Attack_Spike); + return TRUE; + } + + return (self.vore_spike) ? 2 : FALSE; + } + } + + return FALSE; +} + +void spawnfunc_monster_vore() { Monster_Spawn(MON_VORE); } + +// compatibility with old spawns +void spawnfunc_monster_shalrath() { spawnfunc_monster_vore(); } + +float M_Vore(float req) +{ + switch(req) + { + case MR_THINK: + { + return TRUE; + } + case MR_PAIN: + { + self.pain_finished = time + 0.4; + self.frame = vore_anim_pain; + return TRUE; + } + case MR_DEATH: + { + self.frame = vore_anim_death; + return TRUE; + } + case MR_SETUP: + { + if(!self.health) self.health = (autocvar_g_monster_vore_health); + if(!self.speed) { self.speed = (autocvar_g_monster_vore_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_vore_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_vore_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_vore_damageforcescale); } + + self.m_anim_walk = vore_anim_walk; + self.m_anim_run = vore_anim_walk; + self.m_anim_idle = vore_anim_walk; + + self.monster_loot = spawnfunc_ammo_cells; + self.weapon = WEP_MACHINEGUN; + self.frame = vore_anim_walk; + + return TRUE; + } + case MR_PRECACHE: + { + precache_sound (W_Sound("grenade_impact")); + precache_sound (W_Sound("tagexp1")); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/wyvern.qc b/qcsrc/common/monsters/monster/wyvern.qc index a44f6d9c7..0ce51e78b 100644 --- a/qcsrc/common/monsters/monster/wyvern.qc +++ b/qcsrc/common/monsters/monster/wyvern.qc @@ -1,8 +1,8 @@ #ifdef REGISTER_MONSTER REGISTER_MONSTER( /* MON_##id */ WYVERN, -/* function */ m_wyvern, -/* spawnflags */ MONSTER_TYPE_FLY | MONSTER_SIZE_BROKEN | MON_FLAG_RANGED, +/* functions */ M_Wyvern, M_Wyvern_Attack, +/* spawnflags */ MONSTER_TYPE_FLY | MONSTER_SIZE_BROKEN | MON_FLAG_RANGED | MON_FLAG_RIDE, /* mins,maxs */ '-20 -20 -58', '20 20 20', /* model */ "wizard.mdl", /* netname */ "wyvern", @@ -12,6 +12,7 @@ REGISTER_MONSTER( #else #ifdef SVQC float autocvar_g_monster_wyvern_health; +var float autocvar_g_monster_wyvern_damageforcescale = 0.6; float autocvar_g_monster_wyvern_attack_fireball_damage; float autocvar_g_monster_wyvern_attack_fireball_edgedamage; float autocvar_g_monster_wyvern_attack_fireball_damagetime; @@ -28,30 +29,30 @@ const float wyvern_anim_magic = 2; const float wyvern_anim_pain = 3; const float wyvern_anim_death = 4; -void wyvern_fireball_explode() +void M_Wyvern_Attack_Fireball_Explode() { entity e; if(self) { - pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1); + Send_Effect(EFFECT_FIREBALL_EXPLODE, self.origin, '0 0 0', 1); RadiusDamage(self, self.realowner, (autocvar_g_monster_wyvern_attack_fireball_damage), (autocvar_g_monster_wyvern_attack_fireball_edgedamage), (autocvar_g_monster_wyvern_attack_fireball_force), world, world, (autocvar_g_monster_wyvern_attack_fireball_radius), self.projectiledeathtype, world); for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= (autocvar_g_monster_wyvern_attack_fireball_radius)) - Fire_AddDamage(e, self, 5 * Monster_SkillModifier(), (autocvar_g_monster_wyvern_attack_fireball_damagetime), self.projectiledeathtype); + Fire_AddDamage(e, self, 5 * MONSTER_SKILLMOD(self), (autocvar_g_monster_wyvern_attack_fireball_damagetime), self.projectiledeathtype); remove(self); } } -void wyvern_fireball_touch() +void M_Wyvern_Attack_Fireball_Touch() { PROJECTILE_TOUCH; - wyvern_fireball_explode(); + M_Wyvern_Attack_Fireball_Explode(); } -void wyvern_fireball() +void M_Wyvern_Attack_Fireball() { entity missile = spawn(); vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); @@ -68,13 +69,13 @@ void wyvern_fireball() missile.velocity = dir * (autocvar_g_monster_wyvern_attack_fireball_speed); missile.avelocity = '300 300 300'; missile.nextthink = time + 5; - missile.think = wyvern_fireball_explode; + missile.think = M_Wyvern_Attack_Fireball_Explode; missile.enemy = self.enemy; - missile.touch = wyvern_fireball_touch; + missile.touch = M_Wyvern_Attack_Fireball_Touch; CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE); } -float wyvern_attack(float attack_type) +float M_Wyvern_Attack(float attack_type) { switch(attack_type) { @@ -82,8 +83,9 @@ float wyvern_attack(float attack_type) case MONSTER_ATTACK_RANGED: { self.attack_finished_single = time + 1.2; + self.anim_finished = time + 1.2; - wyvern_fireball(); + M_Wyvern_Attack_Fireball(); return TRUE; } @@ -92,23 +94,23 @@ float wyvern_attack(float attack_type) return FALSE; } -void spawnfunc_monster_wyvern() -{ - self.classname = "monster_wyvern"; - - if(!monster_initialize(MON_WYVERN)) { remove(self); return; } -} +void spawnfunc_monster_wyvern() { Monster_Spawn(MON_WYVERN); } +#ifndef MONSTERS_EXTRA // compatibility with old spawns void spawnfunc_monster_wizard() { spawnfunc_monster_wyvern(); } +#endif // extra monsters include original wizard -float m_wyvern(float req) +float M_Wyvern(float req) { switch(req) { case MR_THINK: { - monster_move((autocvar_g_monster_wyvern_speed_run), (autocvar_g_monster_wyvern_speed_walk), (autocvar_g_monster_wyvern_speed_stop), wyvern_anim_fly, wyvern_anim_hover, wyvern_anim_hover); + return TRUE; + } + case MR_PAIN: + { return TRUE; } case MR_DEATH: @@ -122,16 +124,22 @@ float m_wyvern(float req) case MR_SETUP: { if(!self.health) self.health = (autocvar_g_monster_wyvern_health); + if(!self.speed) { self.speed = (autocvar_g_monster_wyvern_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_wyvern_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_wyvern_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_wyvern_damageforcescale); } + + self.m_anim_walk = wyvern_anim_hover; + self.m_anim_run = wyvern_anim_fly; + self.m_anim_idle = wyvern_anim_hover; self.monster_loot = spawnfunc_item_cells; - self.monster_attackfunc = wyvern_attack; self.frame = wyvern_anim_hover; return TRUE; } case MR_PRECACHE: { - precache_model("models/monsters/wizard.mdl"); return TRUE; } } @@ -140,19 +148,4 @@ float m_wyvern(float req) } #endif // SVQC -#ifdef CSQC -float m_wyvern(float req) -{ - switch(req) - { - case MR_PRECACHE: - { - return TRUE; - } - } - - return TRUE; -} - -#endif // CSQC #endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/zombie.qc b/qcsrc/common/monsters/monster/zombie.qc index 25afaf76f..d8f9dc916 100644 --- a/qcsrc/common/monsters/monster/zombie.qc +++ b/qcsrc/common/monsters/monster/zombie.qc @@ -1,8 +1,8 @@ #ifdef REGISTER_MONSTER REGISTER_MONSTER( /* MON_##id */ ZOMBIE, -/* function */ m_zombie, -/* spawnflags */ MON_FLAG_MELEE, +/* functions */ M_Zombie, M_Zombie_Attack, +/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RIDE, /* mins,maxs */ '-18 -18 -25', '18 18 47', /* model */ "zombie.dpm", /* netname */ "zombie", @@ -12,6 +12,7 @@ REGISTER_MONSTER( #else #ifdef SVQC float autocvar_g_monster_zombie_health; +var float autocvar_g_monster_zombie_damageforcescale = 0.55; float autocvar_g_monster_zombie_attack_melee_damage; float autocvar_g_monster_zombie_attack_melee_delay; float autocvar_g_monster_zombie_attack_leap_damage; @@ -54,7 +55,7 @@ const float zombie_anim_runforwardleft = 28; const float zombie_anim_runforwardright = 29; const float zombie_anim_spawn = 30; -void zombie_attack_leap_touch() +void M_Zombie_Attack_Leap_Touch() { if (self.health <= 0) return; @@ -65,38 +66,41 @@ void zombie_attack_leap_touch() { angles_face = vectoangles(self.moveto - self.origin); angles_face = normalize(angles_face) * (autocvar_g_monster_zombie_attack_leap_force); - Damage(other, self, self, (autocvar_g_monster_zombie_attack_leap_damage) * Monster_SkillModifier(), DEATH_MONSTER_ZOMBIE_JUMP, other.origin, angles_face); - self.touch = MonsterTouch; // instantly turn it off to stop damage spam + Damage(other, self, self, (autocvar_g_monster_zombie_attack_leap_damage) * MONSTER_SKILLMOD(self), DEATH_MONSTER_ZOMBIE_JUMP, other.origin, angles_face); + self.touch = Monster_Touch; // instantly turn it off to stop damage spam + self.state = 0; } if (trace_dphitcontents) - self.touch = MonsterTouch; + { + self.state = 0; + self.touch = Monster_Touch; + } } -void zombie_blockend() +void M_Zombie_Defend_Block_End() { if(self.health <= 0) return; self.frame = zombie_anim_blockend; - self.armorvalue = 0; - self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent; + self.armorvalue = autocvar_g_monsters_armor_blockpercent; } -float zombie_block() +float M_Zombie_Defend_Block() { self.frame = zombie_anim_blockstart; - self.armorvalue = 100; - self.m_armor_blockpercent = 0.9; - self.state = MONSTER_STATE_ATTACK_MELEE; // freeze monster + self.armorvalue = 0.9; + self.state = MONSTER_ATTACK_MELEE; // freeze monster self.attack_finished_single = time + 2.1; + self.anim_finished = self.attack_finished_single; - defer(2, zombie_blockend); + Monster_Delay(1, 0, 2, M_Zombie_Defend_Block_End); return TRUE; } -float zombie_attack(float attack_type) +float M_Zombie_Attack(float attack_type) { switch(attack_type) { @@ -112,46 +116,54 @@ float zombie_attack(float attack_type) chosen_anim = zombie_anim_attackstanding3; if(random() < 0.3 && self.health < 75 && self.enemy.health > 10) - return zombie_block(); + return M_Zombie_Defend_Block(); - return monster_melee(self.enemy, (autocvar_g_monster_zombie_attack_melee_damage), chosen_anim, self.attack_range, (autocvar_g_monster_zombie_attack_melee_delay), DEATH_MONSTER_ZOMBIE_MELEE, TRUE); + return Monster_Attack_Melee(self.enemy, (autocvar_g_monster_zombie_attack_melee_damage), chosen_anim, self.attack_range, (autocvar_g_monster_zombie_attack_melee_delay), DEATH_MONSTER_ZOMBIE_MELEE, TRUE); } case MONSTER_ATTACK_RANGED: { makevectors(self.angles); - return monster_leap(zombie_anim_attackleap, zombie_attack_leap_touch, v_forward * (autocvar_g_monster_zombie_attack_leap_speed) + '0 0 200', (autocvar_g_monster_zombie_attack_leap_delay)); + return Monster_Attack_Leap(zombie_anim_attackleap, M_Zombie_Attack_Leap_Touch, v_forward * (autocvar_g_monster_zombie_attack_leap_speed) + '0 0 200', (autocvar_g_monster_zombie_attack_leap_delay)); } } return FALSE; } -void spawnfunc_monster_zombie() -{ - self.classname = "monster_zombie"; - - if(!monster_initialize(MON_ZOMBIE)) { remove(self); return; } -} +void spawnfunc_monster_zombie() { Monster_Spawn(MON_ZOMBIE); } -float m_zombie(float req) +float M_Zombie(float req) { switch(req) { case MR_THINK: { - monster_move((autocvar_g_monster_zombie_speed_run), (autocvar_g_monster_zombie_speed_walk), (autocvar_g_monster_zombie_speed_stop), zombie_anim_runforward, zombie_anim_runforward, zombie_anim_idle); + if(time >= self.spawn_time) + self.damageforcescale = autocvar_g_monster_zombie_damageforcescale; + return TRUE; + } + case MR_PAIN: + { + self.pain_finished = time + 0.34; + self.frame = (random() >= 0.5) ? zombie_anim_painfront1 : zombie_anim_painfront2; return TRUE; } case MR_DEATH: { - self.armorvalue = 0; - self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent; + self.armorvalue = autocvar_g_monsters_armor_blockpercent; self.frame = ((random() > 0.5) ? zombie_anim_deathback1 : zombie_anim_deathfront1); return TRUE; } case MR_SETUP: { if(!self.health) self.health = (autocvar_g_monster_zombie_health); + if(!self.speed) { self.speed = (autocvar_g_monster_zombie_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_zombie_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_zombie_speed_stop); } + + self.m_anim_walk = zombie_anim_runforward; + self.m_anim_run = zombie_anim_runforward; + self.m_anim_idle = zombie_anim_idle; if(self.spawnflags & MONSTERFLAG_NORESPAWN) self.spawnflags &= ~MONSTERFLAG_NORESPAWN; // zombies always respawn @@ -159,17 +171,16 @@ float m_zombie(float req) self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT; self.monster_loot = spawnfunc_item_health_medium; - self.monster_attackfunc = zombie_attack; self.frame = zombie_anim_spawn; self.spawn_time = time + 2.1; self.spawnshieldtime = self.spawn_time; self.respawntime = 0.2; + self.damageforcescale = 0.0001; // no push while spawning return TRUE; } case MR_PRECACHE: { - precache_model("models/monsters/zombie.dpm"); return TRUE; } } @@ -178,19 +189,4 @@ float m_zombie(float req) } #endif // SVQC -#ifdef CSQC -float m_zombie(float req) -{ - switch(req) - { - case MR_PRECACHE: - { - return TRUE; - } - } - - return TRUE; -} - -#endif // CSQC #endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monsters.qc b/qcsrc/common/monsters/monsters.qc index 67e176cf2..a63c0f842 100644 --- a/qcsrc/common/monsters/monsters.qc +++ b/qcsrc/common/monsters/monsters.qc @@ -4,20 +4,45 @@ entity monster_info[MON_MAXCOUNT]; entity dummy_monster_info; -void register_monster(float id, float(float) func, float monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname) +void register_monster(float id, float(float) func, float(float) attackfunc, float monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname) { entity e; + string noext = substring(modelname, 0, strlen(modelname) - 4); + string ext = substring(modelname, strlen(modelname) - 4, strlen(modelname)); monster_info[id - 1] = e = spawn(); e.classname = "monster_info"; e.monsterid = id; e.netname = shortname; e.monster_name = mname; e.monster_func = func; + e.monster_attackfunc = attackfunc; e.mdl = modelname; + if(fexists(strcat("models/monsters/", noext, "_new", ext))) + { + e.mdl = strcat(noext, "_new", ext); + noext = strcat(noext, "_new"); + } e.spawnflags = monsterflags; e.mins = min_s; e.maxs = max_s; - e.model = strzone(strcat("models/monsters/", modelname)); + e.model = strzone(strcat("models/monsters/", e.mdl)); + +#ifndef MENUQC + precache_model(e.model); + func(MR_PRECACHE); // it had to be done eventually + + string trymodel; // check 5 skins + trymodel = sprintf("models/monsters/%s_%d%s", noext, 1, ext); + if(fexists(trymodel)) { precache_model(trymodel); } + trymodel = sprintf("models/monsters/%s_%d%s", noext, 2, ext); + if(fexists(trymodel)) { precache_model(trymodel); } + trymodel = sprintf("models/monsters/%s_%d%s", noext, 3, ext); + if(fexists(trymodel)) { precache_model(trymodel); } + trymodel = sprintf("models/monsters/%s_%d%s", noext, 4, ext); + if(fexists(trymodel)) { precache_model(trymodel); } + trymodel = sprintf("models/monsters/%s_%d%s", noext, 5, ext); + if(fexists(trymodel)) { precache_model(trymodel); } +#endif } float m_null(float dummy) { return 0; } void register_monsters_done() @@ -28,6 +53,7 @@ void register_monsters_done() dummy_monster_info.netname = ""; dummy_monster_info.monster_name = "Monster"; dummy_monster_info.monster_func = m_null; + dummy_monster_info.monster_attackfunc = m_null; dummy_monster_info.mdl = ""; dummy_monster_info.mins = '-0 -0 -0'; dummy_monster_info.maxs = '0 0 0'; diff --git a/qcsrc/common/monsters/monsters.qh b/qcsrc/common/monsters/monsters.qh index c355e12a7..dc68b0a2f 100644 --- a/qcsrc/common/monsters/monsters.qh +++ b/qcsrc/common/monsters/monsters.qh @@ -3,8 +3,9 @@ #define MR_THINK 2 // (SERVER) logic to run every frame #define MR_DEATH 3 // (SERVER) called when monster dies #define MR_PRECACHE 4 // (BOTH) precaches models/sounds used by this monster +#define MR_PAIN 5 // (SERVER) called when monster is damaged -// functions: +// functions entity get_monsterinfo(float id); // special spawn flags @@ -15,12 +16,14 @@ const float MONSTER_SIZE_BROKEN = 128; // TODO: remove when bad models are repla const float MON_FLAG_SUPERMONSTER = 256; // incredibly powerful monster const float MON_FLAG_RANGED = 512; // monster shoots projectiles const float MON_FLAG_MELEE = 1024; +const float MON_FLAG_RIDE = 2048; // monster can be mounted -// entity properties of monsterinfo: +// entity properties of monsterinfo .float monsterid; // MON_... .string netname; // short name .string monster_name; // human readable name -.float(float) monster_func; // m_... +.float(float) monster_func; // M_... +.float(float attack_type) monster_attackfunc; // attack function .string mdl; // currently a copy of the model .string model; // full name of model .float spawnflags; @@ -28,37 +31,43 @@ const float MON_FLAG_MELEE = 1024; // other useful macros #define MON_ACTION(monstertype,mrequest) (get_monsterinfo(monstertype)).monster_func(mrequest) -#define M_NAME(monstertype) (get_monsterinfo(monstertype)).monster_name // ===================== // Monster Registration // ===================== +// enable for testing +//#define MONSTERS_EXTRA + float m_null(float dummy); -void register_monster(float id, float(float) func, float monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname); +void register_monster(float id, float(float) func, float(float) attackfunc, float monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname); void register_monsters_done(); -const float MON_MAXCOUNT = 24; +const float MON_MAXCOUNT = 24; // increase as necessary, limit is infinite, but keep loops small! #define MON_FIRST 1 float MON_COUNT; float MON_LAST; -#define REGISTER_MONSTER_2(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \ +#define REGISTER_MONSTER_2(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \ float id; \ float func(float); \ + float attackfunc(float); \ void RegisterMonsters_##id() \ { \ MON_LAST = (id = MON_FIRST + MON_COUNT); \ ++MON_COUNT; \ - register_monster(id,func,monsterflags,min_s,max_s,modelname,shortname,mname); \ + register_monster(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname); \ } \ ACCUMULATE_FUNCTION(RegisterMonsters, RegisterMonsters_##id) -#ifdef MENUQC -#define REGISTER_MONSTER(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \ - REGISTER_MONSTER_2(MON_##id,m_null,monsterflags,min_s,max_s,modelname,shortname,mname) +#ifdef SVQC +#define REGISTER_MONSTER(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \ + REGISTER_MONSTER_2(MON_##id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) +#elif defined(CSQC) + #define REGISTER_MONSTER(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \ + REGISTER_MONSTER_2(MON_##id,m_null,m_null,monsterflags,min_s,max_s,modelname,shortname,mname) #else -#define REGISTER_MONSTER(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \ - REGISTER_MONSTER_2(MON_##id,func,monsterflags,min_s,max_s,modelname,shortname,mname) +#define REGISTER_MONSTER(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \ + REGISTER_MONSTER_2(MON_##id,m_null,m_null,monsterflags,min_s,max_s,modelname,shortname,mname) #endif #include "all.qh" diff --git a/qcsrc/common/monsters/spawn.qc b/qcsrc/common/monsters/spawn.qc index be5accf5e..42c19edf9 100644 --- a/qcsrc/common/monsters/spawn.qc +++ b/qcsrc/common/monsters/spawn.qc @@ -1,18 +1,12 @@ entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float invincible, float moveflag) { - // ensure spawnfunc database is initialized - //initialize_field_db(); - + float i; entity e = spawn(); - float i; e.spawnflags = MONSTERFLAG_SPAWNED; - if(!respwn) - e.spawnflags |= MONSTERFLAG_NORESPAWN; - - if(invincible) - e.spawnflags |= MONSTERFLAG_INVINCIBLE; + if(!respwn) { e.spawnflags |= MONSTERFLAG_NORESPAWN; } + if(invincible) { e.spawnflags |= MONSTERFLAG_INVINCIBLE; } setorigin(e, orig); @@ -20,12 +14,11 @@ entity spawnmonster (string monster, float monster_id, entity spawnedby, entity { RandomSelection_Init(); for(i = MON_FIRST; i <= MON_LAST; ++i) - RandomSelection_Add(world, 0, (get_monsterinfo(i)).netname, 1, 1); + RandomSelection_Add(world, i, string_null, 1, 1); - monster = RandomSelection_chosen_string; + monster_id = RandomSelection_chosen_float; } - - if(monster != "") + else if(monster != "") { float found = 0; entity mon; @@ -40,13 +33,9 @@ entity spawnmonster (string monster, float monster_id, entity spawnedby, entity } } if(!found) - monster = (get_monsterinfo(MON_FIRST)).netname; + monster_id = ((monster_id > 0) ? monster_id : MON_FIRST); } - if(monster == "") - if(monster_id) - monster = (get_monsterinfo(monster_id)).netname; - e.realowner = spawnedby; if(moveflag) @@ -58,19 +47,16 @@ entity spawnmonster (string monster, float monster_id, entity spawnedby, entity e.team = spawnedby.team; // colors handled in spawn code if(autocvar_g_monsters_owners) - e.monster_owner = own; // using .owner makes the monster non-solid for its master + e.monster_follow = own; // using .owner makes the monster non-solid for its master - e.angles = spawnedby.angles; + e.angles_y = spawnedby.angles_y; } - - //monster = strcat("$ spawnfunc_monster_", monster); + // Monster_Spawn checks if monster is valid entity oldself = self; self = e; - monster_initialize(monster_id); + Monster_Spawn(monster_id); self = oldself; - //target_spawn_edit_entity(e, monster, world, world, world, world, world); - return e; } diff --git a/qcsrc/common/monsters/sv_monsters.qc b/qcsrc/common/monsters/sv_monsters.qc index fd966f5d2..5fa87e526 100644 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@ -1,8 +1,14 @@ -// ========================= -// SVQC Monster Properties -// ========================= +// ========== +// Monsters +// ========== +void monsters_setstatus() +{ + self.stat_monsters_total = monsters_total; + self.stat_monsters_killed = monsters_killed; +} + void monster_dropitem() { if(!self.candrop || !self.monster_loot) @@ -34,109 +40,84 @@ void monster_dropitem() } } -float Monster_SkillModifier() -{ - float t = 0.5+self.monster_skill*((1.2-0.3)/10); - - return t; -} - -float monster_isvalidtarget (entity targ, entity ent) +void monster_makevectors(entity e) { - if(!targ || !ent) - return FALSE; // someone doesn't exist - - if(targ == ent) - return FALSE; // don't attack ourselves - - //traceline(ent.origin, targ.origin, MOVE_NORMAL, ent); - - //if(trace_ent != targ) - //return FALSE; - - if(targ.vehicle_flags & VHF_ISVEHICLE) - if(!((get_monsterinfo(ent.monsterid)).spawnflags & MON_FLAG_RANGED)) - return FALSE; // melee attacks are useless against vehicles - - if(time < game_starttime) - return FALSE; // monsters do nothing before the match has started - - if(targ.takedamage == DAMAGE_NO) - return FALSE; // enemy can't be damaged - - if(targ.items & IT_INVISIBILITY) - return FALSE; // enemy is invisible - - if(substring(targ.classname, 0, 10) == "onslaught_") - return FALSE; // don't attack onslaught targets - - if(IS_SPEC(targ) || IS_OBSERVER(targ)) - return FALSE; // enemy is a spectator - - if(!(targ.vehicle_flags & VHF_ISVEHICLE)) - if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0) - return FALSE; // enemy/self is dead + vector v; - if(ent.monster_owner == targ) - return FALSE; // don't attack our master + v = e.origin + (e.mins + e.maxs) * 0.5; + self.v_angle = vectoangles(v - (self.origin + self.view_ofs)); + self.v_angle_x = -self.v_angle_x; - if(targ.monster_owner == ent) - return FALSE; // don't attack our pet + makevectors(self.v_angle); +} - if(!(targ.vehicle_flags & VHF_ISVEHICLE)) - if(targ.flags & FL_NOTARGET) - return FALSE; // enemy can't be targeted +// =============== +// Target handling +// =============== - if(!autocvar_g_monsters_typefrag) - if(targ.BUTTON_CHAT) - return FALSE; // no typefragging! +float Monster_ValidTarget(entity mon, entity player) +{ + // ensure we're not checking nonexistent monster/target + if(!mon || !player) { return FALSE; } + + if((player == mon) + || (autocvar_g_monsters_lineofsight && !checkpvs(mon.origin + mon.view_ofs, player)) // enemy cannot be seen + || (IS_VEHICLE(player) && !((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_RANGED)) // melee vs vehicle is useless + || (time < game_starttime) // monsters do nothing before match has started + || (player.takedamage == DAMAGE_NO) + || (player.items & IT_INVISIBILITY) + || (IS_SPEC(player) || IS_OBSERVER(player)) // don't attack spectators + || (!IS_VEHICLE(player) && (player.deadflag != DEAD_NO || mon.deadflag != DEAD_NO || player.health <= 0 || mon.health <= 0)) + || (mon.monster_follow == player || player.monster_follow == mon) + || (!IS_VEHICLE(player) && (player.flags & FL_NOTARGET)) + || (!autocvar_g_monsters_typefrag && player.BUTTON_CHAT) + || (SAME_TEAM(player, mon)) + || (player.frozen) + || (player.alpha != 0 && player.alpha < 0.5) + ) + { + // if any of the above checks fail, target is not valid + return FALSE; + } - if(SAME_TEAM(targ, ent)) - return FALSE; // enemy is on our team + traceline(mon.origin + self.view_ofs, player.origin, 0, mon); - if (targ.frozen) - return FALSE; // ignore frozen + if((trace_fraction < 1) && (trace_ent != player)) + return FALSE; - if(autocvar_g_monsters_target_infront || (ent.spawnflags & MONSTERFLAG_INFRONT)) - if(ent.enemy != targ) + if(autocvar_g_monsters_target_infront || (mon.spawnflags & MONSTERFLAG_INFRONT)) + if(mon.enemy != player) { float dot; - makevectors (ent.angles); - dot = normalize (targ.origin - ent.origin) * v_forward; + makevectors (mon.angles); + dot = normalize (player.origin - mon.origin) * v_forward; - if(dot <= 0.3) - return FALSE; + if(dot <= 0.3) { return FALSE; } } - return TRUE; + return TRUE; // this target is valid! } -entity FindTarget (entity ent) +entity Monster_FindTarget(entity mon) { - if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator + if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return mon.enemy; } // Handled by a mutator entity head, closest_target = world; - head = findradius(ent.origin, ent.target_range); - //head = WarpZone_FindRadius(ent.origin, ent.target_range, TRUE); + head = findradius(mon.origin, mon.target_range); while(head) // find the closest acceptable target to pass to { if(head.monster_attack) - if(monster_isvalidtarget(head, ent)) + if(Monster_ValidTarget(mon, head)) { // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) vector head_center = CENTER_OR_VIEWOFS(head); - //vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head)); - vector ent_center = CENTER_OR_VIEWOFS(ent); + vector ent_center = CENTER_OR_VIEWOFS(mon); - traceline(ent_center, head_center, MOVE_NORMAL, ent); - - if(trace_ent == head) if(closest_target) { vector closest_target_center = CENTER_OR_VIEWOFS(closest_target); - //vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target)); if(vlen(ent_center - head_center) < vlen(ent_center - closest_target_center)) { closest_target = head; } } @@ -149,17 +130,84 @@ entity FindTarget (entity ent) return closest_target; } -void MonsterTouch () +void monster_setupcolors(entity mon) { - if(other == world) - return; + if(IS_PLAYER(mon.realowner)) + mon.colormap = mon.realowner.colormap; + else if(teamplay && mon.team) + mon.colormap = 1024 + (mon.team - 1) * 17; + else + { + if(mon.monster_skill <= MONSTER_SKILL_EASY) + mon.colormap = 1029; + else if(mon.monster_skill <= MONSTER_SKILL_MEDIUM) + mon.colormap = 1027; + else if(mon.monster_skill <= MONSTER_SKILL_HARD) + mon.colormap = 1038; + else if(mon.monster_skill <= MONSTER_SKILL_INSANE) + mon.colormap = 1028; + else if(mon.monster_skill <= MONSTER_SKILL_NIGHTMARE) + mon.colormap = 1032; + else + mon.colormap = 1024; + } +} - if(self.enemy != other) - if(!(other.flags & FL_MONSTER)) - if(monster_isvalidtarget(other, self)) - self.enemy = other; +void monster_changeteam(entity ent, float newteam) +{ + if(!teamplay) { return; } + + ent.team = newteam; + ent.monster_attack = TRUE; // new team, activate attacking + monster_setupcolors(ent); + + if(ent.sprite) + { + WaypointSprite_UpdateTeamRadar(ent.sprite, RADARICON_DANGER, ((newteam) ? Team_ColorRGB(newteam) : '1 0 0')); + + ent.sprite.team = newteam; + ent.sprite.SendFlags |= 1; + } } +void Monster_Delay_Action() +{ + entity oldself = self; + self = self.owner; + if(Monster_ValidTarget(self, self.enemy)) { oldself.use(); } + + if(oldself.cnt > 0) + { + oldself.cnt -= 1; + oldself.think = Monster_Delay_Action; + oldself.nextthink = time + oldself.respawn_time; + } + else + { + oldself.think = SUB_Remove; + oldself.nextthink = time; + } +} + +void Monster_Delay(float repeat_count, float repeat_defer, float defer_amnt, void() func) +{ + // deferred attacking, checks if monster is still alive and target is still valid before attacking + entity e = spawn(); + + e.think = Monster_Delay_Action; + e.nextthink = time + defer_amnt; + e.count = defer_amnt; + e.owner = self; + e.use = func; + e.cnt = repeat_count; + e.respawn_time = repeat_defer; +} + + +// ============== +// Monster sounds +// ============== + string get_monster_model_datafilename(string m, float sk, string fil) { if(m) @@ -173,7 +221,7 @@ string get_monster_model_datafilename(string m, float sk, string fil) return strcat(m, ".", fil); } -void PrecacheMonsterSounds(string f) +void Monster_Sound_Precache(string f) { float fh; string s; @@ -192,7 +240,7 @@ void PrecacheMonsterSounds(string f) fclose(fh); } -void precache_monstersounds() +void Monster_Sounds_Precache() { string m = (get_monsterinfo(self.monsterid)).model; float globhandle, n, i; @@ -206,19 +254,19 @@ void precache_monstersounds() { //print(search_getfilename(globhandle, i), "\n"); f = search_getfilename(globhandle, i); - PrecacheMonsterSounds(f); + Monster_Sound_Precache(f); } search_end(globhandle); } -void ClearMonsterSounds() +void Monster_Sounds_Clear() { #define _MSOUND(m) if(self.monstersound_##m) { strunzone(self.monstersound_##m); self.monstersound_##m = string_null; } ALLMONSTERSOUNDS #undef _MSOUND } -.string GetMonsterSoundSampleField(string type) +.string Monster_Sound_SampleField(string type) { GetMonsterSoundSampleField_notFound = 0; switch(type) @@ -231,7 +279,7 @@ void ClearMonsterSounds() return string_null; } -float LoadMonsterSounds(string f, float first) +float Monster_Sounds_Load(string f, float first) { float fh; string s; @@ -240,13 +288,13 @@ float LoadMonsterSounds(string f, float first) if(fh < 0) { dprint("Monster sound file not found: ", f, "\n"); - return 0; + return FALSE; } while((s = fgets(fh))) { if(tokenize_console(s) != 3) continue; - field = GetMonsterSoundSampleField(argv(0)); + field = Monster_Sound_SampleField(argv(0)); if(GetMonsterSoundSampleField_notFound) continue; if(self.field) @@ -254,25 +302,21 @@ float LoadMonsterSounds(string f, float first) self.field = strzone(strcat(argv(1), " ", argv(2))); } fclose(fh); - return 1; + return TRUE; } .float skin_for_monstersound; -void UpdateMonsterSounds() +void Monster_Sounds_Update() { - entity mon = get_monsterinfo(self.monsterid); + if(self.skin == self.skin_for_monstersound) { return; } - if(self.skin == self.skin_for_monstersound) - return; self.skin_for_monstersound = self.skin; - ClearMonsterSounds(); - //LoadMonsterSounds("sound/monsters/default.sounds", 1); - if(!autocvar_g_debug_defaultsounds) - if(!LoadMonsterSounds(get_monster_model_datafilename(mon.model, self.skin, "sounds"), 0)) - LoadMonsterSounds(get_monster_model_datafilename(mon.model, 0, "sounds"), 0); + Monster_Sounds_Clear(); + if(!Monster_Sounds_Load(get_monster_model_datafilename(self.model, self.skin, "sounds"), 0)) + Monster_Sounds_Load(get_monster_model_datafilename(self.model, 0, "sounds"), 0); } -void MonsterSound(.string samplefield, float sound_delay, float delaytoo, float chan) +void Monster_Sound(.string samplefield, float sound_delay, float delaytoo, float chan) { if(!autocvar_g_monsters_sounds) { return; } @@ -284,45 +328,130 @@ void MonsterSound(.string samplefield, float sound_delay, float delaytoo, float self.msound_delay = time + sound_delay; } -void monster_makevectors(entity e) + +// ======================= +// Monster attack handlers +// ======================= + +float Monster_Attack_Melee(entity targ, float damg, float anim, float er, float animtime, float deathtype, float dostop) { - vector v; + if(dostop) { self.state = MONSTER_ATTACK_MELEE; } - v = e.origin + (e.mins + e.maxs) * 0.5; - self.v_angle = vectoangles(v - (self.origin + self.view_ofs)); - self.v_angle_x = -self.v_angle_x; + self.frame = anim; - makevectors(self.v_angle); + if(animtime > 0) { self.attack_finished_single = self.anim_finished = time + animtime; } + + monster_makevectors(targ); + + traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self); + + if(trace_ent.takedamage) + Damage(trace_ent, self, self, damg * MONSTER_SKILLMOD(self), deathtype, trace_ent.origin, normalize(trace_ent.origin - self.origin)); + + return TRUE; +} + +float Monster_Attack_Leap_Check(vector vel) +{ + if(self.state) + return FALSE; // already attacking + if(!(self.flags & FL_ONGROUND)) + return FALSE; // not on the ground + if(self.health <= 0) + return FALSE; // called when dead? + if(time < self.attack_finished_single) + return FALSE; // still attacking + + vector old = self.velocity; + + self.velocity = vel; + tracetoss(self, self); + self.velocity = old; + if (trace_ent != self.enemy) + return FALSE; + + return TRUE; +} + +float Monster_Attack_Leap(float anm, void() touchfunc, vector vel, float animtime) +{ + if(!Monster_Attack_Leap_Check(vel)) + return FALSE; + + self.frame = anm; + self.state = MONSTER_ATTACK_RANGED; + self.touch = touchfunc; + self.origin_z += 1; + self.velocity = vel; + self.flags &= ~FL_ONGROUND; + + self.attack_finished_single = time + animtime; + self.anim_finished = self.attack_finished_single; // TODO: make these frame based + + return TRUE; } -float monster_melee(entity targ, float damg, float anim, float er, float anim_finished, float deathtype, float dostop) +void Monster_Attack_Check(entity e, entity targ) { - if (self.health <= 0) - return FALSE; // attacking while dead?! + if((e == world || targ == world) + || (!e.monster_attackfunc) + || (time < e.attack_finished_single) + ) { return; } + + float targ_vlen = vlen(targ.origin - e.origin); - if(dostop) + if(targ_vlen <= e.attack_range) { - self.velocity_x = 0; - self.velocity_y = 0; - self.state = MONSTER_STATE_ATTACK_MELEE; + float attack_success = e.monster_attackfunc(MONSTER_ATTACK_MELEE); + if(attack_success == 1) + Monster_Sound(monstersound_melee, 0, FALSE, CH_VOICE); + else if(attack_success > 0) + return; } - self.frame = anim; + if(targ_vlen > e.attack_range) + { + float attack_success = e.monster_attackfunc(MONSTER_ATTACK_RANGED); + if(attack_success == 1) + Monster_Sound(monstersound_melee, 0, FALSE, CH_VOICE); + else if(attack_success > 0) + return; + } +} - if(anim_finished != 0) - self.attack_finished_single = time + anim_finished; - monster_makevectors(targ); +// ====================== +// Main monster functions +// ====================== - traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self); +void Monster_Skin_Check() +{ + vector oldmin = self.mins, oldmax = self.maxs; + entity mon = get_monsterinfo(self.monsterid); + string trymodel = sprintf("%s_%d%s", substring(mon.model, 0, strlen(mon.model) - 4), self.skin, substring(mon.model, strlen(mon.model) - 4, strlen(mon.model))); - if(trace_ent.takedamage) - Damage(trace_ent, self, self, damg * Monster_SkillModifier(), deathtype, trace_ent.origin, normalize(trace_ent.origin - self.origin)); + if(fexists(trymodel)) + { + precache_model(trymodel); + setmodel(self, trymodel); + setsize(self, oldmin, oldmax); + CSQCMODEL_AUTOUPDATE(); // do a quick update + } - return TRUE; + self.oldskin = self.skin; } -void Monster_CheckMinibossFlag () +void Monster_Touch() +{ + if(other == world) { return; } + + if(self.enemy != other) + if(!IS_MONSTER(other)) + if(Monster_ValidTarget(self, other)) + self.enemy = other; +} + +void Monster_Miniboss_Check() { if(MUTATOR_CALLHOOK(MonsterCheckBossFlag)) return; @@ -339,35 +468,22 @@ void Monster_CheckMinibossFlag () } } -float Monster_CanRespawn(entity ent) +float Monster_Respawn_Check() { - other = ent; - if(ent.deadflag == DEAD_DEAD) // don't call when monster isn't dead - if(MUTATOR_CALLHOOK(MonsterRespawn)) - return TRUE; // enabled by a mutator - - if(ent.spawnflags & MONSTERFLAG_NORESPAWN) - return FALSE; - - if(!autocvar_g_monsters_respawn) - return FALSE; + if(self.spawnflags & MONSTERFLAG_NORESPAWN) { return FALSE; } + if(!autocvar_g_monsters_respawn) { return FALSE; } return TRUE; } -float monster_initialize(float mon_id); -void monster_respawn() -{ - // is this function really needed? - monster_initialize(self.monsterid); -} +void Monster_Respawn() { Monster_Spawn(self.monsterid); } -void Monster_Fade () +void Monster_Dead_Fade() { - if(Monster_CanRespawn(self)) + if(Monster_Respawn_Check()) { self.spawnflags |= MONSTERFLAG_RESPAWNED; - self.think = monster_respawn; + self.think = Monster_Respawn; self.nextthink = time + self.respawntime; self.monster_lifetime = 0; self.deadflag = DEAD_RESPAWNING; @@ -392,84 +508,12 @@ void Monster_Fade () } } -float Monster_CanJump (vector vel) -{ - if(self.state) - return FALSE; // already attacking - if(!(self.flags & FL_ONGROUND)) - return FALSE; // not on the ground - if(self.health <= 0) - return FALSE; // called when dead? - if(time < self.attack_finished_single) - return FALSE; // still attacking - - vector old = self.velocity; - - self.velocity = vel; - tracetoss(self, self); - self.velocity = old; - if (trace_ent != self.enemy) - return FALSE; - - return TRUE; -} - -float monster_leap (float anm, void() touchfunc, vector vel, float anim_finished) -{ - if(!Monster_CanJump(vel)) - return FALSE; - - self.frame = anm; - self.state = MONSTER_STATE_ATTACK_LEAP; - self.touch = touchfunc; - self.origin_z += 1; - self.velocity = vel; - self.flags &= ~FL_ONGROUND; - - self.attack_finished_single = time + anim_finished; - - return TRUE; -} - -void monster_checkattack(entity e, entity targ) -{ - if(e == world) - return; - if(targ == world) - return; - - if(!e.monster_attackfunc) - return; - - if(time < e.attack_finished_single) - return; - - if(vlen(targ.origin - e.origin) <= e.attack_range) - if(e.monster_attackfunc(MONSTER_ATTACK_MELEE)) - { - MonsterSound(monstersound_melee, 0, FALSE, CH_VOICE); - return; - } - - if(vlen(targ.origin - e.origin) > e.attack_range) - if(e.monster_attackfunc(MONSTER_ATTACK_RANGED)) - { - MonsterSound(monstersound_ranged, 0, FALSE, CH_VOICE); - return; - } -} - -void monster_use () +void Monster_Use() { - if(!self.enemy) - if(self.health > 0) - if(monster_isvalidtarget(activator, self)) - self.enemy = activator; + if(Monster_ValidTarget(self, activator)) { self.enemy = activator; } } -.float last_trace; -.float last_enemycheck; // for checking enemy -vector monster_pickmovetarget(entity targ) +vector Monster_Move_Target(entity targ) { // enemy is always preferred target if(self.enemy) @@ -478,9 +522,8 @@ vector monster_pickmovetarget(entity targ) targ_origin = WarpZone_RefSys_TransformOrigin(self.enemy, self, targ_origin); // origin of target as seen by the monster (us) WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self); - if((self.enemy == world) - || (self.enemy.deadflag != DEAD_NO || self.enemy.health < 1) - || (self.enemy.frozen) + if( (self.enemy == world) + || (Player_Trapped(self.enemy)) || (self.enemy.flags & FL_NOTARGET) || (self.enemy.alpha < 0.5) || (self.enemy.takedamage == DAMAGE_NO) @@ -501,7 +544,10 @@ vector monster_pickmovetarget(entity targ) self.monster_movestate = MONSTER_MOVE_ENEMY; self.last_trace = time + 1.2; - return targ_origin; + if(self.monster_moveto) + return self.monster_moveto; // assumes code is properly setting this when monster has an enemy + else + return targ_origin; } /*makevectors(self.angles); @@ -512,11 +558,11 @@ vector monster_pickmovetarget(entity targ) switch(self.monster_moveflags) { - case MONSTER_MOVE_OWNER: + case MONSTER_MOVE_FOLLOW: { - self.monster_movestate = MONSTER_MOVE_OWNER; + self.monster_movestate = MONSTER_MOVE_FOLLOW; self.last_trace = time + 0.3; - return (self.monster_owner) ? self.monster_owner.origin : self.origin; + return (self.monster_follow) ? self.monster_follow.origin : self.origin; } case MONSTER_MOVE_SPAWNLOC: { @@ -526,8 +572,16 @@ vector monster_pickmovetarget(entity targ) } case MONSTER_MOVE_NOMOVE: { - self.monster_movestate = MONSTER_MOVE_NOMOVE; - self.last_trace = time + 2; + if(self.monster_moveto) + { + self.last_trace = time + 0.5; + return self.monster_moveto; + } + else + { + self.monster_movestate = MONSTER_MOVE_NOMOVE; + self.last_trace = time + 2; + } return self.origin; } default: @@ -536,7 +590,12 @@ vector monster_pickmovetarget(entity targ) vector pos; self.monster_movestate = MONSTER_MOVE_WANDER; - if(targ) + if(self.monster_moveto) + { + self.last_trace = time + 0.5; + pos = self.monster_moveto; + } + else if(targ) { self.last_trace = time + 0.5; pos = targ.origin; @@ -562,11 +621,11 @@ vector monster_pickmovetarget(entity targ) } } -void monster_CalculateVelocity(entity mon, vector to, vector from, float turnrate, float movespeed) +void Monster_CalculateVelocity(entity mon, vector to, vector from, float turnrate, float movespeed) { float current_distance = vlen((('1 0 0' * to_x) + ('0 1 0' * to_y)) - (('1 0 0' * from_x) + ('0 1 0' * from_y))); // for the sake of this check, exclude Z axis float initial_height = 0; //min(50, (targ_distance * tanh(20))); - float current_height = (initial_height * min(1, (current_distance / self.pass_distance))); + float current_height = (initial_height * min(1, (self.pass_distance) ? (current_distance / self.pass_distance) : current_distance)); //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n"); vector targpos; @@ -596,12 +655,9 @@ void monster_CalculateVelocity(entity mon, vector to, vector from, float turnrat //mon.angles = vectoangles(mon.velocity); } -void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle) +void Monster_Move(float runspeed, float walkspeed, float stpspeed, float manim_run, float manim_walk, float manim_idle) { - //fixedmakevectors(self.angles); - - if(self.target2) - self.goalentity = find(world, targetname, self.target2); + if(self.target2) { self.goalentity = find(world, targetname, self.target2); } entity targ; @@ -611,9 +667,10 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ self.health = max(1, self.revive_progress * self.max_health); self.iceblock.alpha = bound(0.2, 1 - self.revive_progress, 1); - WaypointSprite_UpdateHealth(self.sprite, self.health); + if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE) && self.sprite) + WaypointSprite_UpdateHealth(self.sprite, self.health); - movelib_beak_simple(stopspeed); + movelib_beak_simple(stpspeed); self.frame = manim_idle; self.enemy = world; @@ -629,9 +686,10 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ self.revive_progress = bound(0, self.revive_progress - self.ticrate * self.revive_speed, 1); self.health = max(0, autocvar_g_nades_ice_health + (self.max_health-autocvar_g_nades_ice_health) * self.revive_progress ); - WaypointSprite_UpdateHealth(self.sprite, self.health); + if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE) && self.sprite) + WaypointSprite_UpdateHealth(self.sprite, self.health); - movelib_beak_simple(stopspeed); + movelib_beak_simple(stpspeed); self.frame = manim_idle; self.enemy = world; @@ -656,7 +714,6 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ { if(time >= self.last_trace) { - self.fish_wasdrowning = TRUE; self.last_trace = time + 0.4; Damage (self, world, world, 2, DEATH_DROWN, self.origin, '0 0 0'); @@ -680,9 +737,8 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ return; } - else if(self.fish_wasdrowning) + else if(self.movetype == MOVETYPE_BOUNCE) { - self.fish_wasdrowning = FALSE; self.angles_x = 0; self.movetype = MOVETYPE_WALK; } @@ -694,18 +750,25 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ monster_speed_run = runspeed; monster_speed_walk = walkspeed; - if(MUTATOR_CALLHOOK(MonsterMove) || gameover || self.draggedby != world || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time) + if((MUTATOR_CALLHOOK(MonsterMove)) + || (gameover) + || (self.draggedby != world) + || (round_handler_IsActive() && !round_handler_IsRoundStarted()) + || (time < game_starttime) + || (autocvar_g_campaign && !campaign_bots_may_start) + || (time < self.spawn_time) + ) { runspeed = walkspeed = 0; if(time >= self.spawn_time) self.frame = manim_idle; - movelib_beak_simple(stopspeed); + movelib_beak_simple(stpspeed); return; } targ = monster_target; - runspeed = bound(0, monster_speed_run * Monster_SkillModifier(), runspeed * 2); // limit maxspeed to prevent craziness - walkspeed = bound(0, monster_speed_walk * Monster_SkillModifier(), walkspeed * 2); // limit maxspeed to prevent craziness + runspeed = bound(0, monster_speed_run * MONSTER_SKILLMOD(self), runspeed * 2.5); // limit maxspeed to prevent craziness + walkspeed = bound(0, monster_speed_walk * MONSTER_SKILLMOD(self), walkspeed * 2.5); // limit maxspeed to prevent craziness if(time < self.spider_slowness) { @@ -715,69 +778,66 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ if(teamplay) if(autocvar_g_monsters_teams) - if(DIFF_TEAM(self.monster_owner, self)) - self.monster_owner = world; + if(DIFF_TEAM(self.monster_follow, self)) + self.monster_follow = world; if(time >= self.last_enemycheck) { if(!self.enemy) { - self.enemy = FindTarget(self); + self.enemy = Monster_FindTarget(self); if(self.enemy) { WarpZone_RefSys_Copy(self.enemy, self); WarpZone_RefSys_AddInverse(self.enemy, self); // wz1^-1 ... wzn^-1 receiver self.moveto = WarpZone_RefSys_TransformOrigin(self.enemy, self, (0.5 * (self.enemy.absmin + self.enemy.absmax))); + self.monster_moveto = '0 0 0'; + self.monster_face = '0 0 0'; self.pass_distance = vlen((('1 0 0' * self.enemy.origin_x) + ('0 1 0' * self.enemy.origin_y)) - (('1 0 0' * self.origin_x) + ('0 1 0' * self.origin_y))); - MonsterSound(monstersound_sight, 0, FALSE, CH_VOICE); + Monster_Sound(monstersound_sight, 0, FALSE, CH_VOICE); } } self.last_enemycheck = time + 1; // check for enemies every second } - if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single) + if(self.state == MONSTER_ATTACK_RANGED && (self.flags & FL_ONGROUND)) + { self.state = 0; + self.touch = Monster_Touch; + } - if(self.state != MONSTER_STATE_ATTACK_MELEE) // don't move if set - if(time >= self.last_trace || self.enemy) // update enemy instantly - self.moveto = monster_pickmovetarget(targ); + if(self.state && time >= self.attack_finished_single) + self.state = 0; // attack is over - if(!self.enemy) - MonsterSound(monstersound_idle, 7, TRUE, CH_VOICE); + if(self.state != MONSTER_ATTACK_MELEE) // don't move if set + if(time >= self.last_trace || self.enemy || self.piggybacker) // update enemy or rider instantly + self.moveto = Monster_Move_Target(targ); - if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND)) - { - self.state = 0; - self.touch = MonsterTouch; - } + if(!self.enemy) + Monster_Sound(monstersound_idle, 7, TRUE, CH_VOICE); - if(self.state == MONSTER_STATE_ATTACK_MELEE) + if(self.state == MONSTER_ATTACK_MELEE) self.moveto = self.origin; if(self.enemy && self.enemy.vehicle) runspeed = 0; - if(!(((self.flags & FL_FLY) && (self.spawnflags & MONSTERFLAG_FLY_VERTICAL)) || (self.flags & FL_SWIM))) - //v_forward = normalize(self.moveto - self.origin); - //else + if(!(self.spawnflags & MONSTERFLAG_FLY_VERTICAL) && !(self.flags & FL_SWIM)) self.moveto_z = self.origin_z; - if(vlen(self.origin - self.moveto) > 64) + if(vlen(self.origin - self.moveto) > 100) { + float do_run = (enemy || (self.piggybacker && self.monster_moveto)); if((self.flags & FL_ONGROUND) || ((self.flags & FL_FLY) || (self.flags & FL_SWIM))) - monster_CalculateVelocity(self, self.moveto, self.origin, TRUE, ((self.enemy) ? runspeed : walkspeed)); - - /*&if(self.flags & FL_FLY || self.flags & FL_SWIM) - movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6); - else - movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6); */ + Monster_CalculateVelocity(self, self.moveto, self.origin, TRUE, ((do_run) ? runspeed : walkspeed)); - if(time > self.pain_finished) - if(time > self.attack_finished_single) + if(time > self.pain_finished) // TODO: use anim_finished instead! + if(!self.state) + if(time > self.anim_finished) if(vlen(self.velocity) > 10) - self.frame = ((self.enemy) ? manim_run : manim_walk); + self.frame = ((do_run) ? manim_run : manim_walk); else self.frame = manim_idle; } @@ -789,18 +849,19 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ else if(e.target) self.target2 = e.target; - movelib_beak_simple(stopspeed); - if(time > self.attack_finished_single) + movelib_beak_simple(stpspeed); + if(time > self.anim_finished) if(time > self.pain_finished) - if (vlen(self.velocity) <= 30) + if(!self.state) + if(vlen(self.velocity) <= 30) self.frame = manim_idle; } - - self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); - + + self.steerto = steerlib_attract2(((self.monster_face) ? self.monster_face : self.moveto), 0.5, 500, 0.95); + vector real_angle = vectoangles(self.steerto) - self.angles; float turny = 25; - if(self.state == MONSTER_STATE_ATTACK_MELEE) + if(self.state == MONSTER_ATTACK_MELEE) turny = 0; if(turny) { @@ -808,55 +869,42 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ self.angles_y += turny; } - monster_checkattack(self, self.enemy); + Monster_Attack_Check(self, self.enemy); } -void monster_remove(entity mon) +void Monster_Remove(entity mon) { - if(!mon) - return; // nothing to remove - - pointparticles(particleeffectnum("item_pickup"), mon.origin, '0 0 0', 1); + if(!mon) { return; } - if(mon.weaponentity) - remove(mon.weaponentity); - - if(mon.iceblock) - remove(mon.iceblock); + if(!MUTATOR_CALLHOOK(MonsterRemove)) + Send_Effect(EFFECT_SPAWN_NEUTRAL, mon.origin, '0 0 0', 1); + if(mon.weaponentity) { remove(mon.weaponentity); } + if(mon.iceblock) { remove(mon.iceblock); } WaypointSprite_Kill(mon.sprite); - remove(mon); } -void monster_dead_think() +void Monster_Dead_Think() { self.nextthink = time + self.ticrate; - CSQCMODEL_AUTOUPDATE(); - if(self.monster_lifetime != 0) if(time >= self.monster_lifetime) { - Monster_Fade(); + Monster_Dead_Fade(); return; } } -void monsters_setstatus() -{ - self.stat_monsters_total = monsters_total; - self.stat_monsters_killed = monsters_killed; -} - void Monster_Appear() { self.enemy = activator; self.spawnflags &= ~MONSTERFLAG_APPEAR; // otherwise, we get an endless loop - monster_initialize(self.monsterid); + Monster_Spawn(self.monsterid); } -float Monster_CheckAppearFlags(entity ent, float monster_id) +float Monster_Appear_Check(entity ent, float monster_id) { if(!(ent.spawnflags & MONSTERFLAG_APPEAR)) return FALSE; @@ -870,7 +918,7 @@ float Monster_CheckAppearFlags(entity ent, float monster_id) return TRUE; } -void monsters_reset() +void Monster_Reset() { setorigin(self, self.pos1); self.angles = self.pos2; @@ -885,7 +933,7 @@ void monsters_reset() self.moveto = self.origin; } -void monsters_corpse_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +void Monster_Dead_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) { self.health -= damage; @@ -904,9 +952,9 @@ void monsters_corpse_damage (entity inflictor, entity attacker, float damage, fl } } -void monster_die(entity attacker, float gibbed) +void Monster_Dead(entity attacker, float gibbed) { - self.think = monster_dead_think; + self.think = Monster_Dead_Think; self.nextthink = time; self.monster_lifetime = time + 5; @@ -918,7 +966,7 @@ void monster_die(entity attacker, float gibbed) monster_dropitem(); - MonsterSound(monstersound_death, 0, FALSE, CH_VOICE); + Monster_Sound(monstersound_death, 0, FALSE, CH_VOICE); if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !(self.spawnflags & MONSTERFLAG_RESPAWNED)) monsters_killed += 1; @@ -933,38 +981,41 @@ void monster_die(entity attacker, float gibbed) totalspawned -= 1; } - if(self.candrop && self.weapon) - W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325'); - - self.event_damage = ((gibbed) ? func_null : monsters_corpse_damage); + self.event_damage = ((gibbed) ? func_null : Monster_Dead_Damage); self.solid = SOLID_CORPSE; self.takedamage = DAMAGE_AIM; self.deadflag = DEAD_DEAD; self.enemy = world; self.movetype = MOVETYPE_TOSS; self.moveto = self.origin; - self.touch = MonsterTouch; // reset incase monster was pouncing + self.touch = Monster_Touch; // reset incase monster was pouncing self.reset = func_null; self.state = 0; self.attack_finished_single = 0; + self.effects = 0; if(!((self.flags & FL_FLY) || (self.flags & FL_SWIM))) self.velocity = '0 0 0'; + CSQCModel_UnlinkEntity(); + MON_ACTION(self.monsterid, MR_DEATH); + + if(self.candrop && self.weapon) + W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325'); } -void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +void Monster_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) { - if(self.frozen && deathtype != DEATH_KILL && deathtype != DEATH_NADE_ICE_FREEZE) - return; - if((self.spawnflags & MONSTERFLAG_INVINCIBLE) && deathtype != DEATH_KILL) return; - if(time < self.pain_finished && deathtype != DEATH_KILL) + if(self.frozen && deathtype != DEATH_KILL && deathtype != DEATH_NADE_ICE_FREEZE) return; + //if(time < self.pain_finished && deathtype != DEATH_KILL) + //return; + if(time < self.spawnshieldtime && deathtype != DEATH_KILL) return; @@ -974,13 +1025,24 @@ void monsters_damage (entity inflictor, entity attacker, float damage, float dea vector v; float take, save; - v = healtharmor_applydamage(self.armorvalue, self.m_armor_blockpercent, deathtype, damage); + v = healtharmor_applydamage(100, self.armorvalue / 100, deathtype, damage, autocvar_g_balance_armor_block_bycount); take = v_x; save = v_y; - self.health -= take; + damage_take = take; + frag_attacker = attacker; + frag_deathtype = deathtype; + MON_ACTION(self.monsterid, MR_PAIN); + take = damage_take; + + if(take) + { + self.health -= take; + Monster_Sound(monstersound_pain, 1.2, TRUE, CH_PAIN); + } - WaypointSprite_UpdateHealth(self.sprite, self.health); + if(self.sprite) + WaypointSprite_UpdateHealth(self.sprite, self.health); self.dmg_time = time; @@ -989,7 +1051,7 @@ void monsters_damage (entity inflictor, entity attacker, float damage, float dea self.velocity += force * self.damageforcescale; - if(deathtype != DEATH_DROWN) + if(deathtype != DEATH_DROWN && take) { Violence_GibSplash_At(hitloc, force, 2, bound(0, take, 200) / 16, self, attacker); if (take > 50) @@ -1009,7 +1071,7 @@ void monsters_damage (entity inflictor, entity attacker, float damage, float dea SUB_UseTargets(); self.target2 = self.oldtarget2; // reset to original target on death, incase we respawn - monster_die(attacker, (self.health <= -100 || deathtype == DEATH_KILL)); + Monster_Dead(attacker, (self.health <= -100 || deathtype == DEATH_KILL)); WaypointSprite_Kill(self.sprite); @@ -1027,49 +1089,9 @@ void monsters_damage (entity inflictor, entity attacker, float damage, float dea } } -void monster_setupcolors(entity mon) -{ - if(IS_PLAYER(mon.monster_owner)) - mon.colormap = mon.monster_owner.colormap; - else if(teamplay && mon.team) - mon.colormap = 1024 + (mon.team - 1) * 17; - else - { - if(mon.monster_skill <= MONSTER_SKILL_EASY) - mon.colormap = 1029; - else if(mon.monster_skill <= MONSTER_SKILL_MEDIUM) - mon.colormap = 1027; - else if(mon.monster_skill <= MONSTER_SKILL_HARD) - mon.colormap = 1038; - else if(mon.monster_skill <= MONSTER_SKILL_INSANE) - mon.colormap = 1028; - else if(mon.monster_skill <= MONSTER_SKILL_NIGHTMARE) - mon.colormap = 1032; - else - mon.colormap = 1024; - } -} - -void monster_changeteam(entity ent, float newteam) -{ - if(!teamplay) { return; } - - ent.team = newteam; - ent.monster_attack = TRUE; // new team, activate attacking - monster_setupcolors(ent); - - if(ent.sprite) - { - WaypointSprite_UpdateTeamRadar(ent.sprite, RADARICON_DANGER, ((newteam) ? Team_ColorRGB(newteam) : '1 0 0')); - - ent.sprite.team = newteam; - ent.sprite.SendFlags |= 1; - } -} - -void monster_think() +void Monster_Think() { - self.think = monster_think; + self.think = Monster_Think; self.nextthink = self.ticrate; if(self.monster_lifetime) @@ -1079,53 +1101,69 @@ void monster_think() return; } + if(self.skin != self.oldskin) { Monster_Skin_Check(); } + MON_ACTION(self.monsterid, MR_THINK); + Monster_Move(self.speed2, self.speed, self.stopspeed, self.m_anim_run, self.m_anim_walk, self.m_anim_idle); + CSQCMODEL_AUTOUPDATE(); } -float monster_spawn() +float Monster_Spawn_Setup() { MON_ACTION(self.monsterid, MR_SETUP); + // ensure some basic needs are met + if(!self.health) { self.health = 100; } + if(!self.armorvalue) { self.armorvalue = bound(0.2, 0.5 * MONSTER_SKILLMOD(self), 0.9); } + if(!self.target_range) { self.target_range = autocvar_g_monsters_target_range; } + if(!self.respawntime) { self.respawntime = autocvar_g_monsters_respawn_delay; } + if(!self.monster_moveflags) { self.monster_moveflags = MONSTER_MOVE_WANDER; } + if(!self.attack_range) { self.attack_range = autocvar_g_monsters_attack_range; } + if(!self.damageforcescale) { self.damageforcescale = autocvar_g_monsters_damageforcescale; } + if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) { - Monster_CheckMinibossFlag(); - self.health *= Monster_SkillModifier(); + Monster_Miniboss_Check(); + self.health *= MONSTER_SKILLMOD(self); + + if(!self.skin) + self.skin = rint(random() * 4); + + Monster_Skin_Check(); } self.max_health = self.health; self.pain_finished = self.nextthink; - if(IS_PLAYER(self.monster_owner)) + if(IS_PLAYER(self.monster_follow)) self.effects |= EF_DIMLIGHT; - if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) - if(!self.skin) - self.skin = rint(random() * 4); - - if(!self.attack_range) - self.attack_range = autocvar_g_monsters_attack_range; - if(!self.wander_delay) { self.wander_delay = 2; } if(!self.wander_distance) { self.wander_distance = 600; } - precache_monstersounds(); - UpdateMonsterSounds(); + Monster_Sounds_Precache(); + Monster_Sounds_Update(); if(teamplay) self.monster_attack = TRUE; // we can have monster enemies in team games - MonsterSound(monstersound_spawn, 0, FALSE, CH_VOICE); + Monster_Sound(monstersound_spawn, 0, FALSE, CH_VOICE); - WaypointSprite_Spawn(M_NAME(self.monsterid), 0, 1024, self, '0 0 1' * (self.maxs_z + 15), world, self.team, self, sprite, TRUE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0')); - if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE)) + if(autocvar_g_monsters_healthbars) { - WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); - WaypointSprite_UpdateHealth(self.sprite, self.health); + WaypointSprite_Spawn(self.monster_name, 0, 1024, self, '0 0 1' * (self.maxs_z + 15), world, self.team, + self, sprite, TRUE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0')); + + if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE)) + { + WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); + WaypointSprite_UpdateHealth(self.sprite, self.health); + } } - self.think = monster_think; + self.think = Monster_Think; self.nextthink = time + self.ticrate; if(MUTATOR_CALLHOOK(MonsterSpawn)) @@ -1134,21 +1172,26 @@ float monster_spawn() return TRUE; } -float monster_initialize(float mon_id) +float Monster_Spawn(float mon_id) { - if(!autocvar_g_monsters) { return FALSE; } - if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) { MON_ACTION(mon_id, MR_PRECACHE); } - if(Monster_CheckAppearFlags(self, mon_id)) { return TRUE; } // return true so the monster isn't removed - + // setup the basic required properties for a monster entity mon = get_monsterinfo(mon_id); + if(!mon.monsterid) { return FALSE; } // invalid monster + + if(!autocvar_g_monsters) { Monster_Remove(self); return FALSE; } + + self.mdl = mon.model; + if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) { precache_model(self.mdl); } + if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) { MON_ACTION(mon_id, MR_PRECACHE); } + if(Monster_Appear_Check(self, mon_id)) { return TRUE; } // return true so the monster isn't removed if(!self.monster_skill) self.monster_skill = cvar("g_monsters_skill"); // support for quake style removing monsters based on skill - if(self.monster_skill == MONSTER_SKILL_EASY) if(self.spawnflags & MONSTERSKILL_NOTEASY) { return FALSE; } - if(self.monster_skill == MONSTER_SKILL_MEDIUM) if(self.spawnflags & MONSTERSKILL_NOTMEDIUM) { return FALSE; } - if(self.monster_skill == MONSTER_SKILL_HARD) if(self.spawnflags & MONSTERSKILL_NOTHARD) { return FALSE; } + if(self.monster_skill == MONSTER_SKILL_EASY) if(self.spawnflags & MONSTERSKILL_NOTEASY) { Monster_Remove(self); return FALSE; } + if(self.monster_skill == MONSTER_SKILL_MEDIUM) if(self.spawnflags & MONSTERSKILL_NOTMEDIUM) { Monster_Remove(self); return FALSE; } + if(self.monster_skill == MONSTER_SKILL_HARD) if(self.spawnflags & MONSTERSKILL_NOTHARD) { Monster_Remove(self); return FALSE; } if(self.team && !teamplay) self.team = 0; @@ -1157,19 +1200,18 @@ float monster_initialize(float mon_id) if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) // don't count re-spawning monsters either monsters_total += 1; - setmodel(self, mon.model); - //setsize(self, mon.mins, mon.maxs); + setmodel(self, self.mdl); self.flags = FL_MONSTER; + self.classname = "monster"; self.takedamage = DAMAGE_AIM; self.bot_attack = TRUE; self.iscreature = TRUE; self.teleportable = TRUE; self.damagedbycontents = TRUE; self.monsterid = mon_id; - self.damageforcescale = 0; - self.event_damage = monsters_damage; - self.touch = MonsterTouch; - self.use = monster_use; + self.event_damage = Monster_Damage; + self.touch = Monster_Touch; + self.use = Monster_Use; self.solid = SOLID_BBOX; self.movetype = MOVETYPE_WALK; self.spawnshieldtime = time + autocvar_g_monsters_spawnshieldtime; @@ -1178,11 +1220,12 @@ float monster_initialize(float mon_id) self.moveto = self.origin; self.pos1 = self.origin; self.pos2 = self.angles; - self.reset = monsters_reset; + self.reset = Monster_Reset; self.netname = mon.netname; - self.monster_name = M_NAME(mon_id); + self.monster_attackfunc = mon.monster_attackfunc; + self.monster_name = mon.monster_name; self.candrop = TRUE; - self.view_ofs = '0 0 1' * (self.maxs_z * 0.5); + self.view_ofs = '0 0 0.7' * (self.maxs_z * 0.5); self.oldtarget2 = self.target2; self.pass_distance = 0; self.deadflag = DEAD_NO; @@ -1190,22 +1233,15 @@ float monster_initialize(float mon_id) self.spawn_time = time; self.spider_slowness = 0; self.gravity = 1; + self.monster_moveto = '0 0 0'; + self.monster_face = '0 0 0'; self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP; - - if(!self.scale) - self.scale = 1; - - if(autocvar_g_monsters_edit) - self.grab = 1; // owner may carry their monster - - if(autocvar_g_fullbrightplayers) - self.effects |= EF_FULLBRIGHT; - - if(autocvar_g_nodepthtestplayers) - self.effects |= EF_NODEPTHTEST; - - if(mon.spawnflags & MONSTER_TYPE_SWIM) - self.flags |= FL_SWIM; + + if(!self.scale) { self.scale = 1; } + if(autocvar_g_monsters_edit) { self.grab = 1; } + if(autocvar_g_fullbrightplayers) { self.effects |= EF_FULLBRIGHT; } + if(autocvar_g_nodepthtestplayers) { self.effects |= EF_NODEPTHTEST; } + if(mon.spawnflags & MONSTER_TYPE_SWIM) { self.flags |= FL_SWIM; } if(mon.spawnflags & MONSTER_TYPE_FLY) { @@ -1216,25 +1252,16 @@ float monster_initialize(float mon_id) if(mon.spawnflags & MONSTER_SIZE_BROKEN) if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) self.scale *= 1.3; - - setsize(self, mon.mins * self.scale, mon.maxs * self.scale); - - if(!self.ticrate) - self.ticrate = autocvar_g_monsters_think_delay; - - self.ticrate = bound(sys_frametime, self.ticrate, 60); - if(!self.m_armor_blockpercent) - self.m_armor_blockpercent = 0.5; - - if(!self.target_range) - self.target_range = autocvar_g_monsters_target_range; + setsize(self, mon.mins * self.scale, mon.maxs * self.scale); - if(!self.respawntime) - self.respawntime = autocvar_g_monsters_respawn_delay; + self.ticrate = bound(sys_frametime, ((!self.ticrate) ? autocvar_g_monsters_think_delay : self.ticrate), 60); - if(!self.monster_moveflags) - self.monster_moveflags = MONSTER_MOVE_WANDER; + if(!Monster_Spawn_Setup()) + { + Monster_Remove(self); + return FALSE; + } if(!self.noalign) { @@ -1243,9 +1270,6 @@ float monster_initialize(float mon_id) setorigin(self, trace_endpos); } - if(!monster_spawn()) - return FALSE; - if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) monster_setupcolors(self); diff --git a/qcsrc/common/monsters/sv_monsters.qh b/qcsrc/common/monsters/sv_monsters.qh index 239db02bd..05b71b10d 100644 --- a/qcsrc/common/monsters/sv_monsters.qh +++ b/qcsrc/common/monsters/sv_monsters.qh @@ -1,52 +1,85 @@ -.string spawnmob; -.float monster_attack; +// ===================== +// Monster definitions +// For server (SVQC) +// ===================== -.entity monster_owner; // new monster owner entity, fixes non-solid monsters -.float stat_monsters_killed; // stats +// stats networking +.float stat_monsters_killed; .float stat_monsters_total; float monsters_total; float monsters_killed; -void monsters_setstatus(); // monsters.qc -.float monster_moveflags; // checks where to move when not attacking -.float wander_delay; -.float wander_distance; - -.float monster_lifetime; - -.float spider_slowness; // special spider timer - -void monster_remove(entity mon); // removes a monster - -.float(float attack_type) monster_attackfunc; -const float MONSTER_ATTACK_MELEE = 1; -const float MONSTER_ATTACK_RANGED = 2; +// monster properties +.float monster_movestate; // move target priority +.entity monster_follow; // follow target +.float wander_delay; // logic delay between moving while idle +.float wander_distance; // distance to move between wander delays +.float monster_lifetime; // monster dies instantly after this delay, set from spawn +.float attack_range; // melee attack if closer, ranged attack if further away (TODO: separate ranged attack range?) +.float spawn_time; // delay monster thinking until spawn animation has completed +.float candrop; // toggle to allow disabling monster item drops +.float monster_movestate; // will be phased out +.float monster_moveflags; +.string oldtarget2; // a copy of the original follow target string +.float last_trace; // logic delay between target tracing +.float last_enemycheck; // for checking enemy +.float anim_finished; // will be phased out when we have proper animations system +.vector monster_moveto; // custom destination for monster (reset to '0 0 0' when you're done!) +.vector monster_face; // custom looking direction for monster (reset to '0 0 0' when you're done!) +.float speed2; // run speed +.float stopspeed; +.float m_anim_run; +.float m_anim_walk; +.float m_anim_idle; +.float oldskin; + +#define MONSTER_SKILLMOD(mon) (0.5 + mon.monster_skill * ((1.2 - 0.3) / 10)) + +// other properties +.float monster_attack; // indicates whether an entity can be attacked by monsters +.float spider_slowness; // effect time of slowness inflicted by spiders + +// monster state declarations +const float MONSTER_MOVE_FOLLOW = 1; // monster will follow if in range, or stand still +const float MONSTER_MOVE_WANDER = 2; // monster will ignore owner & wander around +const float MONSTER_MOVE_SPAWNLOC = 3; // monster will move to its spawn location when not attacking +const float MONSTER_MOVE_NOMOVE = 4; // monster simply stands still +const float MONSTER_MOVE_ENEMY = 5; // used only as a movestate +const float MONSTER_ATTACK_MELEE = 6; +const float MONSTER_ATTACK_RANGED = 7; -.float monster_skill; +// skill declarations const float MONSTER_SKILL_EASY = 1; const float MONSTER_SKILL_MEDIUM = 3; const float MONSTER_SKILL_HARD = 5; const float MONSTER_SKILL_INSANE = 7; const float MONSTER_SKILL_NIGHTMARE = 10; -.float fish_wasdrowning; // used to reset a drowning fish's angles if it reaches water again - -.float candrop; - -.float attack_range; - -.float spawn_time; // stop monster from moving around right after spawning +const float MONSTERSKILL_NOTEASY = 256; // monster will not spawn on skill <= 1 +const float MONSTERSKILL_NOTMEDIUM = 512; // monster will not spawn on skill 2 +const float MONSTERSKILL_NOTHARD = 1024; // monster will not spawn on skill >= 3 -.string oldtarget2; -.float lastshielded; +// spawn flags +const float MONSTERFLAG_APPEAR = 2; // delay spawn until triggered +const float MONSTERFLAG_NORESPAWN = 4; +const float MONSTERFLAG_FLY_VERTICAL = 8; // fly/swim vertically +const float MONSTERFLAG_INFRONT = 32; // only check for enemies infront of us +const float MONSTERFLAG_MINIBOSS = 64; // monster spawns as mini-boss (also has a chance of naturally becoming one) +const float MONSTERFLAG_INVINCIBLE = 128; // monster doesn't take damage (may be used for map objects & temporary monsters) +const float MONSTERFLAG_SPAWNED = 16384; // flag for spawned monsters +const float MONSTERFLAG_RESPAWNED = 32768; // flag for re-spawned monsters -.vector oldangles; +// compatibility with old maps (soon to be removed) +#define monster_lifetime lifetime +#define monster_skill skill -.float m_armor_blockpercent; +// functions used elsewhere +void Monster_Remove(entity mon); // removes a monster +void monsters_setstatus(); +float Monster_Spawn(float mon_id); // monster sounds -// copied from player sounds .float msound_delay; // temporary antilag system #define ALLMONSTERSOUNDS \ _MSOUND(death) \ @@ -55,34 +88,11 @@ const float MONSTER_SKILL_NIGHTMARE = 10; _MSOUND(melee) \ _MSOUND(pain) \ _MSOUND(spawn) \ - _MSOUND(idle) + _MSOUND(idle) \ + _MSOUND(attack) #define _MSOUND(m) .string monstersound_##m; ALLMONSTERSOUNDS #undef _MSOUND float GetMonsterSoundSampleField_notFound; - -const float MONSTERSKILL_NOTEASY = 256; // monster will not spawn on skill <= 1 -const float MONSTERSKILL_NOTMEDIUM = 512; // monster will not spawn on skill 2 -const float MONSTERSKILL_NOTHARD = 1024; // monster will not spawn on skill >= 3 - -// new flags -const float MONSTERFLAG_APPEAR = 2; // delay spawn until triggered -const float MONSTERFLAG_NORESPAWN = 4; -const float MONSTERFLAG_FLY_VERTICAL = 8; // fly/swim vertically -const float MONSTERFLAG_INFRONT = 32; // only check for enemies infront of us -const float MONSTERFLAG_MINIBOSS = 64; // monster spawns as mini-boss (also has a chance of naturally becoming one) -const float MONSTERFLAG_INVINCIBLE = 128; // monster doesn't take damage (may be used for map objects & temporary monsters) -const float MONSTERFLAG_SPAWNED = 16384; // flag for spawned monsters -const float MONSTERFLAG_RESPAWNED = 32768; // flag for re-spawned monsters - -.float monster_movestate; // used to tell what the monster is currently doing -const float MONSTER_MOVE_OWNER = 1; // monster will move to owner if in range, or stand still -const float MONSTER_MOVE_WANDER = 2; // monster will ignore owner & wander around -const float MONSTER_MOVE_SPAWNLOC = 3; // monster will move to its spawn location when not attacking -const float MONSTER_MOVE_NOMOVE = 4; // monster simply stands still -const float MONSTER_MOVE_ENEMY = 5; // used only as a movestate - -const float MONSTER_STATE_ATTACK_LEAP = 1; -const float MONSTER_STATE_ATTACK_MELEE = 2; diff --git a/qcsrc/common/notifications.qc b/qcsrc/common/notifications.qc index a5a2ff445..f24d130d0 100644 --- a/qcsrc/common/notifications.qc +++ b/qcsrc/common/notifications.qc @@ -582,7 +582,7 @@ void Create_Notification_Entity( { if(notif.nent_enabled) { - precache_sound(sprintf("announcer/%s/%s.wav", autocvar_cl_announcer, snd)); + precache_sound(sprintf("announcer/%s/%s.wav", AnnouncerOption(), snd)); notif.nent_channel = channel; notif.nent_snd = strzone(snd); notif.nent_vol = vol; @@ -1264,7 +1264,7 @@ void Local_Notification_sound( soundchannel, sprintf( "announcer/%s/%s.wav", - autocvar_cl_announcer, + AnnouncerOption(), soundfile ), soundvolume, @@ -1277,7 +1277,7 @@ void Local_Notification_sound( soundchannel, sprintf( "announcer/%s/%s.wav", - autocvar_cl_announcer, + AnnouncerOption(), soundfile ), soundvolume, @@ -1299,7 +1299,7 @@ void Local_Notification_sound( soundchannel, sprintf( "announcer/%s/%s.wav", - autocvar_cl_announcer, + AnnouncerOption(), soundfile ), soundvolume, @@ -1548,6 +1548,14 @@ void Local_Notification(float net_type, float net_name, ...count) #ifdef CSQC if(notif.nent_icon != "") { + if ( notif.nent_iconargs != "" ) + { + notif.nent_icon = Local_Notification_sprintf( + notif.nent_icon,notif.nent_iconargs, + s1, s2, s3, s4, f1, f2, f3, f4); + // remove the newline added by Local_Notification_sprintf + notif.nent_icon = strzone(substring(notif.nent_icon,0,strlen(notif.nent_icon)-1)); + } Local_Notification_HUD_Notify_Push( notif.nent_icon, notif.nent_hudargs, diff --git a/qcsrc/common/notifications.qh b/qcsrc/common/notifications.qh index 0bfd2e5f3..8710079fb 100644 --- a/qcsrc/common/notifications.qh +++ b/qcsrc/common/notifications.qh @@ -241,8 +241,12 @@ void Send_Notification_WOCOVA( MSG_ANNCE_NOTIF(1, ANNCE_ACHIEVEMENT_IMPRESSIVE, CH_INFO, "impressive", VOL_BASEVOICE, ATTEN_NONE) \ MSG_ANNCE_NOTIF(1, ANNCE_ACHIEVEMENT_YODA, CH_INFO, "yoda", VOL_BASEVOICE, ATTEN_NONE) \ MSG_ANNCE_NOTIF(2, ANNCE_BEGIN, CH_INFO, "begin", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(2, ANNCE_BONUSNADE, CH_INFO, "bonusnade", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(2, ANNCE_HEADSHOT, CH_INFO, "headshot", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_KILLSTREAK_02, CH_INFO, "02kills", VOL_BASEVOICE, ATTEN_NONE) \ MSG_ANNCE_NOTIF(1, ANNCE_KILLSTREAK_03, CH_INFO, "03kills", VOL_BASEVOICE, ATTEN_NONE) \ MSG_ANNCE_NOTIF(1, ANNCE_KILLSTREAK_05, CH_INFO, "05kills", VOL_BASEVOICE, ATTEN_NONE) \ + MSG_ANNCE_NOTIF(1, ANNCE_KILLSTREAK_08, CH_INFO, "08kills", VOL_BASEVOICE, ATTEN_NONE) \ MSG_ANNCE_NOTIF(1, ANNCE_KILLSTREAK_10, CH_INFO, "10kills", VOL_BASEVOICE, ATTEN_NONE) \ MSG_ANNCE_NOTIF(1, ANNCE_KILLSTREAK_15, CH_INFO, "15kills", VOL_BASEVOICE, ATTEN_NONE) \ MSG_ANNCE_NOTIF(1, ANNCE_KILLSTREAK_20, CH_INFO, "20kills", VOL_BASEVOICE, ATTEN_NONE) \ @@ -321,7 +325,8 @@ void Send_Notification_WOCOVA( MSG_ANNCE_NOTIF(2, ANNCE_TIMEOUT, CH_INFO, "timeoutcalled", VOL_BASEVOICE, ATTEN_NONE) \ MSG_ANNCE_NOTIF(2, ANNCE_VOTE_ACCEPT, CH_INFO, "voteaccept", VOL_BASEVOICE, ATTEN_NONE) \ MSG_ANNCE_NOTIF(2, ANNCE_VOTE_CALL, CH_INFO, "votecall", VOL_BASEVOICE, ATTEN_NONE) \ - MSG_ANNCE_NOTIF(2, ANNCE_VOTE_FAIL, CH_INFO, "votefail", VOL_BASEVOICE, ATTEN_NONE) + MSG_ANNCE_NOTIF(2, ANNCE_VOTE_FAIL, CH_INFO, "votefail", VOL_BASEVOICE, ATTEN_NONE) \ + JEFF_ANCE_NOTIF #define MULTITEAM_INFO2(default,prefix,strnum,flnum,args,hudargs,icon,normal,gentle) \ MSG_INFO_NOTIF(default, prefix##RED, strnum, flnum, args, hudargs, sprintf(icon, strtolower(STATIC_NAME_TEAM_1)), TCR(normal, COL_TEAM_1, strtoupper(NAME_TEAM_1)), TCR(gentle, COL_TEAM_1, strtoupper(NAME_TEAM_1))) \ @@ -341,25 +346,38 @@ void Send_Notification_WOCOVA( #define MSG_INFO_NOTIFICATIONS \ MSG_INFO_NOTIF(2, INFO_CHAT_NOSPECTATORS, 0, 0, "", "", "", _("^F4NOTE: ^BGSpectator chat is not sent to players during the match"), "") \ MSG_INFO_NOTIF(2, INFO_COINTOSS, 1, 0, "s1", "", "", _("^F2Throwing coin... Result: %s^F2!"), "") \ + MULTITEAM_INFO(1, INFO_CONQUEST_CAPTURE_, 4, 1, 0, "s1", "", "", _("^BG%s^BG was captured by the ^TC^TT^BG team"), "") \ + MULTITEAM_INFO(1, INFO_CONQUEST_LIBERATE_, 4, 1, 0, "s1", "", "", _("^BG%s^BG was liberated from the ^TC^TT^BG team"), "") \ MSG_INFO_NOTIF(1, INFO_JETPACK_NOFUEL, 0, 0, "", "", "", _("^BGYou don't have any fuel for the ^F1Jetpack"), "") \ MSG_INFO_NOTIF(2, INFO_SUPERSPEC_MISSING_UID, 0, 0, "", "", "", _("^F2You lack a UID, superspec options will not be saved/restored"), "") \ MSG_INFO_NOTIF(1, INFO_CA_JOIN_LATE, 0, 0, "", "", "", _("^F1Round already started, you will join the game in the next round"), "") \ MSG_INFO_NOTIF(1, INFO_CA_LEAVE, 0, 0, "", "", "", _("^F2You will spectate in the next round"), "") \ - MULTITEAM_INFO(1, INFO_CTF_CAPTURE_, 2, 1, 0, "s1", "s1", "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag"), "") \ - MULTITEAM_INFO(1, INFO_CTF_CAPTURE_BROKEN_, 2, 2, 2, "s1 f1p2dec s2 f2p2dec", "s1", "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag in ^F1%s^BG seconds, breaking ^BG%s^BG's previous record of ^F2%s^BG seconds"), "") \ - MULTITEAM_INFO(1, INFO_CTF_CAPTURE_TIME_, 2, 1, 1, "s1 f1p2dec", "s1", "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag in ^F1%s^BG seconds"), "") \ - MULTITEAM_INFO(1, INFO_CTF_CAPTURE_UNBROKEN_, 2, 2, 2, "s1 f1p2dec s2 f2p2dec", "s1", "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag in ^F2%s^BG seconds, failing to break ^BG%s^BG's previous record of ^F1%s^BG seconds"), "") \ - MULTITEAM_INFO(1, INFO_CTF_FLAGRETURN_ABORTRUN_, 2, 0, 0, "", "", "", _("^BGThe ^TC^TT^BG flag was returned to base by its owner"), "") \ - MULTITEAM_INFO(1, INFO_CTF_FLAGRETURN_DAMAGED_, 2, 0, 0, "", "", "", _("^BGThe ^TC^TT^BG flag was destroyed and returned to base"), "") \ - MULTITEAM_INFO(1, INFO_CTF_FLAGRETURN_DROPPED_, 2, 0, 0, "", "", "", _("^BGThe ^TC^TT^BG flag was dropped in the base and returned itself"), "") \ - MULTITEAM_INFO(1, INFO_CTF_FLAGRETURN_NEEDKILL_, 2, 0, 0, "", "", "", _("^BGThe ^TC^TT^BG flag fell somewhere it couldn't be reached and returned to base"), "") \ - MULTITEAM_INFO(1, INFO_CTF_FLAGRETURN_SPEEDRUN_, 2, 0, 1, "f1p2dec", "", "", _("^BGThe ^TC^TT^BG flag became impatient after ^F1%.2f^BG seconds and returned itself"), "") \ - MULTITEAM_INFO(1, INFO_CTF_FLAGRETURN_TIMEOUT_, 2, 0, 0, "", "", "", _("^BGThe ^TC^TT^BG flag has returned to the base"), "") \ - MULTITEAM_INFO(1, INFO_CTF_LOST_, 2, 1, 0, "s1", "s1", "notify_%s_lost", _("^BG%s^BG lost the ^TC^TT^BG flag"), "") \ - MULTITEAM_INFO(1, INFO_CTF_PICKUP_, 2, 1, 0, "s1", "s1", "notify_%s_taken", _("^BG%s^BG got the ^TC^TT^BG flag"), "") \ - MULTITEAM_INFO(1, INFO_CTF_RETURN_, 2, 1, 0, "s1", "s1", "notify_%s_returned", _("^BG%s^BG returned the ^TC^TT^BG flag"), "") \ - MULTITEAM_INFO(1, INFO_CTF_RETURN_MONSTER_, 2, 1, 0, "s1", "s1", "notify_%s_returned", _("^BG%s^BG returned the ^TC^TT^BG flag"), "") \ + MULTITEAM_INFO(1, INFO_CTF_CAPTURE_, 4, 1, 0, "s1", "s1", "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag"), "") \ + MULTITEAM_INFO(1, INFO_CTF_CAPTURE_BROKEN_, 4, 2, 2, "s1 f1p2dec s2 f2p2dec", "s1", "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag in ^F1%s^BG seconds, breaking ^BG%s^BG's previous record of ^F2%s^BG seconds"), "") \ + MSG_INFO_NOTIF(1, INFO_CTF_CAPTURE_NEUTRAL, 1, 0, "s1", "s1", "notify_%s_captured", _("^BG%s^BG captured the flag"), "") \ + MULTITEAM_INFO(1, INFO_CTF_CAPTURE_TIME_, 4, 1, 1, "s1 f1p2dec", "s1", "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag in ^F1%s^BG seconds"), "") \ + MULTITEAM_INFO(1, INFO_CTF_CAPTURE_UNBROKEN_, 4, 2, 2, "s1 f1p2dec s2 f2p2dec", "s1", "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag in ^F2%s^BG seconds, failing to break ^BG%s^BG's previous record of ^F1%s^BG seconds"), "") \ + MULTITEAM_INFO(1, INFO_CTF_FLAGRETURN_ABORTRUN_, 4, 0, 0, "", "", "", _("^BGThe ^TC^TT^BG flag was returned to base by its owner"), "") \ + MSG_INFO_NOTIF(1, INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL,0, 0, "", "", "", _("^BGThe flag was returned by its owner"), "") \ + MULTITEAM_INFO(1, INFO_CTF_FLAGRETURN_DAMAGED_, 4, 0, 0, "", "", "", _("^BGThe ^TC^TT^BG flag was destroyed and returned to base"), "") \ + MSG_INFO_NOTIF(1, INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL, 0, 0, "", "", "", _("^BGThe flag was destroyed and returned to base"), "") \ + MULTITEAM_INFO(1, INFO_CTF_FLAGRETURN_DROPPED_, 4, 0, 0, "", "", "", _("^BGThe ^TC^TT^BG flag was dropped in the base and returned itself"), "") \ + MSG_INFO_NOTIF(1, INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL, 0, 0, "", "", "", _("^BGThe flag was dropped in the base and returned itself"), "") \ + MULTITEAM_INFO(1, INFO_CTF_FLAGRETURN_NEEDKILL_, 4, 0, 0, "", "", "", _("^BGThe ^TC^TT^BG flag fell somewhere it couldn't be reached and returned to base"), "") \ + MSG_INFO_NOTIF(1, INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL,0, 0, "", "", "", _("^BGThe flag fell somewhere it couldn't be reached and returned to base"), "") \ + MULTITEAM_INFO(1, INFO_CTF_FLAGRETURN_SPEEDRUN_, 4, 0, 1, "f1p2dec", "", "", _("^BGThe ^TC^TT^BG flag became impatient after ^F1%.2f^BG seconds and returned itself"), "") \ + MSG_INFO_NOTIF(1, INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL,0, 1, "f1p2dec", "", "", _("^BGThe flag became impatient after ^F1%.2f^BG seconds and returned itself"), "") \ + MULTITEAM_INFO(1, INFO_CTF_FLAGRETURN_TIMEOUT_, 4, 0, 0, "", "", "", _("^BGThe ^TC^TT^BG flag has returned to the base"), "") \ + MSG_INFO_NOTIF(1, INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL, 0, 0, "", "", "", _("^BGThe flag has returned to the base"), "") \ + MULTITEAM_INFO(1, INFO_CTF_LOST_, 4, 1, 0, "s1", "s1", "notify_%s_lost", _("^BG%s^BG lost the ^TC^TT^BG flag"), "") \ + MSG_INFO_NOTIF(1, INFO_CTF_LOST_NEUTRAL, 1, 0, "s1", "s1", "notify_%s_lost", _("^BG%s^BG lost the flag"), "") \ + MULTITEAM_INFO(1, INFO_CTF_PICKUP_, 4, 1, 0, "s1", "s1", "notify_%s_taken", _("^BG%s^BG got the ^TC^TT^BG flag"), "") \ + MSG_INFO_NOTIF(1, INFO_CTF_PICKUP_NEUTRAL, 1, 0, "s1", "s1", "notify_%s_taken", _("^BG%s^BG got the flag"), "") \ + MULTITEAM_INFO(1, INFO_CTF_RETURN_, 4, 1, 0, "s1", "s1", "notify_%s_returned", _("^BG%s^BG returned the ^TC^TT^BG flag"), "") \ + MULTITEAM_INFO(1, INFO_CTF_RETURN_MONSTER_, 4, 1, 0, "s1", "s1", "notify_%s_returned", _("^BG%s^BG returned the ^TC^TT^BG flag"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_BUFF, 3, 3, "spree_inf s1 s2 f3buffname s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was killed by ^BG%s^K1's ^BG%s^K1 buff ^K1%s%s"), _("^BG%s%s^K1 was scored against by ^BG%s^K1's ^BG%s^K1 buff ^K1%s%s")) \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_CHEAT, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was unfairly eliminated by ^BG%s^K1%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_CRUSH, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_crush", _("^BG%s%s^K1 was crushed by ^BG%s^K1%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_DROWN, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_water", _("^BG%s%s^K1 was drowned by ^BG%s^K1%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_FALL, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_fall", _("^BG%s%s^K1 was grounded by ^BG%s^K1%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_FIRE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was burnt up into a crisp by ^BG%s^K1%s%s"), _("^BG%s%s^K1 felt a little hot from ^BG%s^K1's fire^K1%s%s")) \ @@ -384,10 +402,11 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_SPID_DEATH, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 got caught in the blast when ^BG%s^K1's Spiderbot exploded%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_SPID_MINIGUN, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 got shredded by ^BG%s^K1's Spiderbot%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_SPID_ROCKET, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was blasted to bits by ^BG%s^K1's Spiderbot%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_TANK_DEATH, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was caught in the blast when ^BG%s^K1's Tank exploded%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_TANKLL48, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was obliterated by ^BG%s^K1's LL48 Tank%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_WAKI_DEATH, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 got caught in the blast when ^BG%s^K1's Racer exploded%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_WAKI_GUN, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was bolted down by ^BG%s^K1's Racer%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_WAKI_ROCKET, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 couldn't find shelter from ^BG%s^K1's Racer%s%s"), "") \ - MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VENGEANCE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was destroyed by the vengeful ^BG%s^K1%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VOID, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_void", _("^BG%s%s^K1 was thrown into a world of hurt by ^BG%s^K1%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_AUTOTEAMCHANGE, 2, 1, "s1 s2loc death_team", "", "", _("^BG%s^K1 was moved into the %s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_BETRAYAL, 2, 1, "s1 s2loc spree_lost", "s1", "notify_teamkill_red", _("^BG%s^K1 became enemies with the Lord of Teamplay%s%s"), "") \ @@ -399,11 +418,23 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_DEATH_SELF_FIRE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 became a bit too crispy%s%s"), _("^BG%s^K1 felt a little hot%s%s")) \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_GENERIC, 2, 1, "s1 s2loc spree_lost", "s1", "notify_selfkill", _("^BG%s^K1 died%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_LAVA, 2, 1, "s1 s2loc spree_lost", "s1", "notify_lava", _("^BG%s^K1 turned into hot slag%s%s"), _("^BG%s^K1 found a hot place%s%s")) \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_AFRIT, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was turned to ash by an Afrit%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_CREEPER, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was blown up by Creeper%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_DEMON_JUMP, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was chopped in half by a pouncing Demon%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_DEMON_MELEE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was eviscerated by a Demon%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_MAGE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was exploded by a Mage%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_OGRE_GRENADE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 couldn't dodge the Ogre grenade%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_OGRE_MACHINEGUN, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was riddled full of holes by an Ogre%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_OGRE_MELEE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was chopped to bits by an Ogre%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_ROTFISH_MELEE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was fed to the Rotfish%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_ROTTWEILER, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was turned to dogmeat by a Rottweiler%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SCRAG, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was bombed from above by a Scrag%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SHAMBLER_CLAW, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1's innards became outwards by a Shambler%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SHAMBLER_SMASH, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was smashed by a Shambler%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SHAMBLER_ZAP, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was zapped to death by a Shambler%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SPIDER, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was bitten by a Spider%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_VORE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was exploded by a Vore%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_VORE_MELEE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was ripped apart by a Vore%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_WYVERN, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was fireballed by a Wyvern%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_ZOMBIE_JUMP, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 joins the Zombies%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_ZOMBIE_MELEE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was given kung fu lessons by a Zombie%s%s"), "") \ @@ -431,7 +462,7 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_DEATH_SELF_TURRET_PLASMA, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 got served some superheated plasma from a turret%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_TURRET_TESLA, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was electrocuted by a Tesla turret%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_TURRET_WALK_GUN, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 got served a lead enrichment by a Walker turret%s%s"), "") \ - MSG_INFO_NOTIF(1, INFO_DEATH_SELF_TURRET_WALK_MEELE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was impaled by a Walker turret%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_TURRET_WALK_MELEE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was impaled by a Walker turret%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_TURRET_WALK_ROCKET, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was blasted away by a Walker turret%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_VH_BUMB_DEATH, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 got caught in the blast of a Bumblebee explosion%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_VH_CRUSH, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was crushed by a vehicle%s%s"), "") \ @@ -439,22 +470,27 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_DEATH_SELF_VH_RAPT_DEATH, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 got caught in the blast of a Raptor explosion%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_VH_SPID_DEATH, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 got caught in the blast of a Spiderbot explosion%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_VH_SPID_ROCKET, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was blasted to bits by a Spiderbot rocket%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_VH_TANK_DEATH, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was caught in the blast of a Tank explosion%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_VH_WAKI_DEATH, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 got caught in the blast of a Racer explosion%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_VH_WAKI_ROCKET, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 couldn't find shelter from a Racer rocket%s%s"), "") \ - MSG_INFO_NOTIF(1, INFO_DEATH_SELF_VOID, 2, 1, "s1 s2loc spree_lost", "s1", "notify_void", _("^BG%s^K1 was in the wrong place%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_VOID, 3, 1, "s1 s2 s3loc spree_lost", "s1", "notify_void", "^BG%s^K1 %s^K1%s%s", "") \ MULTITEAM_INFO(1, INFO_DEATH_TEAMKILL_, 4, 3, 1, "s1 s2 s3loc spree_end", "s2 s1", "notify_teamkill_%s", _("^BG%s^K1 was betrayed by ^BG%s^K1%s%s"), "") \ - MSG_INFO_NOTIF(1, INFO_DOMINATION_CAPTURE_TIME, 2, 2, "s1 s2 f1 f2", "", "", _("^BG%s^BG%s^BG (%s points every %s seconds)"), "") \ + MULTITEAM_INFO(1, INFO_DOMINATION_CAPTURE_, 4, 0, 0, "", "", "", _("^TC^TT^BG team has captured a control point"), "") \ + MULTITEAM_INFO(1, INFO_DOMINATION_CAPTURE_TIME_, 4, 1, 2, "s1 f1 f2", "", "", _("^TC^TT^BG team%s^BG (%s points every %s seconds)"), "") \ + MULTITEAM_INFO(1, INFO_DOMINATION_CAPTURE_TIME_NOMSG_,4,0,2, "f1 f2", "", "", _("^TC^TT^BG team has captured a control point (%s points every %s seconds)"), "") \ MSG_INFO_NOTIF(1, INFO_FREEZETAG_FREEZE, 2, 0, "s1 s2", "", "", _("^BG%s^K1 was frozen by ^BG%s"), "") \ MSG_INFO_NOTIF(1, INFO_FREEZETAG_REVIVED, 2, 0, "s1 s2", "", "", _("^BG%s^K3 was revived by ^BG%s"), "") \ MSG_INFO_NOTIF(1, INFO_FREEZETAG_REVIVED_FALL, 1, 0, "s1", "", "", _("^BG%s^K3 was revived by falling"), "") \ MSG_INFO_NOTIF(1, INFO_FREEZETAG_REVIVED_NADE, 1, 0, "s1", "", "", _("^BG%s^K3 was revived by their Nade explosion"), "") \ MSG_INFO_NOTIF(1, INFO_FREEZETAG_AUTO_REVIVED, 1, 1, "s1 f1", "", "", _("^BG%s^K3 was automatically revived after %s second(s)"), "") \ + MSG_INFO_NOTIF(1, INFO_ROUND_INFECTION_WIN, 0, 0, "", "", "", _("^BGRound over, all players have been infected"), "") \ MULTITEAM_INFO(1, INFO_ROUND_TEAM_WIN_, 4, 0, 0, "", "", "", _("^TC^TT^BG team wins the round"), "") \ MSG_INFO_NOTIF(1, INFO_ROUND_PLAYER_WIN, 1, 0, "s1", "", "", _("^BG%s^BG wins the round"), "") \ MSG_INFO_NOTIF(1, INFO_ROUND_TIED, 0, 0, "", "", "", _("^BGRound tied"), "") \ MSG_INFO_NOTIF(1, INFO_ROUND_OVER, 0, 0, "", "", "", _("^BGRound over, there's no winner"), "") \ MSG_INFO_NOTIF(1, INFO_FREEZETAG_SELF, 1, 0, "s1", "", "", _("^BG%s^K1 froze themself"), "") \ MSG_INFO_NOTIF(1, INFO_GODMODE_OFF, 0, 1, "f1", "", "", _("^BGGodmode saved you %s units of damage, cheater!"), "") \ + MSG_INFO_NOTIF(1, INFO_INFECTION_INFECTED, 2, 0, "s1 s2", "", "", _("^BG%s^BG was infected by ^BG%s"), "") \ MSG_INFO_NOTIF(1, INFO_ITEM_BUFF, 1, 1, "s1 item_buffname", "", "", _("^BG%s^BG got the %s^BG buff!"), "") \ MSG_INFO_NOTIF(1, INFO_ITEM_BUFF_LOST, 1, 1, "s1 item_buffname", "", "", _("^BG%s^BG lost the %s^BG buff!"), "") \ MSG_INFO_NOTIF(1, INFO_ITEM_BUFF_DROP, 0, 1, "item_buffname", "", "", _("^BGYou dropped the %s^BG buff!"), "") \ @@ -465,22 +501,28 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(0, INFO_ITEM_WEAPON_NOAMMO, 0, 1, "item_wepname", "", "", _("^BGYou don't have enough ammo for the ^F1%s"), "") \ MSG_INFO_NOTIF(0, INFO_ITEM_WEAPON_PRIMORSEC, 0, 3, "item_wepname f2primsec f3primsec", "", "", _("^F1%s %s^BG is unable to fire, but its ^F1%s^BG can"), "") \ MSG_INFO_NOTIF(0, INFO_ITEM_WEAPON_UNAVAILABLE, 0, 1, "item_wepname", "", "", _("^F1%s^BG is ^F4not available^BG on this map"), "") \ + MSG_INFO_NOTIF(1, INFO_JAILBREAK_CAPTURE, 2, 0, "s1 s2", "", "", _("^BG%s^BG captured %s"), "") \ + MSG_INFO_NOTIF(1, INFO_JAILBREAK_FREE, 1, 0, "s1", "", "", _("^BG%s^3 has broken free!"), "") \ MSG_INFO_NOTIF(2, INFO_JOIN_CONNECT, 1, 0, "s1", "", "", _("^BG%s^F3 connected%s"), "") \ MULTITEAM_INFO(2, INFO_JOIN_CONNECT_TEAM_, 4, 1, 0, "s1", "", "", _("^BG%s^F3 connected and joined the ^TC^TT team"), "") \ MSG_INFO_NOTIF(1, INFO_JOIN_PLAY, 1, 0, "s1", "", "", _("^BG%s^F3 is now playing"), "") \ MSG_INFO_NOTIF(1, INFO_KEEPAWAY_DROPPED, 1, 0, "s1", "s1", "notify_balldropped", _("^BG%s^BG has dropped the ball!"), "") \ MSG_INFO_NOTIF(1, INFO_KEEPAWAY_PICKUP, 1, 0, "s1", "s1", "notify_ballpickedup", _("^BG%s^BG has picked up the ball!"), "") \ - MULTITEAM_INFO(1, INFO_KEYHUNT_CAPTURE_, 4, 1, 0, "s1", "", "", _("^BG%s^BG captured the keys for the ^TC^TT team"), "") \ + MULTITEAM_INFO(1, INFO_KEYHUNT_CAPTURE_, 4, 1, 0, "s1", "", "", _("^BG%s^BG captured the ^TC^TT key"), "") \ MULTITEAM_INFO(1, INFO_KEYHUNT_DROP_, 4, 1, 0, "s1", "", "", _("^BG%s^BG dropped the ^TC^TT Key"), "") \ + MULTITEAM_INFO(1, INFO_KEYHUNT_KEYRETURN_DAMAGED_, 4, 0, 0, "", "", "", _("^BGThe ^TC^TT^BG key was destroyed"), "") \ + MULTITEAM_INFO(1, INFO_KEYHUNT_KEYRETURN_NEEDKILL_, 4, 0, 0, "", "", "", _("^BGThe ^TC^TT^BG key fell somewhere it couldn't be reached"), "") \ + MULTITEAM_INFO(1, INFO_KEYHUNT_KEYRETURN_TIMEOUT_, 4, 0, 0, "", "", "", _("^BGThe ^TC^TT^BG key was lost"), "") \ MULTITEAM_INFO(1, INFO_KEYHUNT_LOST_, 4, 1, 0, "s1", "", "", _("^BG%s^BG lost the ^TC^TT Key"), "") \ MULTITEAM_INFO(1, INFO_KEYHUNT_PICKUP_, 4, 1, 0, "s1", "", "", _("^BG%s^BG picked up the ^TC^TT Key"), "") \ + MULTITEAM_INFO(1, INFO_KEYHUNT_START_, 4, 1, 0, "s1", "", "", _("^BG%s^BG is starting with the ^TC^TT Key"), "") \ MSG_INFO_NOTIF(1, INFO_LMS_FORFEIT, 1, 0, "s1", "", "", _("^BG%s^F3 forfeited"), "") \ MSG_INFO_NOTIF(1, INFO_LMS_NOLIVES, 1, 0, "s1", "", "", _("^BG%s^F3 has no more lives left"), "") \ MSG_INFO_NOTIF(1, INFO_MONSTERS_DISABLED, 0, 0, "", "", "", _("^BGMonsters are currently disabled"), "") \ - MSG_INFO_NOTIF(1, INFO_POWERUP_INVISIBILITY, 1, 0, "s1", "s1", "strength", _("^BG%s^K1 picked up Invisibility"), "") \ - MSG_INFO_NOTIF(1, INFO_POWERUP_SHIELD, 1, 0, "s1", "s1", "shield", _("^BG%s^K1 picked up Shield"), "") \ - MSG_INFO_NOTIF(1, INFO_POWERUP_SPEED, 1, 0, "s1", "s1", "shield", _("^BG%s^K1 picked up Speed"), "") \ - MSG_INFO_NOTIF(1, INFO_POWERUP_STRENGTH, 1, 0, "s1", "s1", "strength", _("^BG%s^K1 picked up Strength"), "") \ + MSG_INFO_NOTIF(1, INFO_ONSLAUGHT_CAPTURE, 2, 0, "s1 s2", "", "", _("^BG%s^BG captured %s^BG control point"), "") \ + MULTITEAM_INFO(1, INFO_ONSLAUGHT_CPDESTROYED_, 4, 2, 0, "s1 s2", "", "", _("^TC^TT^BG team %s^BG control point has been destroyed by %s"), "") \ + MULTITEAM_INFO(1, INFO_ONSLAUGHT_GENDESTROYED_, 4, 0, 0, "", "", "", _("^TC^TT^BG generator has been destroyed"), "") \ + MULTITEAM_INFO(1, INFO_ONSLAUGHT_GENDESTROYED_OVERTIME_, 4, 0, 0, "", "", "", _("^TC^TT^BG generator spontaneously combusted due to overtime!"), "") \ MSG_INFO_NOTIF(2, INFO_QUIT_DISCONNECT, 1, 0, "s1", "", "", _("^BG%s^F3 disconnected"), "") \ MSG_INFO_NOTIF(2, INFO_QUIT_KICK_IDLING, 1, 0, "s1", "", "", _("^BG%s^F3 was kicked for idling"), "") \ MSG_INFO_NOTIF(1, INFO_QUIT_KICK_SPECTATING, 0, 0, "", "", "", _("^F2You were kicked from the server because you are a spectator and spectators aren't allowed at the moment."), "") \ @@ -493,6 +535,7 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_RACE_NEW_IMPROVED, 1, 3, "s1 race_col f1ord race_col f2race_time race_diff", "s1 f2race_time", "race_newtime", _("^BG%s^BG improved their %s%s^BG place record with %s%s %s"), "") \ MSG_INFO_NOTIF(1, INFO_RACE_NEW_MISSING_UID, 1, 1, "s1 f1race_time", "s1 f1race_time", "race_newfail", _("^BG%s^BG scored a new record with ^F2%s^BG, but unfortunately lacks a UID and will be lost."), "") \ MSG_INFO_NOTIF(1, INFO_RACE_NEW_SET, 1, 2, "s1 race_col f1ord race_col f2race_time", "s1 f2race_time", "race_newrecordserver", _("^BG%s^BG set the %s%s^BG place record with %s%s"), "") \ + MULTIICON_INFO(1, INFO_MINIGAME_INVITE, 2, 0, "s2 minigame1_name s1","s2", "minigame1_d", "minigames/%s/icon_notif",_("^F4You have been invited by ^BG%s^F4 to join their game of ^F2%s^F4 (^F1%s^F4)"), "") \ MULTITEAM_INFO(1, INFO_SCORES_, 4, 0, 0, "", "", "", _("^TC^TT ^BGteam scores!"), "") \ MSG_INFO_NOTIF(1, INFO_SPECTATE_WARNING, 0, 1, "f1secs", "", "", _("^F2You have to become a player within the next %s, otherwise you will be kicked, because spectating isn't allowed at this time!"), "") \ MSG_INFO_NOTIF(1, INFO_SUPERWEAPON_PICKUP, 1, 0, "s1", "s1", "superweapons", _("^BG%s^K1 picked up a Superweapon"), "") \ @@ -501,6 +544,8 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(2, INFO_VERSION_BETA, 2, 0, "s1 s2", "", "", _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s (beta)^BG, you have ^F2Xonotic %s"), "") \ MSG_INFO_NOTIF(2, INFO_VERSION_OLD, 2, 0, "s1 s2", "", "", _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s^BG, you have ^F2Xonotic %s"), "") \ MSG_INFO_NOTIF(2, INFO_VERSION_OUTDATED, 2, 0, "s1 s2", "", "", _("^F4NOTE: ^F1Xonotic %s^BG is out, and you still have ^F2Xonotic %s^BG - get the update from ^F3http://www.xonotic.org/^BG!"), "") \ + MSG_INFO_NOTIF(1, INFO_VIP_DROP, 1, 0, "s1", "", "", _("^BG%s^K1 dropped the soul gem"), "") \ + MSG_INFO_NOTIF(1, INFO_VIP_PICKUP, 1, 0, "s1", "", "", _("^BG%s^K1 picked up the soul gem"), "") \ MSG_INFO_NOTIF(1, INFO_WATERMARK, 1, 0, "s1", "", "", _("^F3SVQC Build information: ^F4%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_ACCORDEON_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weapontuba", _("^BG%s%s^K1 died of ^BG%s^K1's great playing on the @!#%%'n Accordeon%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_ACCORDEON_SUICIDE, 2, 1, "s1 s2loc spree_lost", "s1", "weapontuba", _("^BG%s^K1 hurt their own ears with the @!#%%'n Accordeon%s%s"), "") \ @@ -528,9 +573,12 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_WEAPON_HLAC_SUICIDE, 2, 1, "s1 s2loc spree_lost", "s1", "weaponhlac", _("^BG%s^K1 got a little jumpy with their HLAC%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_HMG_MURDER_SNIPE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponhmg", _("^BG%s%s^K1 was sniped by ^BG%s^K1's Heavy Machine Gun%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_HMG_MURDER_SPRAY, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponhmg", _("^BG%s%s^K1 was torn to bits by ^BG%s^K1's Heavy Machine Gun%s%s"), "") \ - MSG_INFO_NOTIF(1, INFO_WEAPON_HOOK_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponhook", _("^BG%s%s^K1 was caught in ^BG%s^K1's Hook gravity bomb%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_WEAPON_HOOK_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponhook", _("^BG%s%s^K1 was hookfragged by ^BG%s^K1%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_WEAPON_HOOK_MURDER_LASER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponhook", _("^BG%s%s^K1's hook was lasered by ^BG%s^K1%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_WEAPON_HOOK_MURDER_GRAVITYBOMB, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponhook", _("^BG%s%s^K1 was caught in ^BG%s^K1's Hook gravity bomb%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_KLEINBOTTLE_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weapontuba", _("^BG%s%s^K1 died of ^BG%s^K1's great playing on the @!#%%'n Klein Bottle%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_KLEINBOTTLE_SUICIDE, 2, 1, "s1 s2loc spree_lost", "s1", "weapontuba", _("^BG%s^K1 hurt their own ears with the @!#%%'n Klein Bottle%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_WEAPON_LIGHTSABRE_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponlightsabre", _("^BG%s%s^K1's hand was zapped off by ^BG%s^K1's Lightsabre%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_MACHINEGUN_MURDER_SNIPE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponuzi", _("^BG%s%s^K1 was sniped by ^BG%s^K1's Machine Gun%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_MACHINEGUN_MURDER_SPRAY, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponuzi", _("^BG%s%s^K1 was riddled full of holes by ^BG%s^K1's Machine Gun%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_MINELAYER_LIMIT, 0, 1, "f1", "", "", _("^BGYou cannot place more than ^F2%s^BG mines at a time"), "") \ @@ -540,6 +588,7 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_WEAPON_MORTAR_MURDER_EXPLODE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weapongrenadelauncher", _("^BG%s%s^K1 ate ^BG%s^K1's Mortar grenade%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_MORTAR_SUICIDE_BOUNCE, 2, 1, "s1 s2loc spree_lost", "s1", "weapongrenadelauncher", _("^BG%s^K1 didn't see their own Mortar grenade%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_MORTAR_SUICIDE_EXPLODE, 2, 1, "s1 s2loc spree_lost", "s1", "weapongrenadelauncher", _("^BG%s^K1 blew themself up with their own Mortar%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_WEAPON_REVOLVER_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponrevolver", _("^BG%s%s^K1 was put down by ^BG%s^K1's Revolver%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_RIFLE_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponrifle", _("^BG%s%s^K1 was sniped with a Rifle by ^BG%s^K1%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_RIFLE_MURDER_HAIL, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponrifle", _("^BG%s%s^K1 died in ^BG%s^K1's Rifle bullet hail%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_RIFLE_MURDER_HAIL_PIERCING, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponrifle", _("^BG%s%s^K1 failed to hide from ^BG%s^K1's Rifle bullet hail%s%s"), "") \ @@ -559,6 +608,8 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_WEAPON_TUBA_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weapontuba", _("^BG%s%s^K1 died of ^BG%s^K1's great playing on the @!#%%'n Tuba%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_TUBA_SUICIDE, 2, 1, "s1 s2loc spree_lost", "s1", "weapontuba", _("^BG%s^K1 hurt their own ears with the @!#%%'n Tuba%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_VAPORIZER_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponminstanex", _("^BG%s%s^K1 has been sublimated by ^BG%s^K1's Vaporizer%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_WEAPON_VAPORIZER_MURDER_CHARGE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponminstanex", _("^BG%s%s^K1 was blasted by ^BG%s^K1's charged Vaporizer%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_WEAPON_VAPORIZER_SUICIDE, 2, 1, "s1 s2loc spree_lost", "s1", "weaponminstanex", _("^BG%s^K1 blasted themself with their charged Vaporizer%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_WEAPON_VORTEX_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponnex", _("^BG%s%s^K1 has been vaporized by ^BG%s^K1's Vortex%s%s"), "") #define MULTITEAM_CENTER2(default,prefix,strnum,flnum,args,cpid,durcnt,normal,gentle) \ @@ -580,6 +631,11 @@ void Send_Notification_WOCOVA( MSG_CENTER_NOTIF(1, CENTER_ALONE, 0, 0, "", NO_CPID, "0 0", _("^F4You are now alone!"), "") \ MSG_CENTER_NOTIF(1, CENTER_ASSAULT_ATTACKING, 0, 0, "", CPID_ASSAULT_ROLE, "0 0", _("^BGYou are attacking!"), "") \ MSG_CENTER_NOTIF(1, CENTER_ASSAULT_DEFENDING, 0, 0, "", CPID_ASSAULT_ROLE, "0 0", _("^BGYou are defending!"), "") \ + MULTITEAM_CENTER(1, CENTER_CONQUEST_CAPTURE_, 4, 1, 0, "s1", CPID_CONQUEST, "0 0", _("^BG%s^BG has been captured by the ^TC^TT^BG team"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CONQUEST_CAPTURE_TEAM, 1, 0, "s1", CPID_CONQUEST, "0 0", _("^BGYour team has captured %s"), "") \ + MULTITEAM_CENTER(1, CENTER_CONQUEST_LIBERATE_, 4, 1, 0, "s1", CPID_CONQUEST, "0 0", _("^BG%s^BG has been liberated from the ^TC^TT^BG team"), "") \ + MULTITEAM_CENTER(1, CENTER_CONQUEST_LIBERATE_TEAM_, 4, 1, 0, "s1", CPID_CONQUEST, "0 0", _("^BGYour team has liberated %s^BG from the ^TC^TT^BG team"), "") \ + MULTITEAM_CENTER(1, CENTER_CONQUEST_LOST_, 4, 1, 0, "s1", CPID_CONQUEST, "0 0", _("^BG%s^BG has been lost to the ^TC^TT^BG team"), "") \ MSG_CENTER_NOTIF(1, CENTER_COUNTDOWN_BEGIN, 0, 0, "", CPID_ROUND, "2 0", _("^F4Begin!"), "") \ MSG_CENTER_NOTIF(1, CENTER_COUNTDOWN_GAMESTART, 0, 1, "", CPID_ROUND, "1 f1", _("^F4Game starts in ^COUNT"), "") \ MSG_CENTER_NOTIF(1, CENTER_COUNTDOWN_ROUNDSTART, 0, 1, "", CPID_ROUND, "1 f1", _("^F4Round starts in ^COUNT"), "") \ @@ -589,20 +645,34 @@ void Send_Notification_WOCOVA( MSG_CENTER_NOTIF(1, CENTER_CAMPCHECK, 0, 0, "", CPID_CAMPCHECK, "0 0", _("^F2Don't camp!"), "") \ MSG_CENTER_NOTIF(1, CENTER_COINTOSS, 1, 0, "s1", NO_CPID, "0 0", _("^F2Throwing coin... Result: %s^F2!"), "") \ MSG_CENTER_NOTIF(1, CENTER_CTF_CAPTURESHIELD_FREE, 0, 0, "", CPID_CTF_CAPSHIELD, "0 0", _("^BGYou are now free.\n^BGFeel free to ^F2try to capture^BG the flag again\n^BGif you think you will succeed."), "") \ - MSG_CENTER_NOTIF(1, CENTER_CTF_CAPTURESHIELD_SHIELDED, 0, 0, "", CPID_CTF_CAPSHIELD, "0 0", _("^BGYou are now ^F1shielded^BG from the flag\n^BGfor ^F2too many unsuccessful attempts^BG to capture.\n^BGMake some defensive scores before trying again."), "") \ - MULTITEAM_CENTER(1, CENTER_CTF_CAPTURE_, 2, 0, 0, "", CPID_CTF_LOWPRIO, "0 0", _("^BGYou captured the ^TC^TT^BG flag!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_CAPTURESHIELD_INACTIVE, 0, 0, "", CPID_CTF_CAPSHIELD, "0 0", _("^BGThis flag is currently inactive"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_CAPTURESHIELD_SHIELDED, 0, 0, "", CPID_CTF_CAPSHIELD, "0 0", _("^BGYou are now ^F1shielded^BG from the flag(s)\n^BGfor ^F2too many unsuccessful attempts^BG to capture.\n^BGMake some defensive scores before trying again."), "") \ + MULTITEAM_CENTER(1, CENTER_CTF_CAPTURE_, 4, 0, 0, "", CPID_CTF_LOWPRIO, "0 0", _("^BGYou captured the ^TC^TT^BG flag!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_CAPTURE_NEUTRAL, 0, 0, "", CPID_CTF_LOWPRIO, "0 0", _("^BGYou captured the flag!"), "") \ MSG_CENTER_NOTIF(1, CENTER_CTF_FLAG_THROW_PUNISH, 0, 1, "f1secs", CPID_CTF_LOWPRIO, "0 0", _("^BGToo many flag throws! Throwing disabled for %s."), "") \ - MULTITEAM_CENTER(1, CENTER_CTF_PASS_OTHER_, 2, 2, 0, "s1 s2", CPID_CTF_PASS, "0 0", _("^BG%s^BG passed the ^TC^TT^BG flag to %s"), "") \ - MULTITEAM_CENTER(1, CENTER_CTF_PASS_RECEIVED_, 2, 1, 0, "s1", CPID_CTF_PASS, "0 0", _("^BGYou received the ^TC^TT^BG flag from %s"), "") \ + MULTITEAM_CENTER(1, CENTER_CTF_PASS_OTHER_, 4, 2, 0, "s1 s2", CPID_CTF_PASS, "0 0", _("^BG%s^BG passed the ^TC^TT^BG flag to %s"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_PASS_OTHER_NEUTRAL, 2, 0, "s1 s2", CPID_CTF_PASS, "0 0", _("^BG%s^BG passed the flag to %s"), "") \ + MULTITEAM_CENTER(1, CENTER_CTF_PASS_RECEIVED_, 4, 1, 0, "s1", CPID_CTF_PASS, "0 0", _("^BGYou received the ^TC^TT^BG flag from %s"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_PASS_RECEIVED_NEUTRAL, 1, 0, "s1", CPID_CTF_PASS, "0 0", _("^BGYou received the flag from %s"), "") \ MSG_CENTER_NOTIF(1, CENTER_CTF_PASS_REQUESTED, 1, 0, "s1 pass_key", CPID_CTF_PASS, "0 0", _("^BG%s^BG requests you to pass the flag%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_CTF_PASS_REQUESTING, 1, 0, "s1", CPID_CTF_PASS, "0 0", _("^BGRequesting %s^BG to pass you the flag"), "") \ - MULTITEAM_CENTER(1, CENTER_CTF_PASS_SENT_, 2, 1, 0, "s1", CPID_CTF_PASS, "0 0", _("^BGYou passed the ^TC^TT^BG flag to %s"), "") \ - MULTITEAM_CENTER(1, CENTER_CTF_PICKUP_, 2, 0, 0, "", CPID_CTF_LOWPRIO, "0 0", _("^BGYou got the ^TC^TT^BG flag!"), "") \ + MULTITEAM_CENTER(1, CENTER_CTF_PASS_SENT_, 4, 1, 0, "s1", CPID_CTF_PASS, "0 0", _("^BGYou passed the ^TC^TT^BG flag to %s"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_PASS_SENT_NEUTRAL, 1, 0, "s1", CPID_CTF_PASS, "0 0", _("^BGYou passed the flag to %s"), "") \ + MULTITEAM_CENTER(1, CENTER_CTF_PICKUP_, 4, 0, 0, "", CPID_CTF_LOWPRIO, "0 0", _("^BGYou got the ^TC^TT^BG flag!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_PICKUP_NEUTRAL, 0, 0, "", CPID_CTF_LOWPRIO, "0 0", _("^BGYou got the flag!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_PICKUP_TEAM, 1, 0, "s1", CPID_CTF_LOWPRIO, "0 0", _("^BGYou got your %steam^BG's flag, return it!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_PICKUP_TEAM_ENEMY, 1, 0, "s1", CPID_CTF_LOWPRIO, "0 0", _("^BGYou got the %senemy^BG's flag, return it!"), "") \ MSG_CENTER_NOTIF(1, CENTER_CTF_PICKUP_ENEMY, 1, 0, "s1", CPID_CTF_LOWPRIO, "0 0", _("^BGThe %senemy^BG got your flag! Retrieve it!"), "") \ MSG_CENTER_NOTIF(1, CENTER_CTF_PICKUP_ENEMY_VERBOSE, 2, 0, "s1 s2 s1", CPID_CTF_LOWPRIO, "0 0", _("^BGThe %senemy (^BG%s%s)^BG got your flag! Retrieve it!"), "") \ - MSG_CENTER_NOTIF(1, CENTER_CTF_PICKUP_TEAM, 1, 0, "s1", CPID_CTF_LOWPRIO, "0 0", _("^BGYour %steam mate^BG got the flag! Protect them!"), "") \ - MSG_CENTER_NOTIF(1, CENTER_CTF_PICKUP_TEAM_VERBOSE, 2, 0, "s1 s2 s1", CPID_CTF_LOWPRIO, "0 0", _("^BGYour %steam mate (^BG%s%s)^BG got the flag! Protect them!"), "") \ - MULTITEAM_CENTER(1, CENTER_CTF_RETURN_, 2, 0, 0, "", CPID_CTF_LOWPRIO, "0 0", _("^BGYou returned the ^TC^TT^BG flag!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_PICKUP_ENEMY_NEUTRAL, 1, 0, "s1", CPID_CTF_LOWPRIO, "0 0", _("^BGThe %senemy^BG got the flag! Retrieve it!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_PICKUP_ENEMY_NEUTRAL_VERBOSE, 2, 0, "s1 s2 s1",CPID_CTF_LOWPRIO, "0 0", _("^BGThe %senemy (^BG%s%s)^BG got the flag! Retrieve it!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_PICKUP_ENEMY_TEAM, 1, 0, "s1", CPID_CTF_LOWPRIO, "0 0", _("^BGThe %senemy^BG got their flag! Retrieve it!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_PICKUP_ENEMY_TEAM_VERBOSE,2, 0, "s1 s2 s1", CPID_CTF_LOWPRIO, "0 0", _("^BGThe %senemy (^BG%s%s)^BG got their flag! Retrieve it!"), "") \ + MULTITEAM_CENTER(1, CENTER_CTF_PICKUP_TEAM_, 4, 1, 0, "s1", CPID_CTF_LOWPRIO, "0 0", _("^BGYour %steam mate^BG got the ^TC^TT^BG flag! Protect them!"), "") \ + MULTITEAM_CENTER(1, CENTER_CTF_PICKUP_TEAM_VERBOSE_, 4, 2, 0, "s1 s2 s1", CPID_CTF_LOWPRIO, "0 0", _("^BGYour %steam mate (^BG%s%s)^BG got the ^TC^TT^BG flag! Protect them!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_PICKUP_TEAM_NEUTRAL, 1, 0, "s1", CPID_CTF_LOWPRIO, "0 0", _("^BGYour %steam mate^BG got the flag! Protect them!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_CTF_PICKUP_TEAM_VERBOSE_NEUTRAL, 2, 0, "s1 s2 s1", CPID_CTF_LOWPRIO, "0 0", _("^BGYour %steam mate (^BG%s%s)^BG got the flag! Protect them!"), "") \ + MULTITEAM_CENTER(1, CENTER_CTF_RETURN_, 4, 0, 0, "", CPID_CTF_LOWPRIO, "0 0", _("^BGYou returned the ^TC^TT^BG flag!"), "") \ MSG_CENTER_NOTIF(1, CENTER_CTF_STALEMATE_CARRIER, 0, 0, "", CPID_STALEMATE, "0 0", _("^BGStalemate! Enemies can now see you on radar!"), "") \ MSG_CENTER_NOTIF(1, CENTER_CTF_STALEMATE_OTHER, 0, 0, "", CPID_STALEMATE, "0 0", _("^BGStalemate! Flag carriers can now be seen by enemies on radar!"), "") \ MSG_CENTER_NOTIF(1, CENTER_DEATH_MURDER_FRAG, 1, 1, "spree_cen s1", NO_CPID, "0 0", _("^K3%sYou fragged ^BG%s"), _("^K3%sYou scored against ^BG%s")) \ @@ -647,6 +717,7 @@ void Send_Notification_WOCOVA( MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_VH_RAPT_DEATH, 0, 0, "", NO_CPID, "0 0", _("^K1You got caught in the blast of a Raptor explosion!"), "") \ MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_VH_SPID_DEATH, 0, 0, "", NO_CPID, "0 0", _("^K1You got caught in the blast of a Spiderbot explosion!"), "") \ MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_VH_SPID_ROCKET, 0, 0, "", NO_CPID, "0 0", _("^K1You were blasted to bits by a Spiderbot rocket!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_VH_TANK_DEATH, 0, 0, "", NO_CPID, "0 0", _("^K1You were caught in the blast of a Tank explosion!"), "") \ MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_VH_WAKI_DEATH, 0, 0, "", NO_CPID, "0 0", _("^K1You got caught in the blast of a Racer explosion!"), "") \ MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_VH_WAKI_ROCKET, 0, 0, "", NO_CPID, "0 0", _("^K1You couldn't find shelter from a Racer rocket!"), "") \ MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_VOID, 0, 0, "", NO_CPID, "0 0", _("^K1Watch your step!"), "") \ @@ -657,16 +728,22 @@ void Send_Notification_WOCOVA( MSG_CENTER_NOTIF(1, CENTER_DOOR_LOCKED_ALSONEED, 0, 0, "", NO_CPID, "0 0", _("^BGYou also need %s^BG!"), "") \ MSG_CENTER_NOTIF(1, CENTER_DOOR_UNLOCKED, 0, 0, "", NO_CPID, "0 0", _("^BGDoor unlocked!"), "") \ MSG_CENTER_NOTIF(1, CENTER_EXTRALIVES, 0, 0, "", NO_CPID, "0 0", _("^F2You picked up some extra lives"), "") \ + MSG_CENTER_NOTIF(1, CENTER_FREEZE_THAWING, 0, 0, "", CPID_FREEZE, "5 0", _("^BGThawing slowly now\n^BGJump to re-spawn at base instantly"), "") \ MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_FREEZE, 1, 0, "s1", NO_CPID, "0 0", _("^K3You froze ^BG%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_FROZEN, 1, 0, "s1", NO_CPID, "0 0", _("^K1You were frozen by ^BG%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_REVIVE, 1, 0, "s1", NO_CPID, "0 0", _("^K3You revived ^BG%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_REVIVE_SELF, 0, 0, "", NO_CPID, "0 0", _("^K3You revived yourself"), "") \ MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_REVIVED, 1, 0, "s1", NO_CPID, "0 0", _("^K3You were revived by ^BG%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_AUTO_REVIVED, 0, 1, "f1", NO_CPID, "0 0", _("^K3You were automatically revived after %s second(s)"), "") \ + MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_STALEMATE, 0, 0, "", CPID_STALEMATE, "0 0", _("^BGStalemate! Enemies can now be seen on the radar!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_GENERATOR_UNDERATTACK, 0, 0, "", NO_CPID, "0 0", _("^BGThe generator is under attack!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_HEADSHOT, 0, 0, "", NO_CPID, "0 0", _("^F2Headshot!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_ROUND_INFECTION_WIN, 0, 0, "", CPID_ROUND, "0 0", _("^BGRound over, all players have been infected"), "") \ MULTITEAM_CENTER(1, CENTER_ROUND_TEAM_WIN_, 4, 0, 0, "", CPID_ROUND, "0 0", _("^TC^TT^BG team wins the round"), "") \ MSG_CENTER_NOTIF(1, CENTER_ROUND_PLAYER_WIN, 1, 0, "s1", CPID_ROUND, "0 0", _("^BG%s^BG wins the round"), "") \ MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_SELF, 0, 0, "", NO_CPID, "0 0", _("^K1You froze yourself"), "") \ MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_SPAWN_LATE, 0, 0, "", NO_CPID, "0 0", _("^K1Round already started, you spawn as frozen"), "") \ + MSG_CENTER_NOTIF(1, CENTER_INFECTION_INFECTED, 1, 0, "s1", NO_CPID, "0 0", _("^BGYou have been infected by ^BG%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_INVASION_SUPERMONSTER, 1, 0, "s1", NO_CPID, "0 0", _("^K1A %s has arrived!"), "") \ MSG_CENTER_NOTIF(1, CENTER_ITEM_BUFF_DROP, 0, 1, "item_buffname", CPID_ITEM, "item_centime 0", _("^BGYou dropped the %s^BG buff!"), "") \ MSG_CENTER_NOTIF(1, CENTER_ITEM_BUFF_GOT, 0, 1, "item_buffname", CPID_ITEM, "item_centime 0", _("^BGYou got the %s^BG buff!"), "") \ @@ -676,44 +753,66 @@ void Send_Notification_WOCOVA( MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_NOAMMO, 0, 1, "item_wepname", CPID_ITEM, "item_centime 0", _("^BGYou don't have enough ammo for the ^F1%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_PRIMORSEC, 0, 3, "item_wepname f2primsec f3primsec", CPID_ITEM, "item_centime 0", _("^F1%s %s^BG is unable to fire, but its ^F1%s^BG can"), "") \ MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_UNAVAILABLE, 0, 1, "item_wepname", CPID_ITEM, "item_centime 0", _("^F1%s^BG is ^F4not available^BG on this map"), "") \ + MSG_CENTER_NOTIF(1, CENTER_JAILBREAK_DEFENSE, 0, 0, "", CPID_JAILBREAK, "0 0", _("^F2You gained some bonus score for defending the control point!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_JAILBREAK_ESCAPE, 0, 1, "death_team", CPID_JAILBREAK, "0 0", _("^K1The %s^K1 is escaping!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_JAILBREAK_FREE, 0, 0, "", CPID_JAILBREAK, "0 0", _("^F2You're free! Run away!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_JAILBREAK_FREED, 0, 0, "", CPID_JAILBREAK, "0 0", _("^K3You bailed your teammates out of jail!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_JAILBREAK_IMPRISON, 0, 0, "", CPID_JAILBREAK, "0 0", _("^K1You're in jail, prisoner!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_JAILBREAK_NOENTRY, 0, 0, "", CPID_JAILBREAK, "0 0", _("^K1No sneaking into jail!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_JAILBREAK_NOTREADY, 0, 0, "", CPID_JAILBREAK, "0 0", _("^BGThis control point is not ready yet"), "") \ + MSG_CENTER_NOTIF(1, CENTER_JAILBREAK_TEAMALIVE, 0, 0, "", CPID_JAILBREAK, "0 0", _("^BGYour team is already free"), "") \ + MSG_CENTER_NOTIF(1, CENTER_JAILBREAK_TOOLATE, 0, 0, "", CPID_JAILBREAK, "0 0", _("^BGSomeone is already capturing this control point"), "") \ + MSG_CENTER_NOTIF(1, CENTER_JAILBREAK_WRONGTEAM, 0, 0, "", CPID_JAILBREAK, "0 0", _("^BGYou can't capture your team's control points"), "") \ MSG_CENTER_NOTIF(1, CENTER_JOIN_NOSPAWNS, 0, 0, "", CPID_PREVENT_JOIN, "0 0", _("^K1No spawnpoints available!\nHope your team can fix it..."), "") \ MSG_CENTER_NOTIF(1, CENTER_JOIN_PREVENT, 0, 0, "", CPID_PREVENT_JOIN, "0 0", _("^K1You may not join the game at this time.\nThe player limit reached maximum capacity."), "") \ MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_DROPPED, 1, 0, "s1", CPID_KEEPAWAY, "0 0", _("^BG%s^BG has dropped the ball!"), "") \ MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_PICKUP, 1, 0, "s1", CPID_KEEPAWAY, "0 0", _("^BG%s^BG has picked up the ball!"), "") \ MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_PICKUP_SELF, 0, 0, "", CPID_KEEPAWAY, "0 0", _("^BGYou picked up the ball"), "") \ MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_WARN, 0, 0, "", CPID_KEEPAWAY_WARN, "0 0", _("^BGKilling people while you don't have the ball gives no points!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_CAPTURE, 0, 0, "", CPID_KEYHUNT_LOWPRIO, "0 0", _("^BGYou captured the keys!"), "") \ MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_HELP, 0, 0, "", CPID_KEYHUNT, "0 0", _("^BGAll keys are in your team's hands!\nHelp the key carriers to meet!"), "") \ MULTITEAM_CENTER(1, CENTER_KEYHUNT_INTERFERE_, 4, 0, 0, "", CPID_KEYHUNT, "0 0", _("^BGAll keys are in ^TC^TT team^BG's hands!\nInterfere ^F4NOW^BG!"), "") \ MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_MEET, 0, 0, "", CPID_KEYHUNT, "0 0", _("^BGAll keys are in your team's hands!\nMeet the other key carriers ^F4NOW^BG!"), "") \ - MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_ROUNDSTART, 0, 1, "", CPID_KEYHUNT_OTHER, "1 f1", _("^F4Round will start in ^COUNT"), "") \ - MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_SCAN, 0, 1, "", CPID_KEYHUNT_OTHER, "f1 0", _("^BGScanning frequency range..."), "") \ + MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_KEY_THROW_PUNISH, 0, 1, "f1secs", CPID_KEYHUNT_LOWPRIO, "0 0", _("^BGToo many key throws! Throwing disabled for %s."), "") \ + MULTITEAM_CENTER(1, CENTER_KEYHUNT_PASS_OTHER_, 4, 2, 0, "s1 s2", CPID_KEYHUNT_PASS, "0 0", _("^BG%s^BG passed the ^TC^TT^BG key to %s"), "") \ + MULTITEAM_CENTER(1, CENTER_KEYHUNT_PASS_RECEIVED_, 4, 1, 0, "s1", CPID_KEYHUNT_PASS, "0 0", _("^BGYou received the ^TC^TT^BG key from %s"), "") \ + MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_PASS_REQUESTED, 1, 0, "s1 pass_key", CPID_KEYHUNT_PASS, "0 0", _("^BG%s^BG requests you to pass the key%s"), "") \ + MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_PASS_REQUESTING, 1, 0, "s1", CPID_KEYHUNT_PASS, "0 0", _("^BGRequesting %s^BG to pass you the key"), "") \ + MULTITEAM_CENTER(1, CENTER_KEYHUNT_PASS_SENT_, 4, 1, 0, "s1", CPID_KEYHUNT_PASS, "0 0", _("^BGYou passed the ^TC^TT^BG key to %s"), "") \ + MULTITEAM_CENTER(1, CENTER_KEYHUNT_PICKUP_, 4, 0, 0, "", CPID_KEYHUNT_LOWPRIO, "0 0", _("^BGYou got the ^TC^TT^BG key!"), "") \ + MULTITEAM_CENTER(1, CENTER_KEYHUNT_PICKUP_TEAM_, 4, 1, 0, "s1", CPID_KEYHUNT_LOWPRIO, "0 0", _("^BGYour %steam mate^BG got the ^TC^TT^BG key! Protect them!"), "") \ + MULTITEAM_CENTER(1, CENTER_KEYHUNT_PICKUP_TEAM_VERBOSE_,4, 2, 0, "s1 s2 s1", CPID_KEYHUNT_LOWPRIO, "0 0", _("^BGYour %steam mate (^BG%s%s)^BG got the ^TC^TT^BG key! Protect them!"), "") \ MULTITEAM_CENTER(1, CENTER_KEYHUNT_START_, 4, 0, 0, "", CPID_KEYHUNT, "0 0", _("^BGYou are starting with the ^TC^TT Key"), "") \ - MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_WAIT, 0, 1, "missing_teams", CPID_KEYHUNT_OTHER, "0 0", _("^BGWaiting for players to join...\nNeed active players for: %s"), "") \ + MULTITEAM_CENTER(1, CENTER_KEYHUNT_START_TEAM_, 4, 1, 0, "s1", CPID_KEYHUNT_LOWPRIO, "0 0", _("^BGYour %steam mate^BG is starting with the ^TC^TT^BG key!\nProtect them!"), "") \ + MULTITEAM_CENTER(1, CENTER_KEYHUNT_START_TEAM_VERBOSE_, 4, 2, 0, "s1 s2 s1", CPID_KEYHUNT_LOWPRIO, "0 0", _("^BGYour %steam mate (^BG%s%s)^BG is starting with the ^TC^TT^BG key!\nProtect them!"), "") \ MSG_CENTER_NOTIF(1, CENTER_LMS_NOLIVES, 0, 0, "", CPID_LMS, "0 0", _("^BGYou have no lives left, you must wait until the next match"), "") \ MSG_CENTER_NOTIF(1, CENTER_MISSING_TEAMS, 0, 1, "missing_teams", CPID_MISSING_TEAMS, "-1 0", _("^BGWaiting for players to join...\nNeed active players for: %s"), "") \ MSG_CENTER_NOTIF(1, CENTER_MISSING_PLAYERS, 0, 1, "f1", CPID_MISSING_PLAYERS, "-1 0", _("^BGWaiting for %s player(s) to join..."), "") \ + MSG_CENTER_NOTIF(1, CENTER_INSTAGIB_DOWNGRADE, 0, 0, "", CPID_INSTAGIB_FINDAMMO,"5 0", _("^BGYour weapon has been downgraded until you find some ammo!"), "") \ MSG_CENTER_NOTIF(1, CENTER_INSTAGIB_FINDAMMO, 0, 0, "", CPID_INSTAGIB_FINDAMMO,"1 9", _("^F4^COUNT^BG left to find some ammo!"), "") \ MSG_CENTER_NOTIF(1, CENTER_INSTAGIB_FINDAMMO_FIRST, 0, 0, "", CPID_INSTAGIB_FINDAMMO,"1 10", _("^BGGet some ammo or you'll be dead in ^F4^COUNT^BG!"), _("^BGGet some ammo! ^F4^COUNT^BG left!")) \ MSG_CENTER_NOTIF(1, CENTER_INSTAGIB_LIVES_REMAINING, 0, 1, "f1", NO_CPID, "0 0", _("^F2Extra lives remaining: ^K1%s"), "") \ - MSG_CENTER_NOTIF(1, CENTER_MOTD, 1, 0, "s1", CPID_MOTD, "-1 0", "^BG%s", "") \ + MSG_CENTER_NOTIF(1, CENTER_MOTD, 1, 0, "s1", CPID_MOTD, "5 0", "^BG%s", "") \ MSG_CENTER_NOTIF(1, CENTER_NIX_COUNTDOWN, 0, 2, "item_wepname", CPID_NIX, "1 f2", _("^F2^COUNT^BG until weapon change...\nNext weapon: ^F1%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_NIX_NEWWEAPON, 0, 1, "item_wepname", CPID_NIX, "0 0", _("^F2Active weapon: ^F1%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_NADE, 0, 0, "", NO_CPID, "0 0", _("^BGPress ^F2DROPWEAPON^BG again to toss the grenade!"), "") \ - MSG_CENTER_NOTIF(1, CENTER_ONS_NOTSHIELDED, 0, 0, "", CPID_ONSLAUGHT, "0 0", _("^K1Your generator is NOT shielded!\n^BGRe-capture controlpoints to shield it!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_ONS_CAPTURE, 1, 0, "s1", CPID_ONSLAUGHT, "0 0", _("^BGYou captured %s^BG control point"), "") \ + MULTITEAM_CENTER(1, CENTER_ONS_CAPTURE_, 4, 1, 0, "s1", CPID_ONSLAUGHT, "0 0", _("^TC^TT^BG team captured %s^BG control point"), "") \ + MSG_CENTER_NOTIF(1, CENTER_ONS_CONTROLPOINT_SHIELDED, 0, 0, "", CPID_ONS_CAPSHIELD, "0 0", _("^BGThis control point currently cannot be captured"), "") \ + MSG_CENTER_NOTIF(1, CENTER_ONS_GENERATOR_SHIELDED, 0, 0, "", CPID_ONS_CAPSHIELD, "0 0", _("^BGThe enemy generator cannot be destroyed yet\n^F2Capture some control points to unshield it"), "") \ + MULTITEAM_CENTER(1, CENTER_ONS_NOTSHIELDED_, 4, 0, 0, "", CPID_ONSLAUGHT, "0 0", _("^BGThe ^TCenemy^BG generator is no longer shielded!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_ONS_NOTSHIELDED_TEAM, 0, 0, "", CPID_ONSLAUGHT, "0 0", _("^K1Your generator is NOT shielded!\n^BGRe-capture control points to shield it!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_ONS_TELEPORT, 0, 0, "pass_key", CPID_ONSLAUGHT, "0 0", _("^BGPress ^F2DROPFLAG%s^BG to teleport"), "") \ + MSG_CENTER_NOTIF(1, CENTER_ONS_TELEPORT_ANTISPAM, 0, 1, "f1secs", CPID_ONSLAUGHT, "0 0", _("^BGTeleporting disabled for %s"), "") \ + MSG_CENTER_NOTIF(1, CENTER_OVERKILL_CHARGE, 0, 0, "", CPID_OVERKILL, "0 0", _("^BGYour weapon's charge is too low, release the attack button & wait for it to charge"), "") \ MSG_CENTER_NOTIF(1, CENTER_OVERTIME_FRAG, 0, 0, "", CPID_OVERTIME, "0 0", _("^F2Now playing ^F4OVERTIME^F2!\nKeep fragging until we have a winner!"), _("^F2Now playing ^F4OVERTIME^F2!\nKeep scoring until we have a winner!")) \ MSG_CENTER_NOTIF(1, CENTER_OVERTIME_CONTROLPOINT, 0, 0, "", CPID_OVERTIME, "5 0", _("^F2Now playing ^F4OVERTIME^F2!\n\nGenerators are now decaying.\nThe more control points your team holds,\nthe faster the enemy generator decays"), "") \ MSG_CENTER_NOTIF(1, CENTER_OVERTIME_TIME, 0, 1, "f1time", CPID_OVERTIME, "0 0", _("^F2Now playing ^F4OVERTIME^F2!\n^BGAdded ^F4%s^BG to the game!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_PIGGYBACK_CARRYING, 1, 0, "s1", CPID_PIGGYBACK, "0 0", _("^BGYou are now carrying %s^BG!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_PIGGYBACK_RIDING, 1, 0, "s1", CPID_PIGGYBACK, "0 0", _("^BGYou are now riding %s^BG!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_PORTO_FAILED, 0, 0, "", NO_CPID, "0 0", _("^K1Portal deployment failed.\n\n^F2Catch it to try again!"), "") \ MSG_CENTER_NOTIF(1, CENTER_PORTO_CREATED_IN, 0, 0, "", NO_CPID, "0 0", _("^K1In^BG-portal created"), "") \ MSG_CENTER_NOTIF(1, CENTER_PORTO_CREATED_OUT, 0, 0, "", NO_CPID, "0 0", _("^F3Out^BG-portal created"), "") \ - MSG_CENTER_NOTIF(1, CENTER_PORTO_FAILED, 0, 0, "", NO_CPID, "0 0", _("^K1Portal deployment failed.\n\n^F2Catch it to try again!"), "") \ - MSG_CENTER_NOTIF(1, CENTER_POWERDOWN_INVISIBILITY, 0, 0, "", CPID_POWERUP, "0 0", _("^F2Invisibility has worn off"), "") \ - MSG_CENTER_NOTIF(1, CENTER_POWERDOWN_SHIELD, 0, 0, "", CPID_POWERUP, "0 0", _("^F2Shield has worn off"), "") \ - MSG_CENTER_NOTIF(1, CENTER_POWERDOWN_SPEED, 0, 0, "", CPID_POWERUP, "0 0", _("^F2Speed has worn off"), "") \ - MSG_CENTER_NOTIF(1, CENTER_POWERDOWN_STRENGTH, 0, 0, "", CPID_POWERUP, "0 0", _("^F2Strength has worn off"), "") \ - MSG_CENTER_NOTIF(1, CENTER_POWERUP_INVISIBILITY, 0, 0, "", CPID_POWERUP, "0 0", _("^F2You are invisible"), "") \ - MSG_CENTER_NOTIF(1, CENTER_POWERUP_SHIELD, 0, 0, "", CPID_POWERUP, "0 0", _("^F2Shield surrounds you"), "") \ - MSG_CENTER_NOTIF(1, CENTER_POWERUP_SPEED, 0, 0, "", CPID_POWERUP, "0 0", _("^F2You are on speed"), "") \ - MSG_CENTER_NOTIF(1, CENTER_POWERUP_STRENGTH, 0, 0, "", CPID_POWERUP, "0 0", _("^F2Strength infuses your weapons with devastating power"), "") \ MSG_CENTER_NOTIF(1, CENTER_RACE_FINISHLAP, 0, 0, "", CPID_RACE_FINISHLAP, "0 0", _("^F2The race is over, finish your lap!"), "") \ MSG_CENTER_NOTIF(1, CENTER_SECONDARY_NODAMAGE, 0, 0, "", NO_CPID, "0 0", _("^BGSecondary fire inflicts no damage!"), "") \ MSG_CENTER_NOTIF(1, CENTER_SEQUENCE_COMPLETED, 0, 0, "", NO_CPID, "0 0", _("^BGSequence completed!"), "") \ @@ -728,7 +827,21 @@ void Send_Notification_WOCOVA( MSG_CENTER_NOTIF(1, CENTER_TEAMCHANGE_SUICIDE, 0, 1, "", CPID_TEAMCHANGE, "1 f1", _("^K1Suicide in ^COUNT"), "") \ MSG_CENTER_NOTIF(1, CENTER_TIMEOUT_BEGINNING, 0, 1, "", CPID_TIMEOUT, "1 f1", _("^F4Timeout begins in ^COUNT"), "") \ MSG_CENTER_NOTIF(1, CENTER_TIMEOUT_ENDING, 0, 1, "", CPID_TIMEOUT, "1 f1", _("^F4Timeout ends in ^COUNT"), "") \ - MSG_CENTER_NOTIF(1, CENTER_WEAPON_MINELAYER_LIMIT, 0, 1, "f1", NO_CPID, "0 0", _("^BGYou cannot place more than ^F2%s^BG mines at a time"), "") + MSG_CENTER_NOTIF(1, CENTER_VEHICLE_ENTER, 0, 0, "pass_key", CPID_VEHICLES, "0 0", _("^BGPress ^F2DROPFLAG%s^BG to enter/exit the vehicle"), "") \ + MSG_CENTER_NOTIF(1, CENTER_VEHICLE_ENTER_GUNNER, 0, 0, "pass_key", CPID_VEHICLES, "0 0", _("^BGPress ^F2DROPFLAG%s^BG to enter the vehicle gunner"), "") \ + MSG_CENTER_NOTIF(1, CENTER_VEHICLE_ENTER_STEAL, 0, 0, "pass_key", CPID_VEHICLES, "0 0", _("^BGPress ^F2DROPFLAG%s^BG to steal this vehicle"), "") \ + MSG_CENTER_NOTIF(1, CENTER_VEHICLE_STEAL, 0, 0, "", CPID_VEHICLES_OTHER, "0 0", _("^F2The enemy is stealing one of your vehicles!\n^F4Stop them!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_VEHICLE_STEAL_SELF, 0, 0, "", CPID_VEHICLES_OTHER, "4 0", _("^F2You have stolen the enemy's vehicle, you are now visible on their radar!"), "") \ + MULTITEAM_CENTER(1, CENTER_VIP_BEGIN_, 4, 0, 0, "", CPID_VIP, "4 0", _("^BGYou are the ^TC^TT^BG VIP!\n^F2Survive or your team will lose!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_VIP_MISSING, 0, 0, "", CPID_MISSING_TEAMS, "0 0", _("^BGWaiting for VIPs...\nPickup your Soul Gem to become a VIP"), "") \ + MSG_CENTER_NOTIF(1, CENTER_VIP_MISSING_ENEMY, 0, 0, "", CPID_MISSING_TEAMS, "0 0", _("^BGWaiting for an enemy VIP"), "") \ + MSG_CENTER_NOTIF(1, CENTER_VIP_DROP, 1, 0, "s1", CPID_VIP, "0 0", _("^BG%s^F2 is no longer a VIP!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_VIP_DROP_PUNISH, 0, 1, "f1secs", CPID_VIP, "0 0", _("^BGToo many dropped gems! Dropping disabled for %s."), "") \ + MSG_CENTER_NOTIF(1, CENTER_VIP_DROP_SELF, 0, 0, "", CPID_VIP, "0 0", _("^F2You are no longer a VIP!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_VIP_PICKUP, 1, 0, "s1", CPID_VIP, "0 0", _("^BG%s^F2 is now a VIP!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_VIP_PICKUP_SELF, 0, 0, "", CPID_VIP, "0 0", _("^F2You are now a VIP!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_WEAPON_MINELAYER_LIMIT, 0, 1, "f1", NO_CPID, "0 0", _("^BGYou cannot place more than ^F2%s^BG mines at a time"), "") \ + MSG_CENTER_NOTIF(1, CENTER_JOIN_PREVENT_MINIGAME, 0, 0, "", NO_CPID, "0 0", _("^K1Cannot join given minigame session!"), "" ) #define MULTITEAM_MULTI2(default,prefix,anncepre,infopre,centerpre) \ MSG_MULTI_NOTIF(default, prefix##RED, anncepre##RED, infopre##RED, centerpre##RED) \ @@ -746,7 +859,9 @@ void Send_Notification_WOCOVA( MULTITEAM_MULTI##teams(default,prefix,anncepre,infopre,centerpre) #define MSG_MULTI_NOTIFICATIONS \ + MSG_MULTI_NOTIF(1, DEATH_MURDER_BUFF, NO_MSG, INFO_DEATH_MURDER_BUFF, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_CHEAT, NO_MSG, INFO_DEATH_MURDER_CHEAT, NO_MSG) \ + MSG_MULTI_NOTIF(1, DEATH_MURDER_CRUSH, NO_MSG, INFO_DEATH_MURDER_CRUSH, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_DROWN, NO_MSG, INFO_DEATH_MURDER_DROWN, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_FALL, NO_MSG, INFO_DEATH_MURDER_FALL, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_FIRE, NO_MSG, INFO_DEATH_MURDER_FIRE, NO_MSG) \ @@ -771,10 +886,11 @@ void Send_Notification_WOCOVA( MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_SPID_DEATH, NO_MSG, INFO_DEATH_MURDER_VH_SPID_DEATH, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_SPID_MINIGUN, NO_MSG, INFO_DEATH_MURDER_VH_SPID_MINIGUN, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_SPID_ROCKET, NO_MSG, INFO_DEATH_MURDER_VH_SPID_ROCKET, NO_MSG) \ + MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_TANK_DEATH, NO_MSG, INFO_DEATH_MURDER_VH_TANK_DEATH, NO_MSG) \ + MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_TANKLL48, NO_MSG, INFO_DEATH_MURDER_VH_TANKLL48, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_WAKI_DEATH, NO_MSG, INFO_DEATH_MURDER_VH_WAKI_DEATH, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_WAKI_GUN, NO_MSG, INFO_DEATH_MURDER_VH_WAKI_GUN, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_WAKI_ROCKET, NO_MSG, INFO_DEATH_MURDER_VH_WAKI_ROCKET, NO_MSG) \ - MSG_MULTI_NOTIF(1, DEATH_MURDER_VENGEANCE, NO_MSG, INFO_DEATH_MURDER_VENGEANCE, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_VOID, NO_MSG, INFO_DEATH_MURDER_VOID, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_SELF_AUTOTEAMCHANGE, NO_MSG, INFO_DEATH_SELF_AUTOTEAMCHANGE, CENTER_DEATH_SELF_AUTOTEAMCHANGE) \ MSG_MULTI_NOTIF(1, DEATH_SELF_BETRAYAL, NO_MSG, INFO_DEATH_SELF_BETRAYAL, CENTER_DEATH_SELF_BETRAYAL) \ @@ -786,11 +902,23 @@ void Send_Notification_WOCOVA( MSG_MULTI_NOTIF(1, DEATH_SELF_FIRE, NO_MSG, INFO_DEATH_SELF_FIRE, CENTER_DEATH_SELF_FIRE) \ MSG_MULTI_NOTIF(1, DEATH_SELF_GENERIC, NO_MSG, INFO_DEATH_SELF_GENERIC, CENTER_DEATH_SELF_GENERIC) \ MSG_MULTI_NOTIF(1, DEATH_SELF_LAVA, NO_MSG, INFO_DEATH_SELF_LAVA, CENTER_DEATH_SELF_LAVA) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_MON_AFRIT, NO_MSG, INFO_DEATH_SELF_MON_AFRIT, CENTER_DEATH_SELF_MONSTER) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_MON_CREEPER, NO_MSG, INFO_DEATH_SELF_MON_CREEPER, CENTER_DEATH_SELF_MONSTER) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_MON_DEMON_JUMP, NO_MSG, INFO_DEATH_SELF_MON_DEMON_JUMP, CENTER_DEATH_SELF_MONSTER) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_MON_DEMON_MELEE, NO_MSG, INFO_DEATH_SELF_MON_DEMON_MELEE, CENTER_DEATH_SELF_MONSTER) \ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_MAGE, NO_MSG, INFO_DEATH_SELF_MON_MAGE, CENTER_DEATH_SELF_MONSTER) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_MON_OGRE_GRENADE, NO_MSG, INFO_DEATH_SELF_MON_OGRE_GRENADE, CENTER_DEATH_SELF_MONSTER) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_MON_OGRE_MACHINEGUN, NO_MSG, INFO_DEATH_SELF_MON_OGRE_MACHINEGUN, CENTER_DEATH_SELF_MONSTER) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_MON_OGRE_MELEE, NO_MSG, INFO_DEATH_SELF_MON_OGRE_MELEE, CENTER_DEATH_SELF_MONSTER) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ROTFISH, NO_MSG, INFO_DEATH_SELF_MON_ROTFISH_MELEE, CENTER_DEATH_SELF_MONSTER) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ROTTWEILER, NO_MSG, INFO_DEATH_SELF_MON_ROTTWEILER, CENTER_DEATH_SELF_MONSTER) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SCRAG, NO_MSG, INFO_DEATH_SELF_MON_SCRAG, CENTER_DEATH_SELF_MONSTER) \ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SHAMBLER_CLAW, NO_MSG, INFO_DEATH_SELF_MON_SHAMBLER_CLAW, CENTER_DEATH_SELF_MONSTER) \ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SHAMBLER_SMASH, NO_MSG, INFO_DEATH_SELF_MON_SHAMBLER_SMASH, CENTER_DEATH_SELF_MONSTER) \ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SHAMBLER_ZAP, NO_MSG, INFO_DEATH_SELF_MON_SHAMBLER_ZAP, CENTER_DEATH_SELF_MONSTER) \ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SPIDER, NO_MSG, INFO_DEATH_SELF_MON_SPIDER, CENTER_DEATH_SELF_MONSTER) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_MON_VORE, NO_MSG, INFO_DEATH_SELF_MON_VORE, CENTER_DEATH_SELF_MONSTER) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_MON_VORE_MELEE, NO_MSG, INFO_DEATH_SELF_MON_VORE_MELEE, CENTER_DEATH_SELF_MONSTER) \ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_WYVERN, NO_MSG, INFO_DEATH_SELF_MON_WYVERN, CENTER_DEATH_SELF_MONSTER) \ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ZOMBIE_JUMP, NO_MSG, INFO_DEATH_SELF_MON_ZOMBIE_JUMP, CENTER_DEATH_SELF_MONSTER) \ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ZOMBIE_MELEE, NO_MSG, INFO_DEATH_SELF_MON_ZOMBIE_MELEE, CENTER_DEATH_SELF_MONSTER) \ @@ -818,7 +946,7 @@ void Send_Notification_WOCOVA( MSG_MULTI_NOTIF(1, DEATH_SELF_TURRET_PLASMA, NO_MSG, INFO_DEATH_SELF_TURRET_PLASMA, CENTER_DEATH_SELF_TURRET) \ MSG_MULTI_NOTIF(1, DEATH_SELF_TURRET_TESLA, NO_MSG, INFO_DEATH_SELF_TURRET_TESLA, CENTER_DEATH_SELF_TURRET) \ MSG_MULTI_NOTIF(1, DEATH_SELF_TURRET_WALK_GUN, NO_MSG, INFO_DEATH_SELF_TURRET_WALK_GUN, CENTER_DEATH_SELF_TURRET_WALK) \ - MSG_MULTI_NOTIF(1, DEATH_SELF_TURRET_WALK_MEELE, NO_MSG, INFO_DEATH_SELF_TURRET_WALK_MEELE, CENTER_DEATH_SELF_TURRET_WALK) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_TURRET_WALK_MELEE, NO_MSG, INFO_DEATH_SELF_TURRET_WALK_MELEE, CENTER_DEATH_SELF_TURRET_WALK) \ MSG_MULTI_NOTIF(1, DEATH_SELF_TURRET_WALK_ROCKET, NO_MSG, INFO_DEATH_SELF_TURRET_WALK_ROCKET, CENTER_DEATH_SELF_TURRET_WALK) \ MSG_MULTI_NOTIF(1, DEATH_SELF_VH_BUMB_DEATH, NO_MSG, INFO_DEATH_SELF_VH_BUMB_DEATH, CENTER_DEATH_SELF_VH_BUMB_DEATH) \ MSG_MULTI_NOTIF(1, DEATH_SELF_VH_CRUSH, NO_MSG, INFO_DEATH_SELF_VH_CRUSH, CENTER_DEATH_SELF_VH_CRUSH) \ @@ -826,6 +954,7 @@ void Send_Notification_WOCOVA( MSG_MULTI_NOTIF(1, DEATH_SELF_VH_RAPT_DEATH, NO_MSG, INFO_DEATH_SELF_VH_RAPT_DEATH, CENTER_DEATH_SELF_VH_RAPT_DEATH) \ MSG_MULTI_NOTIF(1, DEATH_SELF_VH_SPID_DEATH, NO_MSG, INFO_DEATH_SELF_VH_SPID_DEATH, CENTER_DEATH_SELF_VH_SPID_DEATH) \ MSG_MULTI_NOTIF(1, DEATH_SELF_VH_SPID_ROCKET, NO_MSG, INFO_DEATH_SELF_VH_SPID_ROCKET, CENTER_DEATH_SELF_VH_SPID_ROCKET) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_VH_TANK_DEATH, NO_MSG, INFO_DEATH_SELF_VH_TANK_DEATH, CENTER_DEATH_SELF_VH_TANK_DEATH) \ MSG_MULTI_NOTIF(1, DEATH_SELF_VH_WAKI_DEATH, NO_MSG, INFO_DEATH_SELF_VH_WAKI_DEATH, CENTER_DEATH_SELF_VH_WAKI_DEATH) \ MSG_MULTI_NOTIF(1, DEATH_SELF_VH_WAKI_ROCKET, NO_MSG, INFO_DEATH_SELF_VH_WAKI_ROCKET, CENTER_DEATH_SELF_VH_WAKI_ROCKET) \ MSG_MULTI_NOTIF(1, DEATH_SELF_VOID, NO_MSG, INFO_DEATH_SELF_VOID, CENTER_DEATH_SELF_VOID) \ @@ -839,6 +968,7 @@ void Send_Notification_WOCOVA( MSG_MULTI_NOTIF(1, ITEM_WEAPON_UNAVAILABLE, NO_MSG, INFO_ITEM_WEAPON_UNAVAILABLE, CENTER_ITEM_WEAPON_UNAVAILABLE) \ MSG_MULTI_NOTIF(1, MULTI_COINTOSS, NO_MSG, INFO_COINTOSS, CENTER_COINTOSS) \ MSG_MULTI_NOTIF(1, MULTI_COUNTDOWN_BEGIN, ANNCE_BEGIN, NO_MSG, CENTER_COUNTDOWN_BEGIN) \ + MSG_MULTI_NOTIF(1, MULTI_HEADSHOT, ANNCE_HEADSHOT,NO_MSG, CENTER_HEADSHOT) \ MSG_MULTI_NOTIF(1, MULTI_INSTAGIB_FINDAMMO, ANNCE_NUM_10, NO_MSG, CENTER_INSTAGIB_FINDAMMO_FIRST) \ MSG_MULTI_NOTIF(1, WEAPON_ACCORDEON_MURDER, NO_MSG, INFO_WEAPON_ACCORDEON_MURDER, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_ACCORDEON_SUICIDE, NO_MSG, INFO_WEAPON_ACCORDEON_SUICIDE, CENTER_DEATH_SELF_GENERIC) \ @@ -867,8 +997,11 @@ void Send_Notification_WOCOVA( MSG_MULTI_NOTIF(1, WEAPON_HMG_MURDER_SNIPE, NO_MSG, INFO_WEAPON_HMG_MURDER_SNIPE, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_HMG_MURDER_SPRAY, NO_MSG, INFO_WEAPON_HMG_MURDER_SPRAY, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_HOOK_MURDER, NO_MSG, INFO_WEAPON_HOOK_MURDER, NO_MSG) \ + MSG_MULTI_NOTIF(1, WEAPON_HOOK_MURDER_GRAVITYBOMB, NO_MSG, INFO_WEAPON_HOOK_MURDER_GRAVITYBOMB, NO_MSG) \ + MSG_MULTI_NOTIF(1, WEAPON_HOOK_MURDER_LASER, NO_MSG, INFO_WEAPON_HOOK_MURDER_LASER, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_KLEINBOTTLE_MURDER, NO_MSG, INFO_WEAPON_KLEINBOTTLE_MURDER, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_KLEINBOTTLE_SUICIDE, NO_MSG, INFO_WEAPON_KLEINBOTTLE_SUICIDE, CENTER_DEATH_SELF_GENERIC) \ + MSG_MULTI_NOTIF(1, WEAPON_LIGHTSABRE_MURDER, NO_MSG, INFO_WEAPON_LIGHTSABRE_MURDER, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_MACHINEGUN_MURDER_SNIPE, NO_MSG, INFO_WEAPON_MACHINEGUN_MURDER_SNIPE, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_MACHINEGUN_MURDER_SPRAY, NO_MSG, INFO_WEAPON_MACHINEGUN_MURDER_SPRAY, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_MINELAYER_LIMIT, NO_MSG, INFO_WEAPON_MINELAYER_LIMIT, CENTER_WEAPON_MINELAYER_LIMIT) \ @@ -878,6 +1011,7 @@ void Send_Notification_WOCOVA( MSG_MULTI_NOTIF(1, WEAPON_MORTAR_MURDER_EXPLODE, NO_MSG, INFO_WEAPON_MORTAR_MURDER_EXPLODE, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_MORTAR_SUICIDE_BOUNCE, NO_MSG, INFO_WEAPON_MORTAR_SUICIDE_BOUNCE, CENTER_DEATH_SELF_GENERIC) \ MSG_MULTI_NOTIF(1, WEAPON_MORTAR_SUICIDE_EXPLODE, NO_MSG, INFO_WEAPON_MORTAR_SUICIDE_EXPLODE, CENTER_DEATH_SELF_GENERIC) \ + MSG_MULTI_NOTIF(1, WEAPON_REVOLVER_MURDER, NO_MSG, INFO_WEAPON_REVOLVER_MURDER, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_RIFLE_MURDER, NO_MSG, INFO_WEAPON_RIFLE_MURDER, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_RIFLE_MURDER_HAIL, NO_MSG, INFO_WEAPON_RIFLE_MURDER_HAIL, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_RIFLE_MURDER_HAIL_PIERCING, NO_MSG, INFO_WEAPON_RIFLE_MURDER_HAIL_PIERCING, NO_MSG) \ @@ -897,6 +1031,8 @@ void Send_Notification_WOCOVA( MSG_MULTI_NOTIF(1, WEAPON_TUBA_MURDER, NO_MSG, INFO_WEAPON_TUBA_MURDER, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_TUBA_SUICIDE, NO_MSG, INFO_WEAPON_TUBA_SUICIDE, CENTER_DEATH_SELF_GENERIC) \ MSG_MULTI_NOTIF(1, WEAPON_VAPORIZER_MURDER, NO_MSG, INFO_WEAPON_VAPORIZER_MURDER, NO_MSG) \ + MSG_MULTI_NOTIF(1, WEAPON_VAPORIZER_MURDER_CHARGE, NO_MSG, INFO_WEAPON_VAPORIZER_MURDER_CHARGE, NO_MSG) \ + MSG_MULTI_NOTIF(1, WEAPON_VAPORIZER_SUICIDE, NO_MSG, INFO_WEAPON_VAPORIZER_SUICIDE, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_VORTEX_MURDER, NO_MSG, INFO_WEAPON_VORTEX_MURDER, NO_MSG) #define MULTITEAM_CHOICE2(default,challow,prefix,chtype,optiona,optionb) \ @@ -915,13 +1051,18 @@ void Send_Notification_WOCOVA( MULTITEAM_CHOICE##teams(default,challow,prefix,chtype,optiona,optionb) #define MSG_CHOICE_NOTIFICATIONS \ - MULTITEAM_CHOICE(1, 2, CHOICE_CTF_CAPTURE_BROKEN_, 2, MSG_INFO, INFO_CTF_CAPTURE_, INFO_CTF_CAPTURE_BROKEN_) \ - MULTITEAM_CHOICE(1, 2, CHOICE_CTF_CAPTURE_TIME_, 2, MSG_INFO, INFO_CTF_CAPTURE_, INFO_CTF_CAPTURE_TIME_) \ - MULTITEAM_CHOICE(1, 2, CHOICE_CTF_CAPTURE_UNBROKEN_, 2, MSG_INFO, INFO_CTF_CAPTURE_, INFO_CTF_CAPTURE_UNBROKEN_) \ - MSG_CHOICE_NOTIF(1, 2, CHOICE_CTF_PICKUP_TEAM, MSG_CENTER, CENTER_CTF_PICKUP_TEAM, CENTER_CTF_PICKUP_TEAM_VERBOSE) \ + MULTITEAM_CHOICE(1, 2, CHOICE_CTF_CAPTURE_BROKEN_, 4, MSG_INFO, INFO_CTF_CAPTURE_, INFO_CTF_CAPTURE_BROKEN_) \ + MULTITEAM_CHOICE(1, 2, CHOICE_CTF_CAPTURE_TIME_, 4, MSG_INFO, INFO_CTF_CAPTURE_, INFO_CTF_CAPTURE_TIME_) \ + MULTITEAM_CHOICE(1, 2, CHOICE_CTF_CAPTURE_UNBROKEN_, 4, MSG_INFO, INFO_CTF_CAPTURE_, INFO_CTF_CAPTURE_UNBROKEN_) \ + MULTITEAM_CHOICE(1, 2, CHOICE_CTF_PICKUP_TEAM_, 4, MSG_CENTER, CENTER_CTF_PICKUP_TEAM_, CENTER_CTF_PICKUP_TEAM_VERBOSE_) \ + MSG_CHOICE_NOTIF(1, 2, CHOICE_CTF_PICKUP_TEAM_NEUTRAL, MSG_CENTER, CENTER_CTF_PICKUP_TEAM_NEUTRAL, CENTER_CTF_PICKUP_TEAM_VERBOSE_NEUTRAL) \ MSG_CHOICE_NOTIF(1, 2, CHOICE_CTF_PICKUP_ENEMY, MSG_CENTER, CENTER_CTF_PICKUP_ENEMY, CENTER_CTF_PICKUP_ENEMY_VERBOSE) \ + MSG_CHOICE_NOTIF(1, 2, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, MSG_CENTER, CENTER_CTF_PICKUP_ENEMY_NEUTRAL, CENTER_CTF_PICKUP_ENEMY_NEUTRAL_VERBOSE) \ + MSG_CHOICE_NOTIF(1, 2, CHOICE_CTF_PICKUP_ENEMY_TEAM, MSG_CENTER, CENTER_CTF_PICKUP_ENEMY_TEAM, CENTER_CTF_PICKUP_ENEMY_TEAM_VERBOSE) \ MSG_CHOICE_NOTIF(1, 1, CHOICE_FRAG, MSG_CENTER, CENTER_DEATH_MURDER_FRAG, CENTER_DEATH_MURDER_FRAG_VERBOSE) \ MSG_CHOICE_NOTIF(1, 1, CHOICE_FRAGGED, MSG_CENTER, CENTER_DEATH_MURDER_FRAGGED, CENTER_DEATH_MURDER_FRAGGED_VERBOSE) \ + MULTITEAM_CHOICE(1, 2, CHOICE_KEYHUNT_PICKUP_TEAM_, 4, MSG_CENTER, CENTER_KEYHUNT_PICKUP_TEAM_, CENTER_KEYHUNT_PICKUP_TEAM_VERBOSE_) \ + MULTITEAM_CHOICE(1, 2, CHOICE_KEYHUNT_START_TEAM_, 4, MSG_CENTER, CENTER_KEYHUNT_START_TEAM_, CENTER_KEYHUNT_START_TEAM_VERBOSE_) \ MSG_CHOICE_NOTIF(1, 1, CHOICE_TYPEFRAG, MSG_CENTER, CENTER_DEATH_MURDER_TYPEFRAG, CENTER_DEATH_MURDER_TYPEFRAG_VERBOSE) \ MSG_CHOICE_NOTIF(1, 1, CHOICE_TYPEFRAGGED, MSG_CENTER, CENTER_DEATH_MURDER_TYPEFRAGGED, CENTER_DEATH_MURDER_TYPEFRAGGED_VERBOSE) //MSG_CHOICE_NOTIF(2, CHOICE_) @@ -1011,6 +1152,8 @@ var float autocvar_notification_show_sprees_center_specialonly = TRUE; item_centime: amount of time to display weapon message in centerprint item_buffname: return full name of a buff from buffid death_team: show the full name of the team a player is switching from + minigame1_name: return human readable name of a minigame from its id(s1) + minigame1_d: return descriptor name of a minigame from its id(s1) */ #define NOTIF_MAX_ARGS 7 @@ -1063,17 +1206,22 @@ string arg_slot[NOTIF_MAX_ARGS]; ARG_CASE(ARG_CS_SV, "spree_lost", (autocvar_notification_show_sprees ? notif_arg_spree_inf(-2, "", "", f1) : "")) \ ARG_CASE(ARG_CS_SV, "item_wepname", WEP_NAME(f1)) \ ARG_CASE(ARG_CS_SV, "item_buffname", sprintf("%s%s", rgb_to_hexcolor(Buff_Color(f1)), Buff_PrettyName(f1))) \ + ARG_CASE(ARG_CS_SV, "f3buffname", sprintf("%s%s", rgb_to_hexcolor(Buff_Color(f3)), Buff_PrettyName(f3))) \ ARG_CASE(ARG_CS_SV, "item_wepammo", (s1 != "" ? sprintf(_(" with %s"), s1) : "")) \ ARG_CASE(ARG_DC, "item_centime", ftos(autocvar_notification_item_centerprinttime)) \ ARG_CASE(ARG_SV, "death_team", Team_ColoredFullName(f1)) \ - ARG_CASE(ARG_CS, "death_team", Team_ColoredFullName(f1 - 1)) + ARG_CASE(ARG_CS, "death_team", Team_ColoredFullName(f1 - 1)) \ + ARG_CASE(ARG_CS_SV_HA, "minigame1_name",find(world,netname,s1).descriptor.message) \ + ARG_CASE(ARG_CS_SV_HA, "minigame1_d", find(world,netname,s1).descriptor.netname) #define NOTIF_HIT_MAX(count,funcname) if(sel_num == count) { backtrace(sprintf("%s: Hit maximum arguments!\n", funcname)); break; } #define NOTIF_HIT_UNKNOWN(token,funcname) { backtrace(sprintf("%s: Hit unknown token in selected string! '%s'\n", funcname, selected)); break; } #define KILL_SPREE_LIST \ + SPREE_ITEM(2, 02, _("MULTI FRAG! "), _("%s^K1 made a MULTI FRAG! %s^BG"), _("%s^K1 made a DOUBLE SCORE! %s^BG")) \ SPREE_ITEM(3, 03, _("TRIPLE FRAG! "), _("%s^K1 made a TRIPLE FRAG! %s^BG"), _("%s^K1 made a TRIPLE SCORE! %s^BG")) \ SPREE_ITEM(5, 05, _("RAGE! "), _("%s^K1 unlocked RAGE! %s^BG"), _("%s^K1 made FIVE SCORES IN A ROW! %s^BG")) \ + SPREE_ITEM(8, 08, _("RAMPAGE! "), _("%s^K1 is on a RAMPAGE! %s^BG"), _("%s^K1 made EIGHT SCORES IN A ROW! %s^BG")) \ SPREE_ITEM(10, 10, _("MASSACRE! "), _("%s^K1 started a MASSACRE! %s^BG"), _("%s^K1 made TEN SCORES IN A ROW! %s^BG")) \ SPREE_ITEM(15, 15, _("MAYHEM! "), _("%s^K1 executed MAYHEM! %s^BG"), _("%s^K1 made FIFTEEN SCORES IN A ROW! %s^BG")) \ SPREE_ITEM(20, 20, _("BERSERKER! "), _("%s^K1 is a BERSERKER! %s^BG"), _("%s^K1 made TWENTY SCORES IN A ROW! %s^BG")) \ @@ -1279,11 +1427,11 @@ float NOTIF_CHOICE_COUNT; float NOTIF_CPID_COUNT; // notification limits -- INCREASE AS NECESSARY -#define NOTIF_ANNCE_MAX 100 -#define NOTIF_INFO_MAX 300 -#define NOTIF_CENTER_MAX 200 +#define NOTIF_ANNCE_MAX 200 +#define NOTIF_INFO_MAX 400 +#define NOTIF_CENTER_MAX 350 #define NOTIF_MULTI_MAX 200 -#define NOTIF_CHOICE_MAX 20 +#define NOTIF_CHOICE_MAX 50 // notification entities entity msg_annce_notifs[NOTIF_ANNCE_MAX]; @@ -1425,6 +1573,50 @@ float notif_global_error; } \ ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name); +.string nent_iconargs; +#define MULTIICON_INFO(default,name,strnum,flnum,args,hudargs,iconargs,icon,normal,gentle) \ + NOTIF_ADD_AUTOCVAR(name, default) \ + float name; \ + void RegisterNotification_##name() \ + { \ + SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_INFO_COUNT) \ + CHECK_MAX_COUNT(name, NOTIF_INFO_MAX, NOTIF_INFO_COUNT, "MSG_INFO") \ + Create_Notification_Entity( \ + /* COMMON ======================== */ \ + default, /* var_default */ \ + ACVNN(name), /* var_cvar */ \ + MSG_INFO, /* typeid */ \ + name, /* nameid */ \ + strtoupper(#name), /* namestring */ \ + strnum, /* strnum */ \ + flnum, /* flnum */ \ + /* ANNCE =========== */ \ + NO_MSG, /* channel */ \ + "", /* snd */ \ + NO_MSG, /* vol */ \ + NO_MSG, /* position */ \ + /* INFO & CENTER === */ \ + args, /* args */ \ + hudargs, /* hudargs */ \ + icon, /* icon */ \ + NO_MSG, /* cpid */ \ + "", /* durcnt */ \ + normal, /* normal */ \ + gentle, /* gentle */ \ + /* MULTI ============= */ \ + NO_MSG, /* anncename */ \ + NO_MSG, /* infoname */ \ + NO_MSG, /* centername */ \ + /* CHOICE ============== */ \ + NO_MSG, /* challow_def */ \ + NO_MSG, /* challow_var */ \ + NO_MSG, /* chtype */ \ + NO_MSG, /* optiona */ \ + NO_MSG); /* optionb */ \ + msg_info_notifs[name - 1].nent_iconargs = iconargs; \ + } \ + ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name); + #define MSG_CENTER_NOTIF(default,name,strnum,flnum,args,cpid,durcnt,normal,gentle) \ NOTIF_ADD_AUTOCVAR(name, default) \ float name; \ diff --git a/qcsrc/common/playerstats.qc b/qcsrc/common/playerstats.qc index 2c4a355e0..a624846f8 100644 --- a/qcsrc/common/playerstats.qc +++ b/qcsrc/common/playerstats.qc @@ -83,7 +83,7 @@ void PlayerStats_GameReport_AddEvent(string event_id) // referred to by PS_GR_P_ADDVAL and PS_GR_T_ADDVAL float PlayerStats_GameReport_Event(string prefix, string event_id, float value) { - if((prefix == "") || PS_GR_OUT_DB < 0) { return 0; } + if((prefix == "") || (event_id == "") || PS_GR_OUT_DB < 0) { return 0; } string key = sprintf("%s:%s", prefix, event_id); float val = stof(db_get(PS_GR_OUT_DB, key)); diff --git a/qcsrc/common/playerstats.qh b/qcsrc/common/playerstats.qh index ab4bc166f..5f26205d3 100644 --- a/qcsrc/common/playerstats.qh +++ b/qcsrc/common/playerstats.qh @@ -39,8 +39,10 @@ const string PLAYERSTATS_SCOREBOARD_POS = "scoreboardpos"; const string PLAYERSTATS_TOTAL = "total-"; const string PLAYERSTATS_SCOREBOARD = "scoreboard-"; +const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_2 = ""; // empty string, we don't want this sent const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3 = "achievement-kill-spree-3"; const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5 = "achievement-kill-spree-5"; +const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_8 = ""; // same story here const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10 = "achievement-kill-spree-10"; const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_15 = "achievement-kill-spree-15"; const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_20 = "achievement-kill-spree-20"; diff --git a/qcsrc/common/stats.qh b/qcsrc/common/stats.qh index 295491710..cd141c83b 100644 --- a/qcsrc/common/stats.qh +++ b/qcsrc/common/stats.qh @@ -40,8 +40,8 @@ const float STAT_CTF_STATE = 33; const float STAT_WEAPONS = 35; const float STAT_SWITCHWEAPON = 36; const float STAT_GAMESTARTTIME = 37; -const float STAT_STRENGTH_FINISHED = 38; -const float STAT_INVINCIBLE_FINISHED = 39; +// 38 empty? +// 39 empty? // 40 empty? const float STAT_ARC_HEAT = 41; const float STAT_PRESSED_KEYS = 42; @@ -80,28 +80,24 @@ const float STAT_WEAPONS2 = 74; const float STAT_WEAPONS3 = 75; const float STAT_MONSTERS_TOTAL = 76; const float STAT_MONSTERS_KILLED = 77; -const float STAT_BUFFS = 78; -const float STAT_NADE_BONUS = 79; -const float STAT_NADE_BONUS_TYPE = 80; -const float STAT_NADE_BONUS_SCORE = 81; -const float STAT_HEALING_ORB = 82; -const float STAT_HEALING_ORB_ALPHA = 83; -const float STAT_PLASMA = 84; -const float STAT_OK_AMMO_CHARGE = 85; -const float STAT_OK_AMMO_CHARGEPOOL = 86; -// 87 empty? -// 88 empty? -// 89 empty? -// 90 empty? -// 91 empty? -// 92 empty? -// 93 empty? -// 94 empty? -// 95 empty? -// 96 empty? -// 97 empty? -// 98 empty? -// 99 empty? +const float STAT_OK_AMMO_CHARGE = 78; +const float STAT_OK_AMMO_CHARGEPOOL = 79; +const float STAT_NADE_BONUS = 80; +const float STAT_NADE_BONUS_TYPE = 81; +const float STAT_NADE_BONUS_SCORE = 82; +const float STAT_HEALING_ORB = 83; +const float STAT_HEALING_ORB_ALPHA = 84; +const float STAT_WEAPONSINMAP = 90; +const float STAT_WEAPONSINMAP2 = 91; +const float STAT_WEAPONSINMAP3 = 92; +const float STAT_SUPERCELLS = 93; +const float STAT_CAPTURE_PROGRESS = 94; +const float STAT_PRISONED = 95; +const float STAT_ROUNDLOST = 96; +const float STAT_CTF_FLAGSTATUS = 97; +const float STAT_KH_KEYSTATUS = 97; +const float STAT_BUFFS = 98; +const float STAT_DISCO_MODE = 99; /* The following stats change depending on the gamemode, so can share the same ID */ @@ -127,18 +123,12 @@ const float STAT_VIP_BLUE = 102; const float STAT_VIP_YELLOW = 103; const float STAT_VIP_PINK = 104; -// key hunt -const float STAT_KH_REDKEY_TEAM = 100; -const float STAT_KH_BLUEKEY_TEAM = 101; -const float STAT_KH_YELLOWKEY_TEAM = 102; -const float STAT_KH_PINKKEY_TEAM = 103; - /* Gamemode-specific stats end here */ const float STAT_FROZEN = 105; const float STAT_REVIVE_PROGRESS = 106; -// 107 empty? +const float STAT_PLASMA = 107; // 108 empty? // 109 empty? // 110 empty? diff --git a/qcsrc/common/teams.qh b/qcsrc/common/teams.qh index 069904290..77dc7b94f 100644 --- a/qcsrc/common/teams.qh +++ b/qcsrc/common/teams.qh @@ -157,8 +157,8 @@ float Team_TeamToNumber(float teamid) #define TCR(input,teamcolor,teamtext) strreplace("^TC", teamcolor, strreplace("^TT", teamtext, input)) // safe team comparisons -#define SAME_TEAM(a,b) (teamplay ? ((a.team == b.team) ? 1 : 0) : ((a == b) ? 1 : 0)) -#define DIFF_TEAM(a,b) (teamplay ? ((a.team != b.team) ? 1 : 0) : ((a != b) ? 1 : 0)) +#define SAME_TEAM(a,b) (g_infection ? ((a.inf_team == b.inf_team) ? 1 : 0) : (teamplay ? ((a.team == b.team) ? 1 : 0) : ((a == b) ? 1 : 0))) +#define DIFF_TEAM(a,b) (g_infection ? ((a.inf_team != b.inf_team) ? 1 : 0) : (teamplay ? ((a.team != b.team) ? 1 : 0) : ((a != b) ? 1 : 0))) // used for notification system multi-team identifiers #define APP_TEAM_NUM_2(num,prefix) ((num == NUM_TEAM_1) ? prefix##RED : prefix##BLUE) diff --git a/qcsrc/common/turrets/all.qh b/qcsrc/common/turrets/all.qh new file mode 100644 index 000000000..04bb10f6a --- /dev/null +++ b/qcsrc/common/turrets/all.qh @@ -0,0 +1,12 @@ +#include "unit/ewheel.qc" +#include "unit/flac.qc" +#include "unit/fusionreactor.qc" +#include "unit/hellion.qc" +#include "unit/hk.qc" +#include "unit/machinegun.qc" +#include "unit/mlrs.qc" +#include "unit/phaser.qc" +#include "unit/plasma.qc" +#include "unit/plasma_dual.qc" +#include "unit/tesla.qc" +#include "unit/walker.qc" diff --git a/qcsrc/server/tturrets/units/unit_checkpoint.qc b/qcsrc/common/turrets/checkpoint.qc similarity index 97% rename from qcsrc/server/tturrets/units/unit_checkpoint.qc rename to qcsrc/common/turrets/checkpoint.qc index c919601a3..dde5c2829 100644 --- a/qcsrc/server/tturrets/units/unit_checkpoint.qc +++ b/qcsrc/common/turrets/checkpoint.qc @@ -12,7 +12,6 @@ */ .entity pathgoal; -.entity pathcurrent; /* entity path_makeorcache(entity forwho,entity start, entity end) @@ -38,7 +37,7 @@ void turret_checkpoint_think() { if(self.enemy) te_lightning1(self,self.origin, self.enemy.origin); - + self.nextthink = time + 0.25; } #endif @@ -72,7 +71,7 @@ void spawnfunc_turret_checkpoint() { setorigin(self,self.origin); self.think = turret_checkpoint_init; - self.nextthink = time + 0.2; + self.nextthink = time + 0.2; } // Compat. diff --git a/qcsrc/common/turrets/cl_turrets.qc b/qcsrc/common/turrets/cl_turrets.qc new file mode 100644 index 000000000..3365b9202 --- /dev/null +++ b/qcsrc/common/turrets/cl_turrets.qc @@ -0,0 +1,446 @@ +void turret_remove() +{ + remove(self.tur_head); + //remove(self.enemy); + self.tur_head = world; +} + +.vector glowmod; +void turret_changeteam() +{ + self.glowmod = Team_ColorRGB(self.team - 1) * 2; + self.teamradar_color = Team_ColorRGB(self.team - 1); + + if(self.team) + self.colormap = 1024 + (self.team - 1) * 17; + + self.tur_head.colormap = self.colormap; + self.tur_head.glowmod = self.glowmod; + +} + +void turret_head_draw() +{ + self.drawmask = MASK_NORMAL; +} + +void turret_draw() +{ + float dt; + + dt = time - self.move_time; + self.move_time = time; + if(dt <= 0) + return; + + self.tur_head.angles += dt * self.tur_head.move_avelocity; + + if (self.health < 127) + { + dt = random(); + + if(dt < 0.03) + te_spark(self.origin + '0 0 40', randomvec() * 256 + '0 0 256', 16); + } + + if(self.health < 85) + if(dt < 0.01) + pointparticles(particleeffectnum("smoke_large"), (self.origin + (randomvec() * 80)), '0 0 0', 1); + + if(self.health < 32) + if(dt < 0.015) + pointparticles(particleeffectnum("smoke_small"), (self.origin + (randomvec() * 80)), '0 0 0', 1); + +} + +void turret_draw2d() +{ + if(self.netname == "") + return; + + if(!autocvar_g_waypointsprite_turrets) + return; + + if(autocvar_cl_hidewaypoints) + return; + + float dist = vlen(self.origin - view_origin); + float t = (GetPlayerColor(player_localnum) + 1); + + vector o; + string txt; + + if(autocvar_cl_vehicles_hud_tactical) + if(dist < 10240 && t != self.team) + { + // TODO: Vehicle tactical hud + o = project_3d_to_2d(self.origin + '0 0 32'); + if(o_z < 0 + || o_x < (vid_conwidth * waypointsprite_edgeoffset_left) + || o_y < (vid_conheight * waypointsprite_edgeoffset_top) + || o_x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) + || o_y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom))) + return; // Dont draw wp's for turrets out of view + o_z = 0; + if(hud != HUD_NORMAL) + { + if((get_turretinfo(self.turretid)).spawnflags & TUR_FLAG_MOVE) + txt = "gfx/vehicles/vth-mover.tga"; + else + txt = "gfx/vehicles/vth-stationary.tga"; + + vector pz = drawgetimagesize(txt) * 0.25; + drawpic(o - pz * 0.5, txt, pz , '1 1 1', 0.75, DRAWFLAG_NORMAL); + } + } + + if(dist > self.maxdistance) + return; + + string spriteimage = self.netname; + float a = self.alpha * autocvar_hud_panel_fg_alpha; + vector rgb = spritelookupcolor(spriteimage, self.teamradar_color); + + + if(self.maxdistance > waypointsprite_normdistance) + a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent); + else if(self.maxdistance > 0) + a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha; + + if(rgb == '0 0 0') + { + self.teamradar_color = '1 0 1'; + printf("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage); + } + + txt = self.netname; + if(autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam) + txt = _("Spam"); + else + txt = spritelookuptext(spriteimage); + + if(time - floor(time) > 0.5 && t == self.team) + { + if(self.helpme && time < self.helpme) + { + a *= SPRITE_HELPME_BLINK; + txt = sprintf(_("%s under attack!"), txt); + } + else + a *= spritelookupblinkvalue(spriteimage); + } + + if(autocvar_g_waypointsprite_uppercase) + txt = strtoupper(txt); + + if(a > 1) + { + rgb *= a; + a = 1; + } + + if(a <= 0) + return; + + rgb = fixrgbexcess(rgb); + + o = project_3d_to_2d(self.origin + '0 0 64'); + if(o_z < 0 + || o_x < (vid_conwidth * waypointsprite_edgeoffset_left) + || o_y < (vid_conheight * waypointsprite_edgeoffset_top) + || o_x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) + || o_y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom))) + return; // Dont draw wp's for turrets out of view + + o_z = 0; + + float edgedistance_min, crosshairdistance; + edgedistance_min = min((o_y - (vid_conheight * waypointsprite_edgeoffset_top)), + (o_x - (vid_conwidth * waypointsprite_edgeoffset_left)), + (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o_x, + (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o_y); + + float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height); + + crosshairdistance = sqrt( pow(o_x - vid_conwidth/2, 2) + pow(o_y - vid_conheight/2, 2) ); + + t = waypointsprite_scale * vidscale; + a *= waypointsprite_alpha; + + { + a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1))); + t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1))); + } + if (edgedistance_min < waypointsprite_edgefadedistance) { + a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1))); + t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1))); + } + if(crosshairdistance < waypointsprite_crosshairfadedistance) { + a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1))); + t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1))); + } + + o = drawspritearrow(o, M_PI, rgb, a, SPRITE_ARROW_SCALE * t); + o = drawspritetext(o, M_PI, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt); + drawhealthbar( + o, + 0, + self.health / 255, + '0 0 0', + '0 0 0', + 0.5 * SPRITE_HEALTHBAR_WIDTH * t, + 0.5 * SPRITE_HEALTHBAR_HEIGHT * t, + SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize, + SPRITE_HEALTHBAR_BORDER * t, + 0, + rgb, + a * SPRITE_HEALTHBAR_BORDERALPHA, + rgb, + a * SPRITE_HEALTHBAR_HEALTHALPHA, + DRAWFLAG_NORMAL + ); +} + +void(entity e, entity tagentity, string tagname) setattachment = #443; +void turret_construct() +{ + entity tur = get_turretinfo(self.turretid); + + if(self.tur_head == world) + self.tur_head = spawn(); + + self.netname = TUR_NAME(self.turretid); + + setorigin(self, self.origin); + setmodel(self, tur.model); + setmodel(self.tur_head, tur.head_model); + setsize(self, tur.mins, tur.maxs); + setsize(self.tur_head, '0 0 0', '0 0 0'); + + if(self.turretid == TUR_EWHEEL) + setattachment(self.tur_head, self, ""); + else + setattachment(self.tur_head, self, "tag_head"); + + self.tur_head.classname = "turret_head"; + self.tur_head.owner = self; + self.tur_head.move_movetype = MOVETYPE_NOCLIP; + self.move_movetype = MOVETYPE_NOCLIP; + self.tur_head.angles = self.angles; + self.health = 255; + self.solid = SOLID_BBOX; + self.tur_head.solid = SOLID_NOT; + self.movetype = MOVETYPE_NOCLIP; + self.tur_head.movetype = MOVETYPE_NOCLIP; + self.draw = turret_draw; + self.entremove = turret_remove; + self.drawmask = MASK_NORMAL; + self.tur_head.drawmask = MASK_NORMAL; + self.anim_start_time = 0; + self.draw2d = turret_draw2d; + self.maxdistance = autocvar_g_waypointsprite_turrets_maxdist; + self.teamradar_color = '1 0 0'; + self.alpha = 1; + + TUR_ACTION(self.turretid, TR_SETUP); +} + +entity turret_gibtoss(string _model, vector _from, vector _to, vector _cmod, float _explode); +void turret_gibboom(); +void turret_gib_draw() +{ + Movetype_Physics_MatchTicrate(autocvar_cl_gibs_ticrate, autocvar_cl_gibs_sloppy); + + self.drawmask = MASK_NORMAL; + + if(self.cnt) + { + if(time >= self.nextthink) + { + turret_gibboom(); + remove(self); + } + } + else + { + self.alpha = bound(0, self.nextthink - time, 1); + if(self.alpha < ALPHA_MIN_VISIBLE) + remove(self); + } +} + +void turret_gibboom() +{ + float i; + + sound (self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); + pointparticles(particleeffectnum("rocket_explode"), self.origin, '0 0 0', 1); + + for (i = 1; i < 5; i = i + 1) + turret_gibtoss(strcat("models/turrets/head-gib", ftos(i), ".md3"), self.origin + '0 0 2', self.velocity + randomvec() * 700, '0 0 0', FALSE); +} + +entity turret_gibtoss(string _model, vector _from, vector _to, vector _cmod, float _explode) +{ + entity gib; + + traceline(_from, _to, MOVE_NOMONSTERS, world); + if(trace_startsolid) + return world; + + gib = spawn(); + setorigin(gib, _from); + setmodel(gib, _model); + gib.colormod = _cmod; + gib.solid = SOLID_CORPSE; + gib.draw = turret_gib_draw; + gib.cnt = _explode; + setsize(gib, '-1 -1 -1', '1 1 1'); + if(_explode) + { + gib.nextthink = time + 0.2 * (autocvar_cl_gibs_lifetime * (1 + prandom() * 0.15)); + gib.effects = EF_FLAME; + } + else + gib.nextthink = time + autocvar_cl_gibs_lifetime * (1 + prandom() * 0.15); + + gib.gravity = 1; + gib.move_movetype = MOVETYPE_BOUNCE; + gib.move_origin = _from; + setorigin(gib, _from); + gib.move_velocity = _to; + gib.move_avelocity = prandomvec() * 32; + gib.move_time = time; + gib.damageforcescale = 1; + gib.classname = "turret_gib"; + + return gib; +} + +void turret_die() +{ + sound (self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); + pointparticles(particleeffectnum("rocket_explode"), self.origin, '0 0 0', 1); + if (!autocvar_cl_nogibs) + { + // Base + if(self.turretid == TUR_EWHEEL) + turret_gibtoss((get_turretinfo(self.turretid)).model, self.origin + '0 0 18', self.velocity + '0 0 400' + '0.1 0.1 1' * (random() * 400), '-1 -1 -1', TRUE); + else if (self.turretid == TUR_WALKER) + turret_gibtoss((get_turretinfo(self.turretid)).model, self.origin + '0 0 18', self.velocity + '0 0 300' + '0.1 0.1 1' * (random() * 200), '-1 -1 -1', TRUE); + else if (self.turretid == TUR_TESLA) + turret_gibtoss((get_turretinfo(self.turretid)).model, self.origin + '0 0 18', '0 0 200', '-1 -1 -1', FALSE); + else + { + if (random() > 0.5) + { + turret_gibtoss("models/turrets/base-gib2.md3", self.origin + '0 0 8', '0 0 50' + randomvec() * 150, '0 0 0', FALSE); + turret_gibtoss("models/turrets/base-gib3.md3", self.origin + '0 0 8', '0 0 50' + randomvec() * 150, '0 0 0', FALSE); + turret_gibtoss("models/turrets/base-gib4.md3", self.origin + '0 0 8', '0 0 50' + randomvec() * 150, '0 0 0', FALSE); + } + else + turret_gibtoss("models/turrets/base-gib1.md3", self.origin + '0 0 8', '0 0 0', '0 0 0', TRUE); + + entity headgib = turret_gibtoss((get_turretinfo(self.turretid)).head_model, self.origin + '0 0 32', '0 0 200' + randomvec() * 200, '-1 -1 -1', TRUE); + if(headgib) + { + headgib.angles = headgib.move_angles = self.tur_head.angles; + headgib.avelocity = headgib.move_avelocity = self.tur_head.move_avelocity + randomvec() * 45; + headgib.avelocity_y = headgib.move_avelocity_y = headgib.move_avelocity_y * 5; + headgib.gravity = 0.5; + } + } + } + + setmodel(self, "null"); + setmodel(self.tur_head, "null"); +} + +void ent_turret() +{ + float sf; + sf = ReadByte(); + + if(sf & TNSF_SETUP) + { + self.turretid = ReadByte(); + + self.origin_x = ReadCoord(); + self.origin_y = ReadCoord(); + self.origin_z = ReadCoord(); + setorigin(self, self.origin); + + self.angles_x = ReadAngle(); + self.angles_y = ReadAngle(); + + turret_construct(); + self.colormap = 1024; + self.glowmod = '0 1 1'; + self.tur_head.colormap = self.colormap; + self.tur_head.glowmod = self.glowmod; + } + + if(sf & TNSF_ANG) + { + if(self.tur_head == world) // aparenly this can happpen before TNSF_SETUP. great. + self.tur_head = spawn(); + + self.tur_head.move_angles_x = ReadShort(); + self.tur_head.move_angles_y = ReadShort(); + //self.tur_head.angles = self.angles + self.tur_head.move_angles; + self.tur_head.angles = self.tur_head.move_angles; + } + + if(sf & TNSF_AVEL) + { + if(self.tur_head == world) // aparenly this can happpen before TNSF_SETUP. great. + self.tur_head = spawn(); + + self.tur_head.move_avelocity_x = ReadShort(); + self.tur_head.move_avelocity_y = ReadShort(); + } + + if(sf & TNSF_MOVE) + { + self.origin_x = ReadShort(); + self.origin_y = ReadShort(); + self.origin_z = ReadShort(); + setorigin(self, self.origin); + + self.velocity_x = ReadShort(); + self.velocity_y = ReadShort(); + self.velocity_z = ReadShort(); + + self.move_angles_y = ReadShort(); + + self.move_time = time; + self.move_velocity = self.velocity; + self.move_origin = self.origin; + } + + if(sf & TNSF_ANIM) + { + self.frame1time = ReadCoord(); + self.frame = ReadByte(); + } + + if(sf & TNSF_STATUS) + { + float _tmp; + _tmp = ReadByte(); + if(_tmp != self.team) + { + self.team = _tmp; + turret_changeteam(); + } + + _tmp = ReadByte(); + if(_tmp == 0 && self.health != 0) + turret_die(); + else if(self.health && self.health != _tmp) + self.helpme = servertime + 10; + + self.health = _tmp; + } + //self.enemy.health = self.health / 255; +} diff --git a/qcsrc/common/turrets/cl_turrets.qh b/qcsrc/common/turrets/cl_turrets.qh new file mode 100644 index 000000000..b61678218 --- /dev/null +++ b/qcsrc/common/turrets/cl_turrets.qh @@ -0,0 +1 @@ +void ent_turret(); diff --git a/qcsrc/common/turrets/sv_turrets.qc b/qcsrc/common/turrets/sv_turrets.qc new file mode 100644 index 000000000..c5bb1fbf1 --- /dev/null +++ b/qcsrc/common/turrets/sv_turrets.qc @@ -0,0 +1,1394 @@ +// ========================= +// SVQC Turret Properties +// ========================= + + +// Generic aiming +vector turret_aim_generic() +{ + + vector pre_pos, prep; + float distance, impact_time = 0, i, mintime; + + turret_tag_fire_update(); + + if(self.aim_flags & TFL_AIM_SIMPLE) + return real_origin(self.enemy); + + mintime = max(self.attack_finished_single - time,0) + sys_frametime; + + // Baseline + pre_pos = real_origin(self.enemy); + + // Lead? + if (self.aim_flags & TFL_AIM_LEAD) + { + if (self.aim_flags & TFL_AIM_SHOTTIMECOMPENSATE) // Need to conpensate for shot traveltime + { + prep = pre_pos; + + distance = vlen(prep - self.tur_shotorg); + impact_time = distance / self.shot_speed; + + prep = pre_pos + (self.enemy.velocity * (impact_time + mintime)); + + if(self.aim_flags & TFL_AIM_ZPREDICT) + if(!(self.enemy.flags & FL_ONGROUND)) + if(self.enemy.movetype == MOVETYPE_WALK || self.enemy.movetype == MOVETYPE_TOSS || self.enemy.movetype == MOVETYPE_BOUNCE) + { + float vz; + prep_z = pre_pos_z; + vz = self.enemy.velocity_z; + for(i = 0; i < impact_time; i += sys_frametime) + { + vz = vz - (autocvar_sv_gravity * sys_frametime); + prep_z = prep_z + vz * sys_frametime; + } + } + pre_pos = prep; + } + else + pre_pos = pre_pos + self.enemy.velocity * mintime; + } + + if(self.aim_flags & TFL_AIM_SPLASH) + { + //tracebox(pre_pos + '0 0 32',self.enemy.mins,self.enemy.maxs,pre_pos -'0 0 64',MOVE_WORLDONLY,self.enemy); + traceline(pre_pos + '0 0 32',pre_pos -'0 0 64',MOVE_WORLDONLY,self.enemy); + if(trace_fraction != 1.0) + pre_pos = trace_endpos; + } + + return pre_pos; +} + +float turret_targetscore_support(entity _turret,entity _target) +{ + float score; // Total score + float s_score = 0, d_score; + + if (_turret.enemy == _target) s_score = 1; + + d_score = min(_turret.target_range_optimal,tvt_dist) / max(_turret.target_range_optimal,tvt_dist); + + score = (d_score * _turret.target_select_rangebias) + + (s_score * _turret.target_select_samebias); + + return score; +} + +/* +* Generic bias aware score system. +*/ +float turret_targetscore_generic(entity _turret, entity _target) +{ + float d_dist; // Defendmode Distance + float score; // Total score + float d_score; // Distance score + float a_score; // Angular score + float m_score = 0; // missile score + float p_score = 0; // player score + float ikr; // ideal kill range + + if (_turret.tur_defend) + { + d_dist = vlen(real_origin(_target) - _turret.tur_defend.origin); + ikr = vlen(_turret.origin - _turret.tur_defend.origin); + d_score = 1 - d_dist / _turret.target_range; + } + else + { + // Make a normlized value base on the targets distance from our optimal killzone + ikr = _turret.target_range_optimal; + d_score = min(ikr, tvt_dist) / max(ikr, tvt_dist); + } + + a_score = 1 - tvt_thadf / _turret.aim_maxrotate; + + if ((_turret.target_select_missilebias > 0) && (_target.flags & FL_PROJECTILE)) + m_score = 1; + + if ((_turret.target_select_playerbias > 0) && IS_CLIENT(_target)) + p_score = 1; + + d_score = max(d_score, 0); + a_score = max(a_score, 0); + m_score = max(m_score, 0); + p_score = max(p_score, 0); + + score = (d_score * _turret.target_select_rangebias) + + (a_score * _turret.target_select_anglebias) + + (m_score * _turret.target_select_missilebias) + + (p_score * _turret.target_select_playerbias); + + if(_turret.target_range < vlen(_turret.tur_shotorg - real_origin(_target))) + { + //dprint("Wtf?\n"); + score *= 0.001; + } + +#ifdef TURRET_DEBUG + string sd,sa,sm,sp,ss; + string sdt,sat,smt,spt; + + sd = ftos(d_score); + d_score *= _turret.target_select_rangebias; + sdt = ftos(d_score); + + //sv = ftos(v_score); + //v_score *= _turret.target_select_samebias; + //svt = ftos(v_score); + + sa = ftos(a_score); + a_score *= _turret.target_select_anglebias; + sat = ftos(a_score); + + sm = ftos(m_score); + m_score *= _turret.target_select_missilebias; + smt = ftos(m_score); + + sp = ftos(p_score); + p_score *= _turret.target_select_playerbias; + spt = ftos(p_score); + + + ss = ftos(score); + bprint("^3Target scores^7 \[ ",_turret.netname, " \] ^3for^7 \[ ", _target.netname," \]\n"); + bprint("^5Range:\[ ",sd, " \]^2+bias:\[ ",sdt," \]\n"); + bprint("^5Angle:\[ ",sa, " \]^2+bias:\[ ",sat," \]\n"); + bprint("^5Missile:\[ ",sm," \]^2+bias:\[ ",smt," \]\n"); + bprint("^5Player:\[ ",sp, " \]^2+bias:\[ ",spt," \]\n"); + bprint("^3Total (w/bias):\[^1",ss,"\]\n"); + +#endif + + return score; +} + +// Generic damage handling +void() turret_respawn; +void turret_hide() +{ + self.effects |= EF_NODRAW; + self.nextthink = time + self.respawntime - 0.2; + self.think = turret_respawn; +} + +void turret_die() +{ + self.deadflag = DEAD_DEAD; + self.tur_head.deadflag = self.deadflag; + +// Unsolidify and hide real parts + self.solid = SOLID_NOT; + self.tur_head.solid = self.solid; + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + self.health = 0; + +// Go boom + //RadiusDamage (self,self, min(self.ammo,50),min(self.ammo,50) * 0.25,250,world,min(self.ammo,50)*5,DEATH_TURRET,world); + + if(self.damage_flags & TFL_DMG_DEATH_NORESPAWN) + { + TUR_ACTION(self.turretid, TR_DEATH); + + remove(self.tur_head); + remove(self); + } + else + { + // Setup respawn + self.SendFlags |= TNSF_STATUS; + self.nextthink = time + 0.2; + self.think = turret_hide; + + TUR_ACTION(self.turretid, TR_DEATH); + } +} + +void turret_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce) +{ + // Enougth allready! + if(self.deadflag == DEAD_DEAD) + return; + + // Inactive turrets take no damage. (hm..) + if(!self.active) + return; + + if(SAME_TEAM(self, attacker)) + { + if(autocvar_g_friendlyfire) + damage = damage * autocvar_g_friendlyfire; + else + return; + } + + self.health -= damage; + + // thorw head slightly off aim when hit? + if (self.damage_flags & TFL_DMG_HEADSHAKE) + { + self.tur_head.angles_x = self.tur_head.angles_x + (-0.5 + random()) * damage; + self.tur_head.angles_y = self.tur_head.angles_y + (-0.5 + random()) * damage; + + self.SendFlags |= TNSF_ANG; + } + + if (self.turret_flags & TUR_FLAG_MOVE) + self.velocity = self.velocity + vforce; + + if (self.health <= 0) + { + self.event_damage = func_null; + self.tur_head.event_damage = func_null; + self.takedamage = DAMAGE_NO; + self.nextthink = time; + self.think = turret_die; + } + + self.SendFlags |= TNSF_STATUS; +} + +void() turret_think; +void turret_respawn() +{ + // Make sure all parts belong to the same team since + // this function doubles as "teamchange" function. + self.tur_head.team = self.team; + self.effects &= ~EF_NODRAW; + self.deadflag = DEAD_NO; + self.effects = EF_LOWPRECISION; + self.solid = SOLID_BBOX; + self.takedamage = DAMAGE_AIM; + self.event_damage = turret_damage; + self.avelocity = '0 0 0'; + self.tur_head.avelocity = self.avelocity; + self.tur_head.angles = self.idle_aim; + self.health = self.max_health; + self.enemy = world; + self.volly_counter = self.shot_volly; + self.ammo = self.ammo_max; + + self.nextthink = time + self.ticrate; + self.think = turret_think; + + self.SendFlags = TNSF_FULL_UPDATE; + + TUR_ACTION(self.turretid, TR_SETUP); +} + + +// Main functions +#define cvar_base "g_turrets_unit_" +.float clientframe; +void turrets_setframe(float _frame, float client_only) +{ + if((client_only ? self.clientframe : self.frame ) != _frame) + { + self.SendFlags |= TNSF_ANIM; + self.anim_start_time = time; + } + + if(client_only) + self.clientframe = _frame; + else + self.frame = _frame; + +} + +float turret_send(entity to, float sf) +{ + + WriteByte(MSG_ENTITY, ENT_CLIENT_TURRET); + WriteByte(MSG_ENTITY, sf); + if(sf & TNSF_SETUP) + { + WriteByte(MSG_ENTITY, self.turretid); + + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); + + WriteAngle(MSG_ENTITY, self.angles_x); + WriteAngle(MSG_ENTITY, self.angles_y); + } + + if(sf & TNSF_ANG) + { + WriteShort(MSG_ENTITY, rint(self.tur_head.angles_x)); + WriteShort(MSG_ENTITY, rint(self.tur_head.angles_y)); + } + + if(sf & TNSF_AVEL) + { + WriteShort(MSG_ENTITY, rint(self.tur_head.avelocity_x)); + WriteShort(MSG_ENTITY, rint(self.tur_head.avelocity_y)); + } + + if(sf & TNSF_MOVE) + { + WriteShort(MSG_ENTITY, rint(self.origin_x)); + WriteShort(MSG_ENTITY, rint(self.origin_y)); + WriteShort(MSG_ENTITY, rint(self.origin_z)); + + WriteShort(MSG_ENTITY, rint(self.velocity_x)); + WriteShort(MSG_ENTITY, rint(self.velocity_y)); + WriteShort(MSG_ENTITY, rint(self.velocity_z)); + + WriteShort(MSG_ENTITY, rint(self.angles_y)); + } + + if(sf & TNSF_ANIM) + { + WriteCoord(MSG_ENTITY, self.anim_start_time); + WriteByte(MSG_ENTITY, self.frame); + } + + if(sf & TNSF_STATUS) + { + WriteByte(MSG_ENTITY, self.team); + + if(self.health <= 0) + WriteByte(MSG_ENTITY, 0); + else + WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255)); + } + + return TRUE; +} + +void load_unit_settings(entity ent, string unitname, float is_reload) +{ + string sbase; + + if (ent == world) + return; + + if(!ent.turret_scale_damage) ent.turret_scale_damage = 1; + if(!ent.turret_scale_range) ent.turret_scale_range = 1; + if(!ent.turret_scale_refire) ent.turret_scale_refire = 1; + if(!ent.turret_scale_ammo) ent.turret_scale_ammo = 1; + if(!ent.turret_scale_aim) ent.turret_scale_aim = 1; + if(!ent.turret_scale_health) ent.turret_scale_health = 1; + if(!ent.turret_scale_respawn) ent.turret_scale_respawn = 1; + + sbase = strcat(cvar_base,unitname); + if (is_reload) + { + ent.enemy = world; + ent.tur_head.avelocity = '0 0 0'; + + ent.tur_head.angles = '0 0 0'; + } + + ent.health = cvar(strcat(sbase,"_health")) * ent.turret_scale_health; + ent.respawntime = cvar(strcat(sbase,"_respawntime")) * ent.turret_scale_respawn; + + ent.shot_dmg = cvar(strcat(sbase,"_shot_dmg")) * ent.turret_scale_damage; + ent.shot_refire = cvar(strcat(sbase,"_shot_refire")) * ent.turret_scale_refire; + ent.shot_radius = cvar(strcat(sbase,"_shot_radius")) * ent.turret_scale_damage; + ent.shot_speed = cvar(strcat(sbase,"_shot_speed")); + ent.shot_spread = cvar(strcat(sbase,"_shot_spread")); + ent.shot_force = cvar(strcat(sbase,"_shot_force")) * ent.turret_scale_damage; + ent.shot_volly = cvar(strcat(sbase,"_shot_volly")); + ent.shot_volly_refire = cvar(strcat(sbase,"_shot_volly_refire")) * ent.turret_scale_refire; + + ent.target_range = cvar(strcat(sbase,"_target_range")) * ent.turret_scale_range; + ent.target_range_min = cvar(strcat(sbase,"_target_range_min")) * ent.turret_scale_range; + ent.target_range_optimal = cvar(strcat(sbase,"_target_range_optimal")) * ent.turret_scale_range; + //ent.target_range_fire = cvar(strcat(sbase,"_target_range_fire")) * ent.turret_scale_range; + + ent.target_select_rangebias = cvar(strcat(sbase,"_target_select_rangebias")); + ent.target_select_samebias = cvar(strcat(sbase,"_target_select_samebias")); + ent.target_select_anglebias = cvar(strcat(sbase,"_target_select_anglebias")); + ent.target_select_playerbias = cvar(strcat(sbase,"_target_select_playerbias")); + //ent.target_select_fov = cvar(cvar_gets(sbase,"_target_select_fov")); + + ent.ammo_max = cvar(strcat(sbase,"_ammo_max")) * ent.turret_scale_ammo; + ent.ammo_recharge = cvar(strcat(sbase,"_ammo_recharge")) * ent.turret_scale_ammo; + + ent.aim_firetolerance_dist = cvar(strcat(sbase,"_aim_firetolerance_dist")); + ent.aim_speed = cvar(strcat(sbase,"_aim_speed")) * ent.turret_scale_aim; + ent.aim_maxrotate = cvar(strcat(sbase,"_aim_maxrot")); + ent.aim_maxpitch = cvar(strcat(sbase,"_aim_maxpitch")); + + ent.track_type = cvar(strcat(sbase,"_track_type")); + ent.track_accel_pitch = cvar(strcat(sbase,"_track_accel_pitch")); + ent.track_accel_rotate = cvar(strcat(sbase,"_track_accel_rot")); + ent.track_blendrate = cvar(strcat(sbase,"_track_blendrate")); + + if(is_reload) + TUR_ACTION(self.turretid, TR_SETUP); +} + +void turret_projectile_explode() +{ + + self.takedamage = DAMAGE_NO; + self.event_damage = func_null; +#ifdef TURRET_DEBUG + float d; + d = RadiusDamage (self, self.owner, self.owner.shot_dmg, 0, self.owner.shot_radius, self, world, self.owner.shot_force, self.totalfrags, world); + self.owner.tur_debug_dmg_t_h = self.owner.tur_debug_dmg_t_h + d; + self.owner.tur_debug_dmg_t_f = self.owner.tur_debug_dmg_t_f + self.owner.shot_dmg; +#else + RadiusDamage (self, self.realowner, self.owner.shot_dmg, 0, self.owner.shot_radius, self, world, self.owner.shot_force, self.totalfrags, world); +#endif + remove(self); +} + +void turret_projectile_touch() +{ + PROJECTILE_TOUCH; + turret_projectile_explode(); +} + +void turret_projectile_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce) +{ + self.velocity += vforce; + self.health -= damage; + //self.realowner = attacker; // Dont change realowner, it does not make much sense for turrets + if(self.health <= 0) + W_PrepareExplosionByDamage(self.owner, turret_projectile_explode); +} + +entity turret_projectile(string _snd, float _size, float _health, float _death, float _proj_type, float _cull, float _cli_anim) +{ + entity proj; + + sound (self, CH_WEAPON_A, _snd, VOL_BASE, ATTEN_NORM); + proj = spawn (); + setorigin(proj, self.tur_shotorg); + setsize(proj, '-0.5 -0.5 -0.5' * _size, '0.5 0.5 0.5' * _size); + proj.owner = self; + proj.realowner = self; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = self.shot_dmg; + proj.think = turret_projectile_explode; + proj.touch = turret_projectile_touch; + proj.nextthink = time + 9; + proj.movetype = MOVETYPE_FLYMISSILE; + proj.velocity = normalize(self.tur_shotdir_updated + randomvec() * self.shot_spread) * self.shot_speed; + proj.flags = FL_PROJECTILE; + proj.enemy = self.enemy; + proj.totalfrags = _death; + PROJECTILE_MAKETRIGGER(proj); + if(_health) + { + proj.health = _health; + proj.takedamage = DAMAGE_YES; + proj.event_damage = turret_projectile_damage; + } + else + proj.flags |= FL_NOTARGET; + + CSQCProjectile(proj, _cli_anim, _proj_type, _cull); + + return proj; +} + +/** +** updates enemy distances, predicted impact point/time +** and updated aim<->predict impact distance. +**/ +void turret_do_updates(entity t_turret) +{ + vector enemy_pos; + entity oldself; + + oldself = self; + self = t_turret; + + enemy_pos = real_origin(self.enemy); + + turret_tag_fire_update(); + + self.tur_shotdir_updated = v_forward; + self.tur_dist_enemy = vlen(self.tur_shotorg - enemy_pos); + self.tur_dist_aimpos = vlen(self.tur_shotorg - self.tur_aimpos); + + /*if((self.firecheck_flags & TFL_FIRECHECK_VERIFIED) && (self.enemy)) + { + oldpos = self.enemy.origin; + setorigin(self.enemy, self.tur_aimpos); + tracebox(self.tur_shotorg, '-1 -1 -1', '1 1 1', self.tur_shotorg + (self.tur_shotdir_updated * self.tur_dist_aimpos), MOVE_NORMAL,self); + setorigin(self.enemy, oldpos); + + if(trace_ent == self.enemy) + self.tur_dist_impact_to_aimpos = 0; + else + self.tur_dist_impact_to_aimpos = vlen(trace_endpos - self.tur_aimpos); + } + else*/ + tracebox(self.tur_shotorg, '-1 -1 -1','1 1 1', self.tur_shotorg + (self.tur_shotdir_updated * self.tur_dist_aimpos), MOVE_NORMAL,self); + + self.tur_dist_impact_to_aimpos = vlen(trace_endpos - self.tur_aimpos) - (vlen(self.enemy.maxs - self.enemy.mins) * 0.5); + self.tur_impactent = trace_ent; + self.tur_impacttime = vlen(self.tur_shotorg - trace_endpos) / self.shot_speed; + + self = oldself; +} + +/** +** Handles head rotation according to +** the units .track_type and .track_flags +**/ +.float turret_framecounter; +void turret_track() +{ + vector target_angle; // This is where we want to aim + vector move_angle; // This is where we can aim + float f_tmp; + vector v1, v2; + v1 = self.tur_head.angles; + v2 = self.tur_head.avelocity; + + if (self.track_flags == TFL_TRACK_NO) + return; + + if(!self.active) + target_angle = self.idle_aim - ('1 0 0' * self.aim_maxpitch); + else if (self.enemy == world) + { + if(time > self.lip) + target_angle = self.idle_aim + self.angles; + else + target_angle = vectoangles(normalize(self.tur_aimpos - self.tur_shotorg)); + } + else + { + target_angle = vectoangles(normalize(self.tur_aimpos - self.tur_shotorg)); + } + + self.tur_head.angles_x = anglemods(self.tur_head.angles_x); + self.tur_head.angles_y = anglemods(self.tur_head.angles_y); + + // Find the diffrence between where we currently aim and where we want to aim + //move_angle = target_angle - (self.angles + self.tur_head.angles); + //move_angle = shortangle_vxy(move_angle,(self.angles + self.tur_head.angles)); + + move_angle = AnglesTransform_ToAngles(AnglesTransform_LeftDivide(AnglesTransform_FromAngles(self.angles), AnglesTransform_FromAngles(target_angle))) - self.tur_head.angles; + move_angle = shortangle_vxy(move_angle, self.tur_head.angles); + + switch(self.track_type) + { + case TFL_TRACKTYPE_STEPMOTOR: + f_tmp = self.aim_speed * self.ticrate; // dgr/sec -> dgr/tic + if (self.track_flags & TFL_TRACK_PITCH) + { + self.tur_head.angles_x += bound(-f_tmp,move_angle_x, f_tmp); + if(self.tur_head.angles_x > self.aim_maxpitch) + self.tur_head.angles_x = self.aim_maxpitch; + + if(self.tur_head.angles_x < -self.aim_maxpitch) + self.tur_head.angles_x = self.aim_maxpitch; + } + + if (self.track_flags & TFL_TRACK_ROTATE) + { + self.tur_head.angles_y += bound(-f_tmp, move_angle_y, f_tmp); + if(self.tur_head.angles_y > self.aim_maxrotate) + self.tur_head.angles_y = self.aim_maxrotate; + + if(self.tur_head.angles_y < -self.aim_maxrotate) + self.tur_head.angles_y = self.aim_maxrotate; + } + + // CSQC + self.SendFlags |= TNSF_ANG; + + return; + + case TFL_TRACKTYPE_FLUIDINERTIA: + f_tmp = self.aim_speed * self.ticrate; // dgr/sec -> dgr/tic + move_angle_x = bound(-self.aim_speed, move_angle_x * self.track_accel_pitch * f_tmp, self.aim_speed); + move_angle_y = bound(-self.aim_speed, move_angle_y * self.track_accel_rotate * f_tmp, self.aim_speed); + move_angle = (self.tur_head.avelocity * self.track_blendrate) + (move_angle * (1 - self.track_blendrate)); + break; + + case TFL_TRACKTYPE_FLUIDPRECISE: + + move_angle_y = bound(-self.aim_speed, move_angle_y, self.aim_speed); + move_angle_x = bound(-self.aim_speed, move_angle_x, self.aim_speed); + + break; + } + + // pitch + if (self.track_flags & TFL_TRACK_PITCH) + { + self.tur_head.avelocity_x = move_angle_x; + if((self.tur_head.angles_x + self.tur_head.avelocity_x * self.ticrate) > self.aim_maxpitch) + { + self.tur_head.avelocity_x = 0; + self.tur_head.angles_x = self.aim_maxpitch; + + self.SendFlags |= TNSF_ANG; + } + + if((self.tur_head.angles_x + self.tur_head.avelocity_x * self.ticrate) < -self.aim_maxpitch) + { + self.tur_head.avelocity_x = 0; + self.tur_head.angles_x = -self.aim_maxpitch; + + self.SendFlags |= TNSF_ANG; + } + } + + // rot + if (self.track_flags & TFL_TRACK_ROTATE) + { + self.tur_head.avelocity_y = move_angle_y; + + if((self.tur_head.angles_y + self.tur_head.avelocity_y * self.ticrate) > self.aim_maxrotate) + { + self.tur_head.avelocity_y = 0; + self.tur_head.angles_y = self.aim_maxrotate; + + self.SendFlags |= TNSF_ANG; + } + + if((self.tur_head.angles_y + self.tur_head.avelocity_y * self.ticrate) < -self.aim_maxrotate) + { + self.tur_head.avelocity_y = 0; + self.tur_head.angles_y = -self.aim_maxrotate; + + self.SendFlags |= TNSF_ANG; + } + } + + self.SendFlags |= TNSF_AVEL; + + // Force a angle update every 10'th frame + self.turret_framecounter += 1; + if(self.turret_framecounter >= 10) + { + self.SendFlags |= TNSF_ANG; + self.turret_framecounter = 0; + } +} + +/* + + TFL_TARGETSELECT_NO + + TFL_TARGETSELECT_LOS + + TFL_TARGETSELECT_PLAYERS + + TFL_TARGETSELECT_MISSILES + - TFL_TARGETSELECT_TRIGGERTARGET + + TFL_TARGETSELECT_ANGLELIMITS + + TFL_TARGETSELECT_RANGELIMITS + + TFL_TARGETSELECT_TEAMCHECK + - TFL_TARGETSELECT_NOBUILTIN + + TFL_TARGETSELECT_OWNTEAM +*/ + +/** +** Evaluate a entity for target valitity based on validate_flags +** NOTE: the caller must check takedamage before calling this, to inline this check. +**/ +float turret_validate_target(entity e_turret, entity e_target, float validate_flags) +{ + vector v_tmp; + + //if(!validate_flags & TFL_TARGETSELECT_NOBUILTIN) + // return -0.5; + + if(!e_target) + return -2; + + if(e_target.owner == e_turret) + return -0.5; + + if(!checkpvs(e_target.origin, e_turret)) + return -1; + + if(e_target.alpha <= 0.3) + return -1; + + if(g_onslaught) + if (substring(e_target.classname, 0, 10) == "onslaught_") // don't attack onslaught targets, that's the player's job! + return - 3; + + if (validate_flags & TFL_TARGETSELECT_NO) + return -4; + + // If only this was used more.. + if (e_target.flags & FL_NOTARGET) + return -5; + + // Cant touch this + if(IS_VEHICLE(e_target)) + { + if (e_target.vehicle_health <= 0) + return -6; + } + else if(Player_Trapped(e_target)) + return -6; + + // player + if (IS_CLIENT(e_target)) + { + if(!(validate_flags & TFL_TARGETSELECT_PLAYERS)) + return -7; + + if (e_target.deadflag != DEAD_NO) + return -8; + } + + // enemy turrets + if(validate_flags & TFL_TARGETSELECT_NOTURRETS) + if(e_target.owner.tur_head == e_target) + if(e_target.team != e_turret.team) // Dont break support units. + return -9; + + // Missile + if (e_target.flags & FL_PROJECTILE) + if(!(validate_flags & TFL_TARGETSELECT_MISSILES)) + return -10; + + if (validate_flags & TFL_TARGETSELECT_MISSILESONLY) + if(!(e_target.flags & FL_PROJECTILE)) + return -10.5; + + // Team check + if (validate_flags & TFL_TARGETSELECT_TEAMCHECK) + { + if (validate_flags & TFL_TARGETSELECT_OWNTEAM) + { + if (e_target.team != e_turret.team) + return -11; + + if (e_turret.team != e_target.owner.team) + return -12; + } + else + { + if (e_target.team == e_turret.team) + return -13; + + if (e_turret.team == e_target.owner.team) + return -14; + } + } + + // Range limits? + tvt_dist = vlen(e_turret.origin - real_origin(e_target)); + if (validate_flags & TFL_TARGETSELECT_RANGELIMITS) + { + if (tvt_dist < e_turret.target_range_min) + return -15; + + if (tvt_dist > e_turret.target_range) + return -16; + } + + // Can we even aim this thing? + tvt_thadv = angleofs3(e_turret.tur_head.origin, e_turret.angles + e_turret.tur_head.angles, e_target); + tvt_tadv = shortangle_vxy(angleofs(e_turret, e_target), e_turret.angles); + tvt_thadf = vlen(tvt_thadv); + tvt_tadf = vlen(tvt_tadv); + + /* + if(validate_flags & TFL_TARGETSELECT_FOV) + { + if(e_turret.target_select_fov < tvt_thadf) + return -21; + } + */ + + if (validate_flags & TFL_TARGETSELECT_ANGLELIMITS) + { + if (fabs(tvt_tadv_x) > e_turret.aim_maxpitch) + return -17; + + if (fabs(tvt_tadv_y) > e_turret.aim_maxrotate) + return -18; + } + + // Line of sight? + if (validate_flags & TFL_TARGETSELECT_LOS) + { + v_tmp = real_origin(e_target) + ((e_target.mins + e_target.maxs) * 0.5); + + traceline(e_turret.origin + '0 0 16', v_tmp, 0, e_turret); + + if (e_turret.aim_firetolerance_dist < vlen(v_tmp - trace_endpos)) + return -19; + } + + if (e_target.classname == "grapplinghook") + return -20; + + /* + if (e_target.classname == "func_button") + return -21; + */ + +#ifdef TURRET_DEBUG_TARGETSELECT + dprint("Target:",e_target.netname," is a valid target for ",e_turret.netname,"\n"); +#endif + + return 1; +} + +entity turret_select_target() +{ + entity e; // target looper entity + float score; // target looper entity score + entity e_enemy; // currently best scoreing target + float m_score; // currently best scoreing target's score + + m_score = 0; + if(self.enemy && self.enemy.takedamage && turret_validate_target(self,self.enemy,self.target_validate_flags) > 0) + { + e_enemy = self.enemy; + m_score = self.turret_score_target(self,e_enemy) * self.target_select_samebias; + } + else + e_enemy = self.enemy = world; + + e = findradius(self.origin, self.target_range); + + // Nothing to aim at? + if (!e) + return world; + + while (e) + { + if(e.takedamage) + { + float f = turret_validate_target(self, e, self.target_select_flags); + //dprint("F is: ", ftos(f), "\n"); + if ( f > 0) + { + score = self.turret_score_target(self,e); + if ((score > m_score) && (score > 0)) + { + e_enemy = e; + m_score = score; + } + } + } + e = e.chain; + } + + return e_enemy; +} + + +/* + + = implemented + - = not implemented + + + TFL_FIRECHECK_NO + + TFL_FIRECHECK_WORLD + + TFL_FIRECHECK_DEAD + + TFL_FIRECHECK_DISTANCES + - TFL_FIRECHECK_LOS + + TFL_FIRECHECK_AIMDIST + + TFL_FIRECHECK_REALDIST + - TFL_FIRECHECK_ANGLEDIST + - TFL_FIRECHECK_TEAMCECK + + TFL_FIRECHECK_AFF + + TFL_FIRECHECK_AMMO_OWN + + TFL_FIRECHECK_AMMO_OTHER + + TFL_FIRECHECK_REFIRE +*/ + +/** +** Preforms pre-fire checks based on the uints firecheck_flags +**/ +float turret_firecheck() +{ + // This one just dont care =) + if (self.firecheck_flags & TFL_FIRECHECK_NO) + return 1; + + if (self.enemy == world) + return 0; + + // Ready? + if (self.firecheck_flags & TFL_FIRECHECK_REFIRE) + if (self.attack_finished_single > time) return 0; + + // Special case: volly fire turret that has to fire a full volly if a shot was fired. + if (self.shoot_flags & TFL_SHOOT_VOLLYALWAYS) + if (self.volly_counter != self.shot_volly) + if(self.ammo >= self.shot_dmg) + return 1; + + // Lack of zombies makes shooting dead things unnecessary :P + if (self.firecheck_flags & TFL_FIRECHECK_DEAD) + if (self.enemy.deadflag != DEAD_NO) + return 0; + + // Own ammo? + if (self.firecheck_flags & TFL_FIRECHECK_AMMO_OWN) + if (self.ammo < self.shot_dmg) + return 0; + + // Other's ammo? (support-supply units) + if (self.firecheck_flags & TFL_FIRECHECK_AMMO_OTHER) + if (self.enemy.ammo >= self.enemy.ammo_max) + return 0; + + // Target of opertunity? + if(turret_validate_target(self, self.tur_impactent, self.target_validate_flags) > 0) + { + self.enemy = self.tur_impactent; + return 1; + } + + if (self.firecheck_flags & TFL_FIRECHECK_DISTANCES) + { + // To close? + if (self.tur_dist_aimpos < self.target_range_min) + if(turret_validate_target(self, self.tur_impactent, self.target_validate_flags) > 0) + return 1; // Target of opertunity? + else + return 0; + } + + // Try to avoid FF? + if (self.firecheck_flags & TFL_FIRECHECK_AFF) + if (self.tur_impactent.team == self.team) + return 0; + + // aim<->predicted impact + if (self.firecheck_flags & TFL_FIRECHECK_AIMDIST) + if (self.tur_dist_impact_to_aimpos > self.aim_firetolerance_dist) + return 0; + + // Volly status + if (self.shot_volly > 1) + if (self.volly_counter == self.shot_volly) + if (self.ammo < (self.shot_dmg * self.shot_volly)) + return 0; + + /*if(self.firecheck_flags & TFL_FIRECHECK_VERIFIED) + if(self.tur_impactent != self.enemy) + return 0;*/ + + return 1; +} + +void turret_fire() +{ + if (autocvar_g_turrets_nofire != 0) + return; + + TUR_ACTION(self.turretid, TR_ATTACK); + + self.attack_finished_single = time + self.shot_refire; + self.ammo -= self.shot_dmg; + self.volly_counter = self.volly_counter - 1; + + if (self.volly_counter <= 0) + { + self.volly_counter = self.shot_volly; + + if (self.shoot_flags & TFL_SHOOT_CLEARTARGET) + self.enemy = world; + + if (self.shot_volly > 1) + self.attack_finished_single = time + self.shot_volly_refire; + } + +#ifdef TURRET_DEBUG + if (self.enemy) paint_target3(self.tur_aimpos, 64, self.tur_debug_rvec, self.tur_impacttime + 0.25); +#endif +} + +void turret_think() +{ + entity e; + + self.nextthink = time + self.ticrate; + + // ONS uses somewhat backwards linking. + if (teamplay) + { + if (g_onslaught) + if (self.target) + { + e = find(world, targetname,self.target); + if (e != world) + self.team = e.team; + } + + if (self.team != self.tur_head.team) + turret_respawn(); + } + +#ifdef TURRET_DEBUG + if (self.tur_debug_tmr1 < time) + { + if (self.enemy) paint_target (self.enemy,128,self.tur_debug_rvec,0.9); + paint_target(self,256,self.tur_debug_rvec,0.9); + self.tur_debug_tmr1 = time + 1; + } +#endif + + // Handle ammo + if (!(self.spawnflags & TSF_NO_AMMO_REGEN)) + if (self.ammo < self.ammo_max) + self.ammo = min(self.ammo + self.ammo_recharge, self.ammo_max); + + // Inactive turrets needs to run the think loop, + // So they can handle animation and wake up if need be. + if(!self.active) + { + turret_track(); + return; + } + + // This is typicaly used for zaping every target in range + // turret_fusionreactor uses this to recharge friendlys. + if (self.shoot_flags & TFL_SHOOT_HITALLVALID) + { + // Do a self.turret_fire for every valid target. + e = findradius(self.origin,self.target_range); + while (e) + { + if(e.takedamage) + { + if (turret_validate_target(self,e,self.target_validate_flags)) + { + self.enemy = e; + + turret_do_updates(self); + + if (self.turret_firecheckfunc()) + turret_fire(); + } + } + + e = e.chain; + } + self.enemy = world; + } + else if(self.shoot_flags & TFL_SHOOT_CUSTOM) + { + // This one is doing something.. oddball. assume its handles what needs to be handled. + + // Predict? + if(!(self.aim_flags & TFL_AIM_NO)) + self.tur_aimpos = turret_aim_generic(); + + // Turn & pitch? + if(!(self.track_flags & TFL_TRACK_NO)) + turret_track(); + + turret_do_updates(self); + + // Fire? + if (self.turret_firecheckfunc()) + turret_fire(); + } + else + { + // Special case for volly always. if it fired once it must compleate the volly. + if(self.shoot_flags & TFL_SHOOT_VOLLYALWAYS) + if(self.volly_counter != self.shot_volly) + { + // Predict or whatnot + if(!(self.aim_flags & TFL_AIM_NO)) + self.tur_aimpos = turret_aim_generic(); + + // Turn & pitch + if(!(self.track_flags & TFL_TRACK_NO)) + turret_track(); + + turret_do_updates(self); + + // Fire! + if (self.turret_firecheckfunc() != 0) + turret_fire(); + + TUR_ACTION(self.turretid, TR_THINK); + + return; + } + + // Check if we have a vailid enemy, and try to find one if we dont. + + // g_turrets_targetscan_maxdelay forces a target re-scan at least this often + float do_target_scan = 0; + if((self.target_select_time + autocvar_g_turrets_targetscan_maxdelay) < time) + do_target_scan = 1; + + // Old target (if any) invalid? + if(self.target_validate_time < time) + if (turret_validate_target(self, self.enemy, self.target_validate_flags) <= 0) + { + self.enemy = world; + self.target_validate_time = time + 0.5; + do_target_scan = 1; + } + + // But never more often then g_turrets_targetscan_mindelay! + if (self.target_select_time + autocvar_g_turrets_targetscan_mindelay > time) + do_target_scan = 0; + + if(do_target_scan) + { + self.enemy = turret_select_target(); + self.target_select_time = time; + } + + // No target, just go to idle, do any custom stuff and bail. + if (self.enemy == world) + { + // Turn & pitch + if(!(self.track_flags & TFL_TRACK_NO)) + turret_track(); + + TUR_ACTION(self.turretid, TR_THINK); + + // And bail. + return; + } + else + self.lip = time + autocvar_g_turrets_aimidle_delay; // Keep track of the last time we had a target. + + // Predict? + if(!(self.aim_flags & TFL_AIM_NO)) + self.tur_aimpos = turret_aim_generic(); + + // Turn & pitch? + if(!(self.track_flags & TFL_TRACK_NO)) + turret_track(); + + turret_do_updates(self); + + // Fire? + if (self.turret_firecheckfunc()) + turret_fire(); + } + + TUR_ACTION(self.turretid, TR_THINK); +} + +/* + When .used a turret switch team to activator.team. + If activator is world, the turret go inactive. +*/ +void turret_use() +{ + dprint("Turret ",self.netname, " used by ", activator.classname, "\n"); + + self.team = activator.team; + + if(self.team == 0) + self.active = ACTIVE_NOT; + else + self.active = ACTIVE_ACTIVE; + +} + +void turret_link() +{ + Net_LinkEntity(self, TRUE, 0, turret_send); + self.think = turret_think; + self.nextthink = time; + self.tur_head.effects = EF_NODRAW; +} + +void turrets_manager_think() +{ + self.nextthink = time + 1; + + entity e; + if (autocvar_g_turrets_reloadcvars == 1) + { + e = nextent(world); + while (e) + { + if (IS_TURRET(e)) + { + load_unit_settings(e,e.cvar_basename,1); + TUR_ACTION(self.turretid, TR_THINK); + } + + e = nextent(e); + } + cvar_set("g_turrets_reloadcvars","0"); + } +} + +float turret_initialize(float tur_id) +{ + if(!autocvar_g_turrets) + return FALSE; + + entity e; + entity tur = get_turretinfo(tur_id); + if(tur.turretid == 0) + return FALSE; // invalid turret + + if(!self.tur_head) { TUR_ACTION(tur_id, TR_PRECACHE); } // if tur_head exists, we can assume this turret re-spawned + + e = find(world, classname, "turret_manager"); + if(!e) + { + e = spawn(); + e.classname = "turret_manager"; + e.think = turrets_manager_think; + e.nextthink = time + 2; + } + + if(!(self.spawnflags & TSF_SUSPENDED)) + builtin_droptofloor(); + + self.cvar_basename = tur.cvar_basename; + load_unit_settings(self, self.cvar_basename, 0); + + if(!self.team || !teamplay) { self.team = MAX_SHOT_DISTANCE; } + if(!self.ticrate) { self.ticrate = ((self.turret_flags & TUR_FLAG_SUPPORT) ? 0.2 : 0.1); } + if(!self.health) { self.health = 1000; } + if(!self.shot_refire) { self.shot_refire = 1; } + if(!self.tur_shotorg) { self.tur_shotorg = '50 0 50'; } + if(!self.turret_flags) { self.turret_flags = TUR_FLAG_SPLASH | TUR_FLAG_MEDPROJ | TUR_FLAG_PLAYER; } + if(!self.damage_flags) { self.damage_flags = TFL_DMG_YES | TFL_DMG_RETALIATE | TFL_DMG_AIMSHAKE; } + if(!self.aim_flags) { self.aim_flags = TFL_AIM_LEAD | TFL_AIM_SHOTTIMECOMPENSATE; } + if(!self.track_type) { self.track_type = TFL_TRACKTYPE_STEPMOTOR; } + if(!self.track_flags) { self.track_flags = TFL_TRACK_PITCH | TFL_TRACK_ROTATE; } + if(!self.ammo_flags) { self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE; } + if(!self.target_select_flags) { self.target_select_flags = TFL_TARGETSELECT_LOS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_ANGLELIMITS; } + if(!self.firecheck_flags) { self.firecheck_flags = TFL_FIRECHECK_DEAD | TFL_FIRECHECK_DISTANCES | TFL_FIRECHECK_LOS + | TFL_FIRECHECK_AIMDIST | TFL_FIRECHECK_TEAMCHECK | TFL_FIRECHECK_AMMO_OWN | TFL_FIRECHECK_REFIRE; } + + if(self.track_type != TFL_TRACKTYPE_STEPMOTOR) + { + // Fluid / Ineria mode. Looks mutch nicer. + // Can reduce aim preformance alot, needs a bit diffrent aimspeed + + self.aim_speed = bound(0.1, ((!self.aim_speed) ? 180 : self.aim_speed), 1000); + + if(!self.track_accel_pitch) { self.track_accel_pitch = 0.5; } + if(!self.track_accel_rotate) { self.track_accel_rotate = 0.5; } + if(!self.track_blendrate) { self.track_blendrate = 0.35; } + } + + self.respawntime = max(-1, ((!self.respawntime) ? 60 : self.respawntime)); + self.shot_refire = bound(0.01, ((!self.shot_refire) ? 1 : self.shot_refire), 9999); + self.shot_dmg = max(1, ((!self.shot_dmg) ? self.shot_refire * 50 : self.shot_dmg)); + self.shot_radius = max(1, ((!self.shot_radius) ? self.shot_dmg * 0.5 : self.shot_radius)); + self.shot_speed = max(1, ((!self.shot_speed) ? 2500 : self.shot_speed)); + self.shot_spread = bound(0.0001, ((!self.shot_spread) ? 0.0125 : self.shot_spread), 500); + self.shot_force = bound(0.001, ((!self.shot_force) ? self.shot_dmg * 0.5 + self.shot_radius * 0.5 : self.shot_force), 5000); + self.shot_volly = bound(1, ((!self.shot_volly) ? 1 : self.shot_volly), floor(self.ammo_max / self.shot_dmg)); + self.shot_volly_refire = bound(self.shot_refire, ((!self.shot_volly_refire) ? self.shot_refire * self.shot_volly : self.shot_volly_refire), 60); + self.target_range = bound(0, ((!self.target_range) ? self.shot_speed * 0.5 : self.target_range), MAX_SHOT_DISTANCE); + self.target_range_min = bound(0, ((!self.target_range_min) ? self.shot_radius * 2 : self.target_range_min), MAX_SHOT_DISTANCE); + self.target_range_optimal = bound(0, ((!self.target_range_optimal) ? self.target_range * 0.5 : self.target_range_optimal), MAX_SHOT_DISTANCE); + self.aim_maxrotate = bound(0, ((!self.aim_maxrotate) ? 90 : self.aim_maxrotate), 360); + self.aim_maxpitch = bound(0, ((!self.aim_maxpitch) ? 20 : self.aim_maxpitch), 90); + self.aim_speed = bound(0.1, ((!self.aim_speed) ? 36 : self.aim_speed), 1000); + self.aim_firetolerance_dist = bound(0.1, ((!self.aim_firetolerance_dist) ? 5 + (self.shot_radius * 2) : self.aim_firetolerance_dist), MAX_SHOT_DISTANCE); + self.target_select_rangebias = bound(-10, ((!self.target_select_rangebias) ? 1 : self.target_select_rangebias), 10); + self.target_select_samebias = bound(-10, ((!self.target_select_samebias) ? 1 : self.target_select_samebias), 10); + self.target_select_anglebias = bound(-10, ((!self.target_select_anglebias) ? 1 : self.target_select_anglebias), 10); + self.target_select_missilebias = bound(-10, ((!self.target_select_missilebias) ? 1 : self.target_select_missilebias), 10); + self.target_select_playerbias = bound(-10, ((!self.target_select_playerbias) ? 1 : self.target_select_playerbias), 10); + self.ammo_max = max(self.shot_dmg, ((!self.ammo_max) ? self.shot_dmg * 10 : self.ammo_max)); + self.ammo_recharge = max(0, ((!self.ammo_recharge) ? self.shot_dmg * 0.5 : self.ammo_recharge)); + + self.turret_flags = TUR_FLAG_ISTURRET | (tur.spawnflags); + + if(self.turret_flags & TUR_FLAG_SPLASH) + self.aim_flags |= TFL_AIM_SPLASH; + + if(self.turret_flags & TUR_FLAG_MISSILE) + self.target_select_flags |= TFL_TARGETSELECT_MISSILES; + + if(self.turret_flags & TUR_FLAG_PLAYER) + self.target_select_flags |= TFL_TARGETSELECT_PLAYERS; + + if(self.spawnflags & TSL_NO_RESPAWN) + self.damage_flags |= TFL_DMG_DEATH_NORESPAWN; + + if (self.turret_flags & TUR_FLAG_SUPPORT) + self.turret_score_target = turret_targetscore_support; + else + self.turret_score_target = turret_targetscore_generic; + + ++turret_count; + + setmodel(self, tur.model); + setsize(self, tur.mins, tur.maxs); + + self.turretid = tur_id; + self.classname = "turret_main"; + self.active = ACTIVE_ACTIVE; + self.effects = EF_NODRAW; + self.netname = TUR_NAME(tur_id); + self.ticrate = bound(sys_frametime, self.ticrate, 60); + self.max_health = self.health; + self.target_validate_flags = self.target_select_flags; + self.ammo = self.ammo_max; + self.ammo_recharge *= self.ticrate; + self.solid = SOLID_BBOX; + self.takedamage = DAMAGE_AIM; + self.movetype = MOVETYPE_NOCLIP; + self.view_ofs = '0 0 0'; + self.turret_firecheckfunc = turret_firecheck; + self.event_damage = turret_damage; + self.use = turret_use; + self.bot_attack = TRUE; + self.nextthink = time + 1; + self.nextthink += turret_count * sys_frametime; + + self.tur_head = spawn(); + setmodel(self.tur_head, tur.head_model); + setsize(self.tur_head, '0 0 0', '0 0 0'); + setorigin(self.tur_head, '0 0 0'); + setattachment(self.tur_head, self, "tag_head"); + + self.tur_head.netname = self.tur_head.classname = "turret_head"; + self.tur_head.team = self.team; + self.tur_head.owner = self; + self.tur_head.takedamage = DAMAGE_NO; + self.tur_head.solid = SOLID_NOT; + self.tur_head.movetype = self.movetype; + + if(!self.tur_defend) + if(self.target != "") + { + self.tur_defend = find(world, targetname, self.target); + if (self.tur_defend == world) + { + self.target = ""; + dprint("Turret has invalid defendpoint!\n"); + } + } + + if (self.tur_defend) + self.idle_aim = self.tur_head.angles + angleofs(self.tur_head, self.tur_defend); + else + self.idle_aim = '0 0 0'; + +#ifdef TURRET_DEBUG + self.tur_debug_start = self.nextthink; + while (vlen(self.tur_debug_rvec) < 2) + self.tur_debug_rvec = randomvec() * 4; + + self.tur_debug_rvec_x = fabs(self.tur_debug_rvec_x); + self.tur_debug_rvec_y = fabs(self.tur_debug_rvec_y); + self.tur_debug_rvec_z = fabs(self.tur_debug_rvec_z); +#endif + + turret_link(); + turret_respawn(); + turret_tag_fire_update(); + + TUR_ACTION(tur_id, TR_SETUP); + + if(MUTATOR_CALLHOOK(TurretSpawn)) + return FALSE; + + return TRUE; +} \ No newline at end of file diff --git a/qcsrc/common/turrets/sv_turrets.qh b/qcsrc/common/turrets/sv_turrets.qh new file mode 100644 index 000000000..f89ef1581 --- /dev/null +++ b/qcsrc/common/turrets/sv_turrets.qh @@ -0,0 +1,106 @@ +// turret fields +.float ticrate; // interal ai think rate +.vector aim_idle; // where to aim while idle +.entity tur_head; // top part of the turret +.entity tur_defend; // defend this entity +.vector tur_shotorg; // shot origin +.vector tur_aimpos; // aiming location +.float tur_impacttime; // predicted projectile impact time +.entity tur_impactent; // entity the projectile hit +.float tur_dist_enemy; // distance to enemy +.float tur_dist_aimpos; // distance to aim location +.float tur_dist_impact_to_aimpos; // distance impact<->aim +.float volly_counter; // decrement counter from .shot_volly to 0 + +.float shot_refire; // attack refire +.float shot_speed; // projectile speed +.float shot_spread; // inaccuracy +.float shot_dmg; // core damage of projectile +.float shot_radius; // projectile damage radius +.float shot_force; // projectile damage force +.float shot_volly; // smaller than 1 = shoot # times at target +.float shot_volly_refire; // refire after completed volly + +.float target_range; +.float target_range_min; +.float target_range_optimal; + +.float target_select_rangebias; +.float target_select_samebias; +.float target_select_anglebias; +.float target_select_missilebias; +.float target_select_playerbias; +.float target_select_time; // last time turret had a valid target +.float target_validate_time; // throttle re-validation of current target + +.float aim_firetolerance_dist; +.float aim_speed; +.float aim_maxpitch; +.float aim_maxrotate; + +.float ammo; // current ammo +.float ammo_recharge; // recharge rate +.float ammo_max; // maximum ammo + +.vector idle_aim; + +/// Map time control over pain inflicted +.float turret_scale_damage; +/// Map time control targetting range +.float turret_scale_range; +/// Map time control refire +.float turret_scale_refire; +/// Map time control ammo held and recharged +.float turret_scale_ammo; +/// Map time control aim speed +.float turret_scale_aim; +/// Map time control health +.float turret_scale_health; +/// Map time control respawn time +.float turret_scale_respawn; + +// tracking type +.float track_type; +const float TFL_TRACKTYPE_STEPMOTOR = 1; // hard angle increments, ugly for fast turning with best accuracy +const float TFL_TRACKTYPE_FLUIDPRECISE = 2; // smooth absolute movement, looks OK with fair accuracy +const float TFL_TRACKTYPE_FLUIDINERTIA = 3; // simulated inertia ("wobbly" mode), worst accuracy, depends on below flags +.float track_accel_pitch; +.float track_accel_rotate; +.float track_blendrate; + +/// updates aim org, shot org, shot dir and enemy org for selected turret +void turret_do_updates(entity e_turret); +.vector tur_shotdir_updated; + +.float() turret_firecheckfunc; // TODO: deprecate! + +/// Function to use for target evaluation. usualy turret_targetscore_generic +.float(entity _turret, entity _target) turret_score_target; + +.float(entity e_target,entity e_sender) turret_addtarget; + +.entity pathcurrent; + +float turret_count; + +// debugging +// Uncomment below to enable various debug output. +//#define TURRET_DEBUG +//#define TURRET_DEBUG_TARGETVALIDATE +//#define TURRET_DEBUG_TARGETSELECT +#ifdef TURRET_DEBUG +.float tur_debug_dmg_t_h; // total damage that hit something (can be more than tur_debug_dmg_t_f since it should count radius damage) +.float tur_debug_dmg_t_f; // total damage +.float tur_debug_start; // turret initialization time +.float tur_debug_tmr1; // random timer +.float tur_debug_tmr2; // random timer +.float tur_debug_tmr3; // random timer +.vector tur_debug_rvec; // random vector +#endif + +// aiming +vector tvt_thadv; // turret head angle diff vector, updated by a successful call to turret_validate_target +vector tvt_tadv; // turret angle diff vector, updated by a successful call to turret_validate_target +float tvt_thadf; // turret head angle diff float, updated by a successful call to turret_validate_target +float tvt_tadf; // turret angle diff float, updated by a successful call to turret_validate_target +float tvt_dist; // turret distance, updated by a successful call to turret_validate_target diff --git a/qcsrc/server/tturrets/units/unit_targettrigger.qc b/qcsrc/common/turrets/targettrigger.qc similarity index 82% rename from qcsrc/server/tturrets/units/unit_targettrigger.qc rename to qcsrc/common/turrets/targettrigger.qc index 0f2de3c86..65510645b 100644 --- a/qcsrc/server/tturrets/units/unit_targettrigger.qc +++ b/qcsrc/common/turrets/targettrigger.qc @@ -11,7 +11,7 @@ void turret_targettrigger_touch() e = find(world, targetname, self.target); while (e) { - if (e.turrcaps_flags & TFL_TURRCAPS_RECIVETARGETS) + if (e.turret_flags & TUR_FLAG_RECIEVETARGETS) { self = e; if(e.turret_addtarget) @@ -30,11 +30,7 @@ void turret_targettrigger_touch() */ void spawnfunc_turret_targettrigger() { - if (!autocvar_g_turrets) - { - remove(self); - return; - } + if(!autocvar_g_turrets) { remove(self); return; } InitTrigger (); diff --git a/qcsrc/common/turrets/turrets.qc b/qcsrc/common/turrets/turrets.qc new file mode 100644 index 000000000..4c588e956 --- /dev/null +++ b/qcsrc/common/turrets/turrets.qc @@ -0,0 +1,78 @@ +#include "all.qh" + +// TURRET PLUGIN SYSTEM +entity turret_info[TUR_MAXCOUNT]; +entity dummy_turret_info; + +void turrets_common_precache() +{ + precache_sound ("weapons/rocket_impact.wav"); + precache_model ("models/turrets/base-gib1.md3"); + precache_model ("models/turrets/base-gib2.md3"); + precache_model ("models/turrets/base-gib3.md3"); + precache_model ("models/turrets/base-gib4.md3"); + precache_model ("models/turrets/head-gib1.md3"); + precache_model ("models/turrets/head-gib2.md3"); + precache_model ("models/turrets/head-gib3.md3"); + precache_model ("models/turrets/head-gib4.md3"); + precache_model ("models/turrets/base.md3"); + precache_model ("models/turrets/rocket.md3"); + + precache_model ("models/turrets/c512.md3"); + precache_model ("models/marker.md3"); + +#ifdef TURRET_DEBUG + precache_model ("models/turrets/terrainbase.md3"); + precache_model ("models/turrets/c512.md3"); + precache_model ("models/pathlib/goodsquare.md3"); + precache_model ("models/pathlib/badsquare.md3"); + precache_model ("models/pathlib/square.md3"); + precache_model ("models/pathlib/edge.md3"); +#endif +} + +void register_turret(float id, float(float) func, float turretflags, vector min_s, vector max_s, string modelname, string headmodelname, string shortname, string mname) +{ + entity e; + turret_info[id - 1] = e = spawn(); + e.classname = "turret_info"; + e.turretid = id; + e.netname = shortname; + e.turret_name = mname; + e.turret_func = func; + e.mdl = modelname; + e.cvar_basename = shortname; + e.spawnflags = turretflags; + e.mins = min_s; + e.maxs = max_s; + e.model = strzone(strcat("models/turrets/", modelname)); + e.head_model = strzone(strcat("models/turrets/", headmodelname)); + + #ifndef MENUQC + turrets_common_precache(); + #endif +} +float t_null(float dummy) { return 0; } +void register_turrets_done() +{ + dummy_turret_info = spawn(); + dummy_turret_info.classname = "turret_info"; + dummy_turret_info.turretid = 0; // you can recognize dummies by this + dummy_turret_info.netname = ""; + dummy_turret_info.turret_name = "Turret"; + dummy_turret_info.turret_func = t_null; + dummy_turret_info.mdl = ""; + dummy_turret_info.mins = '-0 -0 -0'; + dummy_turret_info.maxs = '0 0 0'; + dummy_turret_info.model = ""; +} +entity get_turretinfo(float id) +{ + entity m; + if(id < TUR_FIRST || id > TUR_LAST) + return dummy_turret_info; + m = turret_info[id - 1]; + if(m) + return m; + return dummy_turret_info; +} diff --git a/qcsrc/common/turrets/turrets.qh b/qcsrc/common/turrets/turrets.qh new file mode 100644 index 000000000..5491746e9 --- /dev/null +++ b/qcsrc/common/turrets/turrets.qh @@ -0,0 +1,197 @@ +// turret requests +#define TR_SETUP 1 // (BOTH) setup turret data +#define TR_THINK 2 // (SERVER) logic to run every frame +#define TR_DEATH 3 // (SERVER) called when turret dies +#define TR_PRECACHE 4 // (BOTH) precaches models/sounds used by this turret +#define TR_ATTACK 5 // (SERVER) called when turret attacks +#define TR_CONFIG 6 // (ALL) + +// functions: +entity get_turretinfo(float id); + +// fields: +.entity tur_head; + +// target selection flags +.float target_select_flags; +.float target_validate_flags; +const float TFL_TARGETSELECT_NO = 2; // don't automatically find targets +const float TFL_TARGETSELECT_LOS = 4; // require line of sight to find targets +const float TFL_TARGETSELECT_PLAYERS = 8; // target players +const float TFL_TARGETSELECT_MISSILES = 16; // target projectiles +const float TFL_TARGETSELECT_TRIGGERTARGET = 32; // respond to turret_trigger_target events +const float TFL_TARGETSELECT_ANGLELIMITS = 64; // apply extra angular limits to target selection +const float TFL_TARGETSELECT_RANGELIMITS = 128; // limit target selection range +const float TFL_TARGETSELECT_TEAMCHECK = 256; // don't attack teammates +const float TFL_TARGETSELECT_NOBUILTIN = 512; // only attack targets when triggered +const float TFL_TARGETSELECT_OWNTEAM = 1024; // only attack teammates +const float TFL_TARGETSELECT_NOTURRETS = 2048; // don't attack other turrets +const float TFL_TARGETSELECT_FOV = 4096; // extra limits to attack range +const float TFL_TARGETSELECT_MISSILESONLY = 8192; // only attack missiles + +// aim flags +.float aim_flags; +const float TFL_AIM_NO = 1; // no aiming +const float TFL_AIM_SPLASH = 2; // aim for ground around the target's feet +const float TFL_AIM_LEAD = 4; // try to predict target movement +const float TFL_AIM_SHOTTIMECOMPENSATE = 8; // compensate for shot traveltime when leading +const float TFL_AIM_ZPREDICT = 16; // predict target's z position at impact +const float TFL_AIM_SIMPLE = 32; // aim at player's current location + +// tracking flags +.float track_flags; +const float TFL_TRACK_NO = 2; // don't move head +const float TFL_TRACK_PITCH = 4; // pitch head +const float TFL_TRACK_ROTATE = 8; // rotate head + +// prefire checks +.float firecheck_flags; +const float TFL_FIRECHECK_DEAD = 4; // don't attack dead targets (zombies?) +const float TFL_FIRECHECK_DISTANCES = 8; // another range check +const float TFL_FIRECHECK_LOS = 16; // line of sight +const float TFL_FIRECHECK_AIMDIST = 32; // consider distance impactpoint<->aimspot +const float TFL_FIRECHECK_REALDIST = 64; // consider enemy origin<->impactpoint +const float TFL_FIRECHECK_ANGLEDIST = 128; // consider angular diff head<->aimspot +const float TFL_FIRECHECK_TEAMCHECK = 256; // don't attack teammates +const float TFL_FIRECHECK_AFF = 512; // try to avoid any friendly fire +const float TFL_FIRECHECK_AMMO_OWN = 1024; // own ammo needs to be larger than damage dealt +const float TFL_FIRECHECK_AMMO_OTHER = 2048; // target's ammo needs to be less than max +const float TFL_FIRECHECK_REFIRE = 4096; // check single attack finished delays +const float TFL_FIRECHECK_NO = 16384; // no prefire checks + +// attack flags +.float shoot_flags; +const float TFL_SHOOT_NO = 64; // no attacking +const float TFL_SHOOT_VOLLY = 2; // fire in vollies +const float TFL_SHOOT_VOLLYALWAYS = 4; // always do a full volly, even if target is lost +const float TFL_SHOOT_HITALLVALID = 8; // loop through all valid targets +const float TFL_SHOOT_CLEARTARGET = 16; // lose target after attack (after volly is done if in volly mode) +const float TFL_SHOOT_CUSTOM = 32; // custom attacking + +// turret capabilities +.float turret_flags; +const float TUR_FLAG_NONE = 0; // no abilities +const float TUR_FLAG_SNIPER = 2; // sniping turret +const float TUR_FLAG_SPLASH = 4; // can deal splash damage +const float TUR_FLAG_HITSCAN = 8; // hit scan +const float TUR_FLAG_MULTIGUN = 16; // multiple guns +const float TUR_FLAG_GUIDED = 32; // laser guided projectiles +const float TUR_FLAG_SLOWPROJ = 64; // turret fires slow projectiles +const float TUR_FLAG_MEDPROJ = 128; // turret fires medium projectiles +const float TUR_FLAG_FASTPROJ = 256; // turret fires fast projectiles +const float TUR_FLAG_PLAYER = 512; // can damage players +const float TUR_FLAG_MISSILE = 1024; // can damage missiles +const float TUR_FLAG_SUPPORT = 2048; // supports other units +const float TUR_FLAG_AMMOSOURCE = 4096; // can provide ammunition +const float TUR_FLAG_RECIEVETARGETS = 8192; // can recieve targets from external sources +const float TUR_FLAG_MOVE = 16384; // can move +const float TUR_FLAG_ROAM = 32768; // roams around if not attacking +const float TUR_FLAG_ISTURRET = 65536; // identifies this unit as a turret + +// ammo types +#define ammo_flags currentammo +const float TFL_AMMO_NONE = 64; // doesn't use ammo +const float TFL_AMMO_ENERGY = 2; // uses power +const float TFL_AMMO_BULLETS = 4; // uses bullets +const float TFL_AMMO_ROCKETS = 8; // uses explosives +const float TFL_AMMO_RECHARGE = 16; // regenerates ammo +const float TFL_AMMO_RECIEVE = 32; // can recieve ammo from support units + +// damage flags +.float damage_flags; +const float TFL_DMG_NO = 256; // doesn't take damage +const float TFL_DMG_YES = 2; // can be damaged +const float TFL_DMG_TEAM = 4; // can be damaged by teammates +const float TFL_DMG_RETALIATE = 8; // target attackers +const float TFL_DMG_RETALIATE_TEAM = 16; // target attackers, even if on same team +const float TFL_DMG_TARGETLOSS = 32; // loses target when damaged +const float TFL_DMG_AIMSHAKE = 64; // damage throws off aim +const float TFL_DMG_HEADSHAKE = 128; // damage shakes head +const float TFL_DMG_DEATH_NORESPAWN = 256; // no re-spawning + +// spawn flags +const float TSF_SUSPENDED = 1; +const float TSF_TERRAINBASE = 2; // currently unused +const float TSF_NO_AMMO_REGEN = 4; // disable builtin ammo regeneration +const float TSF_NO_PATHBREAK = 8; // don't break path to chase enemies, will still fire at them if possible +const float TSL_NO_RESPAWN = 16; // don't re-spawn +const float TSL_ROAM = 32; // roam while idle + +// send flags +const float TNSF_UPDATE = 2; +const float TNSF_STATUS = 4; +const float TNSF_SETUP = 8; +const float TNSF_ANG = 16; +const float TNSF_AVEL = 32; +const float TNSF_MOVE = 64; +.float anim_start_time; +const float TNSF_ANIM = 128; + +const float TNSF_FULL_UPDATE = 16777215; + + +// entity properties of turretinfo: +.float turretid; // TUR_... +.string netname; // short name +.string turret_name; // human readable name +.float(float) turret_func; // m_... +.string mdl; // currently a copy of the model +.string model; // full name of model +.string head_model; // full name of tur_head model +.string cvar_basename; // TODO: deprecate! +.float spawnflags; +.vector mins, maxs; // turret hitbox size + +// other useful macros +#define TUR_ACTION(turrettype,mrequest) (get_turretinfo(turrettype)).turret_func(mrequest) +#define TUR_NAME(turrettype) (get_turretinfo(turrettype)).turret_name + +// ===================== +// Turret Registration +// ===================== + +float t_null(float dummy); +void register_turret(float id, float(float) func, float turretflags, vector min_s, vector max_s, string modelname, string headmodelname, string shortname, string mname); +void register_turrets_done(); + +const float TUR_MAXCOUNT = 24; +#define TUR_FIRST 1 +float TUR_COUNT; +float TUR_LAST; + +#define REGISTER_TURRET_2(id,func,turretflags,min_s,max_s,modelname,headmodelname,shortname,mname) \ + float id; \ + float func(float); \ + void RegisterTurrets_##id() \ + { \ + TUR_LAST = (id = TUR_FIRST + TUR_COUNT); \ + ++TUR_COUNT; \ + register_turret(id,func,turretflags,min_s,max_s,modelname,headmodelname,shortname,mname); \ + } \ + ACCUMULATE_FUNCTION(RegisterTurrets, RegisterTurrets_##id) +#ifdef MENUQC +#define REGISTER_TURRET(id,func,turretflags,min_s,max_s,modelname,headmodelname,shortname,mname) \ + REGISTER_TURRET_2(TUR_##id,t_null,turretflags,min_s,max_s,modelname,headmodelname,shortname,mname) +#else +#define REGISTER_TURRET(id,func,turretflags,min_s,max_s,modelname,headmodelname,shortname,mname) \ + REGISTER_TURRET_2(TUR_##id,func,turretflags,min_s,max_s,modelname,headmodelname,shortname,mname) +#endif + +#define TUR_DUPECHECK(dupecheck,cvar) \ + #ifndef dupecheck \ + #define dupecheck \ + float cvar; \ + #else \ + #error DUPLICATE TURRET CVAR: cvar \ + #endif + +#define TUR_ADD_CVAR(turret,name) \ + TUR_DUPECHECK(TUR_CVAR_##turret##_##name, autocvar_g_turrets_unit_##turret##_##name) + +#define TUR_CVAR(turret,name) autocvar_g_turrets_unit_##turret##_##name + +#include "all.qh" + +#undef TUR_ADD_CVAR +#undef REGISTER_TURRET +ACCUMULATE_FUNCTION(RegisterTurrets, register_turrets_done); diff --git a/qcsrc/common/turrets/unit/ewheel.qc b/qcsrc/common/turrets/unit/ewheel.qc new file mode 100644 index 000000000..fea43eb36 --- /dev/null +++ b/qcsrc/common/turrets/unit/ewheel.qc @@ -0,0 +1,311 @@ +#ifdef REGISTER_TURRET +REGISTER_TURRET( +/* TUR_##id */ EWHEEL, +/* function */ t_ewheel, +/* spawnflags */ TUR_FLAG_PLAYER | TUR_FLAG_MOVE | TUR_FLAG_ROAM, +/* mins,maxs */ '-32 -32 0', '32 32 48', +/* model */ "ewheel-base2.md3", +/* head_model */ "ewheel-gun1.md3", +/* netname */ "ewheel", +/* fullname */ _("eWheel Turret") +); +#else +#ifdef SVQC +float autocvar_g_turrets_unit_ewheel_speed_fast; +float autocvar_g_turrets_unit_ewheel_speed_slow; +float autocvar_g_turrets_unit_ewheel_speed_slower; +float autocvar_g_turrets_unit_ewheel_speed_stop; +float autocvar_g_turrets_unit_ewheel_turnrate; + +const float ewheel_anim_stop = 0; +const float ewheel_anim_fwd_slow = 1; +const float ewheel_anim_fwd_fast = 2; +const float ewheel_anim_bck_slow = 3; +const float ewheel_anim_bck_fast = 4; + +//#define EWHEEL_FANCYPATH +void ewheel_move_path() +{ +#ifdef EWHEEL_FANCYPATH + // Are we close enougth to a path node to switch to the next? + if (vlen(self.origin - self.pathcurrent.origin) < 64) + if (self.pathcurrent.path_next == world) + { + // Path endpoint reached + pathlib_deletepath(self.pathcurrent.owner); + self.pathcurrent = world; + + if (self.pathgoal) + { + if (self.pathgoal.use) + self.pathgoal.use(); + + if (self.pathgoal.enemy) + { + self.pathcurrent = pathlib_astar(self.pathgoal.origin,self.pathgoal.enemy.origin); + self.pathgoal = self.pathgoal.enemy; + } + } + else + self.pathgoal = world; + } + else + self.pathcurrent = self.pathcurrent.path_next; + +#else + if (vlen(self.origin - self.pathcurrent.origin) < 64) + self.pathcurrent = self.pathcurrent.enemy; +#endif + + if (self.pathcurrent) + { + + self.moveto = self.pathcurrent.origin; + self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); + + movelib_move_simple(v_forward, (autocvar_g_turrets_unit_ewheel_speed_fast), 0.4); + } +} + +void ewheel_move_enemy() +{ + float newframe; + + self.steerto = steerlib_arrive(self.enemy.origin,self.target_range_optimal); + + self.moveto = self.origin + self.steerto * 128; + + if (self.tur_dist_enemy > self.target_range_optimal) + { + if ( self.tur_head.spawnshieldtime < 1 ) + { + newframe = ewheel_anim_fwd_fast; + movelib_move_simple(v_forward, (autocvar_g_turrets_unit_ewheel_speed_fast), 0.4); + } + else if (self.tur_head.spawnshieldtime < 2) + { + + newframe = ewheel_anim_fwd_slow; + movelib_move_simple(v_forward, (autocvar_g_turrets_unit_ewheel_speed_slow), 0.4); + } + else + { + newframe = ewheel_anim_fwd_slow; + movelib_move_simple(v_forward, (autocvar_g_turrets_unit_ewheel_speed_slower), 0.4); + } + } + else if (self.tur_dist_enemy < self.target_range_optimal * 0.5) + { + newframe = ewheel_anim_bck_slow; + movelib_move_simple(v_forward * -1, (autocvar_g_turrets_unit_ewheel_speed_slow), 0.4); + } + else + { + newframe = ewheel_anim_stop; + movelib_beak_simple((autocvar_g_turrets_unit_ewheel_speed_stop)); + } + + turrets_setframe(newframe, FALSE); +} + +void ewheel_move_idle() +{ + if(self.frame != 0) + { + self.SendFlags |= TNSF_ANIM; + self.anim_start_time = time; + } + + self.frame = 0; + if (vlen(self.velocity)) + movelib_beak_simple((autocvar_g_turrets_unit_ewheel_speed_stop)); +} + +void spawnfunc_turret_ewheel() { if(!turret_initialize(TUR_EWHEEL)) remove(self); } + +float t_ewheel(float req) +{ + switch(req) + { + case TR_ATTACK: + { + float i; + entity _mis; + + for (i = 0; i < 1; ++i) + { + turret_do_updates(self); + + _mis = turret_projectile(W_Sound("lasergun_fire"), 1, 0, DEATH_TURRET_EWHEEL, PROJECTILE_BLASTER, TRUE, TRUE); + _mis.missile_flags = MIF_SPLASH; + + Send_Effect(EFFECT_LASER_MUZZLEFLASH, self.tur_shotorg, self.tur_shotdir_updated * 1000, 1); + + self.tur_head.frame += 2; + + if (self.tur_head.frame > 3) + self.tur_head.frame = 0; + } + + return TRUE; + } + case TR_THINK: + { + float vz; + vector wish_angle, real_angle; + + vz = self.velocity_z; + + self.angles_x = anglemods(self.angles_x); + self.angles_y = anglemods(self.angles_y); + + fixedmakevectors(self.angles); + + wish_angle = normalize(self.steerto); + wish_angle = vectoangles(wish_angle); + real_angle = wish_angle - self.angles; + real_angle = shortangle_vxy(real_angle, self.tur_head.angles); + + self.tur_head.spawnshieldtime = fabs(real_angle_y); + real_angle_y = bound(-self.tur_head.aim_speed, real_angle_y, self.tur_head.aim_speed); + self.angles_y = (self.angles_y + real_angle_y); + + if(self.enemy) + ewheel_move_enemy(); + else if(self.pathcurrent) + ewheel_move_path(); + else + ewheel_move_idle(); + + self.velocity_z = vz; + + if(vlen(self.velocity)) + self.SendFlags |= TNSF_MOVE; + + return TRUE; + } + case TR_DEATH: + { + self.velocity = '0 0 0'; + +#ifdef EWHEEL_FANCYPATH + if (self.pathcurrent) + pathlib_deletepath(self.pathcurrent.owner); +#endif + self.pathcurrent = world; + + return TRUE; + } + case TR_SETUP: + { + entity e; + + if(self.movetype == MOVETYPE_WALK) + { + self.velocity = '0 0 0'; + self.enemy = world; + + setorigin(self, self.pos1); + + if (self.target != "") + { + e = find(world, targetname, self.target); + if (!e) + { + dprint("Initital waypoint for ewheel does NOT exsist, fix your map!\n"); + self.target = ""; + } + + if (e.classname != "turret_checkpoint") + dprint("Warning: not a turrret path\n"); + else + { + +#ifdef EWHEEL_FANCYPATH + self.pathcurrent = WALKER_PATH(self.origin,e.origin); + self.pathgoal = e; +#else + self.pathcurrent = e; +#endif + } + } + } + + self.iscreature = TRUE; + self.teleportable = TELEPORT_NORMAL; + self.damagedbycontents = TRUE; + self.movetype = MOVETYPE_WALK; + self.solid = SOLID_SLIDEBOX; + self.takedamage = DAMAGE_AIM; + self.idle_aim = '0 0 0'; + self.pos1 = self.origin; + self.target_select_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_LOS; + self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_LOS; + self.frame = self.tur_head.frame = 1; + self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE | TFL_AMMO_RECIEVE; + + // Convert from dgr / sec to dgr / tic + self.tur_head.aim_speed = (autocvar_g_turrets_unit_ewheel_turnrate); + self.tur_head.aim_speed = self.tur_head.aim_speed / (1 / self.ticrate); + + return TRUE; + } + case TR_PRECACHE: + { + precache_model ("models/turrets/ewheel-base2.md3"); + precache_model ("models/turrets/ewheel-gun1.md3"); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC + +void ewheel_draw() +{ + float dt; + + dt = time - self.move_time; + self.move_time = time; + if(dt <= 0) + return; + + fixedmakevectors(self.angles); + setorigin(self, self.origin + self.velocity * dt); + self.tur_head.angles += dt * self.tur_head.move_avelocity; + self.angles_y = self.move_angles_y; + + if (self.health < 127) + if(random() < 0.05) + te_spark(self.origin + '0 0 40', randomvec() * 256 + '0 0 256', 16); +} + +float t_ewheel(float req) +{ + switch(req) + { + case TR_SETUP: + { + self.gravity = 1; + self.movetype = MOVETYPE_BOUNCE; + self.move_movetype = MOVETYPE_BOUNCE; + self.move_origin = self.origin; + self.move_time = time; + self.draw = ewheel_draw; + + return TRUE; + } + case TR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_TURRET diff --git a/qcsrc/common/turrets/unit/flac.qc b/qcsrc/common/turrets/unit/flac.qc new file mode 100644 index 000000000..02bd7103e --- /dev/null +++ b/qcsrc/common/turrets/unit/flac.qc @@ -0,0 +1,103 @@ +#ifdef REGISTER_TURRET +REGISTER_TURRET( +/* TUR_##id */ FLAC, +/* function */ t_flac, +/* spawnflags */ TUR_FLAG_SPLASH | TUR_FLAG_FASTPROJ | TUR_FLAG_MISSILE, +/* mins,maxs */ '-32 -32 0', '32 32 64', +/* model */ "base.md3", +/* head_model */ "flac.md3", +/* netname */ "flac", +/* fullname */ _("FLAC Cannon") +); +#else +#ifdef SVQC +void turret_flac_projectile_think_explode() +{ + if(self.enemy != world) + if(vlen(self.origin - self.enemy.origin) < self.owner.shot_radius * 3) + setorigin(self,self.enemy.origin + randomvec() * self.owner.shot_radius); + +#ifdef TURRET_DEBUG + float d; + d = RadiusDamage (self, self.owner, self.owner.shot_dmg, self.owner.shot_dmg, self.owner.shot_radius, self, world, self.owner.shot_force, self.totalfrags, world); + self.owner.tur_dbg_dmg_t_h = self.owner.tur_dbg_dmg_t_h + d; + self.owner.tur_dbg_dmg_t_f = self.owner.tur_dbg_dmg_t_f + self.owner.shot_dmg; +#else + RadiusDamage (self, self.realowner, self.owner.shot_dmg, self.owner.shot_dmg, self.owner.shot_radius, self, world, self.owner.shot_force, self.totalfrags, world); +#endif + remove(self); +} + +void spawnfunc_turret_flac() { if(!turret_initialize(TUR_FLAC)) remove(self); } + +float t_flac(float req) +{ + switch(req) + { + case TR_ATTACK: + { + entity proj; + + turret_tag_fire_update(); + + proj = turret_projectile(W_Sound("hagar_fire"), 5, 0, DEATH_TURRET_FLAC, PROJECTILE_HAGAR, TRUE, TRUE); + Send_Effect(EFFECT_LASER_MUZZLEFLASH, self.tur_shotorg, self.tur_shotdir_updated * 1000, 1); + proj.think = turret_flac_projectile_think_explode; + proj.nextthink = time + self.tur_impacttime + (random() * 0.01 - random() * 0.01); + proj.missile_flags = MIF_SPLASH | MIF_PROXY; + + self.tur_head.frame = self.tur_head.frame + 1; + if (self.tur_head.frame >= 4) + self.tur_head.frame = 0; + + return TRUE; + } + case TR_THINK: + { + return TRUE; + } + case TR_DEATH: + { + return TRUE; + } + case TR_SETUP: + { + self.ammo_flags = TFL_AMMO_ROCKETS | TFL_AMMO_RECHARGE; + self.aim_flags = TFL_AIM_LEAD | TFL_AIM_SHOTTIMECOMPENSATE; + self.damage_flags |= TFL_DMG_HEADSHAKE; + self.target_select_flags |= TFL_TARGETSELECT_NOTURRETS | TFL_TARGETSELECT_MISSILESONLY; + + return TRUE; + } + case TR_PRECACHE: + { + precache_model ("models/turrets/base.md3"); + precache_model ("models/turrets/flac.md3"); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float t_flac(float req) +{ + switch(req) + { + case TR_SETUP: + { + return TRUE; + } + case TR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_TURRET diff --git a/qcsrc/common/turrets/unit/fusionreactor.qc b/qcsrc/common/turrets/unit/fusionreactor.qc new file mode 100644 index 000000000..d2a3c408e --- /dev/null +++ b/qcsrc/common/turrets/unit/fusionreactor.qc @@ -0,0 +1,116 @@ +#ifdef REGISTER_TURRET +REGISTER_TURRET( +/* TUR_##id */ FUSIONREACTOR, +/* function */ t_fusionreactor, +/* spawnflags */ TUR_FLAG_SUPPORT | TUR_FLAG_AMMOSOURCE, +/* mins,maxs */ '-34 -34 0', '34 34 90', +/* model */ "base.md3", +/* head_model */ "reactor.md3", +/* netname */ "fusionreactor", +/* fullname */ _("Fusion Reactor") +); +#else +#ifdef SVQC +float turret_fusionreactor_firecheck() +{ + if (self.attack_finished_single > time) + return 0; + + if (self.enemy.deadflag != DEAD_NO) + return 0; + + if (self.enemy == world) + return 0; + + if (self.ammo < self.shot_dmg) + return 0; + + if (self.enemy.ammo >= self.enemy.ammo_max) + return 0; + + if (vlen(self.enemy.origin - self.origin) > self.target_range) + return 0; + + if(self.team != self.enemy.team) + return 0; + + if(!(self.enemy.ammo_flags & TFL_AMMO_ENERGY)) + return 0; + + return 1; +} + +void spawnfunc_turret_fusionreactor() { if(!turret_initialize(TUR_FUSIONREACTOR)) remove(self); } + +float t_fusionreactor(float req) +{ + switch(req) + { + case TR_ATTACK: + { + vector fl_org; + + self.enemy.ammo = min(self.enemy.ammo + self.shot_dmg,self.enemy.ammo_max); + fl_org = 0.5 * (self.enemy.absmin + self.enemy.absmax); + te_smallflash(fl_org); + + return TRUE; + } + case TR_THINK: + { + self.tur_head.avelocity = '0 250 0' * (self.ammo / self.ammo_max); + + return TRUE; + } + case TR_DEATH: + { + return TRUE; + } + case TR_SETUP: + { + self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE; + self.target_select_flags = TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_OWNTEAM | TFL_TARGETSELECT_RANGELIMITS; + self.firecheck_flags = TFL_FIRECHECK_AMMO_OWN | TFL_FIRECHECK_AMMO_OTHER | TFL_FIRECHECK_DISTANCES | TFL_FIRECHECK_DEAD; + self.shoot_flags = TFL_SHOOT_HITALLVALID; + self.aim_flags = TFL_AIM_NO; + self.track_flags = TFL_TRACK_NO; + + self.tur_head.scale = 0.75; + self.tur_head.avelocity = '0 50 0'; + + self.turret_firecheckfunc = turret_fusionreactor_firecheck; + + return TRUE; + } + case TR_PRECACHE: + { + precache_model ("models/turrets/base.md3"); + precache_model ("models/turrets/reactor.md3"); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float t_fusionreactor(float req) +{ + switch(req) + { + case TR_SETUP: + { + return TRUE; + } + case TR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_TURRET diff --git a/qcsrc/common/turrets/unit/hellion.qc b/qcsrc/common/turrets/unit/hellion.qc new file mode 100644 index 000000000..b097cae70 --- /dev/null +++ b/qcsrc/common/turrets/unit/hellion.qc @@ -0,0 +1,160 @@ +#ifdef REGISTER_TURRET +REGISTER_TURRET( +/* TUR_##id */ HELLION, +/* function */ t_hellion, +/* spawnflags */ TUR_FLAG_SPLASH | TUR_FLAG_FASTPROJ | TUR_FLAG_PLAYER | TUR_FLAG_MISSILE, +/* mins,maxs */ '-32 -32 0', '32 32 64', +/* model */ "base.md3", +/* head_model */ "hellion.md3", +/* netname */ "hellion", +/* fullname */ _("Hellion Missile Turret") +); +#else +#ifdef SVQC +float autocvar_g_turrets_unit_hellion_shot_speed_gain; +float autocvar_g_turrets_unit_hellion_shot_speed_max; + +void turret_hellion_missile_think() +{ + vector olddir,newdir; + vector pre_pos; + float itime; + + self.nextthink = time + 0.05; + + olddir = normalize(self.velocity); + + if(self.max_health < time) + turret_projectile_explode(); + + // Enemy dead? just keep on the current heading then. + if ((self.enemy == world) || (self.enemy.deadflag != DEAD_NO)) + { + + // Make sure we dont return to tracking a respawned player + self.enemy = world; + + // Turn model + self.angles = vectoangles(self.velocity); + + if ( (vlen(self.origin - self.owner.origin)) > (self.owner.shot_radius * 5) ) + turret_projectile_explode(); + + // Accelerate + self.velocity = olddir * min(vlen(self.velocity) * (autocvar_g_turrets_unit_hellion_shot_speed_gain), (autocvar_g_turrets_unit_hellion_shot_speed_max)); + + UpdateCSQCProjectile(self); + + return; + } + + // Enemy in range? + if (vlen(self.origin - self.enemy.origin) < self.owner.shot_radius * 0.2) + turret_projectile_explode(); + + // Predict enemy position + itime = vlen(self.enemy.origin - self.origin) / vlen(self.velocity); + pre_pos = self.enemy.origin + self.enemy.velocity * itime; + + pre_pos = (pre_pos + self.enemy.origin) * 0.5; + + // Find out the direction to that place + newdir = normalize(pre_pos - self.origin); + + // Turn + newdir = normalize(olddir + newdir * 0.35); + + // Turn model + self.angles = vectoangles(self.velocity); + + // Accelerate + self.velocity = newdir * min(vlen(self.velocity) * (autocvar_g_turrets_unit_hellion_shot_speed_gain), (autocvar_g_turrets_unit_hellion_shot_speed_max)); + + if (itime < 0.05) + self.think = turret_projectile_explode; + + UpdateCSQCProjectile(self); +} + +void spawnfunc_turret_hellion() { if(!turret_initialize(TUR_HELLION)) remove(self); } + +float t_hellion(float req) +{ + switch(req) + { + case TR_ATTACK: + { + entity missile; + + if(self.tur_head.frame != 0) + self.tur_shotorg = gettaginfo(self.tur_head, gettagindex(self.tur_head, "tag_fire")); + else + self.tur_shotorg = gettaginfo(self.tur_head, gettagindex(self.tur_head, "tag_fire2")); + + missile = turret_projectile(W_Sound("rocket_fire"), 6, 10, DEATH_TURRET_HELLION, PROJECTILE_ROCKET, FALSE, FALSE); + te_explosion (missile.origin); + missile.think = turret_hellion_missile_think; + missile.nextthink = time; + missile.flags = FL_PROJECTILE; + missile.max_health = time + 9; + missile.tur_aimpos = randomvec() * 128; + missile.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_GUIDED_HEAT; + self.tur_head.frame += 1; + + return TRUE; + } + case TR_THINK: + { + if (self.tur_head.frame != 0) + self.tur_head.frame += 1; + + if (self.tur_head.frame >= 7) + self.tur_head.frame = 0; + + return TRUE; + } + case TR_DEATH: + { + return TRUE; + } + case TR_SETUP: + { + self.aim_flags = TFL_AIM_SIMPLE; + self.target_select_flags = TFL_TARGETSELECT_LOS | TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_TEAMCHECK ; + self.firecheck_flags = TFL_FIRECHECK_DEAD | TFL_FIRECHECK_DISTANCES | TFL_FIRECHECK_TEAMCHECK | TFL_FIRECHECK_REFIRE | TFL_FIRECHECK_AFF | TFL_FIRECHECK_AMMO_OWN; + self.ammo_flags = TFL_AMMO_ROCKETS | TFL_AMMO_RECHARGE; + + return TRUE; + } + case TR_PRECACHE: + { + precache_model ("models/turrets/base.md3"); + precache_model ("models/turrets/hellion.md3"); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float t_hellion(float req) +{ + switch(req) + { + case TR_SETUP: + { + return TRUE; + } + case TR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_TURRET diff --git a/qcsrc/common/turrets/unit/hk.qc b/qcsrc/common/turrets/unit/hk.qc new file mode 100644 index 000000000..4fc8dcbe5 --- /dev/null +++ b/qcsrc/common/turrets/unit/hk.qc @@ -0,0 +1,361 @@ +#ifdef REGISTER_TURRET +REGISTER_TURRET( +/* TUR_##id */ HK, +/* function */ t_hk, +/* spawnflags */ TUR_FLAG_SPLASH | TUR_FLAG_MEDPROJ | TUR_FLAG_PLAYER | TUR_FLAG_RECIEVETARGETS, +/* mins,maxs */ '-32 -32 0', '32 32 64', +/* model */ "base.md3", +/* head_model */ "hk.md3", +/* netname */ "hk", +/* fullname */ _("Hunter-Killer Turret") +); +#else +#ifdef SVQC +float autocvar_g_turrets_unit_hk_shot_speed; +float autocvar_g_turrets_unit_hk_shot_speed_accel; +float autocvar_g_turrets_unit_hk_shot_speed_accel2; +float autocvar_g_turrets_unit_hk_shot_speed_decel; +float autocvar_g_turrets_unit_hk_shot_speed_max; +float autocvar_g_turrets_unit_hk_shot_speed_turnrate; + +//#define TURRET_DEBUG_HK + +#ifdef TURRET_DEBUG_HK +.float atime; +#endif + +float hk_is_valid_target(entity e_target) +{ + if (e_target == world) + return 0; + + // If only this was used more.. + if (e_target.flags & FL_NOTARGET) + return 0; + + // Cant touch this + if ((e_target.takedamage == DAMAGE_NO) || (e_target.health < 0)) + return 0; + + // player + if (IS_CLIENT(e_target)) + { + if (self.owner.target_select_playerbias < 0) + return 0; + + if (e_target.deadflag != DEAD_NO) + return 0; + } + + // Missile + if ((e_target.flags & FL_PROJECTILE) && (self.owner.target_select_missilebias < 0)) + return 0; + + // Team check + if ((e_target.team == self.owner.team) || (self.owner.team == e_target.owner.team)) + return 0; + + return 1; +} + +void turret_hk_missile_think() +{ + vector vu, vd, vf, vl, vr, ve; // Vector (direction) + float fu, fd, ff, fl, fr, fe; // Fraction to solid + vector olddir,wishdir,newdir; // Final direction + float lt_for; // Length of Trace FORwrad + float lt_seek; // Length of Trace SEEK (left, right, up down) + float pt_seek; // Pitch of Trace SEEK (How mutch to angele left, right up, down trace towards v_forward) + vector pre_pos; + float myspeed; + entity e; + float ad,edist; + + self.nextthink = time + self.ticrate; + + //if (self.cnt < time) + // turret_hk_missile_explode(); + + if (self.enemy.deadflag != DEAD_NO) + self.enemy = world; + + // Pick the closest valid target. + if (!self.enemy) + { + e = findradius(self.origin, 5000); + while (e) + { + if (hk_is_valid_target(e)) + { + if (!self.enemy) + self.enemy = e; + else + if (vlen(self.origin - e.origin) < vlen(self.origin - self.enemy.origin)) + self.enemy = e; + } + e = e.chain; + } + } + + self.angles = vectoangles(self.velocity); + self.angles_x = self.angles_x * -1; + makevectors(self.angles); + self.angles_x = self.angles_x * -1; + + if (self.enemy) + { + edist = vlen(self.origin - self.enemy.origin); + // Close enougth to do decent damage? + if ( edist <= (self.owner.shot_radius * 0.25) ) + { + turret_projectile_explode(); + return; + } + + // Get data on enemy position + pre_pos = self.enemy.origin + + self.enemy.velocity * + min((vlen(self.enemy.origin - self.origin) / vlen(self.velocity)),0.5); + + traceline(self.origin, pre_pos,TRUE,self.enemy); + ve = normalize(pre_pos - self.origin); + fe = trace_fraction; + + } + else + { + edist = 0; + ve = '0 0 0'; + fe = 0; + } + + if ((fe != 1) || (self.enemy == world) || (edist > 1000)) + { + myspeed = vlen(self.velocity); + + lt_for = myspeed * 3; + lt_seek = myspeed * 2.95; + + // Trace forward + traceline(self.origin, self.origin + v_forward * lt_for,FALSE,self); + vf = trace_endpos; + ff = trace_fraction; + + // Find angular offset + ad = vlen(vectoangles(normalize(self.enemy.origin - self.origin)) - self.angles); + + // To close to something, Slow down! + if ( ((ff < 0.7) || (ad > 4)) && (myspeed > (autocvar_g_turrets_unit_hk_shot_speed)) ) + myspeed = max(myspeed * (autocvar_g_turrets_unit_hk_shot_speed_decel), (autocvar_g_turrets_unit_hk_shot_speed)); + + // Failry clear, accelerate. + if ( (ff > 0.7) && (myspeed < (autocvar_g_turrets_unit_hk_shot_speed_max)) ) + myspeed = min(myspeed * (autocvar_g_turrets_unit_hk_shot_speed_accel), (autocvar_g_turrets_unit_hk_shot_speed_max)); + + // Setup trace pitch + pt_seek = 1 - ff; + pt_seek = bound(0.15,pt_seek,0.8); + if (ff < 0.5) pt_seek = 1; + + // Trace left + traceline(self.origin, self.origin + (-1 * (v_right * pt_seek) + (v_forward * ff)) * lt_seek,FALSE,self); + vl = trace_endpos; + fl = trace_fraction; + + // Trace right + traceline(self.origin, self.origin + ((v_right * pt_seek) + (v_forward * ff)) * lt_seek ,FALSE,self); + vr = trace_endpos; + fr = trace_fraction; + + // Trace up + traceline(self.origin, self.origin + ((v_up * pt_seek) + (v_forward * ff)) * lt_seek ,FALSE,self); + vu = trace_endpos; + fu = trace_fraction; + + // Trace down + traceline(self.origin, self.origin + (-1 * (v_up * pt_seek) + (v_forward * ff)) * lt_seek ,FALSE,self); + vd = trace_endpos; + fd = trace_fraction; + + vl = normalize(vl - self.origin); + vr = normalize(vr - self.origin); + vu = normalize(vu - self.origin); + vd = normalize(vd - self.origin); + + // Panic tresh passed, find a single direction and turn as hard as we can + if (pt_seek == 1) + { + wishdir = v_right; + if (fl > fr) wishdir = -1 * v_right; + if (fu > fl) wishdir = v_up; + if (fd > fu) wishdir = -1 * v_up; + } + else + { + // Normalize our trace vectors to make a smooth path + wishdir = normalize( (vl * fl) + (vr * fr) + (vu * fu) + (vd * fd) ); + } + + if (self.enemy) + { + if (fe < 0.1) fe = 0.1; // Make sure we always try to move sligtly towards our target + wishdir = (wishdir * (1 - fe)) + (ve * fe); + } + } + else + { + // Got a clear path to target, speed up fast (if not at full speed) and go straight for it. + myspeed = vlen(self.velocity); + if (myspeed < (autocvar_g_turrets_unit_hk_shot_speed_max)) + myspeed = min(myspeed * (autocvar_g_turrets_unit_hk_shot_speed_accel2),(autocvar_g_turrets_unit_hk_shot_speed_max)); + + wishdir = ve; + } + + if ((myspeed > (autocvar_g_turrets_unit_hk_shot_speed)) && (self.cnt > time)) + myspeed = min(myspeed * (autocvar_g_turrets_unit_hk_shot_speed_accel2),(autocvar_g_turrets_unit_hk_shot_speed_max)); + + // Ranoutagazfish? + if (self.cnt < time) + { + self.cnt = time + 0.25; + self.nextthink = 0; + self.movetype = MOVETYPE_BOUNCE; + return; + } + + // Calculate new heading + olddir = normalize(self.velocity); + newdir = normalize(olddir + wishdir * (autocvar_g_turrets_unit_hk_shot_speed_turnrate)); + + // Set heading & speed + self.velocity = newdir * myspeed; + + // Align model with new heading + self.angles = vectoangles(self.velocity); + + +#ifdef TURRET_DEBUG_HK + //if(self.atime < time) { + if ((fe <= 0.99)||(edist > 1000)) + { + te_lightning2(world,self.origin, self.origin + vr * lt_seek); + te_lightning2(world,self.origin, self.origin + vl * lt_seek); + te_lightning2(world,self.origin, self.origin + vu * lt_seek); + te_lightning2(world,self.origin, self.origin + vd * lt_seek); + te_lightning2(world,self.origin, vf); + } + else + { + te_lightning2(world,self.origin, self.enemy.origin); + } + bprint("Speed: ", ftos(rint(myspeed)), "\n"); + bprint("Trace to solid: ", ftos(rint(ff * 100)), "%\n"); + bprint("Trace to target:", ftos(rint(fe * 100)), "%\n"); + self.atime = time + 0.2; + //} +#endif + + UpdateCSQCProjectile(self); +} + +float turret_hk_addtarget(entity e_target,entity e_sender) +{ + if (e_target) + { + if (turret_validate_target(self,e_target,self.target_validate_flags) > 0) + { + self.enemy = e_target; + return 1; + } + } + + return 0; +} + +void spawnfunc_turret_hk() { if(!turret_initialize(TUR_HK)) remove(self); } + +float t_hk(float req) +{ + switch(req) + { + case TR_ATTACK: + { + entity missile; + + missile = turret_projectile(W_Sound("rocket_fire"), 6, 10, DEATH_TURRET_HK, PROJECTILE_ROCKET, FALSE, FALSE); + te_explosion (missile.origin); + + missile.think = turret_hk_missile_think; + missile.nextthink = time + 0.25; + missile.movetype = MOVETYPE_BOUNCEMISSILE; + missile.velocity = self.tur_shotdir_updated * (self.shot_speed * 0.75); + missile.angles = vectoangles(missile.velocity); + missile.cnt = time + 30; + missile.ticrate = max(autocvar_sys_ticrate, 0.05); + missile.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_GUIDED_AI; + + if (self.tur_head.frame == 0) + self.tur_head.frame = self.tur_head.frame + 1; + + return TRUE; + } + case TR_THINK: + { + if (self.tur_head.frame != 0) + self.tur_head.frame = self.tur_head.frame + 1; + + if (self.tur_head.frame > 5) + self.tur_head.frame = 0; + + return TRUE; + } + case TR_DEATH: + { + return TRUE; + } + case TR_SETUP: + { + self.ammo_flags = TFL_AMMO_ROCKETS | TFL_AMMO_RECHARGE; + self.aim_flags = TFL_AIM_SIMPLE; + self.target_select_flags = TFL_TARGETSELECT_LOS | TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_TRIGGERTARGET | TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_TEAMCHECK; + self.firecheck_flags = TFL_FIRECHECK_DEAD | TFL_FIRECHECK_TEAMCHECK | TFL_FIRECHECK_REFIRE | TFL_FIRECHECK_AFF; + self.shoot_flags = TFL_SHOOT_CLEARTARGET; + self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_TEAMCHECK; + + self.turret_addtarget = turret_hk_addtarget; + + return TRUE; + } + case TR_PRECACHE: + { + precache_model ("models/turrets/base.md3"); + precache_model ("models/turrets/hk.md3"); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float t_hk(float req) +{ + switch(req) + { + case TR_SETUP: + { + return TRUE; + } + case TR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_TURRET diff --git a/qcsrc/common/turrets/unit/machinegun.qc b/qcsrc/common/turrets/unit/machinegun.qc new file mode 100644 index 000000000..57267ef86 --- /dev/null +++ b/qcsrc/common/turrets/unit/machinegun.qc @@ -0,0 +1,79 @@ +#ifdef REGISTER_TURRET +REGISTER_TURRET( +/* TUR_##id */ MACHINEGUN, +/* function */ t_machinegun, +/* spawnflags */ TUR_FLAG_PLAYER, +/* mins,maxs */ '-32 -32 0', '32 32 64', +/* model */ "base.md3", +/* head_model */ "machinegun.md3", +/* netname */ "machinegun", +/* fullname */ _("Machinegun Turret") +); +#else +#ifdef SVQC +void spawnfunc_turret_machinegun() { if(!turret_initialize(TUR_MACHINEGUN)) remove(self); } + +float t_machinegun(float req) +{ + switch(req) + { + case TR_ATTACK: + { + fireBullet (self.tur_shotorg, self.tur_shotdir_updated, self.shot_spread, 0, self.shot_dmg, self.shot_force, DEATH_TURRET_MACHINEGUN, 0); + + W_MachineGun_MuzzleFlash(); + setattachment(self.muzzle_flash, self.tur_head, "tag_fire"); + + return TRUE; + } + case TR_THINK: + { + return TRUE; + } + case TR_DEATH: + { + return TRUE; + } + case TR_SETUP: + { + self.damage_flags |= TFL_DMG_HEADSHAKE; + self.target_select_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_TEAMCHECK; + self.ammo_flags = TFL_AMMO_BULLETS | TFL_AMMO_RECHARGE | TFL_AMMO_RECIEVE; + self.aim_flags = TFL_AIM_LEAD | TFL_AIM_SHOTTIMECOMPENSATE; + self.turret_flags |= TUR_FLAG_HITSCAN; + + return TRUE; + } + case TR_PRECACHE: + { + precache_model ("models/turrets/base.md3"); + precache_model ("models/turrets/machinegun.md3"); + precache_sound (W_Sound("uzi_fire")); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float t_machinegun(float req) +{ + switch(req) + { + case TR_SETUP: + { + return TRUE; + } + case TR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_TURRET diff --git a/qcsrc/common/turrets/unit/mlrs.qc b/qcsrc/common/turrets/unit/mlrs.qc new file mode 100644 index 000000000..1c979961c --- /dev/null +++ b/qcsrc/common/turrets/unit/mlrs.qc @@ -0,0 +1,90 @@ +#ifdef REGISTER_TURRET +REGISTER_TURRET( +/* TUR_##id */ MLRS, +/* function */ t_mlrs, +/* spawnflags */ TUR_FLAG_SPLASH | TUR_FLAG_MEDPROJ | TUR_FLAG_PLAYER, +/* mins,maxs */ '-32 -32 0', '32 32 64', +/* model */ "base.md3", +/* head_model */ "mlrs.md3", +/* netname */ "mlrs", +/* fullname */ _("MLRS Turret") +); +#else +#ifdef SVQC +void spawnfunc_turret_mlrs() { if(!turret_initialize(TUR_MLRS)) remove(self); } + +float t_mlrs(float req) +{ + switch(req) + { + case TR_ATTACK: + { + entity missile; + + turret_tag_fire_update(); + missile = turret_projectile(W_Sound("rocket_fire"), 6, 10, DEATH_TURRET_MLRS, PROJECTILE_ROCKET, TRUE, TRUE); + missile.nextthink = time + max(self.tur_impacttime,(self.shot_radius * 2) / self.shot_speed); + missile.missile_flags = MIF_SPLASH; + te_explosion (missile.origin); + + return TRUE; + } + case TR_THINK: + { + // 0 = full, 6 = empty + self.tur_head.frame = bound(0, 6 - floor(0.1 + self.ammo / self.shot_dmg), 6); + if(self.tur_head.frame < 0) + { + dprint("ammo:",ftos(self.ammo),"\n"); + dprint("shot_dmg:",ftos(self.shot_dmg),"\n"); + } + + return TRUE; + } + case TR_DEATH: + { + return TRUE; + } + case TR_SETUP: + { + self.ammo_flags = TFL_AMMO_ROCKETS | TFL_AMMO_RECHARGE; + self.aim_flags = TFL_AIM_LEAD | TFL_AIM_SHOTTIMECOMPENSATE; + + self.damage_flags |= TFL_DMG_HEADSHAKE; + self.shoot_flags |= TFL_SHOOT_VOLLYALWAYS; + self.volly_counter = self.shot_volly; + + return TRUE; + } + case TR_PRECACHE: + { + precache_model ("models/turrets/base.md3"); + precache_model ("models/turrets/mlrs.md3"); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float t_mlrs(float req) +{ + switch(req) + { + case TR_SETUP: + { + return TRUE; + } + case TR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_TURRET diff --git a/qcsrc/common/turrets/unit/phaser.qc b/qcsrc/common/turrets/unit/phaser.qc new file mode 100644 index 000000000..4fd0a6581 --- /dev/null +++ b/qcsrc/common/turrets/unit/phaser.qc @@ -0,0 +1,173 @@ +#ifdef REGISTER_TURRET +REGISTER_TURRET( +/* TUR_##id */ PHASER, +/* function */ t_phaser, +/* spawnflags */ TUR_FLAG_SNIPER | TUR_FLAG_HITSCAN | TUR_FLAG_PLAYER, +/* mins,maxs */ '-32 -32 0', '32 32 64', +/* model */ "base.md3", +/* head_model */ "phaser.md3", +/* netname */ "phaser", +/* fullname */ _("Phaser Cannon") +); +#else +#ifdef SVQC +.float fireflag; + +float turret_phaser_firecheck() +{ + if (self.fireflag != 0) return 0; + return turret_firecheck(); +} + +void beam_think() +{ + if ((time > self.cnt) || (self.owner.deadflag != DEAD_NO)) + { + self.owner.attack_finished_single = time + self.owner.shot_refire; + self.owner.fireflag = 2; + self.owner.tur_head.frame = 10; + sound (self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM); + remove(self); + return; + } + + turret_do_updates(self.owner); + + if (time - self.shot_spread > 0) + { + self.shot_spread = time + 2; + sound (self, CH_SHOTS_SINGLE, "turrets/phaser.wav", VOL_BASE, ATTEN_NORM); + } + + + self.nextthink = time + self.ticrate; + + self.owner.attack_finished_single = time + frametime; + entity oldself; + oldself = self; + self = self.owner; + FireImoBeam ( self.tur_shotorg, + self.tur_shotorg + self.tur_shotdir_updated * self.target_range, + '-1 -1 -1' * self.shot_radius, + '1 1 1' * self.shot_radius, + self.shot_force, + oldself.shot_dmg, + 0.75, + DEATH_TURRET_PHASER); + self = oldself; + self.scale = vlen(self.owner.tur_shotorg - trace_endpos) / 256; + +} + +void spawnfunc_turret_phaser() { if(!turret_initialize(TUR_PHASER)) remove(self); } + +float t_phaser(float req) +{ + switch(req) + { + case TR_ATTACK: + { + entity beam; + + beam = spawn(); + beam.ticrate = 0.1; //autocvar_sys_ticrate; + setmodel(beam,"models/turrets/phaser_beam.md3"); + beam.effects = EF_LOWPRECISION; + beam.solid = SOLID_NOT; + beam.think = beam_think; + beam.cnt = time + self.shot_speed; + beam.shot_spread = time + 2; + beam.nextthink = time; + beam.owner = self; + beam.shot_dmg = self.shot_dmg / (self.shot_speed / beam.ticrate); + beam.scale = self.target_range / 256; + beam.movetype = MOVETYPE_NONE; + beam.enemy = self.enemy; + beam.bot_dodge = TRUE; + beam.bot_dodgerating = beam.shot_dmg; + sound (beam, CH_SHOTS_SINGLE, "turrets/phaser.wav", VOL_BASE, ATTEN_NORM); + self.fireflag = 1; + + beam.attack_finished_single = self.attack_finished_single; + self.attack_finished_single = time; // + autocvar_sys_ticrate; + + setattachment(beam,self.tur_head,"tag_fire"); + + soundat (self, trace_endpos, CH_SHOTS, W_Sound("neximpact"), VOL_BASE, ATTEN_NORM); + + if (self.tur_head.frame == 0) + self.tur_head.frame = 1; + + return TRUE; + } + case TR_THINK: + { + if (self.tur_head.frame != 0) + { + if (self.fireflag == 1) + { + if (self.tur_head.frame == 10) + self.tur_head.frame = 1; + else + self.tur_head.frame = self.tur_head.frame +1; + } + else if (self.fireflag == 2 ) + { + self.tur_head.frame = self.tur_head.frame +1; + if (self.tur_head.frame == 15) + { + self.tur_head.frame = 0; + self.fireflag = 0; + } + } + } + + return TRUE; + } + case TR_DEATH: + { + return TRUE; + } + case TR_SETUP: + { + self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE | TFL_AMMO_RECIEVE; + self.aim_flags = TFL_AIM_LEAD; + + self.turret_firecheckfunc = turret_phaser_firecheck; + + return TRUE; + } + case TR_PRECACHE: + { + precache_model ("models/turrets/base.md3"); + precache_model ("models/turrets/phaser.md3"); + precache_model ("models/turrets/phaser_beam.md3"); + precache_sound ("turrets/phaser.wav"); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float t_phaser(float req) +{ + switch(req) + { + case TR_SETUP: + { + return TRUE; + } + case TR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_TURRET diff --git a/qcsrc/common/turrets/unit/plasma.qc b/qcsrc/common/turrets/unit/plasma.qc new file mode 100644 index 000000000..8f925aa40 --- /dev/null +++ b/qcsrc/common/turrets/unit/plasma.qc @@ -0,0 +1,118 @@ +#ifdef REGISTER_TURRET +REGISTER_TURRET( +/* TUR_##id */ PLASMA, +/* function */ t_plasma, +/* spawnflags */ TUR_FLAG_SPLASH | TUR_FLAG_MEDPROJ | TUR_FLAG_PLAYER, +/* mins,maxs */ '-32 -32 0', '32 32 64', +/* model */ "base.md3", +/* head_model */ "plasma.md3", +/* netname */ "plasma", +/* fullname */ _("Plasma Cannon") +); +#else +#ifdef SVQC +void spawnfunc_turret_plasma() { if(!turret_initialize(TUR_PLASMA)) remove(self); } + +float t_plasma(float req) +{ + switch(req) + { + case TR_ATTACK: + { + if(g_instagib) + { + float flying; + flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last + + FireRailgunBullet (self.tur_shotorg, self.tur_shotorg + self.tur_shotdir_updated * MAX_SHOT_DISTANCE, 10000000000, + 800, 0, 0, 0, 0, DEATH_TURRET_PLASMA); + + Send_Effect(EFFECT_VORTEX_MUZZLEFLASH, self.tur_shotorg, self.tur_shotdir_updated * 1000, 1); + + // teamcolor / hit beam effect + vector v; + float f; + v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); + switch(self.team) + { + case NUM_TEAM_1: f = (damage_goodhits) ? EFFECT_VAPORIZER_RED_HIT : EFFECT_VAPORIZER_RED; break; + case NUM_TEAM_2: f = (damage_goodhits) ? EFFECT_VAPORIZER_BLUE_HIT : EFFECT_VAPORIZER_BLUE; break; + case NUM_TEAM_3: f = (damage_goodhits) ? EFFECT_VAPORIZER_YELLOW_HIT : EFFECT_VAPORIZER_YELLOW; break; + case NUM_TEAM_4: f = (damage_goodhits) ? EFFECT_VAPORIZER_PINK_HIT : EFFECT_VAPORIZER_PINK; break; + default: f = (damage_goodhits) ? EFFECT_VAPORIZER_NEUTRAL_HIT : EFFECT_VAPORIZER_NEUTRAL; break; + } + + Send_Effect(f, self.tur_shotorg, v, 0); + + if (self.tur_head.frame == 0) + self.tur_head.frame = 1; + } + else + { + entity missile = turret_projectile(W_Sound("hagar_fire"), 1, 0, DEATH_TURRET_PLASMA, PROJECTILE_ELECTRO_BEAM, TRUE, TRUE); + missile.missile_flags = MIF_SPLASH; + + Send_Effect(EFFECT_LASER_MUZZLEFLASH, self.tur_shotorg, self.tur_shotdir_updated * 1000, 1); + if (self.tur_head.frame == 0) + self.tur_head.frame = 1; + } + + return TRUE; + } + case TR_THINK: + { + if (self.tur_head.frame != 0) + self.tur_head.frame = self.tur_head.frame + 1; + + if (self.tur_head.frame > 5) + self.tur_head.frame = 0; + + return TRUE; + } + case TR_DEATH: + { + return TRUE; + } + case TR_SETUP: + { + self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE | TFL_AMMO_RECIEVE; + self.damage_flags |= TFL_DMG_HEADSHAKE; + self.firecheck_flags |= TFL_FIRECHECK_AFF; + self.aim_flags = TFL_AIM_LEAD | TFL_AIM_SHOTTIMECOMPENSATE | TFL_AIM_SPLASH; + + turret_do_updates(self); + + return TRUE; + } + case TR_PRECACHE: + { + precache_model ("models/turrets/base.md3"); + precache_model ("models/turrets/plasma.md3"); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float t_plasma(float req) +{ + switch(req) + { + case TR_SETUP: + { + return TRUE; + } + case TR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_TURRET diff --git a/qcsrc/common/turrets/unit/plasma_dual.qc b/qcsrc/common/turrets/unit/plasma_dual.qc new file mode 100644 index 000000000..22f272b21 --- /dev/null +++ b/qcsrc/common/turrets/unit/plasma_dual.qc @@ -0,0 +1,116 @@ +#ifdef REGISTER_TURRET +REGISTER_TURRET( +/* TUR_##id */ PLASMA_DUAL, +/* function */ t_plasma_dual, +/* spawnflags */ TUR_FLAG_SPLASH | TUR_FLAG_MEDPROJ | TUR_FLAG_PLAYER, +/* mins,maxs */ '-32 -32 0', '32 32 64', +/* model */ "base.md3", +/* head_model */ "plasmad.md3", +/* netname */ "plasma_dual", +/* fullname */ _("Dual Plasma Cannon") +); +#else +#ifdef SVQC +void spawnfunc_turret_plasma_dual() { if(!turret_initialize(TUR_PLASMA_DUAL)) remove(self); } + +float t_plasma_dual(float req) +{ + switch(req) + { + case TR_ATTACK: + { + if(g_instagib) + { + float flying; + flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last + + FireRailgunBullet (self.tur_shotorg, self.tur_shotorg + self.tur_shotdir_updated * MAX_SHOT_DISTANCE, 10000000000, + 800, 0, 0, 0, 0, DEATH_TURRET_PLASMA); + + + Send_Effect(EFFECT_VORTEX_MUZZLEFLASH, self.tur_shotorg, self.tur_shotdir_updated * 1000, 1); + + // teamcolor / hit beam effect + vector v; + v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); + float f; + switch(self.team) + { + case NUM_TEAM_1: f = (damage_goodhits) ? EFFECT_VAPORIZER_RED_HIT : EFFECT_VAPORIZER_RED; break; + case NUM_TEAM_2: f = (damage_goodhits) ? EFFECT_VAPORIZER_BLUE_HIT : EFFECT_VAPORIZER_BLUE; break; + case NUM_TEAM_3: f = (damage_goodhits) ? EFFECT_VAPORIZER_YELLOW_HIT : EFFECT_VAPORIZER_YELLOW; break; + case NUM_TEAM_4: f = (damage_goodhits) ? EFFECT_VAPORIZER_PINK_HIT : EFFECT_VAPORIZER_PINK; break; + default: f = (damage_goodhits) ? EFFECT_VAPORIZER_NEUTRAL_HIT : EFFECT_VAPORIZER_NEUTRAL; break; + } + + Send_Effect(f, self.tur_shotorg, v, 0); + + self.tur_head.frame += 1; + } + else + { + entity missile = turret_projectile(W_Sound("hagar_fire"), 1, 0, DEATH_TURRET_PLASMA, PROJECTILE_ELECTRO_BEAM, TRUE, TRUE); + missile.missile_flags = MIF_SPLASH; + Send_Effect(EFFECT_LASER_MUZZLEFLASH, self.tur_shotorg, self.tur_shotdir_updated * 1000, 1); + self.tur_head.frame += 1; + } + + return TRUE; + } + case TR_THINK: + { + if ((self.tur_head.frame != 0) && (self.tur_head.frame != 3)) + self.tur_head.frame = self.tur_head.frame + 1; + + if (self.tur_head.frame > 6) + self.tur_head.frame = 0; + + return TRUE; + } + case TR_DEATH: + { + return TRUE; + } + case TR_SETUP: + { + self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE | TFL_AMMO_RECIEVE; + self.damage_flags |= TFL_DMG_HEADSHAKE; + self.firecheck_flags |= TFL_FIRECHECK_AFF; + self.aim_flags = TFL_AIM_LEAD | TFL_AIM_SHOTTIMECOMPENSATE | TFL_AIM_SPLASH; + + turret_do_updates(self); + + return TRUE; + } + case TR_PRECACHE: + { + precache_model ("models/turrets/base.md3"); + precache_model ("models/turrets/plasmad.md3"); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float t_plasma_dual(float req) +{ + switch(req) + { + case TR_SETUP: + { + return TRUE; + } + case TR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_TURRET diff --git a/qcsrc/common/turrets/unit/tesla.qc b/qcsrc/common/turrets/unit/tesla.qc new file mode 100644 index 000000000..2878c3187 --- /dev/null +++ b/qcsrc/common/turrets/unit/tesla.qc @@ -0,0 +1,217 @@ +#ifdef REGISTER_TURRET +REGISTER_TURRET( +/* TUR_##id */ TESLA, +/* function */ t_tesla, +/* spawnflags */ TUR_FLAG_HITSCAN | TUR_FLAG_PLAYER | TUR_FLAG_MISSILE, +/* mins,maxs */ '-60 -60 0', '60 60 128', +/* model */ "tesla_base.md3", +/* head_model */ "tesla_head.md3", +/* netname */ "tesla", +/* fullname */ _("Tesla Coil") +); +#else +#ifdef SVQC +entity toast(entity from, float range, float damage) +{ + entity e; + entity etarget = world; + float d,dd; + float r; + + dd = range + 1; + + e = findradius(from.origin,range); + while (e) + { + if ((e.railgunhit != 1) && (e != from)) + { + r = turret_validate_target(self,e,self.target_validate_flags); + if (r > 0) + { + traceline(from.origin,0.5 * (e.absmin + e.absmax),MOVE_WORLDONLY,from); + if (trace_fraction == 1.0) + { + d = vlen(e.origin - from.origin); + if (d < dd) + { + dd = d; + etarget = e; + } + } + } + } + e = e.chain; + } + + if (etarget) + { + te_csqc_lightningarc(from.origin,etarget.origin); + Damage(etarget, self, self, damage, DEATH_TURRET_TESLA, etarget.origin, '0 0 0'); + etarget.railgunhit = 1; + } + + return etarget; +} + +float turret_tesla_firecheck() +{ + // g_turrets_targetscan_maxdelay forces a target re-scan at least this often + float do_target_scan = 0; + + if((self.target_select_time + autocvar_g_turrets_targetscan_maxdelay) < time) + do_target_scan = 1; + + // Old target (if any) invalid? + if(self.target_validate_time < time) + if (turret_validate_target(self, self.enemy, self.target_validate_flags) <= 0) + { + self.enemy = world; + self.target_validate_time = time + 0.5; + do_target_scan = 1; + } + + // But never more often then g_turrets_targetscan_mindelay! + if (self.target_select_time + autocvar_g_turrets_targetscan_mindelay > time) + do_target_scan = 0; + + if(do_target_scan) + { + self.enemy = turret_select_target(); + self.target_select_time = time; + } + + if(!turret_firecheck()) + return 0; + + if(self.enemy) + return 1; + + return 0; +} + +void spawnfunc_turret_tesla() { if(!turret_initialize(TUR_TESLA)) remove(self); } + +float t_tesla(float req) +{ + switch(req) + { + case TR_ATTACK: + { + entity e, t; + float d, r, i; + + d = self.shot_dmg; + r = self.target_range; + e = spawn(); + setorigin(e,self.tur_shotorg); + + self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_MISSILES | TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_TEAMCHECK; + + t = toast(e,r,d); + remove(e); + + if (t == world) return TRUE; + + self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_MISSILES | TFL_TARGETSELECT_TEAMCHECK; + + self.attack_finished_single = time + self.shot_refire; + for (i = 0; i < 10; ++i) + { + d *= 0.75; + r *= 0.85; + t = toast(t, r, d); + if (t == world) break; + + } + + e = findchainfloat(railgunhit, 1); + while (e) + { + e.railgunhit = 0; + e = e.chain; + } + + return TRUE; + } + case TR_THINK: + { + if(!self.active) + { + self.tur_head.avelocity = '0 0 0'; + return TRUE; + } + + if(self.ammo < self.shot_dmg) + { + self.tur_head.avelocity = '0 45 0' * (self.ammo / self.shot_dmg); + } + else + { + self.tur_head.avelocity = '0 180 0' * (self.ammo / self.shot_dmg); + + if(self.attack_finished_single > time) + return TRUE; + + float f; + f = (self.ammo / self.ammo_max); + f = f * f; + if(f > random()) + if(random() < 0.1) + te_csqc_lightningarc(self.tur_shotorg,self.tur_shotorg + (randomvec() * 350)); + } + + return TRUE; + } + case TR_DEATH: + { + return TRUE; + } + case TR_SETUP: + { + self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_MISSILES | + TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_TEAMCHECK; + + self.turret_firecheckfunc = turret_tesla_firecheck; + self.target_select_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_MISSILES | + TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_TEAMCHECK; + + self.firecheck_flags = TFL_FIRECHECK_REFIRE | TFL_FIRECHECK_AMMO_OWN; + self.shoot_flags = TFL_SHOOT_CUSTOM; + self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE | TFL_AMMO_RECIEVE; + self.aim_flags = TFL_AIM_NO; + self.track_flags = TFL_TRACK_NO; + + return TRUE; + } + case TR_PRECACHE: + { + precache_model ("models/turrets/tesla_base.md3"); + precache_model ("models/turrets/tesla_head.md3"); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float t_tesla(float req) +{ + switch(req) + { + case TR_SETUP: + { + return TRUE; + } + case TR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_TURRET diff --git a/qcsrc/common/turrets/unit/walker.qc b/qcsrc/common/turrets/unit/walker.qc new file mode 100644 index 000000000..644db2c71 --- /dev/null +++ b/qcsrc/common/turrets/unit/walker.qc @@ -0,0 +1,696 @@ +#ifdef REGISTER_TURRET +REGISTER_TURRET( +/* TUR_##id */ WALKER, +/* function */ t_walker, +/* spawnflags */ TUR_FLAG_PLAYER | TUR_FLAG_MOVE, +/* mins,maxs */ '-70 -70 0', '70 70 95', +/* model */ "walker_body.md3", +/* head_model */ "walker_head_minigun.md3", +/* netname */ "walker", +/* fullname */ _("Walker Turret") +); +#else +#ifdef SVQC +float autocvar_g_turrets_unit_walker_melee_damage; +float autocvar_g_turrets_unit_walker_melee_force; +float autocvar_g_turrets_unit_walker_melee_range; +float autocvar_g_turrets_unit_walker_rocket_damage; +float autocvar_g_turrets_unit_walker_rocket_radius; +float autocvar_g_turrets_unit_walker_rocket_force; +float autocvar_g_turrets_unit_walker_rocket_speed; +float autocvar_g_turrets_unit_walker_rocket_range; +float autocvar_g_turrets_unit_walker_rocket_range_min; +float autocvar_g_turrets_unit_walker_rocket_refire; +float autocvar_g_turrets_unit_walker_rocket_turnrate; +float autocvar_g_turrets_unit_walker_speed_stop; +float autocvar_g_turrets_unit_walker_speed_walk; +float autocvar_g_turrets_unit_walker_speed_run; +float autocvar_g_turrets_unit_walker_speed_jump; +float autocvar_g_turrets_unit_walker_speed_swim; +float autocvar_g_turrets_unit_walker_speed_roam; +float autocvar_g_turrets_unit_walker_turn; +float autocvar_g_turrets_unit_walker_turn_walk; +float autocvar_g_turrets_unit_walker_turn_strafe; +float autocvar_g_turrets_unit_walker_turn_swim; +float autocvar_g_turrets_unit_walker_turn_run; + +#define ANIM_NO 0 +#define ANIM_TURN 1 +#define ANIM_WALK 2 +#define ANIM_RUN 3 +#define ANIM_STRAFE_L 4 +#define ANIM_STRAFE_R 5 +#define ANIM_JUMP 6 +#define ANIM_LAND 7 +#define ANIM_PAIN 8 +#define ANIM_MELEE 9 +#define ANIM_SWIM 10 +#define ANIM_ROAM 11 + +.float animflag; +.float idletime; + +#define WALKER_PATH(s,e) pathlib_astar(s,e) + +float walker_firecheck() +{ + if (self.animflag == ANIM_MELEE) + return 0; + + return turret_firecheck(); +} + +void walker_melee_do_dmg() +{ + vector where; + entity e; + + makevectors(self.angles); + where = self.origin + v_forward * 128; + + e = findradius(where,32); + while (e) + { + if (turret_validate_target(self, e, self.target_validate_flags)) + if (e != self && e.owner != self) + Damage(e, self, self, (autocvar_g_turrets_unit_walker_melee_damage), DEATH_TURRET_WALK_MELEE, '0 0 0', v_forward * (autocvar_g_turrets_unit_walker_melee_force)); + + e = e.chain; + } +} + +void walker_setnoanim() +{ + turrets_setframe(ANIM_NO, FALSE); + self.animflag = self.frame; +} +void walker_rocket_explode() +{ + RadiusDamage (self, self.owner, (autocvar_g_turrets_unit_walker_rocket_damage), 0, (autocvar_g_turrets_unit_walker_rocket_radius), self, world, (autocvar_g_turrets_unit_walker_rocket_force), DEATH_TURRET_WALK_ROCKET, world); + remove (self); +} + +void walker_rocket_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce) +{ + self.health = self.health - damage; + self.velocity = self.velocity + vforce; + + if (self.health <= 0) + W_PrepareExplosionByDamage(self.owner, walker_rocket_explode); +} + +#define WALKER_ROCKET_MOVE movelib_move_simple(newdir, (autocvar_g_turrets_unit_walker_rocket_speed), (autocvar_g_turrets_unit_walker_rocket_turnrate)); UpdateCSQCProjectile(self) +void walker_rocket_loop(); +void walker_rocket_think() +{ + vector newdir; + float edist; + float itime; + float m_speed; + + self.nextthink = time; + + edist = vlen(self.enemy.origin - self.origin); + + // Simulate crude guidance + if (self.cnt < time) + { + if (edist < 1000) + self.tur_shotorg = randomvec() * min(edist, 64); + else + self.tur_shotorg = randomvec() * min(edist, 256); + + self.cnt = time + 0.5; + } + + if (edist < 128) + self.tur_shotorg = '0 0 0'; + + if (self.max_health < time) + { + self.think = walker_rocket_explode; + self.nextthink = time; + return; + } + + if (self.shot_dmg != 1337 && random() < 0.01) + { + walker_rocket_loop(); + return; + } + + m_speed = vlen(self.velocity); + + // Enemy dead? just keep on the current heading then. + if (self.enemy == world || self.enemy.deadflag != DEAD_NO) + self.enemy = world; + + if (self.enemy) + { + itime = max(edist / m_speed, 1); + newdir = steerlib_pull(self.enemy.origin + self.tur_shotorg); + } + else + newdir = normalize(self.velocity); + + WALKER_ROCKET_MOVE; +} + +void walker_rocket_loop3() +{ + vector newdir; + self.nextthink = time; + + if (self.max_health < time) + { + self.think = walker_rocket_explode; + return; + } + + if (vlen(self.origin - self.tur_shotorg) < 100 ) + { + self.think = walker_rocket_think; + return; + } + + newdir = steerlib_pull(self.tur_shotorg); + WALKER_ROCKET_MOVE; + + self.angles = vectoangles(self.velocity); +} + +void walker_rocket_loop2() +{ + vector newdir; + + self.nextthink = time; + + if (self.max_health < time) + { + self.think = walker_rocket_explode; + return; + } + + if (vlen(self.origin - self.tur_shotorg) < 100 ) + { + self.tur_shotorg = self.origin - '0 0 200'; + self.think = walker_rocket_loop3; + return; + } + + newdir = steerlib_pull(self.tur_shotorg); + WALKER_ROCKET_MOVE; +} + +void walker_rocket_loop() +{ + self.nextthink = time; + self.tur_shotorg = self.origin + '0 0 300'; + self.think = walker_rocket_loop2; + self.shot_dmg = 1337; +} + +void walker_fire_rocket(vector org) +{ + entity rocket; + + fixedmakevectors(self.angles); + + te_explosion (org); + + rocket = spawn (); + setorigin(rocket, org); + + sound (self, CH_WEAPON_A, W_Sound("hagar_fire"), VOL_BASE, ATTEN_NORM); + setsize (rocket, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot + + rocket.classname = "walker_rocket"; + rocket.owner = self; + rocket.bot_dodge = TRUE; + rocket.bot_dodgerating = 50; + rocket.takedamage = DAMAGE_YES; + rocket.damageforcescale = 2; + rocket.health = 25; + rocket.tur_shotorg = randomvec() * 512; + rocket.cnt = time + 1; + rocket.enemy = self.enemy; + + if (random() < 0.01) + rocket.think = walker_rocket_loop; + else + rocket.think = walker_rocket_think; + + rocket.event_damage = walker_rocket_damage; + + rocket.nextthink = time; + rocket.movetype = MOVETYPE_FLY; + rocket.velocity = normalize((v_forward + v_up * 0.5) + (randomvec() * 0.2)) * (autocvar_g_turrets_unit_walker_rocket_speed); + rocket.angles = vectoangles(rocket.velocity); + rocket.touch = walker_rocket_explode; + rocket.flags = FL_PROJECTILE; + rocket.solid = SOLID_BBOX; + rocket.max_health = time + 9; + rocket.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_GUIDED_HEAT; + + CSQCProjectile(rocket, FALSE, PROJECTILE_ROCKET, FALSE); // no culling, has fly sound +} + +.vector enemy_last_loc; +.float enemy_last_time; +void walker_move_to(vector _target, float _dist) +{ + switch (self.waterlevel) + { + case WATERLEVEL_NONE: + if (_dist > 500) + self.animflag = ANIM_RUN; + else + self.animflag = ANIM_WALK; + case WATERLEVEL_WETFEET: + case WATERLEVEL_SWIMMING: + if (self.animflag != ANIM_SWIM) + self.animflag = ANIM_WALK; + else + self.animflag = ANIM_SWIM; + break; + case WATERLEVEL_SUBMERGED: + self.animflag = ANIM_SWIM; + } + + self.moveto = _target; + self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); + + if(self.enemy) + { + self.enemy_last_loc = _target; + self.enemy_last_time = time; + } +} + +//#define WALKER_FANCYPATHING + +void walker_move_path() +{ +#ifdef WALKER_FANCYPATHING + // Are we close enougth to a path node to switch to the next? + if (vlen(self.origin - self.pathcurrent.origin) < 64) + if (self.pathcurrent.path_next == world) + { + // Path endpoint reached + pathlib_deletepath(self.pathcurrent.owner); + self.pathcurrent = world; + + if (self.pathgoal) + { + if (self.pathgoal.use) + self.pathgoal.use(); + + if (self.pathgoal.enemy) + { + self.pathcurrent = WALKER_PATH(self.pathgoal.origin,self.pathgoal.enemy.origin); + self.pathgoal = self.pathgoal.enemy; + } + } + else + self.pathgoal = world; + } + else + self.pathcurrent = self.pathcurrent.path_next; + + self.moveto = self.pathcurrent.origin; + self.steerto = steerlib_attract2(self.moveto,0.5,500,0.95); + walker_move_to(self.moveto, 0); + +#else + if (vlen(self.origin - self.pathcurrent.origin) < 64) + self.pathcurrent = self.pathcurrent.enemy; + + if(!self.pathcurrent) + return; + + self.moveto = self.pathcurrent.origin; + self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); + walker_move_to(self.moveto, 0); +#endif +} + +void spawnfunc_turret_walker() { if(!turret_initialize(TUR_WALKER)) remove(self); } + +float t_walker(float req) +{ + switch(req) + { + case TR_ATTACK: + { + sound (self, CH_WEAPON_A, W_Sound("uzi_fire"), VOL_BASE, ATTEN_NORM); + fireBullet (self.tur_shotorg, self.tur_shotdir_updated, self.shot_spread, 0, self.shot_dmg, self.shot_force, DEATH_TURRET_WALK_GUN, 0); + Send_Effect(EFFECT_LASER_MUZZLEFLASH, self.tur_shotorg, self.tur_shotdir_updated * 1000, 1); + + return TRUE; + } + case TR_THINK: + { + fixedmakevectors(self.angles); + + if (self.spawnflags & TSF_NO_PATHBREAK && self.pathcurrent) + walker_move_path(); + else if (self.enemy == world) + { + if(self.pathcurrent) + walker_move_path(); + else + { + if(self.enemy_last_time != 0) + { + if(vlen(self.origin - self.enemy_last_loc) < 128 || time - self.enemy_last_time > 10) + self.enemy_last_time = 0; + else + walker_move_to(self.enemy_last_loc, 0); + } + else + { + if(self.animflag != ANIM_NO) + { + traceline(self.origin + '0 0 64', self.origin + '0 0 64' + v_forward * 128, MOVE_NORMAL, self); + + if(trace_fraction != 1.0) + self.tur_head.idletime = -1337; + else + { + traceline(trace_endpos, trace_endpos - '0 0 256', MOVE_NORMAL, self); + if(trace_fraction == 1.0) + self.tur_head.idletime = -1337; + } + + if(self.tur_head.idletime == -1337) + { + self.moveto = self.origin + randomvec() * 256; + self.tur_head.idletime = 0; + } + + self.moveto = self.moveto * 0.9 + ((self.origin + v_forward * 500) + randomvec() * 400) * 0.1; + self.moveto_z = self.origin_z + 64; + walker_move_to(self.moveto, 0); + } + + if(self.idletime < time) + { + if(random() < 0.5 || !(self.spawnflags & TSL_ROAM)) + { + self.idletime = time + 1 + random() * 5; + self.moveto = self.origin; + self.animflag = ANIM_NO; + } + else + { + self.animflag = ANIM_WALK; + self.idletime = time + 4 + random() * 2; + self.moveto = self.origin + randomvec() * 256; + self.tur_head.moveto = self.moveto; + self.tur_head.idletime = 0; + } + } + } + } + } + else + { + if (self.tur_dist_enemy < (autocvar_g_turrets_unit_walker_melee_range) && self.animflag != ANIM_MELEE) + { + vector wish_angle; + + wish_angle = angleofs(self, self.enemy); + if (self.animflag != ANIM_SWIM) + if (fabs(wish_angle_y) < 15) + { + self.moveto = self.enemy.origin; + self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); + self.animflag = ANIM_MELEE; + } + } + else if (self.tur_head.attack_finished_single < time) + { + if(self.tur_head.shot_volly) + { + self.animflag = ANIM_NO; + + self.tur_head.shot_volly = self.tur_head.shot_volly -1; + if(self.tur_head.shot_volly == 0) + self.tur_head.attack_finished_single = time + (autocvar_g_turrets_unit_walker_rocket_refire); + else + self.tur_head.attack_finished_single = time + 0.2; + + if(self.tur_head.shot_volly > 1) + walker_fire_rocket(gettaginfo(self, gettagindex(self, "tag_rocket01"))); + else + walker_fire_rocket(gettaginfo(self, gettagindex(self, "tag_rocket02"))); + } + else + { + if (self.tur_dist_enemy > (autocvar_g_turrets_unit_walker_rocket_range_min)) + if (self.tur_dist_enemy < (autocvar_g_turrets_unit_walker_rocket_range)) + self.tur_head.shot_volly = 4; + } + } + else + { + if (self.animflag != ANIM_MELEE) + walker_move_to(self.enemy.origin, self.tur_dist_enemy); + } + } + + { + vector real_angle; + float turny = 0, turnx = 0; + float vz; + + real_angle = vectoangles(self.steerto) - self.angles; + vz = self.velocity_z; + + switch (self.animflag) + { + case ANIM_NO: + movelib_beak_simple((autocvar_g_turrets_unit_walker_speed_stop)); + break; + + case ANIM_TURN: + turny = (autocvar_g_turrets_unit_walker_turn); + movelib_beak_simple((autocvar_g_turrets_unit_walker_speed_stop)); + break; + + case ANIM_WALK: + turny = (autocvar_g_turrets_unit_walker_turn_walk); + movelib_move_simple(v_forward, (autocvar_g_turrets_unit_walker_speed_walk), 0.6); + break; + + case ANIM_RUN: + turny = (autocvar_g_turrets_unit_walker_turn_run); + movelib_move_simple(v_forward, (autocvar_g_turrets_unit_walker_speed_run), 0.6); + break; + + case ANIM_STRAFE_L: + turny = (autocvar_g_turrets_unit_walker_turn_strafe); + movelib_move_simple(v_right * -1, (autocvar_g_turrets_unit_walker_speed_walk), 0.8); + break; + + case ANIM_STRAFE_R: + turny = (autocvar_g_turrets_unit_walker_turn_strafe); + movelib_move_simple(v_right, (autocvar_g_turrets_unit_walker_speed_walk), 0.8); + break; + + case ANIM_JUMP: + self.velocity += '0 0 1' * (autocvar_g_turrets_unit_walker_speed_jump); + break; + + case ANIM_LAND: + break; + + case ANIM_PAIN: + if(self.frame != ANIM_PAIN) + defer(0.25, walker_setnoanim); + + break; + + case ANIM_MELEE: + if(self.frame != ANIM_MELEE) + { + defer(0.41, walker_setnoanim); + defer(0.21, walker_melee_do_dmg); + } + + movelib_beak_simple((autocvar_g_turrets_unit_walker_speed_stop)); + break; + + case ANIM_SWIM: + turny = (autocvar_g_turrets_unit_walker_turn_swim); + turnx = (autocvar_g_turrets_unit_walker_turn_swim); + + self.angles_x += bound(-10, shortangle_f(real_angle_x, self.angles_x), 10); + movelib_move_simple(v_forward, (autocvar_g_turrets_unit_walker_speed_swim), 0.3); + vz = self.velocity_z + sin(time * 4) * 8; + break; + + case ANIM_ROAM: + turny = (autocvar_g_turrets_unit_walker_turn_walk); + movelib_move_simple(v_forward ,(autocvar_g_turrets_unit_walker_speed_roam), 0.5); + break; + } + + if(turny) + { + turny = bound( turny * -1, shortangle_f(real_angle_y, self.angles_y), turny ); + self.angles_y += turny; + } + + if(turnx) + { + turnx = bound( turnx * -1, shortangle_f(real_angle_x, self.angles_x), turnx ); + self.angles_x += turnx; + } + + self.velocity_z = vz; + } + + + if(self.origin != self.oldorigin) + self.SendFlags |= TNSF_MOVE; + + self.oldorigin = self.origin; + turrets_setframe(self.animflag, FALSE); + + return TRUE; + } + case TR_DEATH: + { +#ifdef WALKER_FANCYPATHING + if (self.pathcurrent) + pathlib_deletepath(self.pathcurrent.owner); +#endif + self.pathcurrent = world; + + return TRUE; + } + case TR_SETUP: + { + self.ticrate = 0.05; + + entity e; + + // Respawn is called & first spawn to, to set team. need to make sure we do not move the initial spawn. + if(self.movetype == MOVETYPE_WALK) + { + if(self.pos1) + setorigin(self, self.pos1); + if(self.pos2) + self.angles = self.pos2; + } + + self.ammo_flags = TFL_AMMO_BULLETS | TFL_AMMO_RECHARGE | TFL_AMMO_RECIEVE; + self.aim_flags = TFL_AIM_LEAD; + self.turret_flags |= TUR_FLAG_HITSCAN; + + self.target_select_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_LOS; + self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMITS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_LOS; + self.iscreature = TRUE; + self.teleportable = TELEPORT_NORMAL; + self.damagedbycontents = TRUE; + self.solid = SOLID_SLIDEBOX; + self.takedamage = DAMAGE_AIM; + if(self.movetype != MOVETYPE_WALK) + { + setorigin(self, self.origin); + tracebox(self.origin + '0 0 128', self.mins, self.maxs, self.origin - '0 0 10000', MOVE_NORMAL, self); + setorigin(self, trace_endpos + '0 0 4'); + self.pos1 = self.origin; + self.pos2 = self.angles; + } + self.movetype = MOVETYPE_WALK; + self.idle_aim = '0 0 0'; + self.turret_firecheckfunc = walker_firecheck; + + if (self.target != "") + { + e = find(world, targetname, self.target); + if (!e) + { + dprint("Initital waypoint for walker does NOT exsist, fix your map!\n"); + self.target = ""; + } + + if (e.classname != "turret_checkpoint") + dprint("Warning: not a turrret path\n"); + else + { +#ifdef WALKER_FANCYPATHING + self.pathcurrent = WALKER_PATH(self.origin, e.origin); + self.pathgoal = e; +#else + self.pathcurrent = e; +#endif + } + } + + return TRUE; + } + case TR_PRECACHE: + { + precache_model ("models/turrets/walker_body.md3"); + precache_model ("models/turrets/walker_head_minigun.md3"); + precache_model ("models/turrets/rocket.md3"); + precache_sound (W_Sound("rocket_impact")); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC + +void walker_draw() +{ + float dt; + + dt = time - self.move_time; + self.move_time = time; + if(dt <= 0) + return; + + fixedmakevectors(self.angles); + movelib_groundalign4point(300, 100, 0.25, 45); + setorigin(self, self.origin + self.velocity * dt); + self.tur_head.angles += dt * self.tur_head.move_avelocity; + self.angles_y = self.move_angles_y; + + if (self.health < 127) + if(random() < 0.15) + te_spark(self.origin + '0 0 40', randomvec() * 256 + '0 0 256', 16); +} + +float t_walker(float req) +{ + switch(req) + { + case TR_SETUP: + { + self.gravity = 1; + self.movetype = MOVETYPE_BOUNCE; + self.move_movetype = MOVETYPE_BOUNCE; + self.move_origin = self.origin; + self.move_time = time; + self.draw = walker_draw; + + return TRUE; + } + case TR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_TURRET diff --git a/qcsrc/common/turrets/util.qc b/qcsrc/common/turrets/util.qc new file mode 100644 index 000000000..6de59027e --- /dev/null +++ b/qcsrc/common/turrets/util.qc @@ -0,0 +1,331 @@ +/* +* Return a angle within +/- 360. +*/ +float anglemods(float v) +{ + v = v - 360 * floor(v / 360); + + if(v >= 180) + return v - 360; + else if(v <= -180) + return v + 360; + else + return v; +} + +/* +* Return the short angle +*/ +float shortangle_f(float ang1, float ang2) +{ + if(ang1 > ang2) + { + if(ang1 > 180) + return ang1 - 360; + } + else + { + if(ang1 < -180) + return ang1 + 360; + } + + return ang1; +} + +vector shortangle_v(vector ang1, vector ang2) +{ + vector vtmp; + + vtmp_x = shortangle_f(ang1_x,ang2_x); + vtmp_y = shortangle_f(ang1_y,ang2_y); + vtmp_z = shortangle_f(ang1_z,ang2_z); + + return vtmp; +} + +vector shortangle_vxy(vector ang1, vector ang2) +{ + vector vtmp = '0 0 0'; + + vtmp_x = shortangle_f(ang1_x,ang2_x); + vtmp_y = shortangle_f(ang1_y,ang2_y); + + return vtmp; +} + + +/* +* Get "real" origin, in worldspace, even if ent is attached to something else. +*/ +vector real_origin(entity ent) +{ + entity e; + vector v = ((ent.absmin + ent.absmax) * 0.5); + + e = ent.tag_entity; + while(e) + { + v = v + ((e.absmin + e.absmax) * 0.5); + e = e.tag_entity; + } + + return v; +} + +/* +* Return the angle between two enteties +*/ +vector angleofs(entity from, entity to) +{ + vector v_res; + + v_res = normalize(to.origin - from.origin); + v_res = vectoangles(v_res); + v_res = v_res - from.angles; + + if (v_res_x < 0) v_res_x += 360; + if (v_res_x > 180) v_res_x -= 360; + + if (v_res_y < 0) v_res_y += 360; + if (v_res_y > 180) v_res_y -= 360; + + return v_res; +} + +vector angleofs3(vector from, vector from_a, entity to) +{ + vector v_res; + + v_res = normalize(to.origin - from); + v_res = vectoangles(v_res); + v_res = v_res - from_a; + + if (v_res_x < 0) v_res_x += 360; + if (v_res_x > 180) v_res_x -= 360; + + if (v_res_y < 0) v_res_y += 360; + if (v_res_y > 180) v_res_y -= 360; + + return v_res; +} + +/* +* Update self.tur_shotorg by getting up2date bone info +* NOTICE this func overwrites the global v_forward, v_right and v_up vectors. +*/ +#define turret_tag_fire_update() self.tur_shotorg = gettaginfo(self.tur_head, gettagindex(self.tur_head, "tag_fire"));v_forward = normalize(v_forward) +float turret_tag_fire_update_s() +{ + if(!self.tur_head) + { + error("Call to turret_tag_fire_update with self.tur_head missing!\n"); + self.tur_shotorg = '0 0 0'; + return FALSE; + } + + self.tur_shotorg = gettaginfo(self.tur_head, gettagindex(self.tur_head, "tag_fire")); + v_forward = normalize(v_forward); + + return TRUE; +} + +/* +* Railgun-like beam, but has thickness and suppots slowing of target +*/ +void FireImoBeam (vector start, vector end, vector smin, vector smax, + float bforce, float f_dmg, float f_velfactor, float deathtype) + +{ + vector hitloc, force, endpoint, dir; + entity ent; + + dir = normalize(end - start); + force = dir * bforce; + + // go a little bit into the wall because we need to hit this wall later + end = end + dir; + + // trace multiple times until we hit a wall, each obstacle will be made unsolid. + // note down which entities were hit so we can damage them later + while (1) + { + tracebox(start, smin, smax, end, FALSE, self); + + // if it is world we can't hurt it so stop now + if (trace_ent == world || trace_fraction == 1) + break; + + if (trace_ent.solid == SOLID_BSP) + 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; + + // stop if this is a wall + + // make the entity non-solid + trace_ent.solid = SOLID_NOT; + } + + endpoint = trace_endpos; + + // 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); + } + + // 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; + ent.railgunhitloc = '0 0 0'; + ent.railgunhitsolidbackup = SOLID_NOT; + ent.railgunhit = FALSE; + + // apply the damage + if (ent.takedamage) + { + Damage (ent, self, self, f_dmg, deathtype, hitloc, force); + ent.velocity = ent.velocity * f_velfactor; + //ent.alpha = 0.25 + random() * 0.75; + } + + // advance to the next entity + ent = findfloat(ent, railgunhit, TRUE); + } + trace_endpos = endpoint; +} + +#ifdef TURRET_DEBUG +void SUB_Remove(); +void marker_think() +{ + if(self.cnt) + if(self.cnt < time) + { + self.think = SUB_Remove; + self.nextthink = time; + return; + } + + self.frame += 1; + if(self.frame > 29) + self.frame = 0; + + self.nextthink = time; +} + +void mark_error(vector where,float lifetime) +{ + entity err; + + err = spawn(); + err.classname = "error_marker"; + setmodel(err,"models/marker.md3"); + setorigin(err,where); + err.movetype = MOVETYPE_NONE; + err.think = marker_think; + err.nextthink = time; + err.skin = 0; + if(lifetime) + err.cnt = lifetime + time; +} + +void mark_info(vector where,float lifetime) +{ + entity err; + + err = spawn(); + err.classname = "info_marker"; + setmodel(err,"models/marker.md3"); + setorigin(err,where); + err.movetype = MOVETYPE_NONE; + err.think = marker_think; + err.nextthink = time; + err.skin = 1; + if(lifetime) + err.cnt = lifetime + time; +} + +entity mark_misc(vector where,float lifetime) +{ + entity err; + + err = spawn(); + err.classname = "mark_misc"; + setmodel(err,"models/marker.md3"); + setorigin(err,where); + err.movetype = MOVETYPE_NONE; + err.think = marker_think; + err.nextthink = time; + err.skin = 3; + if(lifetime) + err.cnt = lifetime + time; + return err; +} + +/* +* Paint a v_color colord circle on target onwho +* that fades away over f_time +*/ +void paint_target(entity onwho, float f_size, vector v_color, float f_time) +{ + entity e; + + e = spawn(); + setmodel(e, "models/turrets/c512.md3"); // precision set above + e.scale = (f_size/512); + //setsize(e, '0 0 0', '0 0 0'); + //setattachment(e,onwho,""); + setorigin(e,onwho.origin + '0 0 1'); + e.alpha = 0.15; + e.movetype = MOVETYPE_FLY; + + e.velocity = (v_color * 32); // + '0 0 1' * 64; + + e.colormod = v_color; + SUB_SetFade(e,time,f_time); +} + +void paint_target2(entity onwho, float f_size, vector v_color, float f_time) +{ + entity e; + + e = spawn(); + setmodel(e, "models/turrets/c512.md3"); // precision set above + e.scale = (f_size/512); + setsize(e, '0 0 0', '0 0 0'); + + setorigin(e,onwho.origin + '0 0 1'); + e.alpha = 0.15; + e.movetype = MOVETYPE_FLY; + + e.velocity = (v_color * 32); // + '0 0 1' * 64; + e.avelocity_x = -128; + + e.colormod = v_color; + SUB_SetFade(e,time,f_time); +} + +void paint_target3(vector where, float f_size, vector v_color, float f_time) +{ + entity e; + e = spawn(); + setmodel(e, "models/turrets/c512.md3"); // precision set above + e.scale = (f_size/512); + setsize(e, '0 0 0', '0 0 0'); + setorigin(e,where+ '0 0 1'); + e.movetype = MOVETYPE_NONE; + e.velocity = '0 0 0'; + e.colormod = v_color; + SUB_SetFade(e,time,f_time); +} +#endif diff --git a/qcsrc/common/util.qc b/qcsrc/common/util.qc index db6f04c62..451a092af 100644 --- a/qcsrc/common/util.qc +++ b/qcsrc/common/util.qc @@ -1841,6 +1841,10 @@ vector healtharmor_maxdamage(float h, float a, float armorblock, float deathtype float healthdamage, armordamage, armorideal; if (deathtype == DEATH_DROWN) // Why should armor help here... armorblock = 0; +#ifdef CHAOS + if(DEATH_ISWEAPON(deathtype, WEP_LIGHTSABRE)) + armorblock = 0; // unique ability +#endif vector v; healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health armordamage = a + (h - 1); // damage we can take if we could use more armor @@ -1859,12 +1863,16 @@ vector healtharmor_maxdamage(float h, float a, float armorblock, float deathtype return v; } -vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage) +vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage, float bycount) { vector v; if (deathtype == DEATH_DROWN) // Why should armor help here... armorblock = 0; - v_y = bound(0, damage * armorblock, a); // save + // block based on amount of carried armor (idea: CUdyin) + if(bycount && armorblock >= 1) + v_y = bound(0, damage * bound(0, ((armorblock*(a/100))) / 100, armorblock / 100), a); // save + else + v_y = bound(0, damage * armorblock, a); // save v_x = bound(0, damage - v_y, damage); // take v_z = 0; return v; @@ -2038,6 +2046,7 @@ float get_model_parameters(string m, float sk) string fn, s, c; float fh, i; + get_model_parameters_description = string_null; get_model_parameters_modelname = string_null; get_model_parameters_modelskin = -1; get_model_parameters_name = string_null; @@ -2054,6 +2063,10 @@ float get_model_parameters(string m, float sk) get_model_parameters_bone_aimweight[i] = 0; } get_model_parameters_fixbone = 0; + get_model_parameters_bone_head = string_null; + get_model_parameters_hat_height = '0 0 0'; + get_model_parameters_hat_scale = 0; + get_model_parameters_hat_angles = '0 0 0'; if (!m) return 1; @@ -2123,6 +2136,14 @@ float get_model_parameters(string m, float sk) } if(c == "fixbone") get_model_parameters_fixbone = stof(s); + if(c == "bone_head") + get_model_parameters_bone_head = s; + if(c == "hat_height") + get_model_parameters_hat_height = stov(s); + if(c == "hat_scale") + get_model_parameters_hat_scale = stof(s); + if(c == "hat_angles") + get_model_parameters_hat_angles = stov(s); } while((s = fgets(fh))) diff --git a/qcsrc/common/util.qh b/qcsrc/common/util.qh index 65b4e67a4..4e59c5846 100644 --- a/qcsrc/common/util.qh +++ b/qcsrc/common/util.qh @@ -1,3 +1,32 @@ +#ifndef MENUQC +void FixSize(entity e) +{ + e.mins_x = rint(e.mins_x); + e.mins_y = rint(e.mins_y); + e.mins_z = rint(e.mins_z); + + e.maxs_x = rint(e.maxs_x); + e.maxs_y = rint(e.maxs_y); + e.maxs_z = rint(e.maxs_z); +} + +vector randompos(vector m1, vector m2) +{ + vector v; + m2 = m2 - m1; + v_x = m2_x * random() + m1_x; + v_y = m2_y * random() + m1_y; + v_z = m2_z * random() + m1_z; + return v; +} + +void setmodel_fixsize(entity e, string m) +{ + setmodel(e, m); + FixSize(e); +} +#endif + // commonly used, but better make them macros #define TRUE 1 #define FALSE 0 @@ -222,7 +251,7 @@ void RandomSelection_Add(entity e, float f, string s, float weight, float priori #ifndef MENUQC vector healtharmor_maxdamage(float h, float a, float armorblock, float deathtype); // returns vector: maxdamage, armorideal, 1 if fully armored -vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage); // returns vector: take, save, 0 +vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage, float bycount); // returns vector: take, save, 0 #endif string getcurrentmod(); @@ -273,6 +302,10 @@ string get_model_parameters_bone_weapon; string get_model_parameters_bone_aim[MAX_AIM_BONES]; float get_model_parameters_bone_aimweight[MAX_AIM_BONES]; float get_model_parameters_fixbone; +string get_model_parameters_bone_head; +vector get_model_parameters_hat_height; +float get_model_parameters_hat_scale; +vector get_model_parameters_hat_angles; string get_model_parameters_desc; float get_model_parameters(string mod, float skn); // call with string_null to clear; skin -1 means mod is the filename of the txt file and is to be split @@ -401,7 +434,7 @@ string CCR(string input); #endif // allow writing to also pass through to spectators (like so spectators see the same centerprints as players for example) -#define WRITESPECTATABLE_MSG_ONE_VARNAME(varname,statement) entity varname; varname = msg_entity; FOR_EACH_REALCLIENT(msg_entity) if(msg_entity == varname || (msg_entity.classname == STR_SPECTATOR && msg_entity.enemy == varname)) statement msg_entity = varname +#define WRITESPECTATABLE_MSG_ONE_VARNAME(varname,statement) entity varname; varname = msg_entity; FOR_EACH_REALCLIENT(msg_entity) if(msg_entity == varname || (IS_SPEC(msg_entity) && msg_entity.enemy == varname)) statement msg_entity = varname #define WRITESPECTATABLE_MSG_ONE(statement) WRITESPECTATABLE_MSG_ONE_VARNAME(oldmsg_entity, statement) #define WRITESPECTATABLE(msg,statement) if(msg == MSG_ONE) { WRITESPECTATABLE_MSG_ONE(statement); } else statement float WRITESPECTATABLE_workaround = 0 @@ -448,4 +481,4 @@ vector bezier_quadratic_getderivative(vector a, vector p, vector b, float t); #define APPEND_TO_STRING(list,sep,add) ((list) = (((list) != "") ? strcat(list, sep, add) : (add))) // Returns the correct difference between two always increasing numbers -#define COMPARE_INCREASING(to,from) (to < from ? from + to + 2 : to - from) \ No newline at end of file +#define COMPARE_INCREASING(to,from) (to < from ? from + to + 2 : to - from) diff --git a/qcsrc/common/vehicles/all.qh b/qcsrc/common/vehicles/all.qh new file mode 100644 index 000000000..de1740a82 --- /dev/null +++ b/qcsrc/common/vehicles/all.qh @@ -0,0 +1,8 @@ +#include "unit/spiderbot.qc" +#include "unit/raptor.qc" +#include "unit/racer.qc" +#ifndef VEHICLES_NO_UNSTABLE +#include "unit/bumblebee.qc" +#include "unit/yugo.qc" +#include "unit/tankll48.qc" +#endif diff --git a/qcsrc/common/vehicles/cl_vehicles.qc b/qcsrc/common/vehicles/cl_vehicles.qc new file mode 100644 index 000000000..9a2598f82 --- /dev/null +++ b/qcsrc/common/vehicles/cl_vehicles.qc @@ -0,0 +1,126 @@ +#define hud_bg "gfx/vehicles/frame.tga" +#define hud_sh "gfx/vehicles/vh-shield.tga" + +#define hud_hp_bar "gfx/vehicles/bar_up_left.tga" +#define hud_hp_ico "gfx/vehicles/health.tga" +#define hud_sh_bar "gfx/vehicles/bar_dwn_left.tga" +#define hud_sh_ico "gfx/vehicles/shield.tga" + +#define hud_ammo1_bar "gfx/vehicles/bar_up_right.tga" +#define hud_ammo1_ico "gfx/vehicles/bullets.tga" +#define hud_ammo2_bar "gfx/vehicles/bar_dwn_right.tga" +#define hud_ammo2_ico "gfx/vehicles/rocket.tga" +#define hud_energy "gfx/vehicles/energy.tga" + +entity dropmark; +var float autocvar_cl_vehicles_hudscale = 0.5; +var float autocvar_cl_vehicles_hudalpha = 0.75; + +const float MAX_AXH = 4; +entity AuxiliaryXhair[MAX_AXH]; + +.string axh_image; +.float axh_fadetime; +.float axh_drawflag; +.float axh_scale; + +float alarm1time; +float alarm2time; + +void vehicle_alarm(entity e, float ch, string s0und) +{ + if(!autocvar_cl_vehicles_alarm) + return; + + sound(e, ch, s0und, VOL_BASEVOICE, ATTEN_NONE); +} + +void AuxiliaryXhair_Draw2D() +{ + vector loc, psize; + + psize = self.axh_scale * draw_getimagesize(self.axh_image); + loc = project_3d_to_2d(self.move_origin) - 0.5 * psize; + if(!(loc_z < 0 || loc_x < 0 || loc_y < 0 || loc_x > vid_conwidth || loc_y > vid_conheight)) + { + loc_z = 0; + psize_z = 0; + drawpic(loc, self.axh_image, psize, self.colormod, self.alpha, self.axh_drawflag); + } + + if(time - self.cnt > self.axh_fadetime) + self.draw2d = func_null; +} + +void Net_AuXair2(float bIsNew) +{ + float axh_id = bound(0, ReadByte(), MAX_AXH); + entity axh = AuxiliaryXhair[axh_id]; + + if(axh == world || wasfreed(axh)) // MADNESS? THIS IS QQQQCCCCCCCCC (wasfreed, why do you exsist?) + { + axh = spawn(); + axh.draw2d = func_null; + axh.drawmask = MASK_NORMAL; + axh.axh_drawflag = DRAWFLAG_ADDITIVE; + axh.axh_fadetime = 0.1; + axh.axh_image = "gfx/vehicles/axh-ring.tga"; + axh.axh_scale = 1; + axh.alpha = 1; + AuxiliaryXhair[axh_id] = axh; + } + + axh.move_origin_x = ReadCoord(); + axh.move_origin_y = ReadCoord(); + axh.move_origin_z = ReadCoord(); + axh.colormod_x = ReadByte() / 255; + axh.colormod_y = ReadByte() / 255; + axh.colormod_z = ReadByte() / 255; + axh.cnt = time; + axh.draw2d = AuxiliaryXhair_Draw2D; +} + +void Net_VehicleSetup() +{ + float i; + + float hud_id = ReadByte(); + + // hud_id == 0 means we exited a vehicle, so stop alarm sound/s + if(hud_id == 0) + { + sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASEVOICE, ATTEN_NONE); + sound(self, CH_PAIN_SINGLE, "misc/null.wav", VOL_BASEVOICE, ATTEN_NONE); + return; + } + + // Init auxiliary crosshairs + entity axh; + for(i = 0; i < MAX_AXH; ++i) + { + axh = AuxiliaryXhair[i]; + if(axh != world && !wasfreed(axh)) // MADNESS? THIS IS QQQQCCCCCCCCC (wasfreed, why do you exsist?) + remove(axh); + + axh = spawn(); + axh.draw2d = func_null; + axh.drawmask = MASK_NORMAL; + axh.axh_drawflag = DRAWFLAG_NORMAL; + axh.axh_fadetime = 0.1; + axh.axh_image = "gfx/vehicles/axh-ring.tga"; + axh.axh_scale = 1; + axh.alpha = 1; + AuxiliaryXhair[i] = axh; + } + + if(hud_id == HUD_BUMBLEBEE_GUN) + { + // Plasma cannons + AuxiliaryXhair[0].axh_image = "gfx/vehicles/axh-bracket.tga"; + AuxiliaryXhair[0].axh_scale = 0.25; + // Raygun + AuxiliaryXhair[1].axh_image = "gfx/vehicles/axh-bracket.tga"; + AuxiliaryXhair[1].axh_scale = 0.25; + } + else { VEH_ACTION(hud_id, VR_SETUP); } +} diff --git a/qcsrc/common/vehicles/cl_vehicles.qh b/qcsrc/common/vehicles/cl_vehicles.qh new file mode 100644 index 000000000..67701e418 --- /dev/null +++ b/qcsrc/common/vehicles/cl_vehicles.qh @@ -0,0 +1,15 @@ +// vehicle cvars +var float autocvar_cl_vehicles_alarm = 1; +var float autocvar_cl_vehicles_hud_tactical = 1; + +void RaptorCBShellfragDraw(); +void RaptorCBShellfragToss(vector _org, vector _vel, vector _ang); + +#define HUD_GETVEHICLESTATS \ + local noref float vh_health = getstati(STAT_VEHICLESTAT_HEALTH); \ + local noref float shield = getstati(STAT_VEHICLESTAT_SHIELD); \ + local noref float energy = getstati(STAT_VEHICLESTAT_ENERGY); \ + local noref float ammo1 = getstati(STAT_VEHICLESTAT_AMMO1); \ + local noref float reload1 = getstati(STAT_VEHICLESTAT_RELOAD1); \ + local noref float ammo2 = getstati(STAT_VEHICLESTAT_AMMO2); \ + local noref float reload2 = getstati(STAT_VEHICLESTAT_RELOAD2); diff --git a/qcsrc/common/vehicles/sv_vehicles.qc b/qcsrc/common/vehicles/sv_vehicles.qc new file mode 100644 index 000000000..25e622a48 --- /dev/null +++ b/qcsrc/common/vehicles/sv_vehicles.qc @@ -0,0 +1,1264 @@ +// ========================= +// SVQC Vehicle Properties +// ========================= + + +float SendAuxiliaryXhair(entity to, float sf) +{ + + WriteByte(MSG_ENTITY, ENT_CLIENT_AUXILIARYXHAIR); + + WriteByte(MSG_ENTITY, self.cnt); + + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); + + WriteByte(MSG_ENTITY, rint(self.colormod_x * 255)); + WriteByte(MSG_ENTITY, rint(self.colormod_y * 255)); + WriteByte(MSG_ENTITY, rint(self.colormod_z * 255)); + + return TRUE; +} + +void UpdateAuxiliaryXhair(entity own, vector loc, vector clr, float axh_id) +{ + if(!IS_REAL_CLIENT(own)) + return; + + entity axh; + + axh_id = bound(0, axh_id, MAX_AXH); + axh = own.(AuxiliaryXhair[axh_id]); + + if(axh == world || wasfreed(axh)) // MADNESS? THIS IS QQQQCCCCCCCCC (wasfreed, why do you exsist?) + { + axh = spawn(); + axh.cnt = axh_id; + axh.drawonlytoclient = own; + axh.owner = own; + Net_LinkEntity(axh, FALSE, 0, SendAuxiliaryXhair); + } + + setorigin(axh, loc); + axh.colormod = clr; + axh.SendFlags = 0x01; + own.(AuxiliaryXhair[axh_id]) = axh; +} + +void CSQCVehicleSetup(entity own, float vehicle_id) +{ + if(!IS_REAL_CLIENT(own)) + return; + + msg_entity = own; + + WriteByte(MSG_ONE, SVC_TEMPENTITY); + WriteByte(MSG_ONE, TE_CSQC_VEHICLESETUP); + WriteByte(MSG_ONE, vehicle_id); +} + +vector targetdrone_getnewspot() +{ + vector spot; + float i; + for(i = 0; i < 100; ++i) + { + spot = self.origin + randomvec() * 1024; + tracebox(spot, self.mins, self.maxs, spot, MOVE_NORMAL, self); + if(trace_fraction == 1.0 && trace_startsolid == 0 && trace_allsolid == 0) + return spot; + } + return self.origin; +} + +void vehicles_locktarget(float incr, float decr, float _lock_time) +{ + if(self.lock_target && self.lock_target.deadflag != DEAD_NO) + { + self.lock_target = world; + self.lock_strength = 0; + self.lock_time = 0; + } + + if(self.lock_time > time) + { + if(self.lock_target) + if(self.lock_soundtime < time) + { + self.lock_soundtime = time + 0.5; + play2(self.owner, "vehicles/locked.wav"); + } + + return; + } + + if(trace_ent != world) + { + if(SAME_TEAM(trace_ent, self)) + trace_ent = world; + + if(trace_ent.deadflag != DEAD_NO) + trace_ent = world; + + if(!(IS_VEHICLE(trace_ent) || IS_TURRET(trace_ent))) + trace_ent = world; + + if(trace_ent.alpha <= 0.5 && trace_ent.alpha != 0) + trace_ent = world; // invisible + } + + if(self.lock_target == world && trace_ent != world) + self.lock_target = trace_ent; + + if(self.lock_target && trace_ent == self.lock_target) + { + if(self.lock_strength != 1 && self.lock_strength + incr >= 1) + { + play2(self.owner, "vehicles/lock.wav"); + self.lock_soundtime = time + 0.8; + } + else if (self.lock_strength != 1 && self.lock_soundtime < time) + { + play2(self.owner, "vehicles/locking.wav"); + self.lock_soundtime = time + 0.3; + } + } + + // Have a locking target + // Trace hit current target + if(trace_ent == self.lock_target && trace_ent != world) + { + self.lock_strength = min(self.lock_strength + incr, 1); + if(self.lock_strength == 1) + self.lock_time = time + _lock_time; + } + else + { + if(trace_ent) + self.lock_strength = max(self.lock_strength - decr * 2, 0); + else + self.lock_strength = max(self.lock_strength - decr, 0); + + if(self.lock_strength == 0) + self.lock_target = world; + } +} + +vector vehicles_force_fromtag_hover(string tag_name, float spring_length, float max_power) +{ + force_fromtag_origin = gettaginfo(self, gettagindex(self, tag_name)); + v_forward = normalize(v_forward) * -1; + traceline(force_fromtag_origin, force_fromtag_origin - (v_forward * spring_length), MOVE_NORMAL, self); + + force_fromtag_power = (1 - trace_fraction) * max_power; + force_fromtag_normpower = force_fromtag_power / max_power; + + return v_forward * force_fromtag_power; +} + +vector vehicles_force_fromtag_maglev(string tag_name, float spring_length, float max_power) +{ + + force_fromtag_origin = gettaginfo(self, gettagindex(self, tag_name)); + v_forward = normalize(v_forward) * -1; + traceline(force_fromtag_origin, force_fromtag_origin - (v_forward * spring_length), MOVE_NORMAL, self); + + // TODO - this may NOT be compatible with wall/celing movement, unhardcode 0.25 (engine count multiplier) + if(trace_fraction == 1.0) + { + force_fromtag_normpower = -0.25; + return '0 0 -200'; + } + + force_fromtag_power = ((1 - trace_fraction) - trace_fraction) * max_power; + force_fromtag_normpower = force_fromtag_power / max_power; + + return v_forward * force_fromtag_power; +} + +// projectile handling +void vehicles_projectile_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + // Ignore damage from oterh projectiles from my owner (dont mess up volly's) + if(inflictor.owner == self.owner) + return; + + self.health -= damage; + self.velocity += force; + if(self.health < 1) + { + self.takedamage = DAMAGE_NO; + self.event_damage = func_null; + self.think = self.use; + self.nextthink = time; + } +} + +void vehicles_projectile_explode() +{ + if(self.owner && other != world) + { + if(other == self.owner.vehicle) + return; + + if(other == self.owner.vehicle.tur_head) + return; + } + + PROJECTILE_TOUCH; + + self.event_damage = func_null; + RadiusDamage (self, self.realowner, self.shot_dmg, 0, self.shot_radius, self, world, self.shot_force, self.totalfrags, other); + + remove (self); +} + +entity vehicles_projectile(string _mzlfx, string _mzlsound, + vector _org, vector _vel, + float _dmg, float _radi, float _force, float _size, + float _deahtype, float _projtype, float _health, + float _cull, float _clianim, entity _owner) +{ + entity proj; + + proj = spawn(); + + PROJECTILE_MAKETRIGGER(proj); + setorigin(proj, _org); + + proj.shot_dmg = _dmg; + proj.shot_radius = _radi; + proj.shot_force = _force; + proj.totalfrags = _deahtype; + proj.solid = SOLID_BBOX; + proj.movetype = MOVETYPE_FLYMISSILE; + proj.flags = FL_PROJECTILE; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = _dmg; + proj.velocity = _vel; + proj.touch = vehicles_projectile_explode; + proj.use = vehicles_projectile_explode; + proj.owner = self; + proj.realowner = _owner; + proj.think = SUB_Remove; + proj.nextthink = time + 30; + + if(_health) + { + proj.takedamage = DAMAGE_AIM; + proj.event_damage = vehicles_projectile_damage; + proj.health = _health; + } + else + proj.flags = FL_PROJECTILE | FL_NOTARGET; + + if(_mzlsound) + sound (self, CH_WEAPON_A, _mzlsound, VOL_BASE, ATTEN_NORM); + + if(_mzlfx) + pointparticles(particleeffectnum(_mzlfx), proj.origin, proj.velocity, 1); + + + setsize (proj, '-1 -1 -1' * _size, '1 1 1' * _size); + + CSQCProjectile(proj, _clianim, _projtype, _cull); + + return proj; +} + +void vehicles_gib_explode() +{ + sound (self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_EXPLOSION_SMALL, randomvec() * 80 + (self.origin + '0 0 100'), '0 0 0', 1); + Send_Effect(EFFECT_EXPLOSION_SMALL, self.wp00.origin + '0 0 64', '0 0 0', 1); + remove(self); +} + +void vehicles_gib_think() +{ + self.alpha -= 0.1; + if(self.cnt >= time) + remove(self); + else + self.nextthink = time + 0.1; +} + +entity vehicle_tossgib(entity _template, vector _vel, string _tag, float _burn, float _explode, float _maxtime, vector _rot) +{ + entity _gib = spawn(); + setmodel(_gib, _template.model); + setorigin(_gib, gettaginfo(self, gettagindex(self, _tag))); + _gib.velocity = _vel; + _gib.movetype = MOVETYPE_TOSS; + _gib.solid = SOLID_CORPSE; + _gib.colormod = '-0.5 -0.5 -0.5'; + _gib.effects = EF_LOWPRECISION; + _gib.avelocity = _rot; + + if(_burn) + _gib.effects |= EF_FLAME; + + if(_explode) + { + _gib.think = vehicles_gib_explode; + _gib.nextthink = time + random() * _explode; + _gib.touch = vehicles_gib_explode; + } + else + { + _gib.cnt = time + _maxtime; + _gib.think = vehicles_gib_think; + _gib.nextthink = time + _maxtime - 1; + _gib.alpha = 1; + } + return _gib; +} + +float vehicle_addplayerslot( entity _owner, + entity _slot, + float _hud, + string _hud_model, + float() _framefunc, + void(float) _exitfunc, float() _enterfunc) +{ + if(!(_owner.vehicle_flags & VHF_MULTISLOT)) + _owner.vehicle_flags |= VHF_MULTISLOT; + + _slot.PlayerPhysplug = _framefunc; + _slot.vehicle_exit = _exitfunc; + _slot.vehicle_enter = _enterfunc; + _slot.hud = _hud; + _slot.vehicle_flags = VHF_PLAYERSLOT; + _slot.vehicle_viewport = spawn(); + _slot.vehicle_hudmodel = spawn(); + _slot.vehicle_hudmodel.viewmodelforclient = _slot; + _slot.vehicle_viewport.effects = (EF_ADDITIVE | EF_DOUBLESIDED | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NOGUNBOB | EF_NOSHADOW | EF_LOWPRECISION | EF_SELECTABLE | EF_TELEPORT_BIT); + + setmodel(_slot.vehicle_hudmodel, _hud_model); + setmodel(_slot.vehicle_viewport, "null"); + + setattachment(_slot.vehicle_hudmodel, _slot, ""); + setattachment(_slot.vehicle_viewport, _slot.vehicle_hudmodel, ""); + + return TRUE; +} + +vector vehicle_aimturret(entity _vehic, vector _target, entity _turrret, string _tagname, + float _pichlimit_min, float _pichlimit_max, + float _rotlimit_min, float _rotlimit_max, float _aimspeed) +{ + vector vtmp, vtag; + float ftmp; + vtag = gettaginfo(_turrret, gettagindex(_turrret, _tagname)); + vtmp = vectoangles(normalize(_target - vtag)); + vtmp = AnglesTransform_ToAngles(AnglesTransform_LeftDivide(AnglesTransform_FromAngles(_vehic.angles), AnglesTransform_FromAngles(vtmp))) - _turrret.angles; + vtmp = AnglesTransform_Normalize(vtmp, TRUE); + ftmp = _aimspeed * frametime; + vtmp_y = bound(-ftmp, vtmp_y, ftmp); + vtmp_x = bound(-ftmp, vtmp_x, ftmp); + _turrret.angles_y = bound(_rotlimit_min, _turrret.angles_y + vtmp_y, _rotlimit_max); + _turrret.angles_x = bound(_pichlimit_min, _turrret.angles_x + vtmp_x, _pichlimit_max); + return vtag; +} + +void vehicles_reset_colors() +{ + entity e; + float _effects = 0, _colormap; + vector _glowmod, _colormod; + + if(autocvar_g_nodepthtestplayers) + _effects |= EF_NODEPTHTEST; + + if(autocvar_g_fullbrightplayers) + _effects |= EF_FULLBRIGHT; + + if(self.team) + _colormap = 1024 + (self.team - 1) * 17; + else + _colormap = 1024; + + _glowmod = '0 0 0'; + _colormod = '0 0 0'; + + // Find all ents attacked to main model and setup effects, colormod etc. + e = findchainentity(tag_entity, self); + while(e) + { + if(e != self.vehicle_shieldent) + { + e.effects = _effects; // | EF_LOWPRECISION; + e.colormod = _colormod; + e.colormap = _colormap; + e.alpha = 1; + } + e = e.chain; + } + // Also check head tags + e = findchainentity(tag_entity, self.tur_head); + while(e) + { + if(e != self.vehicle_shieldent) + { + e.effects = _effects; // | EF_LOWPRECISION; + e.colormod = _colormod; + e.colormap = _colormap; + e.alpha = 1; + } + e = e.chain; + } + + self.vehicle_hudmodel.effects = self.effects = _effects; // | EF_LOWPRECISION; + self.vehicle_hudmodel.colormod = self.colormod = _colormod; + self.vehicle_hudmodel.colormap = self.colormap = _colormap; + self.vehicle_viewport.effects = (EF_ADDITIVE | EF_DOUBLESIDED | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NOGUNBOB | EF_NOSHADOW | EF_LOWPRECISION | EF_SELECTABLE | EF_TELEPORT_BIT); + + self.alpha = 1; + self.avelocity = '0 0 0'; + self.velocity = '0 0 0'; + self.effects = _effects; +} + +void vehicles_clearreturn(entity veh) +{ + entity ret; + // Remove "return helper", if any. + ret = findchain(classname, "vehicle_return"); + while(ret) + { + if(ret.wp00 == veh) + { + ret.classname = ""; + ret.think = SUB_Remove; + ret.nextthink = time + 0.1; + + if(ret.waypointsprite_attached) + WaypointSprite_Kill(ret.waypointsprite_attached); + + return; + } + ret = ret.chain; + } +} + +void vehicles_spawn(); +void vehicles_return() +{ + Send_Effect(EFFECT_TELEPORT, self.wp00.origin + '0 0 64', '0 0 0', 1); + + self.wp00.think = vehicles_spawn; + self.wp00.nextthink = time; + + if(self.waypointsprite_attached) + WaypointSprite_Kill(self.waypointsprite_attached); + + remove(self); +} + +void vehicles_showwp_goaway() +{ + if(self.waypointsprite_attached) + WaypointSprite_Kill(self.waypointsprite_attached); + + remove(self); + +} + +void vehicles_showwp() +{ + entity oldself = world; + vector rgb; + + if(self.cnt) + { + self.think = vehicles_return; + self.nextthink = self.cnt; + } + else + { + self.think = vehicles_return; + self.nextthink = time +1; + + oldself = self; + self = spawn(); + setmodel(self, "null"); + self.team = oldself.wp00.team; + self.wp00 = oldself.wp00; + setorigin(self, oldself.wp00.pos1); + + self.nextthink = time + 5; + self.think = vehicles_showwp_goaway; + } + + if(teamplay && self.team) + rgb = Team_ColorRGB(self.team); + else + rgb = '1 1 1'; + WaypointSprite_Spawn("vehicle", 0, 0, self, '0 0 64', world, 0, self, waypointsprite_attached, TRUE, RADARICON_POWERUP, rgb); + if(self.waypointsprite_attached) + { + WaypointSprite_UpdateRule(self.waypointsprite_attached, self.wp00.team, SPRITERULE_DEFAULT); + if(oldself == world) + WaypointSprite_UpdateBuildFinished(self.waypointsprite_attached, self.nextthink); + WaypointSprite_Ping(self.waypointsprite_attached); + } + + if(oldself != world) + self = oldself; +} + +void vehicles_setreturn(entity veh) +{ + entity ret; + + vehicles_clearreturn(veh); + + ret = spawn(); + ret.classname = "vehicle_return"; + ret.wp00 = veh; + ret.team = veh.team; + ret.think = vehicles_showwp; + + if(veh.deadflag != DEAD_NO) + { + ret.cnt = time + veh.respawntime; + ret.nextthink = min(time + veh.respawntime, time + veh.respawntime - 5); + } + else + { + ret.nextthink = min(time + veh.respawntime, time + veh.respawntime - 1); + } + + setmodel(ret, "null"); + setorigin(ret, veh.pos1 + '0 0 96'); + +} + +void vehicle_use() +{ + dprint("vehicle ",self.netname, " used by ", activator.classname, "\n"); + + self.tur_head.team = activator.team; + + if(self.tur_head.team == 0) + self.active = ACTIVE_NOT; + else + self.active = ACTIVE_ACTIVE; + + if(self.active == ACTIVE_ACTIVE && self.deadflag == DEAD_NO && !gameover) + { + dprint("Respawning vehicle: ", self.netname, "\n"); + if(self.effects & EF_NODRAW) + { + self.think = vehicles_spawn; + self.nextthink = time + 3; + } + else + { + vehicles_setreturn(self); + vehicles_reset_colors(); + } + } +} + +void vehicles_regen(float timer, .float regen_field, float field_max, float rpause, float regen, float delta_time, float _healthscale) +{ + if(self.regen_field < field_max) + if(timer + rpause < time) + { + if(_healthscale) + regen = regen * (self.vehicle_health / self.max_health); + + self.regen_field = min(self.regen_field + regen * delta_time, field_max); + + if(self.owner) + self.owner.regen_field = (self.regen_field / field_max) * 100; + } +} + +void shieldhit_think() +{ + self.alpha -= 0.1; + if (self.alpha <= 0) + { + //setmodel(self, ""); + self.alpha = -1; + self.effects |= EF_NODRAW; + } + else + { + self.nextthink = time + 0.1; + } +} + +void vehicles_painframe() +{ + if(self.owner.vehicle_health <= 50) + if(self.pain_frame < time) + { + float _ftmp; + _ftmp = self.owner.vehicle_health / 50; + self.pain_frame = time + 0.1 + (random() * 0.5 * _ftmp); + pointparticles(particleeffectnum("smoke_small"), (self.origin + (randomvec() * 80)), '0 0 0', 1); + + if(self.vehicle_flags & VHF_DMGSHAKE) + self.velocity += randomvec() * 30; + + if(self.vehicle_flags & VHF_DMGROLL) + if(self.vehicle_flags & VHF_DMGHEADROLL) + self.tur_head.angles += randomvec(); + else + self.angles += randomvec(); + + } +} + +void vehicles_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + self.dmg_time = time; + + // WEAPONTODO + if(DEATH_ISWEAPON(deathtype, WEP_VORTEX)) + damage *= autocvar_g_vehicles_vortex_damagerate; + + if(DEATH_ISWEAPON(deathtype, WEP_MACHINEGUN)) + damage *= autocvar_g_vehicles_machinegun_damagerate; + + if(DEATH_ISWEAPON(deathtype, WEP_RIFLE)) + damage *= autocvar_g_vehicles_rifle_damagerate; + + if(DEATH_ISWEAPON(deathtype, WEP_VAPORIZER)) + damage *= autocvar_g_vehicles_vaporizer_damagerate; + + if(DEATH_ISWEAPON(deathtype, WEP_SEEKER)) + damage *= autocvar_g_vehicles_tag_damagerate; + + self.enemy = attacker; + + self.pain_finished = time; + + if((self.vehicle_flags & VHF_HASSHIELD) && (self.vehicle_shield > 0)) + { + if (wasfreed(self.vehicle_shieldent) || self.vehicle_shieldent == world) + { + self.vehicle_shieldent = spawn(); + self.vehicle_shieldent.effects = EF_LOWPRECISION; + + setmodel(self.vehicle_shieldent, "models/vhshield.md3"); + setattachment(self.vehicle_shieldent, self, ""); + setorigin(self.vehicle_shieldent, real_origin(self) - self.origin); + self.vehicle_shieldent.scale = 256 / vlen(self.maxs - self.mins); + self.vehicle_shieldent.think = shieldhit_think; + } + + self.vehicle_shieldent.colormod = '1 1 1'; + self.vehicle_shieldent.alpha = 0.45; + self.vehicle_shieldent.angles = vectoangles(normalize(hitloc - (self.origin + self.vehicle_shieldent.origin))) - self.angles; + self.vehicle_shieldent.nextthink = time; + self.vehicle_shieldent.effects &= ~EF_NODRAW; + + self.vehicle_shield -= damage; + + if(self.vehicle_shield < 0) + { + self.vehicle_health -= fabs(self.vehicle_shield); + self.vehicle_shieldent.colormod = '2 0 0'; + self.vehicle_shield = 0; + self.vehicle_shieldent.alpha = 0.75; + + if(sound_allowed(MSG_BROADCAST, attacker)) + spamsound (self, CH_PAIN, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER + } + else + if(sound_allowed(MSG_BROADCAST, attacker)) + spamsound (self, CH_PAIN, "onslaught/electricity_explode.wav", VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER + + } + else + { + self.vehicle_health -= damage; + + if(sound_allowed(MSG_BROADCAST, attacker)) + spamsound (self, CH_PAIN, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER + } + + if(self.damageforcescale < 1 && self.damageforcescale > 0) + self.velocity += force * self.damageforcescale; + else + self.velocity += force; + + if(self.vehicle_health <= 0) + { + if(self.owner) + if(self.vehicle_flags & VHF_DEATHEJECT) + vehicles_exit(VHEF_EJECT); + else + vehicles_exit(VHEF_RELEASE); + + + antilag_clear(self); + + VEH_ACTION(self.vehicleid, VR_DEATH); + vehicles_setreturn(self); + } +} + +float vehicles_crushable(entity e) +{ + if(IS_PLAYER(e)) + return TRUE; + + if(IS_MONSTER(e)) + return TRUE; + + return FALSE; +} + +void vehicles_impact(float _minspeed, float _speedfac, float _maxpain) +{ + if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) + return; + + if(self.play_time < time) + { + float wc = vlen(self.velocity - self.oldvelocity); + //dprint("oldvel: ", vtos(self.oldvelocity), "\n"); + //dprint("vel: ", vtos(self.velocity), "\n"); + if(_minspeed < wc) + { + float take = min(_speedfac * wc, _maxpain); + Damage (self, world, world, take, DEATH_FALL, self.origin, '0 0 0'); + self.play_time = time + 0.25; + + //dprint("wc: ", ftos(wc), "\n"); + //dprint("take: ", ftos(take), "\n"); + } + } +} + +// vehicle enter/exit handling +vector vehicles_findgoodexit(vector prefer_spot) +{ + //vector exitspot; + float mysize; + + tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, prefer_spot, MOVE_NORMAL, self.owner); + if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) + return prefer_spot; + + mysize = 1.5 * vlen(self.maxs - self.mins); + float i; + vector v, v2; + v2 = 0.5 * (self.absmin + self.absmax); + for(i = 0; i < 100; ++i) + { + v = randomvec(); + v_z = 0; + v = v2 + normalize(v) * mysize; + tracebox(v2, PL_MIN, PL_MAX, v, MOVE_NORMAL, self.owner); + if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) + return v; + } + + /* + exitspot = (self.origin + '0 0 48') + v_forward * mysize; + tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner); + if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) + return exitspot; + + exitspot = (self.origin + '0 0 48') - v_forward * mysize; + tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner); + if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) + return exitspot; + + exitspot = (self.origin + '0 0 48') + v_right * mysize; + tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner); + if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) + return exitspot; + + exitspot = (self.origin + '0 0 48') - v_right * mysize; + tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner); + if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) + return exitspot; + */ + + return self.origin; +} + +void vehicles_exit(float eject) +{ + entity _vehicle; + entity _player; + entity _oldself = self; + + if(vehicles_exit_running) + { + dprint("^1vehicles_exit allready running! this is not good..\n"); + return; + } + + vehicles_exit_running = TRUE; + if(IS_CLIENT(self)) + { + _vehicle = self.vehicle; + + if (_vehicle.vehicle_flags & VHF_PLAYERSLOT) + { + _vehicle.vehicle_exit(eject); + self = _oldself; + vehicles_exit_running = FALSE; + return; + } + } + else + _vehicle = self; + + _player = _vehicle.owner; + + self = _vehicle; + + if (_player) + { + if (IS_REAL_CLIENT(_player)) + { + msg_entity = _player; + WriteByte (MSG_ONE, SVC_SETVIEWPORT); + WriteEntity( MSG_ONE, _player); + + WriteByte (MSG_ONE, SVC_SETVIEWANGLES); + WriteAngle(MSG_ONE, 0); + WriteAngle(MSG_ONE, _vehicle.angles_y); + WriteAngle(MSG_ONE, 0); + } + + setsize(_player, PL_MIN,PL_MAX); + + _player.takedamage = DAMAGE_AIM; + _player.solid = SOLID_SLIDEBOX; + _player.movetype = MOVETYPE_WALK; + _player.effects &= ~EF_NODRAW; + _player.teleportable = TELEPORT_NORMAL; + _player.alpha = 1; + _player.PlayerPhysplug = func_null; + _player.vehicle = world; + _player.view_ofs = PL_VIEW_OFS; + _player.event_damage = PlayerDamage; + _player.hud = HUD_NORMAL; + _player.switchweapon = _vehicle.switchweapon; + _player.last_vehiclecheck = time + 3; + + CSQCVehicleSetup(_player, HUD_NORMAL); + } + _vehicle.flags |= FL_NOTARGET; + + if(_vehicle.deadflag == DEAD_NO) + _vehicle.avelocity = '0 0 0'; + + _vehicle.tur_head.nodrawtoclient = world; + + if(!teamplay) + _vehicle.team = 0; + + Kill_Notification(NOTIF_ONE, _player, MSG_CENTER_CPID, CPID_VEHICLES); + Kill_Notification(NOTIF_ONE, _player, MSG_CENTER_CPID, CPID_VEHICLES_OTHER); // kill all vehicle notifications when exiting a vehicle? + + WaypointSprite_Kill(_vehicle.wps_intruder); + + vh_player = _player; + vh_vehicle = _vehicle; + MUTATOR_CALLHOOK(VehicleExit); + _player = vh_player; + _vehicle = vh_vehicle; + + _vehicle.team = _vehicle.tur_head.team; + + sound (_vehicle, CH_TRIGGER_SINGLE, "misc/null.wav", 1, ATTEN_NORM); + _vehicle.vehicle_hudmodel.viewmodelforclient = _vehicle; + _vehicle.phase = time + 1; + + _vehicle.vehicle_exit(eject); + + vehicles_setreturn(_vehicle); + vehicles_reset_colors(); + _vehicle.owner = world; + + CSQCMODEL_AUTOINIT(); + + self = _oldself; + + vehicles_exit_running = FALSE; +} + +void vehicles_touch() +{ + if(MUTATOR_CALLHOOK(VehicleTouch)) + return; + + // Vehicle currently in use + if(self.owner) + { + if(!forbidWeaponUse(self.owner)) + if(other != world) + if((self.origin_z + self.maxs_z) > (other.origin_z)) + if(vehicles_crushable(other)) + { + if(vlen(self.velocity) != 0) + Damage(other, self, self.owner, autocvar_g_vehicles_crush_dmg, DEATH_VH_CRUSH, '0 0 0', normalize(other.origin - self.origin) * autocvar_g_vehicles_crush_force); + + return; // Dont do selfdamage when hitting "soft targets". + } + + if(self.play_time < time) + VEH_ACTION(self.vehicleid, VR_IMPACT); + + return; + } + + if(autocvar_g_vehicles_enter) + return; + + vehicles_enter(other, self); +} + +void vehicles_enter(entity pl, entity veh) +{ + // Remove this when bots know how to use vehicles + if (IS_BOT_CLIENT(pl)) + if (autocvar_g_vehicles_allow_bots) + dprint("Bot enters vehicle\n"); // This is where we need to disconnect (some, all?) normal bot AI and hand over to vehicle's _aiframe() + else + return; + + if(!IS_PLAYER(pl)) + return; + + if(veh.phase > time) + return; + + if(pl.frozen) + return; + + if(pl.deadflag != DEAD_NO) + return; + + if(pl.vehicle) + return; + + if(autocvar_g_vehicles_enter) // skip if we're using regular touch code + if(veh.vehicle_flags & VHF_MULTISLOT) + if(veh.owner) + { + entity oldself = self; + self = veh; + other = pl; // TODO: fix + + if(!veh.gunner1) + if(veh.gun1.phase <= time) + if(veh.gun1.vehicle_enter) + if(veh.gun1.vehicle_enter()) + { + self = oldself; + return; + } + + if(!veh.gunner2) + if(veh.gun2.phase <= time) + if(veh.gun2.vehicle_enter) + if(veh.gun2.vehicle_enter()) + { + self = oldself; + return; + } + + self = oldself; + } + + if(teamplay) + if(veh.team) + if(DIFF_TEAM(pl, veh)) + if(autocvar_g_vehicles_steal) + { + entity head; + FOR_EACH_PLAYER(head) if(SAME_TEAM(head, veh)) + Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_VEHICLE_STEAL); + + Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_VEHICLE_STEAL_SELF); + + if(autocvar_g_vehicles_steal_show_waypoint) + WaypointSprite_Spawn("intruder", 0, 0, pl, '0 0 68', world, veh.team, veh, wps_intruder, TRUE, RADARICON_DANGER, Team_ColorRGB(pl.team)); + } + else return; + + jeff_Announcer_VehicleEnter(pl, veh); + + RemoveGrapplingHook(pl); + + veh.vehicle_ammo1 = 0; + veh.vehicle_ammo2 = 0; + veh.vehicle_reload1 = 0; + veh.vehicle_reload2 = 0; + veh.vehicle_energy = 0; + + veh.owner = pl; + pl.vehicle = veh; + + // .viewmodelforclient works better. + //veh.vehicle_hudmodel.drawonlytoclient = veh.owner; + + veh.vehicle_hudmodel.viewmodelforclient = pl; + + tracebox(pl.origin, PL_MIN, PL_MAX, pl.origin, FALSE, pl); + pl.crouch = FALSE; + pl.view_ofs = PL_VIEW_OFS; + setsize (pl, PL_MIN, PL_MAX); + + veh.event_damage = vehicles_damage; + veh.nextthink = 0; + pl.angles = veh.angles; + pl.takedamage = DAMAGE_NO; + pl.solid = SOLID_NOT; + pl.movetype = MOVETYPE_NOCLIP; + pl.teleportable = FALSE; + pl.alpha = -1; + pl.event_damage = func_null; + pl.view_ofs = '0 0 0'; + veh.colormap = pl.colormap; + if(veh.tur_head) + veh.tur_head.colormap = pl.colormap; + veh.switchweapon = pl.switchweapon; + pl.hud = veh.vehicleid; + pl.PlayerPhysplug = veh.PlayerPhysplug; + + pl.vehicle_ammo1 = veh.vehicle_ammo1; + pl.vehicle_ammo2 = veh.vehicle_ammo2; + pl.vehicle_reload1 = veh.vehicle_reload1; + pl.vehicle_reload2 = veh.vehicle_reload2; + + // Cant do this, hides attached objects too. + //veh.exteriormodeltoclient = veh.owner; + //veh.tur_head.exteriormodeltoclient = veh.owner; + + pl.flags &= ~FL_ONGROUND; + veh.flags &= ~FL_ONGROUND; + + veh.team = pl.team; + veh.flags -= FL_NOTARGET; + + if (IS_REAL_CLIENT(pl)) + { + Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_VEHICLE_ENTER); + + msg_entity = pl; + WriteByte (MSG_ONE, SVC_SETVIEWPORT); + WriteEntity(MSG_ONE, veh.vehicle_viewport); + + WriteByte (MSG_ONE, SVC_SETVIEWANGLES); + if(veh.tur_head) + { + WriteAngle(MSG_ONE, veh.tur_head.angles_x + veh.angles_x); // tilt + WriteAngle(MSG_ONE, veh.tur_head.angles_y + veh.angles_y); // yaw + WriteAngle(MSG_ONE, 0); // roll + } + else + { + WriteAngle(MSG_ONE, veh.angles_x * -1); // tilt + WriteAngle(MSG_ONE, veh.angles_y); // yaw + WriteAngle(MSG_ONE, 0); // roll + } + } + + vehicles_clearreturn(veh); + + CSQCVehicleSetup(pl, veh.vehicleid); + + vh_player = pl; + vh_vehicle = veh; + MUTATOR_CALLHOOK(VehicleEnter); + + entity oldself = self; + self = veh; + CSQCModel_UnlinkEntity(); + VEH_ACTION(veh.vehicleid, VR_ENTER); + self = oldself; + + antilag_clear(pl); +} + +void vehicles_think() +{ + self.nextthink = time; + + if(self.owner) + self.owner.vehicle_weapon2mode = self.vehicle_weapon2mode; + + VEH_ACTION(self.vehicleid, VR_THINK); + + CSQCMODEL_AUTOUPDATE(); +} + +// initialization +void vehicles_spawn() +{ + dprint("Spawning vehicle: ", self.classname, "\n"); + + // disown & reset + self.vehicle_hudmodel.viewmodelforclient = self; + + self.owner = world; + self.touch = vehicles_touch; + self.event_damage = vehicles_damage; + self.iscreature = TRUE; + self.teleportable = FALSE; // no teleporting for vehicles, too buggy + self.damagedbycontents = TRUE; + self.movetype = MOVETYPE_WALK; + self.solid = SOLID_SLIDEBOX; + self.takedamage = DAMAGE_AIM; + self.deadflag = DEAD_NO; + self.bot_attack = TRUE; + self.flags = FL_NOTARGET; + self.avelocity = '0 0 0'; + self.velocity = '0 0 0'; + self.think = vehicles_think; + self.nextthink = time; + + // Reset locking + self.lock_strength = 0; + self.lock_target = world; + self.misc_bulletcounter = 0; + + // Return to spawn + self.angles = self.pos2; + setorigin(self, self.pos1); + // Show it + Send_Effect(EFFECT_TELEPORT, self.origin + '0 0 64', '0 0 0', 1); + + if(self.vehicle_controller) + self.team = self.vehicle_controller.team; + + entity head; // remove hooks (if any) + FOR_EACH_PLAYER(head) + if(head.hook.aiment == self) + RemoveGrapplingHook(head); + + vehicles_reset_colors(); + + VEH_ACTION(self.vehicleid, VR_SPAWN); + + CSQCMODEL_AUTOINIT(); +} + +float vehicle_initialize(float vehicle_id, float nodrop) +{ + if(!autocvar_g_vehicles) + return FALSE; + + entity veh = get_vehicleinfo(vehicle_id); + + if(!veh.vehicleid) + return FALSE; + + if(!veh.tur_head) { VEH_ACTION(vehicle_id, VR_PRECACHE); } + + if(self.targetname && self.targetname != "") + { + self.vehicle_controller = find(world, target, self.targetname); + if(!self.vehicle_controller) + { + bprint("^1WARNING: ^7Vehicle with invalid .targetname\n"); + self.active = ACTIVE_ACTIVE; + } + else + { + self.team = self.vehicle_controller.team; + self.use = vehicle_use; + + if(teamplay) + { + if(self.vehicle_controller.team == 0) + self.active = ACTIVE_NOT; + else + self.active = ACTIVE_ACTIVE; + } + } + } + else { self.active = ACTIVE_ACTIVE; } + + if(self.team && (!teamplay || !autocvar_g_vehicles_teams)) + self.team = 0; + + self.vehicle_flags |= VHF_ISVEHICLE; + + setmodel(self, veh.model); + + self.vehicle_viewport = spawn(); + self.vehicle_hudmodel = spawn(); + self.tur_head = spawn(); + self.tur_head.owner = self; + self.takedamage = DAMAGE_NO; + self.bot_attack = TRUE; + self.iscreature = TRUE; + self.teleportable = FALSE; // no teleporting for vehicles, too buggy + self.damagedbycontents = TRUE; + self.vehicleid = vehicle_id; + self.PlayerPhysplug = veh.PlayerPhysplug; + self.event_damage = func_null; + self.touch = vehicles_touch; + self.think = vehicles_spawn; + self.nextthink = time; + self.effects = EF_NODRAW; + self.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_SOLID; + + if(autocvar_g_playerclip_collisions) + self.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP; + + if(autocvar_g_nodepthtestplayers) + self.effects |= EF_NODEPTHTEST; + + if(autocvar_g_fullbrightplayers) + self.effects |= EF_FULLBRIGHT; + + setmodel(self.vehicle_hudmodel, veh.hud_model); + setmodel(self.vehicle_viewport, "null"); + + if(veh.head_model != "") + { + setmodel(self.tur_head, veh.head_model); + setattachment(self.tur_head, self, veh.tag_head); + setattachment(self.vehicle_hudmodel, self.tur_head, veh.tag_hud); + setattachment(self.vehicle_viewport, self.vehicle_hudmodel, veh.tag_view); + } + else + { + setattachment(self.tur_head, self, ""); + setattachment(self.vehicle_hudmodel, self, veh.tag_hud); + setattachment(self.vehicle_viewport, self.vehicle_hudmodel, veh.tag_view); + } + + setsize(self, veh.mins, veh.maxs); + + if(!nodrop) + { + setorigin(self, self.origin); + tracebox(self.origin + '0 0 100', veh.mins, veh.maxs, self.origin - '0 0 10000', MOVE_WORLDONLY, self); + setorigin(self, trace_endpos); + } + + self.pos1 = self.origin; + self.pos2 = self.angles; + self.tur_head.team = self.team; + + VEH_ACTION(vehicle_id, VR_SETUP); + + if(self.active == ACTIVE_NOT) + self.nextthink = 0; // wait until activated + else if(autocvar_g_vehicles_delayspawn) + self.nextthink = time + self.respawntime + (random() * autocvar_g_vehicles_delayspawn_jitter); + else + self.nextthink = time + game_starttime; + + if(MUTATOR_CALLHOOK(VehicleSpawn)) + return FALSE; + + return TRUE; +} diff --git a/qcsrc/server/vehicles/vehicles_def.qh b/qcsrc/common/vehicles/sv_vehicles.qh similarity index 57% rename from qcsrc/server/vehicles/vehicles_def.qh rename to qcsrc/common/vehicles/sv_vehicles.qh index 56fc9ea00..2e8844eef 100644 --- a/qcsrc/server/vehicles/vehicles_def.qh +++ b/qcsrc/common/vehicles/sv_vehicles.qh @@ -1,23 +1,26 @@ // #define VEHICLES_USE_ODE -#define VEHICLES_ENABLED -#ifdef VEHICLES_ENABLED - -.float vehicle_flags; -const float VHF_ISVEHICLE = 2; /// Indicates vehicle -const float VHF_HASSHIELD = 4; /// Vehicle has shileding -const float VHF_SHIELDREGEN = 8; /// Vehicles shield regenerates -const float VHF_HEALTHREGEN = 16; /// Vehicles health regenerates -const float VHF_ENERGYREGEN = 32; /// Vehicles energy regenerates -const float VHF_DEATHEJECT = 64; /// Vehicle ejects pilot upon fatal damage -const float VHF_MOVE_GROUND = 128; /// Vehicle moves on gound -const float VHF_MOVE_HOVER = 256; /// Vehicle hover close to gound -const float VHF_MOVE_FLY = 512; /// Vehicle is airborn -const float VHF_DMGSHAKE = 1024; /// Add random velocity each frame if health < 50% -const float VHF_DMGROLL = 2048; /// Add random angles each frame if health < 50% -const float VHF_DMGHEADROLL = 4096; /// Add random head angles each frame if health < 50% -const float VHF_MULTISLOT = 8192; /// Vehicle has multiple player slots -const float VHF_PLAYERSLOT = 16384; /// This ent is a player slot on a multi-person vehicle +// vehicle cvars +float autocvar_g_vehicles; +float autocvar_g_vehicles_enter; +float autocvar_g_vehicles_enter_radius; +float autocvar_g_vehicles_extra; +float autocvar_g_vehicles_steal; +float autocvar_g_vehicles_steal_show_waypoint; +float autocvar_g_vehicles_crush_dmg; +float autocvar_g_vehicles_crush_force; +float autocvar_g_vehicles_delayspawn; +float autocvar_g_vehicles_delayspawn_jitter; +float autocvar_g_vehicles_allow_bots; +float autocvar_g_vehicles_teams; +float autocvar_g_vehicles_teleportable; +var float autocvar_g_vehicles_vortex_damagerate = 0.5; +var float autocvar_g_vehicles_machinegun_damagerate = 0.5; +var float autocvar_g_vehicles_rifle_damagerate = 0.75; +var float autocvar_g_vehicles_vaporizer_damagerate = 0.001; +var float autocvar_g_vehicles_tag_damagerate = 5; + +// vehicle definitions .entity gun1; .entity gun2; .entity gun3; @@ -42,32 +45,60 @@ const float VHF_PLAYERSLOT = 16384; /// This ent is a player slot on a mul .float sound_nexttime; #define VOL_VEHICLEENGINE 1 -.float hud; -.float dmg_time; -.float vehicle_respawntime; -//.void() vehicle_spawn; - -void vehicles_exit(float eject); -.void(float exit_flags) vehicle_exit; -const float VHEF_NORMAL = 0; /// User pressed exit key -const float VHEF_EJECT = 1; /// User pressed exit key 3 times fast (not implemented) or vehile is dying -const float VHEF_RELESE = 2; /// Release ownership, client possibly allready dissconnected / went spec / changed team / used "kill" (not implemented) - const float SVC_SETVIEWPORT = 5; // Net.Protocol 0x05 const float SVC_SETVIEWANGLES = 10; // Net.Protocol 0x0A const float SVC_UPDATEENTITY = 128; // Net.Protocol 0x80 -.void() vehicle_enter; /// Vehicles custom funciton to be executed when owner exit it -.void() vehicle_die; /// Vehicles custom function to be executed when vehile die #define VHSF_NORMAL 0 #define VHSF_FACTORY 2 -.void(float _spawnflag) vehicle_spawn; /// Vehicles custom fucntion to be efecuted when vehicle (re)spawns -.float(float _imp) vehicles_impulse; -.float vehicle_weapon2mode = volly_counter; + +.float hud; +.float dmg_time; + +.float volly_counter; + +const float MAX_AXH = 4; +.entity AuxiliaryXhair[MAX_AXH]; + +.entity wps_intruder; + +.entity lock_target; +.float lock_strength; +.float lock_time; +.float lock_soundtime; +const float DAMAGE_TARGETDRONE = 10; + +float force_fromtag_power; +float force_fromtag_normpower; +vector force_fromtag_origin; + +float vehicles_exit_running; #ifdef VEHICLES_USE_ODE void(entity e, float physics_enabled) physics_enable = #540; // enable or disable physics on object void(entity e, vector force, vector force_pos) physics_addforce = #541; // apply a force from certain origin, length of force vector is power of force void(entity e, vector torque) physics_addtorque = #542; // add relative torque #endif // VEHICLES_USE_ODE -#endif // VEHICLES_ENABLED + +// functions used outside the vehicle code +void vehicles_exit(float eject); +void vehicles_enter(entity pl, entity veh); + +// vehicle functions +.void(float _spawnflag) vehicle_spawn; /// Vehicles custom fucntion to be efecuted when vehicle (re)spawns +.float(float _imp) vehicles_impulse; +.float vehicle_weapon2mode; +.void(float exit_flags) vehicle_exit; +.float() vehicle_enter; +const float VHEF_NORMAL = 0; /// User pressed exit key +const float VHEF_EJECT = 1; /// User pressed exit key 3 times fast (not implemented) or vehile is dying +const float VHEF_RELEASE = 2; /// Release ownership, client possibly allready dissconnected / went spec / changed team / used "kill" (not implemented) + +// macros +#define VEHICLE_UPDATE_PLAYER(ply,fld,vhname) \ + ply.vehicle_##fld = (self.vehicle_##fld / autocvar_g_vehicle_##vhname##_##fld) * 100 + +#define vehicles_sweap_collision(orig,vel,dt,acm,mult) \ + traceline(orig, orig + vel * dt, MOVE_NORMAL, self); \ + if(trace_fraction != 1) \ + acm += normalize(self.origin - trace_endpos) * (vlen(vel) * mult) \ No newline at end of file diff --git a/qcsrc/server/vehicles/bumblebee.qc b/qcsrc/common/vehicles/unit/bumblebee.qc similarity index 51% rename from qcsrc/server/vehicles/bumblebee.qc rename to qcsrc/common/vehicles/unit/bumblebee.qc index 8b8d308f9..cd76b30d8 100644 --- a/qcsrc/server/vehicles/bumblebee.qc +++ b/qcsrc/common/vehicles/unit/bumblebee.qc @@ -1,9 +1,23 @@ -#define BRG_SETUP 2 -#define BRG_START 4 -#define BRG_END 8 +#ifdef REGISTER_VEHICLE +REGISTER_VEHICLE( +/* VEH_##id */ BUMBLEBEE, +/* function */ v_bumblebee, +/* spawnflags */ VHF_DMGSHAKE, +/* mins,maxs */ '-245 -130 -130', '230 130 130', +/* model */ "models/vehicles/bumblebee_body.dpm", +/* head_model */ "", +/* hud_model */ "models/vehicles/spiderbot_cockpit.dpm", +/* tags */ "", "", "tag_viewport", +/* netname */ "bumblebee", +/* fullname */ _("Bumblebee") +); +#else + +const float BRG_SETUP = 2; +const float BRG_START = 4; +const float BRG_END = 8; #ifdef SVQC -// Auto cvars float autocvar_g_vehicle_bumblebee_speed_forward; float autocvar_g_vehicle_bumblebee_speed_strafe; float autocvar_g_vehicle_bumblebee_speed_up; @@ -77,28 +91,22 @@ var float autocvar_g_vehicle_bumblebee = 0; float bumble_raygun_send(entity to, float sf); -#define BUMB_MIN '-130 -130 -130' -#define BUMB_MAX '130 130 130' - -void bumb_fire_cannon(entity _gun, string _tagname, entity _owner) +void bumblebee_fire_cannon(entity _gun, string _tagname, entity _owner) { vector v = gettaginfo(_gun, gettagindex(_gun, _tagname)); - vehicles_projectile("bigplasma_muzzleflash", "weapons/flacexp3.wav", + vehicles_projectile("bigplasma_muzzleflash", W_Sound("flacexp3"), v, normalize(v_forward + randomvec() * autocvar_g_vehicle_bumblebee_cannon_spread) * autocvar_g_vehicle_bumblebee_cannon_speed, autocvar_g_vehicle_bumblebee_cannon_damage, autocvar_g_vehicle_bumblebee_cannon_radius, autocvar_g_vehicle_bumblebee_cannon_force, 0, DEATH_VH_BUMB_GUN, PROJECTILE_BUMBLE_GUN, 0, TRUE, TRUE, _owner); } -float bumb_gunner_frame() +float bumblebee_gunner_frame() { entity vehic = self.vehicle.owner; entity gun = self.vehicle; entity gunner = self; self = vehic; - - - vehic.solid = SOLID_NOT; //setorigin(gunner, vehic.origin); gunner.velocity = vehic.velocity; @@ -152,7 +160,7 @@ float bumb_gunner_frame() if(gun.enemy) { - float i, distance, impact_time; + float distance, impact_time; vector vf = real_origin(gun.enemy); vector _vel = gun.enemy.velocity; @@ -161,12 +169,9 @@ float bumb_gunner_frame() ad = vf; - for(i = 0; i < 4; ++i) - { - distance = vlen(ad - gunner.origin); - impact_time = distance / autocvar_g_vehicle_bumblebee_cannon_speed; - ad = vf + _vel * impact_time; - } + distance = vlen(ad - gunner.origin); + impact_time = distance / autocvar_g_vehicle_bumblebee_cannon_speed; + ad = vf + _vel * impact_time; trace_endpos = ad; @@ -181,12 +186,13 @@ float bumb_gunner_frame() autocvar_g_vehicle_bumblebee_cannon_pitchlimit_down * -1, autocvar_g_vehicle_bumblebee_cannon_pitchlimit_up, _out * -1, _in, autocvar_g_vehicle_bumblebee_cannon_turnspeed); + if(!forbidWeaponUse(gunner)) if(gunner.BUTTON_ATCK) if(time > gun.attack_finished_single) if(gun.vehicle_energy >= autocvar_g_vehicle_bumblebee_cannon_cost) { gun.vehicle_energy -= autocvar_g_vehicle_bumblebee_cannon_cost; - bumb_fire_cannon(gun, "fire", gunner); + bumblebee_fire_cannon(gun, "fire", gunner); gun.delay = time; gun.attack_finished_single = time + autocvar_g_vehicle_bumblebee_cannon_refire; } @@ -212,7 +218,7 @@ float bumb_gunner_frame() return 1; } -void bumb_gunner_exit(float _exitflag) +void bumblebee_gunner_exit(float _exitflag) { if(IS_REAL_CLIENT(self)) { @@ -238,6 +244,7 @@ void bumb_gunner_exit(float _exitflag) self.view_ofs = PL_VIEW_OFS; self.event_damage = PlayerDamage; self.hud = HUD_NORMAL; + self.teleportable = TELEPORT_NORMAL; self.switchweapon = self.vehicle.switchweapon; vh_player = self; @@ -273,7 +280,7 @@ void bumb_gunner_exit(float _exitflag) self.vehicle = world; } -float bumb_gunner_enter() +float bumblebee_gunner_enter() { RemoveGrapplingHook(other); entity _gun, _gunner; @@ -298,7 +305,7 @@ float bumb_gunner_enter() _gunner = other; _gunner.vehicle = _gun; _gun.switchweapon = other.switchweapon; - _gun.vehicle_exit = bumb_gunner_exit; + _gun.vehicle_exit = bumblebee_gunner_exit; other.angles = self.angles; other.takedamage = DAMAGE_NO; @@ -308,22 +315,27 @@ float bumb_gunner_enter() other.event_damage = func_null; other.view_ofs = '0 0 0'; other.hud = _gun.hud; + other.teleportable = FALSE; other.PlayerPhysplug = _gun.PlayerPhysplug; other.vehicle_ammo1 = self.vehicle_ammo1; other.vehicle_ammo2 = self.vehicle_ammo2; other.vehicle_reload1 = self.vehicle_reload1; other.vehicle_reload2 = self.vehicle_reload2; other.vehicle_energy = self.vehicle_energy; - other.PlayerPhysplug = bumb_gunner_frame; + other.PlayerPhysplug = bumblebee_gunner_frame; other.flags &= ~FL_ONGROUND; - msg_entity = other; - WriteByte(MSG_ONE, SVC_SETVIEWPORT); - WriteEntity(MSG_ONE, _gun.vehicle_viewport); - WriteByte(MSG_ONE, SVC_SETVIEWANGLES); - WriteAngle(MSG_ONE, _gun.angles_x + self.angles_x); // tilt - WriteAngle(MSG_ONE, _gun.angles_y + self.angles_y); // yaw - WriteAngle(MSG_ONE, 0); // roll + if(IS_REAL_CLIENT(other)) + { + msg_entity = other; + WriteByte(MSG_ONE, SVC_SETVIEWPORT); + WriteEntity(MSG_ONE, _gun.vehicle_viewport); + WriteByte(MSG_ONE, SVC_SETVIEWANGLES); + WriteAngle(MSG_ONE, _gun.angles_x + self.angles_x); // tilt + WriteAngle(MSG_ONE, _gun.angles_y + self.angles_y); // yaw + WriteAngle(MSG_ONE, 0); // roll + } + _gun.vehicle_hudmodel.viewmodelforclient = other; CSQCVehicleSetup(other, other.hud); @@ -358,8 +370,9 @@ float vehicles_valid_pilot() return TRUE; } -void bumb_touch() +void bumblebee_touch() { + if(autocvar_g_vehicles_enter) { return; } if(self.gunner1 != world && self.gunner2 != world) { @@ -370,18 +383,18 @@ void bumb_touch() if(vehicles_valid_pilot()) { if(self.gun1.phase <= time) - if(bumb_gunner_enter()) + if(bumblebee_gunner_enter()) return; if(self.gun2.phase <= time) - if(bumb_gunner_enter()) + if(bumblebee_gunner_enter()) return; } vehicles_touch(); } -void bumb_regen() +void bumblebee_regen() { if(self.gun1.delay + autocvar_g_vehicle_bumblebee_cannon_ammo_regen_pause < time) self.gun1.vehicle_energy = min(autocvar_g_vehicle_bumblebee_cannon_ammo, @@ -402,16 +415,22 @@ void bumb_regen() } -float bumb_pilot_frame() +float bumblebee_pilot_frame() { entity pilot, vehic; vector newvel; + + if(intermission_running) + { + self.vehicle.velocity = '0 0 0'; + self.vehicle.avelocity = '0 0 0'; + return 1; + } pilot = self; vehic = self.vehicle; self = vehic; - if(vehic.deadflag != DEAD_NO) { self = pilot; @@ -419,7 +438,7 @@ float bumb_pilot_frame() return 1; } - bumb_regen(); + bumblebee_regen(); crosshair_trace(pilot); @@ -528,6 +547,7 @@ float bumb_pilot_frame() autocvar_g_vehicle_bumblebee_raygun_pitchlimit_down * -1, autocvar_g_vehicle_bumblebee_raygun_pitchlimit_up, autocvar_g_vehicle_bumblebee_raygun_turnlimit_sides * -1, autocvar_g_vehicle_bumblebee_raygun_turnlimit_sides, autocvar_g_vehicle_bumblebee_raygun_turnspeed); + if(!forbidWeaponUse(pilot)) if((pilot.BUTTON_ATCK || pilot.BUTTON_ATCK2) && (vehic.vehicle_energy > autocvar_g_vehicle_bumblebee_raygun_dps * sys_frametime || autocvar_g_vehicle_bumblebee_raygun == 0)) { vehic.gun3.enemy.realowner = pilot; @@ -553,11 +573,11 @@ float bumb_pilot_frame() if(trace_ent.vehicle_flags & VHF_ISVEHICLE) { - if(autocvar_g_vehicle_bumblebee_healgun_sps && trace_ent.vehicle_health <= trace_ent.tur_health) - trace_ent.vehicle_shield = min(trace_ent.vehicle_shield + autocvar_g_vehicle_bumblebee_healgun_sps * frametime, trace_ent.tur_head.tur_health); + if(autocvar_g_vehicle_bumblebee_healgun_sps && trace_ent.vehicle_health <= trace_ent.max_health) + trace_ent.vehicle_shield = min(trace_ent.vehicle_shield + autocvar_g_vehicle_bumblebee_healgun_sps * frametime, trace_ent.tur_head.max_health); if(autocvar_g_vehicle_bumblebee_healgun_hps) - trace_ent.vehicle_health = min(trace_ent.vehicle_health + autocvar_g_vehicle_bumblebee_healgun_hps * frametime, trace_ent.tur_health); + trace_ent.vehicle_health = min(trace_ent.vehicle_health + autocvar_g_vehicle_bumblebee_healgun_hps * frametime, trace_ent.max_health); } else if(IS_CLIENT(trace_ent)) { @@ -569,10 +589,10 @@ float bumb_pilot_frame() trace_ent.health = min(trace_ent.health + autocvar_g_vehicle_bumblebee_healgun_hps * frametime, autocvar_g_vehicle_bumblebee_healgun_hmax); } - else if(trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET) + else if(IS_TURRET(trace_ent)) { - if(trace_ent.health <= trace_ent.tur_health && autocvar_g_vehicle_bumblebee_healgun_hps) - trace_ent.health = min(trace_ent.health + autocvar_g_vehicle_bumblebee_healgun_hps * frametime, trace_ent.tur_health); + if(trace_ent.health <= trace_ent.max_health && autocvar_g_vehicle_bumblebee_healgun_hps) + trace_ent.health = min(trace_ent.health + autocvar_g_vehicle_bumblebee_healgun_hps * frametime, trace_ent.max_health); //else ..hmmm what? ammo? trace_ent.SendFlags |= TNSF_STATUS; @@ -617,61 +637,40 @@ float bumb_pilot_frame() return 1; } -void bumb_think() +void bumblebee_land() { - self.movetype = MOVETYPE_TOSS; + float hgt; - //self.velocity = self.velocity * 0.5; - self.angles_z *= 0.8; - self.angles_x *= 0.8; + hgt = raptor_altitude(512); + self.velocity = (self.velocity * 0.9) + ('0 0 -1800' * (hgt / 256) * sys_frametime); + self.angles_x *= 0.95; + self.angles_z *= 0.95; - self.nextthink = time + 0.05; - - if(!self.owner) - { - entity oldself = self; - if(self.gunner1) - { - self = self.gunner1; - oldself.gun1.vehicle_exit(VHEF_EJECT); - entity oldother = other; - other = self; - self = oldself; - self.phase = 0; - self.touch(); - other = oldother; - return; - } - - if(self.gunner2) - { - self = self.gunner2; - oldself.gun2.vehicle_exit(VHEF_EJECT); - entity oldother = other; - other = self; - self = oldself; - self.phase = 0; - self.touch(); - other = oldother; - return; - } - } + if(hgt < 16) + self.think = vehicles_think; + self.nextthink = time; + + CSQCMODEL_AUTOUPDATE(); } -void bumb_enter() +void bumblebee_exit(float eject) { - self.touch = bumb_touch; - self.nextthink = 0; - self.movetype = MOVETYPE_BOUNCEMISSILE; - //setattachment(self.owner, self.vehicle_viewport, ""); -} + if(self.owner.vehicleid == VEH_BUMBLEBEE) + { + bumblebee_gunner_exit(eject); + return; + } -void bumb_exit(float eject) -{ self.touch = vehicles_touch; - self.think = bumb_think; - self.nextthink = time; + + if(self.deadflag == DEAD_NO) + { + self.think = bumblebee_land; + self.nextthink = time; + } + + self.movetype = MOVETYPE_TOSS; if(!self.owner) return; @@ -679,9 +678,9 @@ void bumb_exit(float eject) fixedmakevectors(self.angles); vector spot; if(vlen(self.velocity) > autocvar_g_vehicle_bumblebee_speed_forward * 0.5) - spot = self.origin + v_up * 128 + v_forward * 200; + spot = self.origin + v_up * 128 + v_forward * 300; else - spot = self.origin + v_up * 128 - v_forward * 200; + spot = self.origin + v_up * 128 - v_forward * 300; spot = vehicles_findgoodexit(spot); @@ -698,7 +697,7 @@ void bumb_exit(float eject) self.owner = world; } -void bumb_blowup() +void bumblebee_blowup() { RadiusDamage(self, self.enemy, autocvar_g_vehicle_bumblebee_blowup_coredamage, autocvar_g_vehicle_bumblebee_blowup_edgedamage, @@ -706,8 +705,8 @@ void bumb_blowup() autocvar_g_vehicle_bumblebee_blowup_forceintensity, DEATH_VH_BUMB_DEATH, world); - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); - pointparticles(particleeffectnum("explosion_large"), randomvec() * 80 + (self.origin + '0 0 100'), '0 0 0', 1); + sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_EXPLOSION_BIG, (self.origin + '0 0 100') + (randomvec() * 80), '0 0 0', 1); if(self.owner.deadflag == DEAD_DYING) self.owner.deadflag = DEAD_DEAD; @@ -715,250 +714,422 @@ void bumb_blowup() remove(self); } -void bumb_diethink() +void bumblebee_diethink() { if(time >= self.wait) - self.think = bumb_blowup; + self.think = bumblebee_blowup; if(random() < 0.1) { - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); - pointparticles(particleeffectnum("explosion_small"), randomvec() * 80 + (self.origin + '0 0 100'), '0 0 0', 1); + sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_EXPLOSION_SMALL, (self.origin + '0 0 100') + (randomvec() * 80), '0 0 0', 1); } self.nextthink = time + 0.1; } -void bumb_die() +float bumble_raygun_send(entity to, float sf) { - entity oldself = self; - - // Hide beam - if(self.gun3.enemy || !wasfreed(self.gun3.enemy)) - self.gun3.enemy.effects |= EF_NODRAW; + WriteByte(MSG_ENTITY, ENT_CLIENT_BUMBLE_RAYGUN); - if(self.gunner1) + WriteByte(MSG_ENTITY, sf); + if(sf & BRG_SETUP) { - self = self.gunner1; - oldself.gun1.vehicle_exit(VHEF_EJECT); - self = oldself; + WriteByte(MSG_ENTITY, num_for_edict(self.realowner)); + WriteByte(MSG_ENTITY, self.realowner.team); + WriteByte(MSG_ENTITY, self.cnt); } - if(self.gunner2) + if(sf & BRG_START) { - self = self.gunner2; - oldself.gun2.vehicle_exit(VHEF_EJECT); - self = oldself; + WriteCoord(MSG_ENTITY, self.hook_start_x); + WriteCoord(MSG_ENTITY, self.hook_start_y); + WriteCoord(MSG_ENTITY, self.hook_start_z); } - self.vehicle_exit(VHEF_EJECT); - - fixedmakevectors(self.angles); - vehicle_tossgib(self.gun1, self.velocity + v_right * 300 + v_up * 100 + randomvec() * 200, "cannon_right", rint(random()), rint(random()), 6, randomvec() * 200); - vehicle_tossgib(self.gun2, self.velocity + v_right * -300 + v_up * 100 + randomvec() * 200, "cannon_left", rint(random()), rint(random()), 6, randomvec() * 200); - vehicle_tossgib(self.gun3, self.velocity + v_forward * 300 + v_up * -100 + randomvec() * 200, "raygun", rint(random()), rint(random()), 6, randomvec() * 300); - - entity _body = vehicle_tossgib(self, self.velocity + randomvec() * 200, "", rint(random()), rint(random()), 6, randomvec() * 100); + if(sf & BRG_END) + { + WriteCoord(MSG_ENTITY, self.hook_end_x); + WriteCoord(MSG_ENTITY, self.hook_end_y); + WriteCoord(MSG_ENTITY, self.hook_end_z); + } - if(random() > 0.5) - _body.touch = bumb_blowup; - else - _body.touch = func_null; - - _body.think = bumb_diethink; - _body.nextthink = time; - _body.wait = time + 2 + (random() * 8); - _body.owner = self; - _body.enemy = self.enemy; - - pointparticles(particleeffectnum("explosion_medium"), findbetterlocation(self.origin, 16), '0 0 0', 1); - - self.health = 0; - self.event_damage = func_null; - self.solid = SOLID_CORPSE; - self.takedamage = DAMAGE_NO; - self.deadflag = DEAD_DYING; - self.movetype = MOVETYPE_NONE; - self.effects = EF_NODRAW; - self.colormod = '0 0 0'; - self.avelocity = '0 0 0'; - self.velocity = '0 0 0'; - self.touch = func_null; - self.nextthink = 0; - - setorigin(self, self.pos1); + return TRUE; } -void bumb_impact() +void spawnfunc_vehicle_bumblebee() { - if(autocvar_g_vehicle_bumblebee_bouncepain_x) - vehicles_impact(autocvar_g_vehicle_bumblebee_bouncepain_x, autocvar_g_vehicle_bumblebee_bouncepain_y, autocvar_g_vehicle_bumblebee_bouncepain_z); + if(!autocvar_g_vehicle_bumblebee) { remove(self); return; } + if(!vehicle_initialize(VEH_BUMBLEBEE, FALSE)) { remove(self); return; } } -void bumb_spawn(float _f) +float v_bumblebee(float req) { - /* - float i; - for(i=1; gettaginfo(self.gun1, i), gettaginfo_name; ++i) + switch(req) { + case VR_IMPACT: + { + if(autocvar_g_vehicle_bumblebee_bouncepain) + vehicles_impact(autocvar_g_vehicle_bumblebee_bouncepain_x, autocvar_g_vehicle_bumblebee_bouncepain_y, autocvar_g_vehicle_bumblebee_bouncepain_z); + + return TRUE; + } + case VR_ENTER: + { + self.touch = bumblebee_touch; + self.nextthink = 0; + self.movetype = MOVETYPE_BOUNCEMISSILE; + return TRUE; + } + case VR_THINK: + { + self.angles_z *= 0.8; + self.angles_x *= 0.8; + + self.nextthink = time; + + if(!self.owner) + { + entity oldself = self; + if(self.gunner1) + { + self = self.gunner1; + oldself.gun1.vehicle_exit(VHEF_EJECT); + entity oldother = other; + other = self; + self = oldself; + self.phase = 0; + self.touch(); + other = oldother; + return TRUE; + } + + if(self.gunner2) + { + self = self.gunner2; + oldself.gun2.vehicle_exit(VHEF_EJECT); + entity oldother = other; + other = self; + self = oldself; + self.phase = 0; + self.touch(); + other = oldother; + return TRUE; + } + } + + return TRUE; + } + case VR_DEATH: + { + entity oldself = self; + + CSQCModel_UnlinkEntity(); + + // Hide beam + if(self.gun3.enemy || !wasfreed(self.gun3.enemy)) + self.gun3.enemy.effects |= EF_NODRAW; + + if(self.gunner1) + { + self = self.gunner1; + oldself.gun1.vehicle_exit(VHEF_EJECT); + self = oldself; + } - dprint(" ------- ^1gettaginfo_name^2(",ftos(i),") ^3=", gettaginfo_name, "\n"); - } - */ - if(!self.gun1) - { - // for some reason, autosizing of the shiled entity refuses to work for this one so set it up in advance. - self.vehicle_shieldent = spawn(); - self.vehicle_shieldent.effects = EF_LOWPRECISION; - setmodel(self.vehicle_shieldent, "models/vhshield.md3"); - setattachment(self.vehicle_shieldent, self, ""); - setorigin(self.vehicle_shieldent, real_origin(self) - self.origin); - self.vehicle_shieldent.scale = 512 / vlen(self.maxs - self.mins); - self.vehicle_shieldent.think = shieldhit_think; - self.vehicle_shieldent.alpha = -1; - self.vehicle_shieldent.effects = EF_LOWPRECISION | EF_NODRAW; - - self.gun1 = spawn(); - self.gun2 = spawn(); - self.gun3 = spawn(); - - self.vehicle_flags |= VHF_MULTISLOT; - - self.gun1.owner = self; - self.gun2.owner = self; - self.gun3.owner = self; - - setmodel(self.gun1, "models/vehicles/bumblebee_plasma_right.dpm"); - setmodel(self.gun2, "models/vehicles/bumblebee_plasma_left.dpm"); - setmodel(self.gun3, "models/vehicles/bumblebee_ray.dpm"); - - setattachment(self.gun1, self, "cannon_right"); - setattachment(self.gun2, self, "cannon_left"); - - // Angled bones are no fun, messes up gun-aim; so work arround it. - self.gun3.pos1 = self.angles; - self.angles = '0 0 0'; - vector ofs = gettaginfo(self, gettagindex(self, "raygun")); - ofs -= self.origin; - setattachment(self.gun3, self, ""); - setorigin(self.gun3, ofs); - self.angles = self.gun3.pos1; - - vehicle_addplayerslot(self, self.gun1, HUD_BUMBLEBEE_GUN, "models/vehicles/wakizashi_cockpit.dpm", bumb_gunner_frame, bumb_gunner_exit); - vehicle_addplayerslot(self, self.gun2, HUD_BUMBLEBEE_GUN, "models/vehicles/wakizashi_cockpit.dpm", bumb_gunner_frame, bumb_gunner_exit); - - setorigin(self.vehicle_hudmodel, '50 0 -5'); // Move cockpit forward - down. - setorigin(self.vehicle_viewport, '5 0 2'); // Move camera forward up - - //fixme-model-bones - setorigin(self.gun1.vehicle_hudmodel, '90 -27 -23'); - setorigin(self.gun1.vehicle_viewport, '-85 0 50'); - //fixme-model-bones - setorigin(self.gun2.vehicle_hudmodel, '90 27 -23'); - setorigin(self.gun2.vehicle_viewport, '-85 0 50'); - - self.scale = 1.5; - - // Raygun beam - if(self.gun3.enemy == world) + if(self.gunner2) + { + self = self.gunner2; + oldself.gun2.vehicle_exit(VHEF_EJECT); + self = oldself; + } + + self.vehicle_exit(VHEF_EJECT); + + fixedmakevectors(self.angles); + vehicle_tossgib(self.gun1, self.velocity + v_right * 300 + v_up * 100 + randomvec() * 200, "cannon_right", rint(random()), rint(random()), 6, randomvec() * 200); + vehicle_tossgib(self.gun2, self.velocity + v_right * -300 + v_up * 100 + randomvec() * 200, "cannon_left", rint(random()), rint(random()), 6, randomvec() * 200); + vehicle_tossgib(self.gun3, self.velocity + v_forward * 300 + v_up * -100 + randomvec() * 200, "raygun", rint(random()), rint(random()), 6, randomvec() * 300); + + entity _body = vehicle_tossgib(self, self.velocity + randomvec() * 200, "", rint(random()), rint(random()), 6, randomvec() * 100); + + if(random() > 0.5) + _body.touch = bumblebee_blowup; + else + _body.touch = func_null; + + _body.think = bumblebee_diethink; + _body.nextthink = time; + _body.wait = time + 2 + (random() * 8); + _body.owner = self; + _body.enemy = self.enemy; + _body.scale = 1.5; + _body.angles = self.angles; + + Send_Effect(EFFECT_EXPLOSION_MEDIUM, findbetterlocation (self.origin, 16), '0 0 0', 1); + + self.health = 0; + self.event_damage = func_null; + self.solid = SOLID_NOT; + self.takedamage = DAMAGE_NO; + self.deadflag = DEAD_DYING; + self.movetype = MOVETYPE_NONE; + self.effects = EF_NODRAW; + self.colormod = '0 0 0'; + self.avelocity = '0 0 0'; + self.velocity = '0 0 0'; + self.touch = func_null; + self.nextthink = 0; + + setorigin(self, self.pos1); + return TRUE; + } + case VR_SPAWN: + { + if(!self.gun1) + { + // for some reason, autosizing of the shield entity refuses to work for this one so set it up in advance. + self.vehicle_shieldent = spawn(); + self.vehicle_shieldent.effects = EF_LOWPRECISION; + setmodel(self.vehicle_shieldent, "models/vhshield.md3"); + setattachment(self.vehicle_shieldent, self, ""); + setorigin(self.vehicle_shieldent, real_origin(self) - self.origin); + self.vehicle_shieldent.scale = 512 / vlen(self.maxs - self.mins); + self.vehicle_shieldent.think = shieldhit_think; + self.vehicle_shieldent.alpha = -1; + self.vehicle_shieldent.effects = EF_LOWPRECISION | EF_NODRAW; + + self.gun1 = spawn(); + self.gun2 = spawn(); + self.gun3 = spawn(); + + self.vehicle_flags |= VHF_MULTISLOT; + + self.gun1.owner = self; + self.gun2.owner = self; + self.gun3.owner = self; + + setmodel(self.gun1, "models/vehicles/bumblebee_plasma_right.dpm"); + setmodel(self.gun2, "models/vehicles/bumblebee_plasma_left.dpm"); + setmodel(self.gun3, "models/vehicles/bumblebee_ray.dpm"); + + setattachment(self.gun1, self, "cannon_right"); + setattachment(self.gun2, self, "cannon_left"); + + // Angled bones are no fun, messes up gun-aim; so work arround it. + self.gun3.pos1 = self.angles; + self.angles = '0 0 0'; + vector ofs = gettaginfo(self, gettagindex(self, "raygun")); + ofs -= self.origin; + setattachment(self.gun3, self, ""); + setorigin(self.gun3, ofs); + self.angles = self.gun3.pos1; + + vehicle_addplayerslot(self, self.gun1, HUD_BUMBLEBEE_GUN, "models/vehicles/wakizashi_cockpit.dpm", bumblebee_gunner_frame, bumblebee_gunner_exit, bumblebee_gunner_enter); + vehicle_addplayerslot(self, self.gun2, HUD_BUMBLEBEE_GUN, "models/vehicles/wakizashi_cockpit.dpm", bumblebee_gunner_frame, bumblebee_gunner_exit, bumblebee_gunner_enter); + + setorigin(self.vehicle_hudmodel, '50 0 -5'); // Move cockpit forward - down. + setorigin(self.vehicle_viewport, '5 0 2'); // Move camera forward up + + //fixme-model-bones + setorigin(self.gun1.vehicle_hudmodel, '90 -27 -23'); + setorigin(self.gun1.vehicle_viewport, '-85 0 50'); + //fixme-model-bones + setorigin(self.gun2.vehicle_hudmodel, '90 27 -23'); + setorigin(self.gun2.vehicle_viewport, '-85 0 50'); + + self.scale = 1.5; + + // Raygun beam + if(self.gun3.enemy == world) + { + self.gun3.enemy = spawn(); + Net_LinkEntity(self.gun3.enemy, TRUE, 0, bumble_raygun_send); + self.gun3.enemy.SendFlags = BRG_SETUP; + self.gun3.enemy.cnt = autocvar_g_vehicle_bumblebee_raygun; + self.gun3.enemy.effects = EF_NODRAW | EF_LOWPRECISION; + } + } + + self.vehicle_health = autocvar_g_vehicle_bumblebee_health; + self.vehicle_shield = autocvar_g_vehicle_bumblebee_shield; + self.solid = SOLID_BBOX; + self.movetype = MOVETYPE_TOSS; + self.damageforcescale = 0.025; + + self.PlayerPhysplug = bumblebee_pilot_frame; + + setorigin(self, self.origin + '0 0 25'); + return TRUE; + } + case VR_SETUP: { - self.gun3.enemy = spawn(); - Net_LinkEntity(self.gun3.enemy, TRUE, 0, bumble_raygun_send); - self.gun3.enemy.SendFlags = BRG_SETUP; - self.gun3.enemy.cnt = autocvar_g_vehicle_bumblebee_raygun; - self.gun3.enemy.effects = EF_NODRAW | EF_LOWPRECISION; + if(autocvar_g_vehicle_bumblebee_energy) + if(autocvar_g_vehicle_bumblebee_energy_regen) + self.vehicle_flags |= VHF_ENERGYREGEN; + + if(autocvar_g_vehicle_bumblebee_shield) + self.vehicle_flags |= VHF_HASSHIELD; + + if(autocvar_g_vehicle_bumblebee_shield_regen) + self.vehicle_flags |= VHF_SHIELDREGEN; + + if(autocvar_g_vehicle_bumblebee_health_regen) + self.vehicle_flags |= VHF_HEALTHREGEN; + + self.vehicle_exit = bumblebee_exit; + self.respawntime = autocvar_g_vehicle_bumblebee_respawntime; + self.vehicle_health = autocvar_g_vehicle_bumblebee_health; + self.max_health = self.vehicle_health; + self.vehicle_shield = autocvar_g_vehicle_bumblebee_shield; + + return TRUE; + } + case VR_PRECACHE: + { + precache_model("models/vehicles/bumblebee_body.dpm"); + precache_model("models/vehicles/bumblebee_plasma_left.dpm"); + precache_model("models/vehicles/bumblebee_plasma_right.dpm"); + precache_model("models/vehicles/bumblebee_ray.dpm"); + precache_model("models/vehicles/wakizashi_cockpit.dpm"); + precache_model("models/vehicles/spiderbot_cockpit.dpm"); + precache_model("models/vehicles/raptor_cockpit.dpm"); + return TRUE; } } - self.vehicle_health = autocvar_g_vehicle_bumblebee_health; - self.vehicle_shield = autocvar_g_vehicle_bumblebee_shield; - self.solid = SOLID_BBOX; - //self.movetype = MOVETYPE_BOUNCEMISSILE; - self.movetype = MOVETYPE_TOSS; - self.vehicle_impact = bumb_impact; - self.damageforcescale = 0.025; - - setorigin(self, self.origin + '0 0 25'); + return TRUE; } -void spawnfunc_vehicle_bumblebee() +#endif // SVQC +#ifdef CSQC + +#define bumb_ico "gfx/vehicles/bumb.tga" +#define bumb_lgun "gfx/vehicles/bumb_lgun.tga" +#define bumb_rgun "gfx/vehicles/bumb_rgun.tga" + +#define bumb_gun_ico "gfx/vehicles/bumb_side.tga" +#define bumb_gun_gun "gfx/vehicles/bumb_side_gun.tga" + +void CSQC_BUMBLE_GUN_HUD() { - if(!autocvar_g_vehicle_bumblebee) - { - remove(self); - return; - } - precache_model("models/vehicles/bumblebee_body.dpm"); - precache_model("models/vehicles/bumblebee_plasma_left.dpm"); - precache_model("models/vehicles/bumblebee_plasma_right.dpm"); - precache_model("models/vehicles/bumblebee_ray.dpm"); - precache_model("models/vehicles/wakizashi_cockpit.dpm"); - precache_model("models/vehicles/spiderbot_cockpit.dpm"); - precache_model("models/vehicles/raptor_cockpit.dpm"); - - if(autocvar_g_vehicle_bumblebee_energy) - if(autocvar_g_vehicle_bumblebee_energy_regen) - self.vehicle_flags |= VHF_ENERGYREGEN; - - if(autocvar_g_vehicle_bumblebee_shield) - self.vehicle_flags |= VHF_HASSHIELD; - - if(autocvar_g_vehicle_bumblebee_shield_regen) - self.vehicle_flags |= VHF_SHIELDREGEN; - - if(autocvar_g_vehicle_bumblebee_health_regen) - self.vehicle_flags |= VHF_HEALTHREGEN; - - if(!vehicle_initialize( - "Bumblebee", "models/vehicles/bumblebee_body.dpm", - "", "models/vehicles/spiderbot_cockpit.dpm", "", "", "tag_viewport", - HUD_BUMBLEBEE, BUMB_MIN, BUMB_MAX, FALSE, - bumb_spawn, autocvar_g_vehicle_bumblebee_respawntime, - bumb_pilot_frame, bumb_enter, bumb_exit, - bumb_die, bumb_think, FALSE, autocvar_g_vehicle_bumblebee_health, autocvar_g_vehicle_bumblebee_shield)) - { - remove(self); + if(autocvar_r_letterbox) return; - } -} -float bumble_raygun_send(entity to, float sf) -{ - WriteByte(MSG_ENTITY, ENT_CLIENT_BUMBLE_RAYGUN); + vector picsize, hudloc = '0 0 0', pic2size, picloc; - WriteByte(MSG_ENTITY, sf); - if(sf & BRG_SETUP) + // Fetch health & ammo stats + HUD_GETVEHICLESTATS + + picsize = draw_getimagesize(hud_bg) * autocvar_cl_vehicles_hudscale; + hudloc_y = vid_conheight - picsize_y; + hudloc_x = vid_conwidth * 0.5 - picsize_x * 0.5; + + drawpic(hudloc, hud_bg, picsize, '1 1 1', autocvar_cl_vehicles_hudalpha, DRAWFLAG_NORMAL); + + shield *= 0.01; + vh_health *= 0.01; + energy *= 0.01; + reload1 *= 0.01; + + pic2size = draw_getimagesize(bumb_gun_ico) * (autocvar_cl_vehicles_hudscale * 0.8); + picloc = picsize * 0.5 - pic2size * 0.5; + + if(vh_health < 0.25) + drawpic(hudloc + picloc, bumb_gun_ico, pic2size, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, bumb_gun_ico, pic2size, '1 1 1' * vh_health + '1 0 0' * (1 - vh_health), 1, DRAWFLAG_NORMAL); + + drawpic(hudloc + picloc, bumb_gun_gun, pic2size, '1 1 1' * energy + '1 0 0' * (1 - energy), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, hud_sh, pic2size, '1 1 1', shield, DRAWFLAG_NORMAL); + +// Health bar + picsize = draw_getimagesize(hud_hp_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - vh_health)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_hp_bar, picsize, '1 1 1', 1 , DRAWFLAG_NORMAL); + drawresetcliparea(); +// .. and icon + picsize = draw_getimagesize(hud_hp_ico) * autocvar_cl_vehicles_hudscale; + picloc = '37 65 0' * autocvar_cl_vehicles_hudscale; + if(vh_health < 0.25) { - WriteByte(MSG_ENTITY, num_for_edict(self.realowner)); - WriteByte(MSG_ENTITY, self.realowner.team); - WriteByte(MSG_ENTITY, self.cnt); - } + if(alarm1time < time) + { + alarm1time = time + 2; + vehicle_alarm(self, CH_PAIN_SINGLE, "vehicles/alarm.wav"); + } - if(sf & BRG_START) + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else { - WriteCoord(MSG_ENTITY, self.hook_start_x); - WriteCoord(MSG_ENTITY, self.hook_start_y); - WriteCoord(MSG_ENTITY, self.hook_start_z); + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm1time) + { + vehicle_alarm(self, CH_PAIN_SINGLE, "misc/null.wav"); + alarm1time = 0; + } } - if(sf & BRG_END) +// Shield bar + picsize = draw_getimagesize(hud_sh_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 140 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - shield)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_sh_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); +// .. and icon + picloc = '40 136 0' * autocvar_cl_vehicles_hudscale; + picsize = draw_getimagesize(hud_sh_ico) * autocvar_cl_vehicles_hudscale; + if(shield < 0.25) { - WriteCoord(MSG_ENTITY, self.hook_end_x); - WriteCoord(MSG_ENTITY, self.hook_end_y); - WriteCoord(MSG_ENTITY, self.hook_end_z); + if(alarm2time < time) + { + alarm2time = time + 1; + vehicle_alarm(self, CH_TRIGGER_SINGLE, "vehicles/alarm_shield.wav"); + } + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm2time) + { + vehicle_alarm(self, CH_TRIGGER_SINGLE, "misc/null.wav"); + alarm2time = 0; + } } - return TRUE; -} -#endif // SVQC +// Gun bar + picsize = draw_getimagesize(hud_ammo1_bar) * autocvar_cl_vehicles_hudscale; + picloc = '450 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x, picloc_y, picsize_x * energy, vid_conheight); + drawpic(hudloc + picloc, hud_ammo1_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + +// .. and icon + picsize = 1.5 * draw_getimagesize(hud_energy) * autocvar_cl_vehicles_hudscale; + picloc = '664 60 0' * autocvar_cl_vehicles_hudscale; + if(energy < 0.2) + drawpic(hudloc + picloc, hud_energy, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, hud_energy, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); -#ifdef CSQC -/* -.vector raygun_l1 -.vector raygun_l2; -.vector raygun_l3; -*/ + if (scoreboard_showscores) + HUD_DrawScoreboard(); + /* + else + { + picsize = draw_getimagesize(waki_xhair); + picsize_x *= 0.5; + picsize_y *= 0.5; + + + drawpic('0.5 0 0' * (vid_conwidth - picsize_x) + '0 0.5 0' * (vid_conheight - picsize_y), waki_xhair, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + } + */ +} void bumble_raygun_draw() { @@ -1036,23 +1207,182 @@ void bumble_raygun_read(float bIsNew) } } -void bumblebee_draw() +float v_bumblebee(float req) { + switch(req) + { + case VR_HUD: + { + if(autocvar_r_letterbox) + return TRUE; -} + vector picsize, hudloc = '0 0 0', pic2size, picloc; -void bumblebee_draw2d() -{ + // Fetch health & ammo stats + HUD_GETVEHICLESTATS -} + picsize = draw_getimagesize(hud_bg) * autocvar_cl_vehicles_hudscale; + hudloc_y = vid_conheight - picsize_y; + hudloc_x = vid_conwidth * 0.5 - picsize_x * 0.5; -void bumblebee_read_extra() -{ + drawpic(hudloc, hud_bg, picsize, '1 1 1', autocvar_cl_vehicles_hudalpha, DRAWFLAG_NORMAL); -} + shield *= 0.01; + vh_health *= 0.01; + energy *= 0.01; + reload1 *= 0.01; -void vehicle_bumblebee_assemble() -{ + pic2size = draw_getimagesize(bumb_ico) * (autocvar_cl_vehicles_hudscale * 0.8); + picloc = picsize * 0.5 - pic2size * 0.5; + + if(vh_health < 0.25) + drawpic(hudloc + picloc, bumb_ico, pic2size, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, bumb_ico, pic2size, '1 1 1' * vh_health + '1 0 0' * (1 - vh_health), 1, DRAWFLAG_NORMAL); + + drawpic(hudloc + picloc, bumb_lgun, pic2size, '1 1 1' * energy + '1 0 0' * (1 - energy), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, bumb_lgun, pic2size, '1 1 1' * energy + '1 0 0' * (1 - energy), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, hud_sh, pic2size, '1 1 1', shield, DRAWFLAG_NORMAL); + + // Health bar + picsize = draw_getimagesize(hud_hp_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - vh_health)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_hp_bar, picsize, '1 1 1', 1 , DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picsize = draw_getimagesize(hud_hp_ico) * autocvar_cl_vehicles_hudscale; + picloc = '37 65 0' * autocvar_cl_vehicles_hudscale; + if(vh_health < 0.25) + { + if(alarm1time < time) + { + alarm1time = time + 2; + vehicle_alarm(self, CH_PAIN_SINGLE, "vehicles/alarm.wav"); + } + + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm1time) + { + vehicle_alarm(self, CH_PAIN_SINGLE, "misc/null.wav"); + alarm1time = 0; + } + } + + // Shield bar + picsize = draw_getimagesize(hud_sh_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 140 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - shield)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_sh_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picloc = '40 136 0' * autocvar_cl_vehicles_hudscale; + picsize = draw_getimagesize(hud_sh_ico) * autocvar_cl_vehicles_hudscale; + if(shield < 0.25) + { + if(alarm2time < time) + { + alarm2time = time + 1; + vehicle_alarm(self, CH_TRIGGER_SINGLE, "vehicles/alarm_shield.wav"); + } + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm2time) + { + vehicle_alarm(self, CH_TRIGGER_SINGLE, "misc/null.wav"); + alarm2time = 0; + } + } + + ammo1 *= 0.01; + ammo2 *= 0.01; + + // Gunner1 bar + picsize = draw_getimagesize(hud_ammo1_bar) * autocvar_cl_vehicles_hudscale; + picloc = '450 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x, picloc_y, picsize_x * ammo1, vid_conheight); + drawpic(hudloc + picloc, hud_ammo1_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + + // Right gunner slot occupied? + if(!AuxiliaryXhair[1].draw2d) + { + shield = (picsize_x * 0.5) - (0.5 * stringwidth(_("No right gunner!"), FALSE, '1 0 0' * picsize_y + '0 1 0' * picsize_y)); + drawfill(hudloc + picloc - '0.2 0.2 0', picsize + '0.4 0.4 0', '0.25 0.25 0.25', 0.75, DRAWFLAG_NORMAL); + drawstring(hudloc + picloc + '1 0 0' * shield, _("No right gunner!"), '1 0 0' * picsize_y + '0 1 0' * picsize_y, '1 0 0' + '0 1 1' * sin(time * 10), 1, DRAWFLAG_NORMAL); + } + + // .. and icon + picsize = 1.5 * draw_getimagesize(hud_energy) * autocvar_cl_vehicles_hudscale; + picloc = '664 60 0' * autocvar_cl_vehicles_hudscale; + if(ammo1 < 0.2) + drawpic(hudloc + picloc, hud_energy, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, hud_energy, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + + // Gunner2 bar + picsize = draw_getimagesize(hud_ammo2_bar) * autocvar_cl_vehicles_hudscale; + picloc = '450 140 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x, picloc_y, picsize_x * ammo2, vid_conheight); + drawpic(hudloc + picloc, hud_ammo2_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // Left gunner slot occupied? + if(!AuxiliaryXhair[2].draw2d) + { + shield = (picsize_x * 0.5) - (0.5 * stringwidth(_("No left gunner!"), FALSE, '1 0 0' * picsize_y + '0 1 0' * picsize_y)); + drawfill(hudloc + picloc - '0.2 0.2 0', picsize + '0.4 0.4 0', '0.25 0.25 0.25', 0.75, DRAWFLAG_NORMAL); + drawstring(hudloc + picloc + '1 0 0' * shield, _("No left gunner!"), '1 0 0' * picsize_y + '0 1 0' * picsize_y, '1 0 0' + '0 1 1' * sin(time * 10), 1, DRAWFLAG_NORMAL); + } + + // .. and icon + picsize = 1.5 * draw_getimagesize(hud_energy) * autocvar_cl_vehicles_hudscale; + picloc = '664 130 0' * autocvar_cl_vehicles_hudscale; + if(ammo2 < 0.2) + drawpic(hudloc + picloc, hud_energy, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, hud_energy, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if (scoreboard_showscores) + HUD_DrawScoreboard(); + else + { + picsize = draw_getimagesize(waki_xhair); + picsize_x *= 0.5; + picsize_y *= 0.5; + drawpic('0.5 0 0' * (vid_conwidth - picsize_x) + '0 0.5 0' * (vid_conheight - picsize_y), waki_xhair, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + } + return TRUE; + } + case VR_SETUP: + { + // raygun-locked + AuxiliaryXhair[0].axh_image = "gfx/vehicles/axh-bracket.tga"; + AuxiliaryXhair[0].axh_scale = 0.5; + + // Gunner1 + AuxiliaryXhair[1].axh_image = "gfx/vehicles/axh-target.tga"; + AuxiliaryXhair[1].axh_scale = 0.75; + + // Gunner2 + AuxiliaryXhair[2].axh_image = "gfx/vehicles/axh-target.tga"; + AuxiliaryXhair[2].axh_scale = 0.75; + return TRUE; + } + case VR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; } -#endif //CSQC + +#endif // CSQC +#endif // REGISTER_VEHICLE diff --git a/qcsrc/common/vehicles/unit/racer.qc b/qcsrc/common/vehicles/unit/racer.qc new file mode 100644 index 000000000..dae5219d6 --- /dev/null +++ b/qcsrc/common/vehicles/unit/racer.qc @@ -0,0 +1,909 @@ +#ifdef REGISTER_VEHICLE +REGISTER_VEHICLE( +/* VEH_##id */ RACER, +/* function */ v_racer, +/* spawnflags */ VHF_DMGSHAKE | VHF_DMGROLL, +/* mins,maxs */ '-120 -120 -40' * 0.5, '120 120 40' * 0.5, +/* model */ "models/vehicles/wakizashi.dpm", +/* head_model */ "null", +/* hud_model */ "models/vehicles/wakizashi_cockpit.dpm", +/* tags */ "", "", "tag_viewport", +/* netname */ "racer", +/* fullname */ _("Racer") +); +#else +#ifdef SVQC +float autocvar_g_vehicle_racer; + +float autocvar_g_vehicle_racer_speed_afterburn; +float autocvar_g_vehicle_racer_afterburn_cost; + +float autocvar_g_vehicle_racer_waterburn_cost; +float autocvar_g_vehicle_racer_waterburn_speed; + +float autocvar_g_vehicle_racer_water_speed_forward; +float autocvar_g_vehicle_racer_water_speed_strafe; + +float autocvar_g_vehicle_racer_anglestabilizer; +float autocvar_g_vehicle_racer_downforce; + +float autocvar_g_vehicle_racer_speed_forward; +float autocvar_g_vehicle_racer_speed_strafe; +float autocvar_g_vehicle_racer_springlength; +float autocvar_g_vehicle_racer_upforcedamper; +float autocvar_g_vehicle_racer_friction; + +var float autocvar_g_vehicle_racer_water_time = 5; + +float autocvar_g_vehicle_racer_hovertype; +float autocvar_g_vehicle_racer_hoverpower; + +float autocvar_g_vehicle_racer_turnroll; +float autocvar_g_vehicle_racer_turnspeed; +float autocvar_g_vehicle_racer_pitchspeed; + +float autocvar_g_vehicle_racer_energy; +float autocvar_g_vehicle_racer_energy_regen; +float autocvar_g_vehicle_racer_energy_regen_pause; + +float autocvar_g_vehicle_racer_health; +float autocvar_g_vehicle_racer_health_regen; +float autocvar_g_vehicle_racer_health_regen_pause; + +float autocvar_g_vehicle_racer_shield; +float autocvar_g_vehicle_racer_shield_regen; +float autocvar_g_vehicle_racer_shield_regen_pause; + +float autocvar_g_vehicle_racer_cannon_cost; +float autocvar_g_vehicle_racer_cannon_damage; +float autocvar_g_vehicle_racer_cannon_radius; +float autocvar_g_vehicle_racer_cannon_refire; +float autocvar_g_vehicle_racer_cannon_speed; +float autocvar_g_vehicle_racer_cannon_spread; +float autocvar_g_vehicle_racer_cannon_force; + +float autocvar_g_vehicle_racer_rocket_accel; +float autocvar_g_vehicle_racer_rocket_damage; +float autocvar_g_vehicle_racer_rocket_radius; +float autocvar_g_vehicle_racer_rocket_force; +float autocvar_g_vehicle_racer_rocket_refire; +float autocvar_g_vehicle_racer_rocket_speed; +float autocvar_g_vehicle_racer_rocket_turnrate; + +float autocvar_g_vehicle_racer_rocket_locktarget; +float autocvar_g_vehicle_racer_rocket_locking_time; +float autocvar_g_vehicle_racer_rocket_locking_releasetime; +float autocvar_g_vehicle_racer_rocket_locked_time; +float autocvar_g_vehicle_racer_rocket_locked_maxangle; +float autocvar_g_vehicle_racer_rocket_climbspeed; + +float autocvar_g_vehicle_racer_respawntime; + +float autocvar_g_vehicle_racer_blowup_radius; +float autocvar_g_vehicle_racer_blowup_coredamage; +float autocvar_g_vehicle_racer_blowup_edgedamage; +float autocvar_g_vehicle_racer_blowup_forceintensity; + +float autocvar_g_vehicle_racer_bouncefactor; +float autocvar_g_vehicle_racer_bouncestop; +vector autocvar_g_vehicle_racer_bouncepain; + +var vector racer_force_from_tag(string tag_name, float spring_length, float max_power); + +void racer_align4point(float _delta) +{ + vector push_vector; + float fl_push, fr_push, bl_push, br_push; + + push_vector = racer_force_from_tag("tag_engine_fr", autocvar_g_vehicle_racer_springlength, autocvar_g_vehicle_racer_hoverpower); + fr_push = force_fromtag_normpower; + //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_racer_collision_multiplier); + + push_vector += racer_force_from_tag("tag_engine_fl", autocvar_g_vehicle_racer_springlength, autocvar_g_vehicle_racer_hoverpower); + fl_push = force_fromtag_normpower; + //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_racer_collision_multiplier); + + push_vector += racer_force_from_tag("tag_engine_br", autocvar_g_vehicle_racer_springlength, autocvar_g_vehicle_racer_hoverpower); + br_push = force_fromtag_normpower; + //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_racer_collision_multiplier); + + push_vector += racer_force_from_tag("tag_engine_bl", autocvar_g_vehicle_racer_springlength, autocvar_g_vehicle_racer_hoverpower); + bl_push = force_fromtag_normpower; + //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_racer_collision_multiplier); + + self.velocity += push_vector * _delta; + + if(pointcontents(self.origin - '0 0 64') == CONTENT_WATER) + if(self.owner.BUTTON_CROUCH && time < self.air_finished) + self.velocity_z += 30; + else + self.velocity_z += 200; + + // Anti ocilation + if(self.velocity_z > 0) + self.velocity_z *= 1 - autocvar_g_vehicle_racer_upforcedamper * _delta; + + push_vector_x = (fl_push - bl_push); + push_vector_x += (fr_push - br_push); + push_vector_x *= 360; + + push_vector_z = (fr_push - fl_push); + push_vector_z += (br_push - bl_push); + push_vector_z *= 360; + + // Apply angle diffrance + self.angles_z += push_vector_z * _delta; + self.angles_x += push_vector_x * _delta; + + // Apply stabilizer + self.angles_x *= 1 - (autocvar_g_vehicle_racer_anglestabilizer * _delta); + self.angles_z *= 1 - (autocvar_g_vehicle_racer_anglestabilizer * _delta); +} + +void racer_fire_cannon(string tagname) +{ + vector v; + entity bolt; + + v = gettaginfo(self, gettagindex(self, tagname)); + bolt = vehicles_projectile("wakizashi_gun_muzzleflash", W_Sound("lasergun_fire"), + v, normalize(v_forward + randomvec() * autocvar_g_vehicle_racer_cannon_spread) * autocvar_g_vehicle_racer_cannon_speed, + autocvar_g_vehicle_racer_cannon_damage, autocvar_g_vehicle_racer_cannon_radius, autocvar_g_vehicle_racer_cannon_force, 0, + DEATH_VH_WAKI_GUN, PROJECTILE_WAKICANNON, 0, TRUE, TRUE, self.owner); + + // Fix z-aim (for chase mode) + v = normalize(trace_endpos - bolt.origin); + v_forward_z = v_z * 0.5; + bolt.velocity = v_forward * autocvar_g_vehicle_racer_cannon_speed; +} + +void racer_rocket_groundhugger() +{ + vector olddir, newdir; + float oldvel, newvel; + + self.nextthink = time; + + if(self.owner.deadflag != DEAD_NO || self.cnt < time) + { + self.use(); + return; + } + + if(!self.realowner.vehicle) + { + UpdateCSQCProjectile(self); + return; + } + + olddir = normalize(self.velocity); + oldvel = vlen(self.velocity); + newvel = oldvel + self.lip; + + tracebox(self.origin, self.mins, self.maxs, self.origin + olddir * 64, MOVE_WORLDONLY,self); + if(trace_fraction <= 0.5) + { + // Hitting somethign soon, just speed ahead + self.velocity = olddir * newvel; + UpdateCSQCProjectile(self); + return; + } + + traceline(trace_endpos, trace_endpos - '0 0 64', MOVE_NORMAL, self); + if(trace_fraction != 1.0) + { + newdir = normalize(trace_endpos + '0 0 64' - self.origin) * autocvar_g_vehicle_racer_rocket_turnrate; + self.velocity = normalize(olddir + newdir) * newvel; + } + else + { + self.velocity = olddir * newvel; + self.velocity_z -= 1600 * sys_frametime; // 2x grav looks better for this one + } + + if(pointcontents(self.origin - '0 0 32') == CONTENT_WATER) + self.velocity_z += 200; + + UpdateCSQCProjectile(self); + return; +} + +void racer_rocket_tracker() +{ + vector olddir, newdir; + float oldvel, newvel; + + self.nextthink = time; + + if (self.owner.deadflag != DEAD_NO || self.cnt < time) + { + self.use(); + return; + } + + if(!self.realowner.vehicle) + { + UpdateCSQCProjectile(self); + return; + } + + olddir = normalize(self.velocity); + oldvel = vlen(self.velocity); + newvel = oldvel + self.lip; + makevectors(vectoangles(olddir)); + + float time_to_impact = min(vlen(self.enemy.origin - self.origin) / vlen(self.velocity), 1); + vector predicted_origin = self.enemy.origin + self.enemy.velocity * time_to_impact; + + traceline(self.origin, self.origin + v_forward * 64 - '0 0 32', MOVE_NORMAL, self); + newdir = normalize(predicted_origin - self.origin); + + //vector + float height_diff = predicted_origin_z - self.origin_z; + + if(vlen(newdir - v_forward) > autocvar_g_vehicle_racer_rocket_locked_maxangle) + { + //bprint("Target lost!\n"); + //dprint("OF:", ftos(vlen(newdir - v_forward)), "\n"); + self.think = racer_rocket_groundhugger; + return; + } + + if(trace_fraction != 1.0 && trace_ent != self.enemy) + newdir_z += 16 * sys_frametime; + + self.velocity = normalize(olddir + newdir * autocvar_g_vehicle_racer_rocket_turnrate) * newvel; + self.velocity_z -= 800 * sys_frametime; + self.velocity_z += max(height_diff, autocvar_g_vehicle_racer_rocket_climbspeed) * sys_frametime ; + + UpdateCSQCProjectile(self); + return; +} + +void racer_fire_rocket(string tagname, entity trg) +{ + vector v = gettaginfo(self, gettagindex(self, tagname)); + entity rocket = rocket = vehicles_projectile("wakizashi_rocket_launch", W_Sound("rocket_fire"), + v, v_forward * autocvar_g_vehicle_racer_rocket_speed, + autocvar_g_vehicle_racer_rocket_damage, autocvar_g_vehicle_racer_rocket_radius, autocvar_g_vehicle_racer_rocket_force, 3, + DEATH_VH_WAKI_ROCKET, PROJECTILE_WAKIROCKET, 20, FALSE, FALSE, self.owner); + + rocket.lip = autocvar_g_vehicle_racer_rocket_accel * sys_frametime; + rocket.wait = autocvar_g_vehicle_racer_rocket_turnrate; + rocket.nextthink = time; + rocket.enemy = trg; + rocket.cnt = time + 15; + + if(trg) + rocket.think = racer_rocket_tracker; + else + rocket.think = racer_rocket_groundhugger; +} + +float racer_frame() +{ + entity player, racer; + vector df; + float ftmp; + + if(intermission_running) + { + self.vehicle.velocity = '0 0 0'; + self.vehicle.avelocity = '0 0 0'; + return 1; + } + + player = self; + racer = self.vehicle; + self = racer; + + vehicles_painframe(); + + if(pointcontents(racer.origin) != CONTENT_WATER) + racer.air_finished = time + autocvar_g_vehicle_racer_water_time; + + if(racer.deadflag != DEAD_NO) + { + self = player; + player.BUTTON_ATCK = player.BUTTON_ATCK2 = 0; + return 1; + } + + racer_align4point(frametime); + + player.BUTTON_ZOOM = player.BUTTON_CROUCH = 0; + + crosshair_trace(player); + + racer.angles_x *= -1; + + // Yaw + ftmp = autocvar_g_vehicle_racer_turnspeed * frametime; + ftmp = bound(-ftmp, shortangle_f(player.v_angle_y - racer.angles_y, racer.angles_y), ftmp); + racer.angles_y = anglemods(racer.angles_y + ftmp); + + // Roll + racer.angles_z += -ftmp * autocvar_g_vehicle_racer_turnroll * frametime; + + // Pitch + ftmp = autocvar_g_vehicle_racer_pitchspeed * frametime; + ftmp = bound(-ftmp, shortangle_f(player.v_angle_x - racer.angles_x, racer.angles_x), ftmp); + racer.angles_x = bound(-30, anglemods(racer.angles_x + ftmp), 30); + + makevectors(racer.angles); + racer.angles_x *= -1; + + //ftmp = racer.velocity_z; + df = racer.velocity * -autocvar_g_vehicle_racer_friction; + //racer.velocity_z = ftmp; + + if(vlen(player.movement) != 0) + { + if(pointcontents(racer.origin) == CONTENT_WATER) + { + if(player.movement_x) { df += v_forward * ((player.movement_x > 0) ? autocvar_g_vehicle_racer_water_speed_forward : -autocvar_g_vehicle_racer_water_speed_forward); } + if(player.movement_y) { df += v_right * ((player.movement_y > 0) ? autocvar_g_vehicle_racer_water_speed_strafe : -autocvar_g_vehicle_racer_water_speed_strafe); } + } + else + { + if(player.movement_x) { df += v_forward * ((player.movement_x > 0) ? autocvar_g_vehicle_racer_speed_forward : -autocvar_g_vehicle_racer_speed_forward); } + if(player.movement_y) { df += v_right * ((player.movement_y > 0) ? autocvar_g_vehicle_racer_speed_strafe : -autocvar_g_vehicle_racer_speed_strafe); } + } + + if(self.sound_nexttime < time || self.sounds != 1) + { + self.sounds = 1; + self.sound_nexttime = time + 10.922667; //soundlength("vehicles/racer_move.wav"); + sound (self, CH_TRIGGER_SINGLE, "vehicles/racer_move.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + } + else + { + if(self.sound_nexttime < time || self.sounds != 0) + { + self.sounds = 0; + self.sound_nexttime = time + 11.888604; //soundlength("vehicles/racer_idle.wav"); + sound (self, CH_TRIGGER_SINGLE, "vehicles/racer_idle.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + } + + // Afterburn + if (player.BUTTON_JUMP && racer.vehicle_energy >= (autocvar_g_vehicle_racer_afterburn_cost * frametime)) + { + if(time - racer.wait > 0.2) + pointparticles(particleeffectnum("wakizashi_booster_smoke"), self.origin - v_forward * 32, v_forward * vlen(self.velocity), 1); + + racer.wait = time; + + if(pointcontents(racer.origin) == CONTENT_WATER) + { + racer.vehicle_energy -= autocvar_g_vehicle_racer_waterburn_cost * frametime; + df += (v_forward * autocvar_g_vehicle_racer_waterburn_speed); + } + else + { + racer.vehicle_energy -= autocvar_g_vehicle_racer_afterburn_cost * frametime; + df += (v_forward * autocvar_g_vehicle_racer_speed_afterburn); + } + + if(racer.invincible_finished < time) + { + traceline(racer.origin, racer.origin - '0 0 256', MOVE_NORMAL, self); + if(trace_fraction != 1.0) + pointparticles(particleeffectnum("smoke_small"), trace_endpos, '0 0 0', 1); + + racer.invincible_finished = time + 0.1 + (random() * 0.1); + } + + if(racer.strength_finished < time) + { + racer.strength_finished = time + 10.922667; //soundlength("vehicles/racer_boost.wav"); + sound (racer.tur_head, CH_TRIGGER_SINGLE, "vehicles/racer_boost.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + } + else + { + racer.strength_finished = 0; + sound (racer.tur_head, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + + df -= v_up * (vlen(racer.velocity) * autocvar_g_vehicle_racer_downforce); + player.movement = racer.velocity += df * frametime; + + if(!forbidWeaponUse(player)) + if(player.BUTTON_ATCK) + if(time > racer.attack_finished_single) + if(racer.vehicle_energy >= autocvar_g_vehicle_racer_cannon_cost) + { + racer.vehicle_energy -= autocvar_g_vehicle_racer_cannon_cost; + racer.wait = time; + + crosshair_trace(player); + if(racer.cnt) + { + racer_fire_cannon("tag_fire1"); + racer.cnt = 0; + } + else + { + racer_fire_cannon("tag_fire2"); + racer.cnt = 1; + } + racer.attack_finished_single = time + autocvar_g_vehicle_racer_cannon_refire; + } + + if(autocvar_g_vehicle_racer_rocket_locktarget) + { + vehicles_locktarget((1 / autocvar_g_vehicle_racer_rocket_locking_time) * frametime, + (1 / autocvar_g_vehicle_racer_rocket_locking_releasetime) * frametime, + autocvar_g_vehicle_racer_rocket_locked_time); + + if(self.lock_target) + { + if(racer.lock_strength == 1) + UpdateAuxiliaryXhair(player, real_origin(self.lock_target), '1 0 0', 0); + else if(self.lock_strength > 0.5) + UpdateAuxiliaryXhair(player, real_origin(self.lock_target), '0 1 0', 0); + else if(self.lock_strength < 0.5) + UpdateAuxiliaryXhair(player, real_origin(self.lock_target), '0 0 1', 0); + } + } + + if(!forbidWeaponUse(player)) + if(time > racer.delay) + if(player.BUTTON_ATCK2) + { + racer.misc_bulletcounter += 1; + racer.delay = time + 0.3; + + if(racer.misc_bulletcounter == 1) + racer_fire_rocket("tag_rocket_r", (racer.lock_strength == 1 && racer.lock_target) ? racer.lock_target : world); + else if(racer.misc_bulletcounter == 2) + { + racer_fire_rocket("tag_rocket_l", (racer.lock_strength == 1 && racer.lock_target) ? racer.lock_target : world); + racer.lock_strength = 0; + racer.lock_target = world; + racer.misc_bulletcounter = 0; + + racer.delay = time + autocvar_g_vehicle_racer_rocket_refire; + racer.lip = time; + } + } + player.vehicle_reload1 = bound(0, 100 * ((time - racer.lip) / (racer.delay - racer.lip)), 100); + + if(racer.vehicle_flags & VHF_SHIELDREGEN) + vehicles_regen(racer.dmg_time, vehicle_shield, autocvar_g_vehicle_racer_shield, autocvar_g_vehicle_racer_shield_regen_pause, autocvar_g_vehicle_racer_shield_regen, frametime, TRUE); + + if(racer.vehicle_flags & VHF_HEALTHREGEN) + vehicles_regen(racer.dmg_time, vehicle_health, autocvar_g_vehicle_racer_health, autocvar_g_vehicle_racer_health_regen_pause, autocvar_g_vehicle_racer_health_regen, frametime, FALSE); + + if(racer.vehicle_flags & VHF_ENERGYREGEN) + vehicles_regen(racer.wait, vehicle_energy, autocvar_g_vehicle_racer_energy, autocvar_g_vehicle_racer_energy_regen_pause, autocvar_g_vehicle_racer_energy_regen, frametime, FALSE); + + + VEHICLE_UPDATE_PLAYER(player, health, racer); + VEHICLE_UPDATE_PLAYER(player, energy, racer); + + if(racer.vehicle_flags & VHF_HASSHIELD) + VEHICLE_UPDATE_PLAYER(player, shield, racer); + + player.BUTTON_ATCK = player.BUTTON_ATCK2 = 0; + setorigin(player,racer.origin + '0 0 32'); + player.velocity = racer.velocity; + + self = player; + return 1; +} + +void racer_think() +{ + self.nextthink = time; + + float pushdeltatime = time - self.lastpushtime; + if (pushdeltatime > 0.15) pushdeltatime = 0; + self.lastpushtime = time; + if(!pushdeltatime) return; + + tracebox(self.origin, self.mins, self.maxs, self.origin - ('0 0 1' * autocvar_g_vehicle_racer_springlength), MOVE_NOMONSTERS, self); + + vector df = self.velocity * -autocvar_g_vehicle_racer_friction; + df_z += (1 - trace_fraction) * autocvar_g_vehicle_racer_hoverpower + sin(time * 2) * (autocvar_g_vehicle_racer_springlength * 2); + + if(pointcontents(self.origin - '0 0 64') == CONTENT_WATER) + self.velocity_z += 200; + + self.velocity += df * pushdeltatime; + if(self.velocity_z > 0) + self.velocity_z *= 1 - autocvar_g_vehicle_racer_upforcedamper * pushdeltatime; + + self.angles_x *= 1 - (autocvar_g_vehicle_racer_anglestabilizer * pushdeltatime); + self.angles_z *= 1 - (autocvar_g_vehicle_racer_anglestabilizer * pushdeltatime); + + CSQCMODEL_AUTOUPDATE(); +} + +void racer_exit(float eject) +{ + vector spot; + + self.think = racer_think; + self.nextthink = time; + self.movetype = MOVETYPE_BOUNCE; + sound (self.tur_head, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + + if(!self.owner) + return; + + makevectors(self.angles); + if(eject) + { + spot = self.origin + v_forward * 100 + '0 0 64'; + spot = vehicles_findgoodexit(spot); + setorigin(self.owner , spot); + self.owner.velocity = (v_up + v_forward * 0.25) * 750; + self.owner.oldvelocity = self.owner.velocity; + } + else + { + if(vlen(self.velocity) > 2 * autocvar_sv_maxairspeed) + { + self.owner.velocity = normalize(self.velocity) * autocvar_sv_maxairspeed * 2; + self.owner.velocity_z += 200; + spot = self.origin + v_forward * 32 + '0 0 32'; + spot = vehicles_findgoodexit(spot); + } + else + { + self.owner.velocity = self.velocity * 0.5; + self.owner.velocity_z += 10; + spot = self.origin - v_forward * 200 + '0 0 32'; + spot = vehicles_findgoodexit(spot); + } + self.owner.oldvelocity = self.owner.velocity; + setorigin(self.owner , spot); + } + antilag_clear(self.owner); + self.owner = world; +} + +void racer_blowup() +{ + self.deadflag = DEAD_DEAD; + self.vehicle_exit(VHEF_NORMAL); + + RadiusDamage (self, self.enemy, autocvar_g_vehicle_racer_blowup_coredamage, + autocvar_g_vehicle_racer_blowup_edgedamage, + autocvar_g_vehicle_racer_blowup_radius, world, world, + autocvar_g_vehicle_racer_blowup_forceintensity, + DEATH_VH_WAKI_DEATH, world); + + self.nextthink = time + autocvar_g_vehicle_racer_respawntime; + self.think = vehicles_spawn; + self.movetype = MOVETYPE_NONE; + self.effects = EF_NODRAW; + + self.colormod = '0 0 0'; + self.avelocity = '0 0 0'; + self.velocity = '0 0 0'; + + setorigin(self, self.pos1); +} + +void racer_blowup_think() +{ + self.nextthink = time; + + if(time >= self.delay) + racer_blowup(); + + CSQCMODEL_AUTOUPDATE(); +} + +void racer_deadtouch() +{ + self.avelocity_x *= 0.7; + self.cnt -= 1; + if(self.cnt <= 0) + racer_blowup(); +} + +void spawnfunc_vehicle_racer() +{ + if(!autocvar_g_vehicle_racer) { remove(self); return; } + if(!vehicle_initialize(VEH_RACER, FALSE)) { remove(self); return; } +} + +float v_racer(float req) +{ + switch(req) + { + case VR_IMPACT: + { + if(autocvar_g_vehicle_racer_bouncepain) + vehicles_impact(autocvar_g_vehicle_racer_bouncepain_x, autocvar_g_vehicle_racer_bouncepain_y, autocvar_g_vehicle_racer_bouncepain_z); + return TRUE; + } + case VR_ENTER: + { + self.movetype = MOVETYPE_BOUNCE; + self.owner.vehicle_health = (self.vehicle_health / autocvar_g_vehicle_racer_health) * 100; + self.owner.vehicle_shield = (self.vehicle_shield / autocvar_g_vehicle_racer_shield) * 100; + + if(self.owner.flagcarried) + setorigin(self.owner.flagcarried, '-190 0 96'); + + return TRUE; + } + case VR_THINK: + { + return TRUE; + } + case VR_DEATH: + { + self.health = 0; + self.event_damage = func_null; + self.solid = SOLID_CORPSE; + self.takedamage = DAMAGE_NO; + self.deadflag = DEAD_DYING; + self.movetype = MOVETYPE_BOUNCE; + self.wait = time; + self.delay = 2 + time + random() * 3; + self.cnt = 1 + random() * 2; + self.touch = racer_deadtouch; + + Send_Effect(EFFECT_EXPLOSION_MEDIUM, self.origin, '0 0 0', 1); + + if(random() < 0.5) + self.avelocity_z = 32; + else + self.avelocity_z = -32; + + self.avelocity_x = -vlen(self.velocity) * 0.2; + self.velocity += '0 0 700'; + self.colormod = '-0.5 -0.5 -0.5'; + + self.think = racer_blowup_think; + self.nextthink = time; + + return TRUE; + } + case VR_SPAWN: + { + if(self.scale != 0.5) + { + if(autocvar_g_vehicle_racer_hovertype != 0) + racer_force_from_tag = vehicles_force_fromtag_maglev; + else + racer_force_from_tag = vehicles_force_fromtag_hover; + + // FIXME: this be hakkz, fix the models insted (scale body, add tag_viewport to the hudmodel). + self.scale = 0.5; + setattachment(self.vehicle_hudmodel, self, ""); + setattachment(self.vehicle_viewport, self, "tag_viewport"); + + self.mass = 900; + } + + self.think = racer_think; + self.nextthink = time; + self.vehicle_health = autocvar_g_vehicle_racer_health; + self.vehicle_shield = autocvar_g_vehicle_racer_shield; + + self.movetype = MOVETYPE_TOSS; + self.solid = SOLID_SLIDEBOX; + self.delay = time; + self.scale = 0.5; + + self.PlayerPhysplug = racer_frame; + + self.bouncefactor = autocvar_g_vehicle_racer_bouncefactor; + self.bouncestop = autocvar_g_vehicle_racer_bouncestop; + self.damageforcescale = 0.5; + self.vehicle_health = autocvar_g_vehicle_racer_health; + self.vehicle_shield = autocvar_g_vehicle_racer_shield; + + return TRUE; + } + case VR_SETUP: + { + if(autocvar_g_vehicle_racer_energy) + if(autocvar_g_vehicle_racer_energy_regen) + self.vehicle_flags |= VHF_ENERGYREGEN; + + if(autocvar_g_vehicle_racer_shield) + self.vehicle_flags |= VHF_HASSHIELD; + + if(autocvar_g_vehicle_racer_shield_regen) + self.vehicle_flags |= VHF_SHIELDREGEN; + + if(autocvar_g_vehicle_racer_health_regen) + self.vehicle_flags |= VHF_HEALTHREGEN; + + self.vehicle_exit = racer_exit; + self.respawntime = autocvar_g_vehicle_racer_respawntime; + self.vehicle_health = autocvar_g_vehicle_racer_health; + self.vehicle_shield = autocvar_g_vehicle_racer_shield; + self.max_health = self.vehicle_health; + + return TRUE; + } + case VR_PRECACHE: + { + precache_sound (W_Sound("lasergun_fire")); + precache_sound (W_Sound("rocket_fire")); + + precache_sound ("vehicles/racer_idle.wav"); + precache_sound ("vehicles/racer_move.wav"); + precache_sound ("vehicles/racer_boost.wav"); + + precache_model ("models/vhshield.md3"); + precache_model ("models/vehicles/wakizashi.dpm"); + precache_model ("models/vehicles/wakizashi_cockpit.dpm"); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC + +#define waki_ico "gfx/vehicles/waki.tga" +#define waki_eng "gfx/vehicles/waki_e.tga" +#define waki_gun "gfx/vehicles/waki_guns.tga" +#define waki_rkt "gfx/vehicles/waki_rockets.tga" +#define waki_xhair "gfx/vehicles/axh-special1.tga" + +float v_racer(float req) +{ + switch(req) + { + case VR_HUD: + { + if(autocvar_r_letterbox) + return TRUE; + + vector picsize, hudloc = '0 0 0', pic2size, picloc; + + // Fetch health & ammo stats + HUD_GETVEHICLESTATS + + picsize = draw_getimagesize(hud_bg) * autocvar_cl_vehicles_hudscale; + hudloc_y = vid_conheight - picsize_y; + hudloc_x = vid_conwidth * 0.5 - picsize_x * 0.5; + + drawpic(hudloc, hud_bg, picsize, '1 1 1', autocvar_cl_vehicles_hudalpha, DRAWFLAG_NORMAL); + + shield *= 0.01; + vh_health *= 0.01; + energy *= 0.01; + reload1 *= 0.01; + + pic2size = draw_getimagesize(waki_ico) * (autocvar_cl_vehicles_hudscale * 0.8); + picloc = picsize * 0.5 - pic2size * 0.5; + if(vh_health < 0.25) + drawpic(hudloc + picloc, waki_ico, pic2size, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, waki_ico, pic2size, '1 1 1' * vh_health + '1 0 0' * (1 - vh_health), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, waki_eng, pic2size, '1 1 1' * energy + '1 0 0' * (1 - energy), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, waki_gun, pic2size, '1 1 1' * energy + '1 0 0' * (1 - energy), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, waki_rkt, pic2size, '1 1 1' * reload1 + '1 0 0' * (1 - reload1), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, hud_sh, pic2size, '1 1 1', shield, DRAWFLAG_NORMAL); + + // Health bar + picsize = draw_getimagesize(hud_hp_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - vh_health)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_hp_bar, picsize, '1 1 1', 1 , DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picsize = draw_getimagesize(hud_hp_ico) * autocvar_cl_vehicles_hudscale; + picloc = '37 65 0' * autocvar_cl_vehicles_hudscale; + if(vh_health < 0.25) + { + if(alarm1time < time) + { + alarm1time = time + 2; + vehicle_alarm(self, CH_PAIN_SINGLE, "vehicles/alarm.wav"); + } + + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm1time) + { + vehicle_alarm(self, CH_PAIN_SINGLE, "misc/null.wav"); + alarm1time = 0; + } + } + + + // Shield bar + picsize = draw_getimagesize(hud_sh_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 140 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - shield)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_sh_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picloc = '40 136 0' * autocvar_cl_vehicles_hudscale; + picsize = draw_getimagesize(hud_sh_ico) * autocvar_cl_vehicles_hudscale; + if(shield < 0.25) + { + if(alarm2time < time) + { + alarm2time = time + 1; + vehicle_alarm(self, CH_TRIGGER_SINGLE, "vehicles/alarm_shield.wav"); + } + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm2time) + { + vehicle_alarm(self, CH_TRIGGER_SINGLE, "misc/null.wav"); + alarm2time = 0; + } + } + + // Gun bar + picsize = draw_getimagesize(hud_ammo1_bar) * autocvar_cl_vehicles_hudscale; + picloc = '450 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x, picloc_y, picsize_x * energy, vid_conheight); + drawpic(hudloc + picloc, hud_ammo1_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picsize = draw_getimagesize(hud_ammo1_ico) * autocvar_cl_vehicles_hudscale; + picloc = '664 60 0' * autocvar_cl_vehicles_hudscale; + if(energy < 0.2) + drawpic(hudloc + picloc, hud_ammo1_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, hud_ammo1_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + + // Bomb bar + picsize = draw_getimagesize(hud_ammo2_bar) * autocvar_cl_vehicles_hudscale; + picloc = '450 140 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x, hudloc_y + picloc_y, picsize_x * reload1, vid_conheight); + drawpic(hudloc + picloc, hud_ammo2_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + pic2size = draw_getimagesize(hud_ammo2_ico) * autocvar_cl_vehicles_hudscale; + picloc = '664 130 0' * autocvar_cl_vehicles_hudscale; + if(reload1 != 1) + drawpic(hudloc + picloc, hud_ammo2_ico, pic2size, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, hud_ammo2_ico, pic2size, '1 1 1', 1, DRAWFLAG_NORMAL); + + if (scoreboard_showscores) + HUD_DrawScoreboard(); + else + { + picsize = draw_getimagesize(waki_xhair); + picsize_x *= 0.5; + picsize_y *= 0.5; + + + drawpic('0.5 0 0' * (vid_conwidth - picsize_x) + '0 0.5 0' * (vid_conheight - picsize_y), waki_xhair, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + } + return TRUE; + } + case VR_SETUP: + { + AuxiliaryXhair[0].axh_image = "gfx/vehicles/axh-bracket.tga"; + AuxiliaryXhair[0].axh_scale = 0.25; + return TRUE; + } + case VR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_VEHICLE diff --git a/qcsrc/common/vehicles/unit/raptor.qc b/qcsrc/common/vehicles/unit/raptor.qc new file mode 100644 index 000000000..ed5c00aa0 --- /dev/null +++ b/qcsrc/common/vehicles/unit/raptor.qc @@ -0,0 +1,1222 @@ +#ifdef REGISTER_VEHICLE +REGISTER_VEHICLE( +/* VEH_##id */ RAPTOR, +/* function */ v_raptor, +/* spawnflags */ VHF_DMGSHAKE | VHF_DMGROLL, +/* mins,maxs */ '-80 -80 0', '80 80 70', +/* model */ "models/vehicles/raptor.dpm", +/* head_model */ "", +/* hud_model */ "models/vehicles/raptor_cockpit.dpm", +/* tags */ "", "tag_hud", "tag_camera", +/* netname */ "raptor", +/* fullname */ _("Raptor") +); +#else + +const float RSM_FIRST = 1; +const float RSM_BOMB = 1; +const float RSM_FLARE = 2; +const float RSM_LAST = 2; + +#ifdef SVQC +float autocvar_g_vehicle_raptor; + +float autocvar_g_vehicle_raptor_respawntime; +float autocvar_g_vehicle_raptor_takeofftime; + +float autocvar_g_vehicle_raptor_movestyle; +float autocvar_g_vehicle_raptor_turnspeed; +float autocvar_g_vehicle_raptor_pitchspeed; +float autocvar_g_vehicle_raptor_pitchlimit; + +float autocvar_g_vehicle_raptor_speed_forward; +float autocvar_g_vehicle_raptor_speed_strafe; +float autocvar_g_vehicle_raptor_speed_up; +float autocvar_g_vehicle_raptor_speed_down; +float autocvar_g_vehicle_raptor_friction; + +float autocvar_g_vehicle_raptor_bomblets; +float autocvar_g_vehicle_raptor_bomblet_alt; +float autocvar_g_vehicle_raptor_bomblet_time; +float autocvar_g_vehicle_raptor_bomblet_damage; +float autocvar_g_vehicle_raptor_bomblet_spread; +float autocvar_g_vehicle_raptor_bomblet_edgedamage; +float autocvar_g_vehicle_raptor_bomblet_radius; +float autocvar_g_vehicle_raptor_bomblet_force; +float autocvar_g_vehicle_raptor_bomblet_explode_delay; +float autocvar_g_vehicle_raptor_bombs_refire; + +float autocvar_g_vehicle_raptor_flare_refire; +float autocvar_g_vehicle_raptor_flare_lifetime; +float autocvar_g_vehicle_raptor_flare_chase; +float autocvar_g_vehicle_raptor_flare_range; + +float autocvar_g_vehicle_raptor_cannon_turnspeed; +float autocvar_g_vehicle_raptor_cannon_turnlimit; +float autocvar_g_vehicle_raptor_cannon_pitchlimit_up; +float autocvar_g_vehicle_raptor_cannon_pitchlimit_down; + +float autocvar_g_vehicle_raptor_cannon_locktarget; +float autocvar_g_vehicle_raptor_cannon_locking_time; +float autocvar_g_vehicle_raptor_cannon_locking_releasetime; +float autocvar_g_vehicle_raptor_cannon_locked_time; +float autocvar_g_vehicle_raptor_cannon_predicttarget; + +float autocvar_g_vehicle_raptor_cannon_cost; +float autocvar_g_vehicle_raptor_cannon_damage; +float autocvar_g_vehicle_raptor_cannon_radius; +float autocvar_g_vehicle_raptor_cannon_refire; +float autocvar_g_vehicle_raptor_cannon_speed; +float autocvar_g_vehicle_raptor_cannon_spread; +float autocvar_g_vehicle_raptor_cannon_force; + +float autocvar_g_vehicle_raptor_energy; +float autocvar_g_vehicle_raptor_energy_regen; +float autocvar_g_vehicle_raptor_energy_regen_pause; + +float autocvar_g_vehicle_raptor_health; +float autocvar_g_vehicle_raptor_health_regen; +float autocvar_g_vehicle_raptor_health_regen_pause; + +float autocvar_g_vehicle_raptor_shield; +float autocvar_g_vehicle_raptor_shield_regen; +float autocvar_g_vehicle_raptor_shield_regen_pause; + +float autocvar_g_vehicle_raptor_bouncefactor; +float autocvar_g_vehicle_raptor_bouncestop; +vector autocvar_g_vehicle_raptor_bouncepain; + +.entity bomb1; +.entity bomb2; + +float raptor_altitude(float amax) +{ + tracebox(self.origin, self.mins, self.maxs, self.origin - ('0 0 1' * amax), MOVE_WORLDONLY, self); + return vlen(self.origin - trace_endpos); +} + +void raptor_bomblet_boom() +{ + RadiusDamage (self, self.realowner, autocvar_g_vehicle_raptor_bomblet_damage, + autocvar_g_vehicle_raptor_bomblet_edgedamage, + autocvar_g_vehicle_raptor_bomblet_radius, world, world, + autocvar_g_vehicle_raptor_bomblet_force, DEATH_VH_RAPT_BOMB, world); + remove(self); +} + +void raptor_bomblet_touch() +{ + if(other == self.owner) + return; + + PROJECTILE_TOUCH; + self.think = raptor_bomblet_boom; + self.nextthink = time + random() * autocvar_g_vehicle_raptor_bomblet_explode_delay; +} + +void raptor_bomb_burst() +{ + if(self.cnt > time) + if(autocvar_g_vehicle_raptor_bomblet_alt) + { + self.nextthink = time; + traceline(self.origin, self.origin + (normalize(self.velocity) * autocvar_g_vehicle_raptor_bomblet_alt), MOVE_NORMAL, self); + if((trace_fraction == 1.0) || (vlen(self.origin - self.owner.origin) < autocvar_g_vehicle_raptor_bomblet_radius)) + { + UpdateCSQCProjectile(self); + return; + } + } + + entity bomblet; + float i; + + Damage_DamageInfo(self.origin, 0, 0, 0, '0 0 0', DEATH_VH_RAPT_FRAGMENT, 0, self); + + for(i = 0; i < autocvar_g_vehicle_raptor_bomblets; ++i) + { + bomblet = spawn(); + setorigin(bomblet, self.origin); + + bomblet.movetype = MOVETYPE_TOSS; + bomblet.touch = raptor_bomblet_touch; + bomblet.think = raptor_bomblet_boom; + bomblet.nextthink = time + 5; + bomblet.owner = self.owner; + bomblet.realowner = self.realowner; + bomblet.velocity = normalize(normalize(self.velocity) + (randomvec() * autocvar_g_vehicle_raptor_bomblet_spread)) * vlen(self.velocity); + + PROJECTILE_MAKETRIGGER(bomblet); + CSQCProjectile(bomblet, TRUE, PROJECTILE_RAPTORBOMBLET, TRUE); + } + + remove(self); +} + +void raptor_bombdrop() +{ + entity bomb_1, bomb_2; + + bomb_1 = spawn(); + bomb_2 = spawn(); + + setorigin(bomb_1, gettaginfo(self, gettagindex(self, "bombmount_left"))); + setorigin(bomb_2, gettaginfo(self, gettagindex(self, "bombmount_right"))); + + bomb_1.movetype = bomb_2.movetype = MOVETYPE_BOUNCE; + bomb_1.velocity = bomb_2.velocity = self.velocity; + bomb_1.touch = bomb_2.touch = raptor_bomb_burst; + bomb_1.think = bomb_2.think = raptor_bomb_burst; + bomb_1.cnt = bomb_2.cnt = time + 10; + + if(autocvar_g_vehicle_raptor_bomblet_alt) + bomb_1.nextthink = bomb_2.nextthink = time; + else + bomb_1.nextthink = bomb_2.nextthink = time + autocvar_g_vehicle_raptor_bomblet_time; + + bomb_1.owner = bomb_2.owner = self; + bomb_1.realowner = bomb_2.realowner = self.owner; + bomb_1.solid = bomb_2.solid = SOLID_BBOX; + bomb_1.gravity = bomb_2.gravity = 1; + + PROJECTILE_MAKETRIGGER(bomb_1); + PROJECTILE_MAKETRIGGER(bomb_2); + + CSQCProjectile(bomb_1, TRUE, PROJECTILE_RAPTORBOMB, TRUE); + CSQCProjectile(bomb_2, TRUE, PROJECTILE_RAPTORBOMB, TRUE); +} + + +void raptor_fire_cannon(entity gun, string tagname) +{ + vehicles_projectile("raptor_cannon_muzzleflash", W_Sound("lasergun_fire"), + gettaginfo(gun, gettagindex(gun, tagname)), normalize(v_forward + randomvec() * autocvar_g_vehicle_raptor_cannon_spread) * autocvar_g_vehicle_raptor_cannon_speed, + autocvar_g_vehicle_raptor_cannon_damage, autocvar_g_vehicle_raptor_cannon_radius, autocvar_g_vehicle_raptor_cannon_force, 0, + DEATH_VH_RAPT_CANNON, PROJECTILE_RAPTORCANNON, 0, TRUE, TRUE, self.owner); +} + +void raptor_land() +{ + float hgt; + + hgt = raptor_altitude(512); + self.velocity = (self.velocity * 0.9) + ('0 0 -1800' * (hgt / 256) * sys_frametime); + self.angles_x *= 0.95; + self.angles_z *= 0.95; + + if(hgt < 128) + if(hgt > 0) + self.frame = (hgt / 128) * 25; + + self.bomb1.gun1.avelocity_y = 90 + ((self.frame / 25) * 2000); + self.bomb1.gun2.avelocity_y = -self.bomb1.gun1.avelocity_y; + + if(hgt < 16) + { + self.movetype = MOVETYPE_TOSS; + self.think = vehicles_think; + self.frame = 0; + } + + self.nextthink = time; + + CSQCMODEL_AUTOUPDATE(); +} + +void raptor_exit(float eject) +{ + vector spot; + self.tur_head.exteriormodeltoclient = world; + + if(self.deadflag == DEAD_NO) + { + self.think = raptor_land; + self.nextthink = time; + } + + if(!self.owner) + return; + + makevectors(self.angles); + if(eject) + { + spot = self.origin + v_forward * 100 + '0 0 64'; + spot = vehicles_findgoodexit(spot); + setorigin(self.owner , spot); + self.owner.velocity = (v_up + v_forward * 0.25) * 750; + self.owner.oldvelocity = self.owner.velocity; + } + else + { + if(vlen(self.velocity) > 2 * autocvar_sv_maxairspeed) + { + self.owner.velocity = normalize(self.velocity) * autocvar_sv_maxairspeed * 2; + self.owner.velocity_z += 200; + spot = self.origin + v_forward * 32 + '0 0 64'; + spot = vehicles_findgoodexit(spot); + } + else + { + self.owner.velocity = self.velocity * 0.5; + self.owner.velocity_z += 10; + spot = self.origin - v_forward * 200 + '0 0 64'; + spot = vehicles_findgoodexit(spot); + } + self.owner.oldvelocity = self.owner.velocity; + setorigin(self.owner , spot); + } + + antilag_clear(self.owner); + self.owner = world; +} + +void raptor_flare_touch() +{ + remove(self); +} + +void raptor_flare_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + self.health -= damage; + if(self.health <= 0) + remove(self); +} + +void raptor_flare_think() +{ + self.nextthink = time + 0.1; + entity _missile = findchainentity(enemy, self.owner); + while(_missile) + { + if(_missile.flags & FL_PROJECTILE) + if(vlen(self.origin - _missile.origin) < autocvar_g_vehicle_raptor_flare_range) + if(random() > autocvar_g_vehicle_raptor_flare_chase) + _missile.enemy = self; + _missile = _missile.chain; + } + + if(self.tur_impacttime < time) + remove(self); +} + +float raptor_frame() +{ + entity player, raptor; + float ftmp = 0; + vector df; + + if(intermission_running) + { + self.vehicle.velocity = '0 0 0'; + self.vehicle.avelocity = '0 0 0'; + return 1; + } + + player = self; + raptor = self.vehicle; + self = raptor; + + vehicles_painframe(); + /* + ftmp = vlen(self.velocity); + if(ftmp > autocvar_g_vehicle_raptor_speed_forward) + ftmp = 1; + else + ftmp = ftmp / autocvar_g_vehicle_raptor_speed_forward; + */ + + if(self.sound_nexttime < time) + { + self.sound_nexttime = time + 7.955812; + //sound (self.tur_head, CH_TRIGGER_SINGLE, "vehicles/raptor_fly.wav", 1 - ftmp, ATTEN_NORM ); + sound (self, CH_TRIGGER_SINGLE, "vehicles/raptor_speed.wav", 1, ATTEN_NORM); + self.wait = ftmp; + } + /* + else if(fabs(ftmp - self.wait) > 0.2) + { + sound (self.tur_head, CH_TRIGGER_SINGLE, "", 1 - ftmp, ATTEN_NORM ); + sound (self, CH_TRIGGER_SINGLE, "", ftmp, ATTEN_NORM); + self.wait = ftmp; + } + */ + + if(raptor.deadflag != DEAD_NO) + { + self = player; + player.BUTTON_ATCK = player.BUTTON_ATCK2 = 0; + return 1; + } + crosshair_trace(player); + + vector vang; + vang = raptor.angles; + df = vectoangles(normalize(trace_endpos - self.origin + '0 0 32')); + vang_x *= -1; + df_x *= -1; + if(df_x > 180) df_x -= 360; + if(df_x < -180) df_x += 360; + if(df_y > 180) df_y -= 360; + if(df_y < -180) df_y += 360; + + ftmp = shortangle_f(player.v_angle_y - vang_y, vang_y); + if(ftmp > 180) ftmp -= 360; if(ftmp < -180) ftmp += 360; + raptor.avelocity_y = bound(-autocvar_g_vehicle_raptor_turnspeed, ftmp + raptor.avelocity_y * 0.9, autocvar_g_vehicle_raptor_turnspeed); + + // Pitch + ftmp = 0; + if(player.movement_x > 0 && vang_x < autocvar_g_vehicle_raptor_pitchlimit) ftmp = 5; + else if(player.movement_x < 0 && vang_x > -autocvar_g_vehicle_raptor_pitchlimit) ftmp = -20; + + df_x = bound(-autocvar_g_vehicle_raptor_pitchlimit, df_x , autocvar_g_vehicle_raptor_pitchlimit); + ftmp = vang_x - bound(-autocvar_g_vehicle_raptor_pitchlimit, df_x + ftmp, autocvar_g_vehicle_raptor_pitchlimit); + raptor.avelocity_x = bound(-autocvar_g_vehicle_raptor_pitchspeed, ftmp + raptor.avelocity_x * 0.9, autocvar_g_vehicle_raptor_pitchspeed); + + raptor.angles_x = anglemods(raptor.angles_x); + raptor.angles_y = anglemods(raptor.angles_y); + raptor.angles_z = anglemods(raptor.angles_z); + + if(autocvar_g_vehicle_raptor_movestyle == 1) + makevectors('0 1 0' * raptor.angles_y); + else + makevectors(player.v_angle); + + df = raptor.velocity * -autocvar_g_vehicle_raptor_friction; + + if(player.movement_x != 0) + { + if(player.movement_x > 0) + df += v_forward * autocvar_g_vehicle_raptor_speed_forward; + else if(player.movement_x < 0) + df -= v_forward * autocvar_g_vehicle_raptor_speed_forward; + } + + if(player.movement_y != 0) + { + if(player.movement_y < 0) + df -= v_right * autocvar_g_vehicle_raptor_speed_strafe; + else if(player.movement_y > 0) + df += v_right * autocvar_g_vehicle_raptor_speed_strafe; + + raptor.angles_z = bound(-30,raptor.angles_z + (player.movement_y / autocvar_g_vehicle_raptor_speed_strafe),30); + } + else + { + raptor.angles_z *= 0.95; + if(raptor.angles_z >= -1 && raptor.angles_z <= -1) + raptor.angles_z = 0; + } + + if(player.BUTTON_CROUCH) + df -= v_up * autocvar_g_vehicle_raptor_speed_down; + else if (player.BUTTON_JUMP) + df += v_up * autocvar_g_vehicle_raptor_speed_up; + + raptor.velocity += df * frametime; + player.velocity = player.movement = raptor.velocity; + setorigin(player, raptor.origin + '0 0 32'); + + player.vehicle_weapon2mode = raptor.vehicle_weapon2mode; + + vector vf, ad; + // Target lock & predict + if(autocvar_g_vehicle_raptor_cannon_locktarget == 2) + { + if(raptor.gun1.lock_time < time || raptor.gun1.enemy.deadflag) + raptor.gun1.enemy = world; + + if(trace_ent) + if(trace_ent.movetype) + if(trace_ent.takedamage) + if(!trace_ent.deadflag) + { + if(teamplay) + { + if(trace_ent.team != player.team) + { + raptor.gun1.enemy = trace_ent; + raptor.gun1.lock_time = time + 5; + } + } + else + { + raptor.gun1.enemy = trace_ent; + raptor.gun1.lock_time = time + 0.5; + } + } + + if(raptor.gun1.enemy) + { + float distance, impact_time; + + vf = real_origin(raptor.gun1.enemy); + UpdateAuxiliaryXhair(player, vf, '1 0 0', 1); + vector _vel = raptor.gun1.enemy.velocity; + if(raptor.gun1.enemy.movetype == MOVETYPE_WALK) + _vel_z *= 0.1; + + if(autocvar_g_vehicle_raptor_cannon_predicttarget) + { + ad = vf; + distance = vlen(ad - player.origin); + impact_time = distance / autocvar_g_vehicle_raptor_cannon_speed; + ad = vf + _vel * impact_time; + trace_endpos = ad; + } + else + trace_endpos = vf; + } + } + else if(autocvar_g_vehicle_raptor_cannon_locktarget == 1) + { + + vehicles_locktarget((1 / autocvar_g_vehicle_raptor_cannon_locking_time) * frametime, + (1 / autocvar_g_vehicle_raptor_cannon_locking_releasetime) * frametime, + autocvar_g_vehicle_raptor_cannon_locked_time); + + if(self.lock_target != world) + if(autocvar_g_vehicle_raptor_cannon_predicttarget) + if(self.lock_strength == 1) + { + float i, distance, impact_time; + + vf = real_origin(raptor.lock_target); + ad = vf; + for(i = 0; i < 4; ++i) + { + distance = vlen(ad - raptor.origin); + impact_time = distance / autocvar_g_vehicle_raptor_cannon_speed; + ad = vf + raptor.lock_target.velocity * impact_time; + } + trace_endpos = ad; + } + + if(self.lock_target) + { + if(raptor.lock_strength == 1) + UpdateAuxiliaryXhair(player, real_origin(raptor.lock_target), '1 0 0', 1); + else if(self.lock_strength > 0.5) + UpdateAuxiliaryXhair(player, real_origin(raptor.lock_target), '0 1 0', 1); + else if(self.lock_strength < 0.5) + UpdateAuxiliaryXhair(player, real_origin(raptor.lock_target), '0 0 1', 1); + } + } + + + vehicle_aimturret(raptor, trace_endpos, raptor.gun1, "fire1", + autocvar_g_vehicle_raptor_cannon_pitchlimit_down * -1, autocvar_g_vehicle_raptor_cannon_pitchlimit_up, + autocvar_g_vehicle_raptor_cannon_turnlimit * -1, autocvar_g_vehicle_raptor_cannon_turnlimit, autocvar_g_vehicle_raptor_cannon_turnspeed); + + vehicle_aimturret(raptor, trace_endpos, raptor.gun2, "fire1", + autocvar_g_vehicle_raptor_cannon_pitchlimit_down * -1, autocvar_g_vehicle_raptor_cannon_pitchlimit_up, + autocvar_g_vehicle_raptor_cannon_turnlimit * -1, autocvar_g_vehicle_raptor_cannon_turnlimit, autocvar_g_vehicle_raptor_cannon_turnspeed); + + /* + ad = ad * 0.5; + v_forward = vf * 0.5; + traceline(ad, ad + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, raptor); + UpdateAuxiliaryXhair(player, trace_endpos, '0 1 0', 0); + */ + + if(!forbidWeaponUse(player)) + if(player.BUTTON_ATCK) + if(raptor.attack_finished_single <= time) + if(raptor.vehicle_energy > autocvar_g_vehicle_raptor_cannon_cost) + { + raptor.misc_bulletcounter += 1; + raptor.attack_finished_single = time + autocvar_g_vehicle_raptor_cannon_refire; + if(raptor.misc_bulletcounter <= 2) + raptor_fire_cannon(self.gun1, "fire1"); + else if(raptor.misc_bulletcounter == 3) + raptor_fire_cannon(self.gun2, "fire1"); + else + { + raptor.attack_finished_single = time + autocvar_g_vehicle_raptor_cannon_refire * 2; + raptor_fire_cannon(self.gun2, "fire1"); + raptor.misc_bulletcounter = 0; + } + raptor.vehicle_energy -= autocvar_g_vehicle_raptor_cannon_cost; + self.cnt = time; + } + + if(self.vehicle_flags & VHF_SHIELDREGEN) + vehicles_regen(raptor.dmg_time, vehicle_shield, autocvar_g_vehicle_raptor_shield, autocvar_g_vehicle_raptor_shield_regen_pause, autocvar_g_vehicle_raptor_shield_regen, frametime, TRUE); + + if(self.vehicle_flags & VHF_HEALTHREGEN) + vehicles_regen(raptor.dmg_time, vehicle_health, autocvar_g_vehicle_raptor_health, autocvar_g_vehicle_raptor_health_regen_pause, autocvar_g_vehicle_raptor_health_regen, frametime, FALSE); + + if(self.vehicle_flags & VHF_ENERGYREGEN) + vehicles_regen(raptor.cnt, vehicle_energy, autocvar_g_vehicle_raptor_energy, autocvar_g_vehicle_raptor_energy_regen_pause, autocvar_g_vehicle_raptor_energy_regen, frametime, FALSE); + + if(!forbidWeaponUse(player)) + if(raptor.vehicle_weapon2mode == RSM_BOMB) + { + if(time > raptor.lip + autocvar_g_vehicle_raptor_bombs_refire) + if(player.BUTTON_ATCK2) + { + raptor_bombdrop(); + raptor.delay = time + autocvar_g_vehicle_raptor_bombs_refire; + raptor.lip = time; + } + } + else + { + if(time > raptor.lip + autocvar_g_vehicle_raptor_flare_refire) + if(player.BUTTON_ATCK2) + { + float i; + entity _flare; + + for(i = 0; i < 3; ++i) + { + _flare = spawn(); + setmodel(_flare, "models/runematch/rune.mdl"); + _flare.effects = EF_LOWPRECISION | EF_FLAME; + _flare.scale = 0.5; + setorigin(_flare, self.origin - '0 0 16'); + _flare.movetype = MOVETYPE_TOSS; + _flare.gravity = 0.15; + _flare.velocity = 0.25 * raptor.velocity + (v_forward + randomvec() * 0.25)* -500; + _flare.think = raptor_flare_think; + _flare.nextthink = time; + _flare.owner = raptor; + _flare.solid = SOLID_CORPSE; + _flare.takedamage = DAMAGE_YES; + _flare.event_damage = raptor_flare_damage; + _flare.health = 20; + _flare.tur_impacttime = time + autocvar_g_vehicle_raptor_flare_lifetime; + _flare.touch = raptor_flare_touch; + } + raptor.delay = time + autocvar_g_vehicle_raptor_flare_refire; + raptor.lip = time; + } + } + + raptor.bomb1.alpha = raptor.bomb2.alpha = (time - raptor.lip) / (raptor.delay - raptor.lip); + player.vehicle_reload2 = bound(0, raptor.bomb1.alpha * 100, 100); + + if(self.bomb1.cnt < time) + { + entity _missile = findchainentity(enemy, raptor); + float _incomming = 0; + while(_missile) + { + if(_missile.flags & FL_PROJECTILE) + if(MISSILE_IS_TRACKING(_missile)) + if(vlen(self.origin - _missile.origin) < 2 * autocvar_g_vehicle_raptor_flare_range) + ++_incomming; + + _missile = _missile.chain; + } + + if(_incomming) + sound(self, CH_PAIN_SINGLE, "vehicles/missile_alarm.wav", VOL_BASE, ATTEN_NONE); + + self.bomb1.cnt = time + 1; + } + + + VEHICLE_UPDATE_PLAYER(player, health, raptor); + VEHICLE_UPDATE_PLAYER(player, energy, raptor); + if(self.vehicle_flags & VHF_HASSHIELD) + VEHICLE_UPDATE_PLAYER(player, shield, raptor); + + player.BUTTON_ATCK = player.BUTTON_ATCK2 = player.BUTTON_CROUCH = 0; + + self = player; + return 1; +} + +float raptor_takeoff() +{ + entity player, raptor; + + player = self; + raptor = self.vehicle; + self = raptor; + + self.nextthink = time; + CSQCMODEL_AUTOUPDATE(); + self.nextthink = 0; // will this work? + + if(self.sound_nexttime < time) + { + self.sound_nexttime = time + 7.955812; //soundlength("vehicles/raptor_fly.wav"); + sound (self, CH_TRIGGER_SINGLE, "vehicles/raptor_speed.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + + // Takeoff sequense + if(raptor.frame < 25) + { + raptor.frame += 25 / (autocvar_g_vehicle_raptor_takeofftime / sys_frametime); + raptor.velocity_z = min(raptor.velocity_z * 1.5, 256); + self.bomb1.gun1.avelocity_y = 90 + ((raptor.frame / 25) * 25000); + self.bomb1.gun2.avelocity_y = -self.bomb1.gun1.avelocity_y; + player.BUTTON_ATCK = player.BUTTON_ATCK2 = player.BUTTON_CROUCH = 0; + + setorigin(player, raptor.origin + '0 0 32'); + } + else + player.PlayerPhysplug = raptor_frame; + + if(self.vehicle_flags & VHF_SHIELDREGEN) + vehicles_regen(raptor.dmg_time, vehicle_shield, autocvar_g_vehicle_raptor_shield, autocvar_g_vehicle_raptor_shield_regen_pause, autocvar_g_vehicle_raptor_shield_regen, frametime, TRUE); + + if(self.vehicle_flags & VHF_HEALTHREGEN) + vehicles_regen(raptor.dmg_time, vehicle_health, autocvar_g_vehicle_raptor_health, autocvar_g_vehicle_raptor_health_regen_pause, autocvar_g_vehicle_raptor_health_regen, frametime, FALSE); + + if(self.vehicle_flags & VHF_ENERGYREGEN) + vehicles_regen(raptor.cnt, vehicle_energy, autocvar_g_vehicle_raptor_energy, autocvar_g_vehicle_raptor_energy_regen_pause, autocvar_g_vehicle_raptor_energy_regen, frametime, FALSE); + + + raptor.bomb1.alpha = raptor.bomb2.alpha = (time - raptor.lip) / (raptor.delay - raptor.lip); + player.vehicle_reload2 = bound(0, raptor.bomb1.alpha * 100, 100); + + VEHICLE_UPDATE_PLAYER(player, health, raptor); + VEHICLE_UPDATE_PLAYER(player, energy, raptor); + if(self.vehicle_flags & VHF_HASSHIELD) + VEHICLE_UPDATE_PLAYER(player, shield, raptor); + + player.BUTTON_ATCK = player.BUTTON_ATCK2 = player.BUTTON_CROUCH = 0; + self = player; + return 1; +} + +void raptor_blowup() +{ + self.deadflag = DEAD_DEAD; + self.vehicle_exit(VHEF_NORMAL); + RadiusDamage (self, self.enemy, 250, 15, 250, world, world, 250, DEATH_VH_RAPT_DEATH, world); + + self.alpha = -1; + self.movetype = MOVETYPE_NONE; + self.effects = EF_NODRAW; + self.colormod = '0 0 0'; + self.avelocity = '0 0 0'; + self.velocity = '0 0 0'; + + setorigin(self, self.pos1); + self.touch = func_null; + self.nextthink = 0; +} + +void raptor_diethink() +{ + if(time >= self.wait) + self.think = raptor_blowup; + + if(random() < 0.05) + { + sound (self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_EXPLOSION_SMALL, randomvec() * 80 + (self.origin + '0 0 100'), '0 0 0', 1); + } + self.nextthink = time; + + CSQCMODEL_AUTOUPDATE(); +} + +// If we dont do this ever now and then, the raptors rotors +// stop working, presumably due to angle overflow. cute. +void raptor_rotor_anglefix() +{ + self.gun1.angles_y = anglemods(self.gun1.angles_y); + self.gun2.angles_y = anglemods(self.gun2.angles_y); + self.nextthink = time + 15; +} + +float raptor_impulse(float _imp) +{ + switch(_imp) + { + case 10: + case 15: + case 18: + self.vehicle.vehicle_weapon2mode += 1; + if(self.vehicle.vehicle_weapon2mode > RSM_LAST) + self.vehicle.vehicle_weapon2mode = RSM_FIRST; + + CSQCVehicleSetup(self, 0); + return TRUE; + case 12: + case 16: + case 19: + self.vehicle.vehicle_weapon2mode -= 1; + if(self.vehicle.vehicle_weapon2mode < RSM_FIRST) + self.vehicle.vehicle_weapon2mode = RSM_LAST; + + CSQCVehicleSetup(self, 0); + return TRUE; + + /* + case 17: // toss gun, could be used to exit? + break; + case 20: // Manual minigun reload? + break; + */ + } + return FALSE; +} + +void spawnfunc_vehicle_raptor() +{ + if(!autocvar_g_vehicle_raptor) { remove(self); return; } + if(!vehicle_initialize(VEH_RAPTOR, FALSE)) { remove(self); return; } +} + +float v_raptor(float req) +{ + switch(req) + { + case VR_IMPACT: + { + if(autocvar_g_vehicle_raptor_bouncepain) + vehicles_impact(autocvar_g_vehicle_raptor_bouncepain_x, autocvar_g_vehicle_raptor_bouncepain_y, autocvar_g_vehicle_raptor_bouncepain_z); + + return TRUE; + } + case VR_ENTER: + { + self.vehicle_weapon2mode = RSM_BOMB; + self.owner.PlayerPhysplug = raptor_takeoff; + self.movetype = MOVETYPE_BOUNCEMISSILE; + self.solid = SOLID_SLIDEBOX; + self.owner.vehicle_health = (self.vehicle_health / autocvar_g_vehicle_raptor_health) * 100; + self.owner.vehicle_shield = (self.vehicle_shield / autocvar_g_vehicle_raptor_shield) * 100; + self.velocity_z = 1; // Nudge upwards to takeoff sequense can work. + self.tur_head.exteriormodeltoclient = self.owner; + + self.delay = time + autocvar_g_vehicle_raptor_bombs_refire; + self.lip = time; + + if(self.owner.flagcarried) + setorigin(self.owner.flagcarried, '-20 0 96'); + + CSQCVehicleSetup(self.owner, 0); + return TRUE; + } + case VR_THINK: + { + return TRUE; + } + case VR_DEATH: + { + self.health = 0; + self.event_damage = func_null; + self.solid = SOLID_CORPSE; + self.takedamage = DAMAGE_NO; + self.deadflag = DEAD_DYING; + self.movetype = MOVETYPE_BOUNCE; + self.think = raptor_diethink; + self.nextthink = time; + self.wait = time + 5 + (random() * 5); + + Send_Effect(EFFECT_EXPLOSION_MEDIUM, findbetterlocation (self.origin, 16), '0 0 0', 1); + + self.velocity_z += 600; + + self.avelocity = '0 0.5 1' * (random() * 400); + self.avelocity -= '0 0.5 1' * (random() * 400); + + self.colormod = '-0.5 -0.5 -0.5'; + self.touch = raptor_blowup; + return TRUE; + } + case VR_SPAWN: + { + if(!self.gun1) + { + entity spinner; + vector ofs; + + //FIXME: Camera is in a bad place in HUD model. + //setorigin(self.vehicle_viewport, '25 0 5'); + + self.vehicles_impulse = raptor_impulse; + + self.frame = 0; + + self.bomb1 = spawn(); + self.bomb2 = spawn(); + self.gun1 = spawn(); + self.gun2 = spawn(); + + setmodel(self.bomb1,"models/vehicles/clusterbomb_folded.md3"); + setmodel(self.bomb2,"models/vehicles/clusterbomb_folded.md3"); + setmodel(self.gun1, "models/vehicles/raptor_gun.dpm"); + setmodel(self.gun2, "models/vehicles/raptor_gun.dpm"); + setmodel(self.tur_head, "models/vehicles/raptor_body.dpm"); + + setattachment(self.bomb1, self, "bombmount_left"); + setattachment(self.bomb2, self, "bombmount_right"); + setattachment(self.tur_head, self,"root"); + + // FIXMODEL Guns mounts to angled bones + self.bomb1.angles = self.angles; + self.angles = '0 0 0'; + // This messes up gun-aim, so work arround it. + //setattachment(self.gun1, self, "gunmount_left"); + ofs = gettaginfo(self, gettagindex(self, "gunmount_left")); + ofs -= self.origin; + setattachment(self.gun1, self, ""); + setorigin(self.gun1, ofs); + + //setattachment(self.gun2, self, "gunmount_right"); + ofs = gettaginfo(self, gettagindex(self, "gunmount_right")); + ofs -= self.origin; + setattachment(self.gun2, self, ""); + setorigin(self.gun2, ofs); + + self.angles = self.bomb1.angles; + self.bomb1.angles = '0 0 0'; + + spinner = spawn(); + spinner.owner = self; + setmodel(spinner,"models/vehicles/spinner.dpm"); + setattachment(spinner, self, "engine_left"); + spinner.movetype = MOVETYPE_NOCLIP; + spinner.avelocity = '0 90 0'; + self.bomb1.gun1 = spinner; + + spinner = spawn(); + spinner.owner = self; + setmodel(spinner,"models/vehicles/spinner.dpm"); + setattachment(spinner, self, "engine_right"); + spinner.movetype = MOVETYPE_NOCLIP; + spinner.avelocity = '0 -90 0'; + self.bomb1.gun2 = spinner; + + // Sigh. + self.bomb1.think = raptor_rotor_anglefix; + self.bomb1.nextthink = time; + + self.mass = 1 ; + } + + self.frame = 0; + self.vehicle_health = autocvar_g_vehicle_raptor_health; + self.vehicle_shield = autocvar_g_vehicle_raptor_shield; + self.movetype = MOVETYPE_TOSS; + self.solid = SOLID_SLIDEBOX; + self.vehicle_energy = 1; + + self.PlayerPhysplug = raptor_frame; + + self.bomb1.gun1.avelocity_y = 90; + self.bomb1.gun2.avelocity_y = -90; + + self.delay = time; + + self.bouncefactor = autocvar_g_vehicle_raptor_bouncefactor; + self.bouncestop = autocvar_g_vehicle_raptor_bouncestop; + self.damageforcescale = 0.25; + self.vehicle_health = autocvar_g_vehicle_raptor_health; + self.vehicle_shield = autocvar_g_vehicle_raptor_shield; + return TRUE; + } + case VR_SETUP: + { + if(autocvar_g_vehicle_raptor_shield) + self.vehicle_flags |= VHF_HASSHIELD; + + if(autocvar_g_vehicle_raptor_shield_regen) + self.vehicle_flags |= VHF_SHIELDREGEN; + + if(autocvar_g_vehicle_raptor_health_regen) + self.vehicle_flags |= VHF_HEALTHREGEN; + + if(autocvar_g_vehicle_raptor_energy_regen) + self.vehicle_flags |= VHF_ENERGYREGEN; + + self.vehicle_exit = raptor_exit; + self.respawntime = autocvar_g_vehicle_raptor_respawntime; + self.vehicle_health = autocvar_g_vehicle_raptor_health; + self.vehicle_shield = autocvar_g_vehicle_raptor_shield; + self.max_health = self.vehicle_health; + + return TRUE; + } + case VR_PRECACHE: + { + precache_model ("models/vehicles/raptor.dpm"); + precache_model ("models/vehicles/raptor_gun.dpm"); + precache_model ("models/vehicles/spinner.dpm"); + precache_model ("models/vehicles/raptor_cockpit.dpm"); + precache_model ("models/vehicles/clusterbomb_folded.md3"); + precache_model ("models/vehicles/raptor_body.dpm"); + + precache_sound ("vehicles/raptor_fly.wav"); + precache_sound ("vehicles/raptor_speed.wav"); + precache_sound ("vehicles/missile_alarm.wav"); + + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +#define raptor_ico "gfx/vehicles/raptor.tga" +#define raptor_gun "gfx/vehicles/raptor_guns.tga" +#define raptor_bomb "gfx/vehicles/raptor_bombs.tga" +#define raptor_drop "gfx/vehicles/axh-dropcross.tga" + +void RaptorCBShellfragDraw() +{ + if(wasfreed(self)) + return; + + Movetype_Physics_MatchTicrate(autocvar_cl_gibs_ticrate, autocvar_cl_gibs_sloppy); + self.move_avelocity += randomvec() * 15; + self.renderflags = 0; + + if(self.cnt < time) + self.alpha = bound(0, self.nextthink - time, 1); + + if(self.alpha < ALPHA_MIN_VISIBLE) + remove(self); +} + +void RaptorCBShellfragToss(vector _org, vector _vel, vector _ang) +{ + entity sfrag; + + sfrag = spawn(); + setmodel(sfrag, "models/vehicles/clusterbomb_fragment.md3"); + setorigin(sfrag, _org); + + sfrag.move_movetype = MOVETYPE_BOUNCE; + sfrag.gravity = 0.15; + sfrag.solid = SOLID_CORPSE; + + sfrag.draw = RaptorCBShellfragDraw; + + sfrag.move_origin = sfrag.origin = _org; + sfrag.move_velocity = _vel; + sfrag.move_avelocity = prandomvec() * vlen(sfrag.move_velocity); + sfrag.angles = self.move_angles = _ang; + + sfrag.move_time = time; + sfrag.damageforcescale = 4; + + sfrag.nextthink = time + 3; + sfrag.cnt = time + 2; + sfrag.alpha = 1; + sfrag.drawmask = MASK_NORMAL; +} + +float v_raptor(float req) +{ + switch(req) + { + case VR_HUD: + { + if(autocvar_r_letterbox) + return TRUE; + + vector picsize, hudloc = '0 0 0', pic2size, picloc; + string raptor_xhair; + + // Fetch health & ammo stats + HUD_GETVEHICLESTATS + + picsize = draw_getimagesize(hud_bg) * autocvar_cl_vehicles_hudscale; + hudloc_y = vid_conheight - picsize_y; + hudloc_x = vid_conwidth * 0.5 - picsize_x * 0.5; + + drawpic(hudloc, hud_bg, picsize, '1 1 1', autocvar_cl_vehicles_hudalpha, DRAWFLAG_NORMAL); + + ammo1 *= 0.01; + ammo2 *= 0.01; + shield *= 0.01; + vh_health *= 0.01; + energy *= 0.01; + reload1 = reload2 * 0.01; + //reload2 *= 0.01; + + pic2size = draw_getimagesize(raptor_ico) * (autocvar_cl_vehicles_hudscale * 0.8); + picloc = picsize * 0.5 - pic2size * 0.5; + if(vh_health < 0.25) + drawpic(hudloc + picloc, raptor_ico, pic2size, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, raptor_ico, pic2size, '1 1 1' * vh_health + '1 0 0' * (1 - vh_health), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, raptor_bomb, pic2size, '1 1 1' * reload1 + '1 0 0' * (1 - reload1), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, raptor_gun, pic2size, '1 1 1' * energy + '1 0 0' * (1 - energy), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, hud_sh, pic2size, '1 1 1', shield, DRAWFLAG_NORMAL); + + // Health bar + picsize = draw_getimagesize(hud_hp_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - vh_health)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_hp_bar, picsize, '1 1 1', 1 , DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picsize = draw_getimagesize(hud_hp_ico) * autocvar_cl_vehicles_hudscale; + picloc = '37 65 0' * autocvar_cl_vehicles_hudscale; + if(vh_health < 0.25) + { + if(alarm1time < time) + { + alarm1time = time + 2; + vehicle_alarm(self, CH_PAIN_SINGLE, "vehicles/alarm.wav"); + } + + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm1time) + { + vehicle_alarm(self, CH_PAIN_SINGLE, "misc/null.wav"); + alarm1time = 0; + } + } + + // Shield bar + picsize = draw_getimagesize(hud_sh_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 140 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - shield)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_sh_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picloc = '40 136 0' * autocvar_cl_vehicles_hudscale; + picsize = draw_getimagesize(hud_sh_ico) * autocvar_cl_vehicles_hudscale; + if(shield < 0.25) + { + if(alarm2time < time) + { + alarm2time = time + 1; + vehicle_alarm(self, CH_TRIGGER_SINGLE, "vehicles/alarm_shield.wav"); + } + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm2time) + { + vehicle_alarm(self, CH_TRIGGER_SINGLE, "misc/null.wav"); + alarm2time = 0; + } + } + + // Gun bar + picsize = draw_getimagesize(hud_ammo1_bar) * autocvar_cl_vehicles_hudscale; + picloc = '450 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x, picloc_y, picsize_x * energy, vid_conheight); + drawpic(hudloc + picloc, hud_ammo1_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picsize = draw_getimagesize(hud_ammo1_ico) * autocvar_cl_vehicles_hudscale; + picloc = '664 60 0' * autocvar_cl_vehicles_hudscale; + if(energy < 0.2) + drawpic(hudloc + picloc, hud_ammo1_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, hud_ammo1_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + + // Bomb bar + picsize = draw_getimagesize(hud_ammo2_bar) * autocvar_cl_vehicles_hudscale; + picloc = '450 140 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x, hudloc_y + picloc_y, picsize_x * reload1, vid_conheight); + drawpic(hudloc + picloc, hud_ammo2_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + pic2size = draw_getimagesize(hud_ammo2_ico) * autocvar_cl_vehicles_hudscale; + picloc = '664 130 0' * autocvar_cl_vehicles_hudscale; + if(reload1 != 1) + drawpic(hudloc + picloc, hud_ammo2_ico, pic2size, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, hud_ammo2_ico, pic2size, '1 1 1', 1, DRAWFLAG_NORMAL); + + if(getstati(STAT_VEHICLESTAT_W2MODE) == RSM_FLARE) + { + raptor_xhair = "gfx/vehicles/axh-bracket.tga"; + } + else + { + raptor_xhair = "gfx/vehicles/axh-ring.tga"; + + // Bombing crosshair + if(!dropmark) + { + dropmark = spawn(); + dropmark.owner = self; + dropmark.gravity = 1; + } + + if(reload2 == 100) + { + vector where; + + setorigin(dropmark, pmove_org); + dropmark.velocity = pmove_vel; + tracetoss(dropmark, self); + + where = project_3d_to_2d(trace_endpos); + + setorigin(dropmark, trace_endpos); + picsize = draw_getimagesize(raptor_drop) * 0.2; + + if(!(where_z < 0 || where_x < 0 || where_y < 0 || where_x > vid_conwidth || where_y > vid_conheight)) + { + where_x -= picsize_x * 0.5; + where_y -= picsize_y * 0.5; + where_z = 0; + drawpic(where, raptor_drop, picsize, '0 2 0', 1, DRAWFLAG_ADDITIVE); + } + dropmark.cnt = time + 5; + } + else + { + vector where; + if(dropmark.cnt > time) + { + where = project_3d_to_2d(dropmark.origin); + picsize = draw_getimagesize(raptor_drop) * 0.25; + + if(!(where_z < 0 || where_x < 0 || where_y < 0 || where_x > vid_conwidth || where_y > vid_conheight)) + { + where_x -= picsize_x * 0.5; + where_y -= picsize_y * 0.5; + where_z = 0; + drawpic(where, raptor_drop, picsize, '2 0 0', 1, DRAWFLAG_ADDITIVE); + } + } + } + } + + if (scoreboard_showscores) + HUD_DrawScoreboard(); + else + { + picsize = draw_getimagesize(raptor_xhair); + picsize_x *= 0.5; + picsize_y *= 0.5; + + drawpic('0.5 0 0' * (vid_conwidth - picsize_x) + '0 0.5 0' * (vid_conheight - picsize_y), raptor_xhair, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + } + + return TRUE; + } + case VR_SETUP: + { + AuxiliaryXhair[0].axh_image = "gfx/vehicles/axh-special2.tga"; + AuxiliaryXhair[0].axh_scale = 0.5; + + AuxiliaryXhair[1].axh_image = "gfx/vehicles/axh-bracket.tga"; + AuxiliaryXhair[1].axh_scale = 0.25; + return TRUE; + } + case VR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_VEHICLE diff --git a/qcsrc/common/vehicles/unit/spiderbot.qc b/qcsrc/common/vehicles/unit/spiderbot.qc new file mode 100644 index 000000000..8ecdb36b7 --- /dev/null +++ b/qcsrc/common/vehicles/unit/spiderbot.qc @@ -0,0 +1,1080 @@ +#ifdef REGISTER_VEHICLE +REGISTER_VEHICLE( +/* VEH_##id */ SPIDERBOT, +/* function */ v_spiderbot, +/* spawnflags */ VHF_DMGSHAKE, +/* mins,maxs */ '-75 -75 10', '75 75 125', +/* model */ "models/vehicles/spiderbot.dpm", +/* head_model */ "models/vehicles/spiderbot_top.dpm", +/* hud_model */ "models/vehicles/spiderbot_cockpit.dpm", +/* tags */ "tag_head", "tag_hud", "", +/* netname */ "spiderbot", +/* fullname */ _("Spiderbot") +); +#else + +const float SBRM_FIRST = 1; +const float SBRM_VOLLY = 1; +const float SBRM_GUIDE = 2; +const float SBRM_ARTILLERY = 3; +const float SBRM_LAST = 3; + +#ifdef SVQC +float autocvar_g_vehicle_spiderbot; + +float autocvar_g_vehicle_spiderbot_respawntime; + +float autocvar_g_vehicle_spiderbot_speed_stop; +float autocvar_g_vehicle_spiderbot_speed_strafe; +float autocvar_g_vehicle_spiderbot_speed_walk; +float autocvar_g_vehicle_spiderbot_turnspeed; +float autocvar_g_vehicle_spiderbot_turnspeed_strafe; +float autocvar_g_vehicle_spiderbot_movement_inertia; + +float autocvar_g_vehicle_spiderbot_springlength; +float autocvar_g_vehicle_spiderbot_springup; +float autocvar_g_vehicle_spiderbot_springblend; +float autocvar_g_vehicle_spiderbot_tiltlimit; + +float autocvar_g_vehicle_spiderbot_head_pitchlimit_down; +float autocvar_g_vehicle_spiderbot_head_pitchlimit_up; +float autocvar_g_vehicle_spiderbot_head_turnlimit; +float autocvar_g_vehicle_spiderbot_head_turnspeed; + +float autocvar_g_vehicle_spiderbot_health; +float autocvar_g_vehicle_spiderbot_health_regen; +float autocvar_g_vehicle_spiderbot_health_regen_pause; + +float autocvar_g_vehicle_spiderbot_shield; +float autocvar_g_vehicle_spiderbot_shield_regen; +float autocvar_g_vehicle_spiderbot_shield_regen_pause; + +float autocvar_g_vehicle_spiderbot_minigun_damage; +float autocvar_g_vehicle_spiderbot_minigun_refire; +float autocvar_g_vehicle_spiderbot_minigun_spread; +float autocvar_g_vehicle_spiderbot_minigun_ammo_cost; +float autocvar_g_vehicle_spiderbot_minigun_ammo_max; +float autocvar_g_vehicle_spiderbot_minigun_ammo_regen; +float autocvar_g_vehicle_spiderbot_minigun_ammo_regen_pause; +float autocvar_g_vehicle_spiderbot_minigun_force; +float autocvar_g_vehicle_spiderbot_minigun_solidpenetration; + +float autocvar_g_vehicle_spiderbot_rocket_damage; +float autocvar_g_vehicle_spiderbot_rocket_force; +float autocvar_g_vehicle_spiderbot_rocket_radius; +float autocvar_g_vehicle_spiderbot_rocket_speed; +float autocvar_g_vehicle_spiderbot_rocket_spread; +float autocvar_g_vehicle_spiderbot_rocket_refire; +float autocvar_g_vehicle_spiderbot_rocket_refire2; +float autocvar_g_vehicle_spiderbot_rocket_reload; +float autocvar_g_vehicle_spiderbot_rocket_health; +float autocvar_g_vehicle_spiderbot_rocket_noise; +float autocvar_g_vehicle_spiderbot_rocket_turnrate; +float autocvar_g_vehicle_spiderbot_rocket_lifetime; + +vector autocvar_g_vehicle_spiderbot_bouncepain; + +void spiderbot_rocket_artillery() +{ + self.nextthink = time; + UpdateCSQCProjectile(self); +} + +void spiderbot_rocket_unguided() +{ + vector newdir, olddir; + + self.nextthink = time; + + olddir = normalize(self.velocity); + newdir = normalize(self.pos1 - self.origin) + randomvec() * autocvar_g_vehicle_spiderbot_rocket_noise; + self.velocity = normalize(olddir + newdir * autocvar_g_vehicle_spiderbot_rocket_turnrate) * autocvar_g_vehicle_spiderbot_rocket_speed; + + UpdateCSQCProjectile(self); + + if (self.owner.deadflag != DEAD_NO || self.cnt < time || vlen(self.pos1 - self.origin) < 16) + self.use(); +} + +void spiderbot_rocket_guided() +{ + vector newdir, olddir; + + self.nextthink = time; + + if(!self.realowner.vehicle) + self.think = spiderbot_rocket_unguided; + + crosshair_trace(self.realowner); + olddir = normalize(self.velocity); + newdir = normalize(trace_endpos - self.origin) + randomvec() * autocvar_g_vehicle_spiderbot_rocket_noise; + self.velocity = normalize(olddir + newdir * autocvar_g_vehicle_spiderbot_rocket_turnrate) * autocvar_g_vehicle_spiderbot_rocket_speed; + + UpdateCSQCProjectile(self); + + if (self.owner.deadflag != DEAD_NO || self.cnt < time) + self.use(); +} + +void spiderbot_guide_release() +{ + entity rkt; + rkt = findchainentity(realowner, self.owner); + if(!rkt) + return; + + crosshair_trace(self.owner); + while(rkt) + { + if(rkt.think == spiderbot_rocket_guided) + { + rkt.pos1 = trace_endpos; + rkt.think = spiderbot_rocket_unguided; + } + rkt = rkt.chain; + } +} + +float spiberbot_calcartillery_flighttime; +vector spiberbot_calcartillery(vector org, vector tgt, float ht) +{ + float grav, sdist, zdist, vs, vz, jumpheight; + vector sdir; + + grav = autocvar_sv_gravity; + zdist = tgt_z - org_z; + sdist = vlen(tgt - org - zdist * '0 0 1'); + sdir = normalize(tgt - org - zdist * '0 0 1'); + + // how high do we need to go? + jumpheight = fabs(ht); + if(zdist > 0) + jumpheight = jumpheight + zdist; + + // push so high... + vz = sqrt(2 * grav * jumpheight); // NOTE: sqrt(positive)! + + // we start with downwards velocity only if it's a downjump and the jump apex should be outside the jump! + if(ht < 0) + if(zdist < 0) + vz = -vz; + + vector solution; + solution = solve_quadratic(0.5 * grav, -vz, zdist); // equation "z(ti) = zdist" + // ALWAYS solvable because jumpheight >= zdist + if(!solution_z) + solution_y = solution_x; // just in case it is not solvable due to roundoff errors, assume two equal solutions at their center (this is mainly for the usual case with ht == 0) + if(zdist == 0) + solution_x = solution_y; // solution_x is 0 in this case, so don't use it, but rather use solution_y (which will be sqrt(0.5 * jumpheight / grav), actually) + + if(zdist < 0) + { + // down-jump + if(ht < 0) + { + // almost straight line type + // jump apex is before the jump + // we must take the larger one + spiberbot_calcartillery_flighttime = solution_y; + } + else + { + // regular jump + // jump apex is during the jump + // we must take the larger one too + spiberbot_calcartillery_flighttime = solution_y; + } + } + else + { + // up-jump + if(ht < 0) + { + // almost straight line type + // jump apex is after the jump + // we must take the smaller one + spiberbot_calcartillery_flighttime = solution_x; + } + else + { + // regular jump + // jump apex is during the jump + // we must take the larger one + spiberbot_calcartillery_flighttime = solution_y; + } + } + vs = sdist / spiberbot_calcartillery_flighttime; + + // finally calculate the velocity + return sdir * vs + '0 0 1' * vz; +} + +void spiderbot_rocket_do() +{ + vector v; + entity rocket = world; + + if (self.wait != -10) + { + if (self.owner.BUTTON_ATCK2 && self.vehicle_weapon2mode == SBRM_GUIDE) + { + if (self.wait == 1) + if (self.tur_head.frame == 9 || self.tur_head.frame == 1) + { + if(self.gun2.cnt < time && self.tur_head.frame == 9) + self.tur_head.frame = 1; + + return; + } + self.wait = 1; + } + else + { + if(self.wait) + spiderbot_guide_release(); + + self.wait = 0; + } + } + + if(self.gun2.cnt > time) + return; + + if (self.tur_head.frame >= 9) + { + self.tur_head.frame = 1; + self.wait = 0; + } + + if(self.wait != -10) + if(!self.owner.BUTTON_ATCK2) + return; + + if(forbidWeaponUse(self.owner)) + return; + + v = gettaginfo(self.tur_head,gettagindex(self.tur_head,"tag_fire")); + + switch(self.vehicle_weapon2mode) + { + case SBRM_VOLLY: + rocket = vehicles_projectile("spiderbot_rocket_launch", W_Sound("rocket_fire"), + v, normalize(randomvec() * autocvar_g_vehicle_spiderbot_rocket_spread + v_forward) * autocvar_g_vehicle_spiderbot_rocket_speed, + autocvar_g_vehicle_spiderbot_rocket_damage, autocvar_g_vehicle_spiderbot_rocket_radius, autocvar_g_vehicle_spiderbot_rocket_force, 1, + DEATH_VH_SPID_ROCKET, PROJECTILE_SPIDERROCKET, autocvar_g_vehicle_spiderbot_rocket_health, FALSE, TRUE, self.owner); + crosshair_trace(self.owner); + float _dist = (random() * autocvar_g_vehicle_spiderbot_rocket_radius) + vlen(v - trace_endpos); + _dist -= (random() * autocvar_g_vehicle_spiderbot_rocket_radius) ; + rocket.nextthink = time + (_dist / autocvar_g_vehicle_spiderbot_rocket_speed); + rocket.think = vehicles_projectile_explode; + + if(self.owner.BUTTON_ATCK2 && self.tur_head.frame == 1) + self.wait = -10; + break; + case SBRM_GUIDE: + rocket = vehicles_projectile("spiderbot_rocket_launch", W_Sound("rocket_fire"), + v, normalize(v_forward) * autocvar_g_vehicle_spiderbot_rocket_speed, + autocvar_g_vehicle_spiderbot_rocket_damage, autocvar_g_vehicle_spiderbot_rocket_radius, autocvar_g_vehicle_spiderbot_rocket_force, 1, + DEATH_VH_SPID_ROCKET, PROJECTILE_SPIDERROCKET, autocvar_g_vehicle_spiderbot_rocket_health, FALSE, FALSE, self.owner); + crosshair_trace(self.owner); + rocket.pos1 = trace_endpos; + rocket.nextthink = time; + rocket.think = spiderbot_rocket_guided; + + + break; + case SBRM_ARTILLERY: + rocket = vehicles_projectile("spiderbot_rocket_launch", W_Sound("rocket_fire"), + v, normalize(v_forward) * autocvar_g_vehicle_spiderbot_rocket_speed, + autocvar_g_vehicle_spiderbot_rocket_damage, autocvar_g_vehicle_spiderbot_rocket_radius, autocvar_g_vehicle_spiderbot_rocket_force, 1, + DEATH_VH_SPID_ROCKET, PROJECTILE_SPIDERROCKET, autocvar_g_vehicle_spiderbot_rocket_health, FALSE, TRUE, self.owner); + + crosshair_trace(self.owner); + + rocket.pos1 = trace_endpos + randomvec() * (0.75 * autocvar_g_vehicle_spiderbot_rocket_radius); + rocket.pos1_z = trace_endpos_z; + + traceline(v, v + '0 0 1' * MAX_SHOT_DISTANCE, MOVE_WORLDONLY, self); + float h1 = 0.75 * vlen(v - trace_endpos); + + //v = trace_endpos; + traceline(v , rocket.pos1 + '0 0 1' * MAX_SHOT_DISTANCE, MOVE_WORLDONLY, self); + float h2 = 0.75 * vlen(rocket.pos1 - v); + + rocket.velocity = spiberbot_calcartillery(v, rocket.pos1, ((h1 < h2) ? h1 : h2)); + rocket.movetype = MOVETYPE_TOSS; + rocket.gravity = 1; + //rocket.think = spiderbot_rocket_artillery; + break; + } + rocket.classname = "spiderbot_rocket"; + + rocket.cnt = time + autocvar_g_vehicle_spiderbot_rocket_lifetime; + + self.tur_head.frame += 1; + if (self.tur_head.frame == 9) + self.attack_finished_single = autocvar_g_vehicle_spiderbot_rocket_reload; + else + self.attack_finished_single = ((self.vehicle_weapon2mode == SBRM_VOLLY) ? autocvar_g_vehicle_spiderbot_rocket_refire2 : autocvar_g_vehicle_spiderbot_rocket_refire); + + self.gun2.cnt = time + self.attack_finished_single; +} + +float spiderbot_frame() +{ + vector ad, vf; + entity player, spider; + float ftmp; + + if(intermission_running) + { + self.vehicle.velocity = '0 0 0'; + self.vehicle.avelocity = '0 0 0'; + return 1; + } + + player = self; + spider = self.vehicle; + self = spider; + + vehicles_painframe(); + + player.BUTTON_ZOOM = 0; + player.BUTTON_CROUCH = 0; + player.switchweapon = 0; + player.vehicle_weapon2mode = spider.vehicle_weapon2mode; + + +#if 1 // 0 to enable per-gun impact aux crosshairs + // Avarage gun impact point's -> aux cross + ad = gettaginfo(spider.tur_head, gettagindex(spider.tur_head, "tag_hardpoint01")); + vf = v_forward; + ad += gettaginfo(spider.tur_head, gettagindex(spider.tur_head, "tag_hardpoint02")); + vf += v_forward; + ad = ad * 0.5; + v_forward = vf * 0.5; + traceline(ad, ad + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, spider); + UpdateAuxiliaryXhair(player, trace_endpos, ('1 0 0' * player.vehicle_reload1) + ('0 1 0' * (1 - player.vehicle_reload1)), 0); +#else + ad = gettaginfo(spider.gun1, gettagindex(spider.gun1, "barrels")); + traceline(ad, ad + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, spider); + UpdateAuxiliaryXhair(player, trace_endpos, ('1 0 0' * player.vehicle_reload1) + ('0 1 0' * (1 - player.vehicle_reload1)), 0); + vf = ad; + ad = gettaginfo(spider.gun2, gettagindex(spider.gun2, "barrels")); + traceline(ad, ad + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, spider); + UpdateAuxiliaryXhair(player, trace_endpos, ('1 0 0' * player.vehicle_reload1) + ('0 1 0' * (1 - player.vehicle_reload1)), 1); + ad = 0.5 * (ad + vf); +#endif + + crosshair_trace(player); + ad = vectoangles(normalize(trace_endpos - ad)); + ad = AnglesTransform_ToAngles(AnglesTransform_LeftDivide(AnglesTransform_FromAngles(spider.angles), AnglesTransform_FromAngles(ad))) - spider.tur_head.angles; + ad = AnglesTransform_Normalize(ad, TRUE); + //UpdateAuxiliaryXhair(player, trace_endpos, ('1 0 0' * player.vehicle_reload2) + ('0 1 0' * (1 - player.vehicle_reload2)), 2); + + // Rotate head + ftmp = autocvar_g_vehicle_spiderbot_head_turnspeed * sys_frametime; + ad_y = bound(-ftmp, ad_y, ftmp); + spider.tur_head.angles_y = bound(autocvar_g_vehicle_spiderbot_head_turnlimit * -1, spider.tur_head.angles_y + ad_y, autocvar_g_vehicle_spiderbot_head_turnlimit); + + // Pitch head + ad_x = bound(ftmp * -1, ad_x, ftmp); + spider.tur_head.angles_x = bound(autocvar_g_vehicle_spiderbot_head_pitchlimit_down, spider.tur_head.angles_x + ad_x, autocvar_g_vehicle_spiderbot_head_pitchlimit_up); + + + //fixedmakevectors(spider.angles); + makevectors(spider.angles + '-2 0 0' * spider.angles_x); + + movelib_groundalign4point(autocvar_g_vehicle_spiderbot_springlength, autocvar_g_vehicle_spiderbot_springup, autocvar_g_vehicle_spiderbot_springblend, autocvar_g_vehicle_spiderbot_tiltlimit); + + if(spider.flags & FL_ONGROUND) + { + if(spider.frame == 4 && self.tur_head.wait != 0) + { + sound (self, CH_TRIGGER_SINGLE, "vehicles/spiderbot_land.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + spider.frame = 5; + } + + if(player.BUTTON_JUMP && self.tur_head.wait < time) + { + sound (self, CH_TRIGGER_SINGLE, "vehicles/spiderbot_jump.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + //dprint("spiderbot_jump:", ftos(soundlength("vehicles/spiderbot_jump.wav")), "\n"); + self.delay = 0; + + self.tur_head.wait = time + 2; + player.BUTTON_JUMP = 0; + spider.velocity = v_forward * 700 + v_up * 600; + spider.frame = 4; + } + else + { + if(vlen(player.movement) == 0) + { + if(self.sound_nexttime < time || self.delay != 3) + { + self.delay = 3; + self.sound_nexttime = time + 6.486500; //soundlength("vehicles/spiderbot_idle.wav"); + //dprint("spiderbot_idle:", ftos(soundlength("vehicles/spiderbot_idle.wav")), "\n"); + sound (self, CH_TRIGGER_SINGLE, "vehicles/spiderbot_idle.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + movelib_beak_simple(autocvar_g_vehicle_spiderbot_speed_stop); + spider.frame = 5; + } + else + { + // Turn Body + if(player.movement_x == 0 && player.movement_y != 0) + ftmp = autocvar_g_vehicle_spiderbot_turnspeed_strafe * sys_frametime; + else + ftmp = autocvar_g_vehicle_spiderbot_turnspeed * sys_frametime; + + ftmp = bound(-ftmp, spider.tur_head.angles_y, ftmp); + spider.angles_y = anglemods(spider.angles_y + ftmp); + spider.tur_head.angles_y -= ftmp; + + if(player.movement_x != 0) + { + if(player.movement_x > 0) + { + player.movement_x = 1; + spider.frame = 0; + } + else if(player.movement_x < 0) + { + player.movement_x = -1; + spider.frame = 1; + } + player.movement_y = 0; + movelib_move_simple(normalize(v_forward * player.movement_x),autocvar_g_vehicle_spiderbot_speed_walk,autocvar_g_vehicle_spiderbot_movement_inertia); + + if(self.sound_nexttime < time || self.delay != 1) + { + self.delay = 1; + self.sound_nexttime = time + 6.486500; //soundlength("vehicles/spiderbot_walk.wav"); + sound (self, CH_TRIGGER_SINGLE, "vehicles/spiderbot_walk.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + //dprint("spiderbot_walk:", ftos(soundlength("vehicles/spiderbot_walk.wav")), "\n"); + } + } + else if(player.movement_y != 0) + { + if(player.movement_y < 0) + { + player.movement_y = -1; + spider.frame = 2; + } + else if(player.movement_y > 0) + { + player.movement_y = 1; + spider.frame = 3; + } + movelib_move_simple(normalize(v_right * player.movement_y),autocvar_g_vehicle_spiderbot_speed_strafe,autocvar_g_vehicle_spiderbot_movement_inertia); + if(self.sound_nexttime < time || self.delay != 2) + { + self.delay = 2; + self.sound_nexttime = time + 6.486500; //soundlength("vehicles/spiderbot_strafe.wav"); + sound (self, CH_TRIGGER_SINGLE, "vehicles/spiderbot_strafe.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + //dprint("spiderbot_strafe:", ftos(soundlength("vehicles/spiderbot_strafe.wav")), "\n"); + } + } + } + } + } + + self.angles_x = bound(-autocvar_g_vehicle_spiderbot_tiltlimit, self.angles_x, autocvar_g_vehicle_spiderbot_tiltlimit); + self.angles_z = bound(-autocvar_g_vehicle_spiderbot_tiltlimit, self.angles_z, autocvar_g_vehicle_spiderbot_tiltlimit); + + if(!forbidWeaponUse(player)) + if(player.BUTTON_ATCK) + { + spider.cnt = time; + if(spider.vehicle_ammo1 >= autocvar_g_vehicle_spiderbot_minigun_ammo_cost && spider.tur_head.attack_finished_single <= time) + { + entity gun; + vector v; + spider.misc_bulletcounter += 1; + + self = player; + + gun = (spider.misc_bulletcounter % 2) ? spider.gun1 : spider.gun2; + + v = gettaginfo(gun, gettagindex(gun, "barrels")); + v_forward = normalize(v_forward); + v += v_forward * 50; + + fireBullet(v, v_forward, autocvar_g_vehicle_spiderbot_minigun_spread, autocvar_g_vehicle_spiderbot_minigun_solidpenetration, + autocvar_g_vehicle_spiderbot_minigun_damage, autocvar_g_vehicle_spiderbot_minigun_force, DEATH_VH_SPID_MINIGUN, 0); + + sound (gun, CH_WEAPON_A, W_Sound("uzi_fire"), VOL_BASE, ATTEN_NORM); + //trailparticles(self, particleeffectnum("spiderbot_minigun_trail"), v, trace_endpos); + pointparticles(particleeffectnum("spiderbot_minigun_muzzleflash"), v, v_forward * 2500, 1); + + self = spider; + + spider.vehicle_ammo1 -= autocvar_g_vehicle_spiderbot_minigun_ammo_cost; + spider.tur_head.attack_finished_single = time + autocvar_g_vehicle_spiderbot_minigun_refire; + player.vehicle_ammo1 = (spider.vehicle_ammo1 / autocvar_g_vehicle_spiderbot_minigun_ammo_max) * 100; + spider.gun1.angles_z += 45; + spider.gun2.angles_z -= 45; + if(spider.gun1.angles_z >= 360) + { + spider.gun1.angles_z = 0; + spider.gun2.angles_z = 0; + } + } + } + else + vehicles_regen(spider.cnt, vehicle_ammo1, autocvar_g_vehicle_spiderbot_minigun_ammo_max, + autocvar_g_vehicle_spiderbot_minigun_ammo_regen_pause, + autocvar_g_vehicle_spiderbot_minigun_ammo_regen, frametime, FALSE); + + + spiderbot_rocket_do(); + + if(self.vehicle_flags & VHF_SHIELDREGEN) + vehicles_regen(spider.dmg_time, vehicle_shield, autocvar_g_vehicle_spiderbot_shield, autocvar_g_vehicle_spiderbot_shield_regen_pause, autocvar_g_vehicle_spiderbot_shield_regen, frametime, TRUE); + + if(self.vehicle_flags & VHF_HEALTHREGEN) + vehicles_regen(spider.dmg_time, vehicle_health, autocvar_g_vehicle_spiderbot_health, autocvar_g_vehicle_spiderbot_health_regen_pause, autocvar_g_vehicle_spiderbot_health_regen, frametime, FALSE); + + player.BUTTON_ATCK = player.BUTTON_ATCK2 = 0; + player.vehicle_ammo2 = spider.tur_head.frame; + + if(spider.gun2.cnt <= time) + player.vehicle_reload2 = 100; + else + player.vehicle_reload2 = 100 - ((spider.gun2.cnt - time) / spider.attack_finished_single) * 100; + + setorigin(player, spider.origin + '0 0 1' * spider.maxs_z); + player.velocity = spider.velocity; + + VEHICLE_UPDATE_PLAYER(player, health, spiderbot); + + if(self.vehicle_flags & VHF_HASSHIELD) + VEHICLE_UPDATE_PLAYER(player, shield, spiderbot); + + self = player; + return 1; +} + +void spiderbot_exit(float eject) +{ + entity e; + vector spot; + + e = findchain(classname,"spiderbot_rocket"); + while(e) + { + if(e.owner == self.owner) + { + e.realowner = self.owner; + e.owner = world; + } + e = e.chain; + } + + self.think = vehicles_think; + self.nextthink = time; + self.frame = 5; + self.movetype = MOVETYPE_WALK; + + if(!self.owner) + return; + + makevectors(self.angles); + if(eject) + { + spot = self.origin + v_forward * 100 + '0 0 64'; + spot = vehicles_findgoodexit(spot); + setorigin(self.owner , spot); + self.owner.velocity = (v_up + v_forward * 0.25) * 750; + self.owner.oldvelocity = self.owner.velocity; + } + else + { + if(vlen(self.velocity) > autocvar_g_vehicle_spiderbot_speed_strafe) + { + self.owner.velocity = normalize(self.velocity) * vlen(self.velocity); + self.owner.velocity_z += 200; + spot = self.origin + v_forward * 128 + '0 0 64'; + spot = vehicles_findgoodexit(spot); + } + else + { + self.owner.velocity = self.velocity * 0.5; + self.owner.velocity_z += 10; + spot = self.origin + v_forward * 256 + '0 0 64'; + spot = vehicles_findgoodexit(spot); + } + self.owner.oldvelocity = self.owner.velocity; + setorigin(self.owner , spot); + } + + antilag_clear(self.owner); + self.owner = world; +} + +void spiderbot_headfade() +{ + self.think = spiderbot_headfade; + self.nextthink = self.fade_time; + self.alpha = 1 - (time - self.fade_time) * self.fade_rate; + + if(self.cnt < time || self.alpha < 0.1) + { + if(self.alpha > 0.1) + { + sound (self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_EXPLOSION_BIG, self.origin + '0 0 100', '0 0 0', 1); + } + remove(self); + } +} + +void spiderbot_blowup() +{ + if(self.cnt > time) + { + if(random() < 0.1) + { + sound (self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_EXPLOSION_SMALL, randomvec() * 80 + (self.origin + '0 0 100'), '0 0 0', 1); + } + self.nextthink = time + 0.1; + return; + } + + entity h, g1, g2, b; + b = spawn(); + h = spawn(); + g1 = spawn(); + g2 = spawn(); + + setmodel(b, "models/vehicles/spiderbot.dpm"); + setmodel(h, "models/vehicles/spiderbot_top.dpm"); + setmodel(g1, "models/vehicles/spiderbot_barrels.dpm"); + setmodel(g2, "models/vehicles/spiderbot_barrels.dpm"); + + setorigin(b, self.origin); + b.frame = 11; + b.angles = self.angles; + setsize(b, self.mins, self.maxs); + + setorigin(h, gettaginfo(self, gettagindex(self, "tag_head"))); + h.movetype = MOVETYPE_BOUNCE; + h.solid = SOLID_BBOX; + h.velocity = v_up * (500 + random() * 500) + randomvec() * 128; + h.modelflags = MF_ROCKET; + h.effects = EF_FLAME | EF_LOWPRECISION; + h.avelocity = randomvec() * 360; + + h.alpha = 1; + h.cnt = time + (3.5 * random()); + h.fade_rate = 1 / min(self.respawntime, 10); + h.fade_time = time; + h.think = spiderbot_headfade; + h.nextthink = time; + + setorigin(g1, gettaginfo(self.tur_head, gettagindex(self.tur_head, "tag_hardpoint01"))); + g1.movetype = MOVETYPE_TOSS; + g1.solid = SOLID_CORPSE; + g1.velocity = v_forward * 700 + (randomvec() * 32); + g1.avelocity = randomvec() * 180; + + setorigin(g2, gettaginfo(self.tur_head, gettagindex(self.tur_head, "tag_hardpoint02"))); + g2.movetype = MOVETYPE_TOSS; + g2.solid = SOLID_CORPSE; + g2.velocity = v_forward * 700 + (randomvec() * 32); + g2.avelocity = randomvec() * 180; + + h.colormod = b.colormod = g1.colormod = g2.colormod = '-2 -2 -2'; + + SUB_SetFade(b, time + 5, min(self.respawntime, 1)); + //SUB_SetFade(h, time, min(self.respawntime, 10)); + SUB_SetFade(g1, time, min(self.respawntime, 10)); + SUB_SetFade(g2, time, min(self.respawntime, 10)); + + RadiusDamage (self, self.enemy, 250, 15, 250, world, world, 250, DEATH_VH_SPID_DEATH, world); + + self.alpha = self.tur_head.alpha = self.gun1.alpha = self.gun2.alpha = -1; + self.movetype = MOVETYPE_NONE; + self.deadflag = DEAD_DEAD; + self.solid = SOLID_NOT; + self.tur_head.effects &= ~EF_FLAME; + self.vehicle_hudmodel.viewmodelforclient = self; +} + +float spiderbot_impulse(float _imp) +{ + switch(_imp) + { + case 10: + case 15: + case 18: + self.vehicle.vehicle_weapon2mode += 1; + if(self.vehicle.vehicle_weapon2mode > SBRM_LAST) + self.vehicle.vehicle_weapon2mode = SBRM_FIRST; + + //centerprint(self, strcat("Rocket mode is ", ftos(self.vehicle.vehicle_weapon2mode))); + CSQCVehicleSetup(self, 0); + return TRUE; + case 12: + case 16: + case 19: + self.vehicle.vehicle_weapon2mode -= 1; + if(self.vehicle.vehicle_weapon2mode < SBRM_FIRST) + self.vehicle.vehicle_weapon2mode = SBRM_LAST; + + //centerprint(self, strcat("Rocket mode is ", ftos(self.vehicle.vehicle_weapon2mode))); + CSQCVehicleSetup(self, 0); + return TRUE; + + /* + case 17: // toss gun, could be used to exit? + break; + case 20: // Manual minigun reload? + break; + */ + } + return FALSE; +} + +void spawnfunc_vehicle_spiderbot() +{ + if(!autocvar_g_vehicle_spiderbot) { remove(self); return; } + if(!vehicle_initialize(VEH_SPIDERBOT, FALSE)) { remove(self); return; } +} + +float v_spiderbot(float req) +{ + switch(req) + { + case VR_IMPACT: + { + if(autocvar_g_vehicle_spiderbot_bouncepain) + vehicles_impact(autocvar_g_vehicle_spiderbot_bouncepain_x, autocvar_g_vehicle_spiderbot_bouncepain_y, autocvar_g_vehicle_spiderbot_bouncepain_z); + + return TRUE; + } + case VR_ENTER: + { + self.vehicle_weapon2mode = SBRM_GUIDE; + self.movetype = MOVETYPE_WALK; + CSQCVehicleSetup(self.owner, 0); + self.owner.vehicle_health = (self.vehicle_health / autocvar_g_vehicle_spiderbot_health) * 100; + self.owner.vehicle_shield = (self.vehicle_shield / autocvar_g_vehicle_spiderbot_shield) * 100; + + if(self.owner.flagcarried) + { + setattachment(self.owner.flagcarried, self.tur_head, ""); + setorigin(self.owner.flagcarried, '-20 0 120'); + } + + return TRUE; + } + case VR_THINK: + { + if(self.flags & FL_ONGROUND) + movelib_beak_simple(autocvar_g_vehicle_spiderbot_speed_stop); + + return TRUE; + } + case VR_DEATH: + { + self.health = 0; + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + self.touch = func_null; + self.cnt = 3.4 + time + random() * 2; + self.think = spiderbot_blowup; + self.nextthink = time; + self.deadflag = DEAD_DYING; + self.frame = 5; + self.tur_head.effects |= EF_FLAME; + self.colormod = self.tur_head.colormod = '-1 -1 -1'; + self.frame = 10; + self.movetype = MOVETYPE_TOSS; + + CSQCModel_UnlinkEntity(); // networking the death scene would be a nightmare + + return TRUE; + } + case VR_SPAWN: + { + if(!self.gun1) + { + self.vehicles_impulse = spiderbot_impulse; + self.gun1 = spawn(); + self.gun2 = spawn(); + setmodel(self.gun1, "models/vehicles/spiderbot_barrels.dpm"); + setmodel(self.gun2, "models/vehicles/spiderbot_barrels.dpm"); + setattachment(self.gun1, self.tur_head, "tag_hardpoint01"); + setattachment(self.gun2, self.tur_head, "tag_hardpoint02"); + self.gravity = 2; + self.mass = 5000; + } + + self.frame = 5; + self.tur_head.frame = 1; + self.movetype = MOVETYPE_WALK; + self.solid = SOLID_SLIDEBOX; + self.alpha = self.tur_head.alpha = self.gun1.alpha = self.gun2.alpha = 1; + self.tur_head.angles = '0 0 0'; + self.vehicle_exit = spiderbot_exit; + + setorigin(self, self.pos1 + '0 0 128'); + self.angles = self.pos2; + self.damageforcescale = 0.03; + self.vehicle_health = autocvar_g_vehicle_spiderbot_health; + self.vehicle_shield = autocvar_g_vehicle_spiderbot_shield; + + self.PlayerPhysplug = spiderbot_frame; + + return TRUE; + } + case VR_SETUP: + { + if(autocvar_g_vehicle_spiderbot_shield) + self.vehicle_flags |= VHF_HASSHIELD; + + if(autocvar_g_vehicle_spiderbot_shield_regen) + self.vehicle_flags |= VHF_SHIELDREGEN; + + if(autocvar_g_vehicle_spiderbot_health_regen) + self.vehicle_flags |= VHF_HEALTHREGEN; + + self.respawntime = autocvar_g_vehicle_spiderbot_respawntime; + self.vehicle_health = autocvar_g_vehicle_spiderbot_health; + self.vehicle_shield = autocvar_g_vehicle_spiderbot_shield; + self.max_health = self.vehicle_health; + self.pushable = TRUE; // spiderbot can use jumppads + + return TRUE; + } + case VR_PRECACHE: + { + precache_model ("models/vhshield.md3"); + precache_model ("models/vehicles/spiderbot.dpm"); + precache_model ("models/vehicles/spiderbot_top.dpm"); + precache_model ("models/vehicles/spiderbot_barrels.dpm"); + precache_model ("models/vehicles/spiderbot_cockpit.dpm"); + precache_model ( "models/uziflash.md3"); + + precache_sound (W_Sound("uzi_fire")); + precache_sound (W_Sound("rocket_impact")); + + precache_sound ("vehicles/spiderbot_die.wav"); + precache_sound ("vehicles/spiderbot_idle.wav"); + precache_sound ("vehicles/spiderbot_jump.wav"); + precache_sound ("vehicles/spiderbot_strafe.wav"); + precache_sound ("vehicles/spiderbot_walk.wav"); + precache_sound ("vehicles/spiderbot_land.wav"); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +var float autocvar_cl_vehicle_spiderbot_cross_alpha = 0.6; +var float autocvar_cl_vehicle_spiderbot_cross_size = 1; + +#define spider_ico "gfx/vehicles/sbot.tga" +#define spider_rkt "gfx/vehicles/sbot_rpods.tga" +#define spider_mgun "gfx/vehicles/sbot_mguns.tga" +string spider_xhair; // = "gfx/vehicles/axh-special1.tga"; + +float v_spiderbot(float req) +{ + switch(req) + { + case VR_HUD: + { + if(autocvar_r_letterbox) + return TRUE; + + vector picsize, hudloc = '0 0 0', pic2size, picloc; + float i; + + // Fetch health & ammo stats + HUD_GETVEHICLESTATS + + picsize = draw_getimagesize(hud_bg) * autocvar_cl_vehicles_hudscale; + hudloc_y = vid_conheight - picsize_y; + hudloc_x = vid_conwidth * 0.5 - picsize_x * 0.5; + + drawpic(hudloc, hud_bg, picsize, '1 1 1', autocvar_cl_vehicles_hudalpha, DRAWFLAG_NORMAL); + + ammo1 *= 0.01; + shield *= 0.01; + vh_health *= 0.01; + reload2 *= 0.01; + + pic2size = draw_getimagesize(spider_ico) * (autocvar_cl_vehicles_hudscale * 0.8); + picloc = picsize * 0.5 - pic2size * 0.5; + if(vh_health < 0.25) + drawpic(hudloc + picloc, spider_ico, pic2size, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, spider_ico, pic2size, '1 1 1' * vh_health + '1 0 0' * (1 - vh_health), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, spider_rkt, pic2size, '1 1 1' * reload2 + '1 0 0' * (1 - reload2), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, spider_mgun, pic2size, '1 1 1' * ammo1 + '1 0 0' * (1 - ammo1), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, hud_sh, pic2size, '1 1 1', shield, DRAWFLAG_NORMAL); + + // Health bar + picsize = draw_getimagesize(hud_hp_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - vh_health)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_hp_bar, picsize, '1 1 1', 1 , DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picsize = draw_getimagesize(hud_hp_ico) * autocvar_cl_vehicles_hudscale; + picloc = '37 65 0' * autocvar_cl_vehicles_hudscale; + if(vh_health < 0.25) + { + if(alarm1time < time) + { + alarm1time = time + 2; + vehicle_alarm(self, CH_PAIN_SINGLE, "vehicles/alarm.wav"); + } + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm1time) + { + vehicle_alarm(self, CH_PAIN_SINGLE, "misc/null.wav"); + alarm1time = 0; + } + } + // Shield bar + picsize = draw_getimagesize(hud_sh_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 140 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - shield)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_sh_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picloc = '40 136 0' * autocvar_cl_vehicles_hudscale; + picsize = draw_getimagesize(hud_sh_ico) * autocvar_cl_vehicles_hudscale; + if(shield < 0.25) + { + if(alarm2time < time) + { + alarm2time = time + 1; + vehicle_alarm(self, CH_PAIN_SINGLE, "vehicles/alarm_shield.wav"); + } + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm2time) + { + vehicle_alarm(self, CH_PAIN_SINGLE, "misc/null.wav"); + alarm2time = 0; + } + } + + // Minigun bar + picsize = draw_getimagesize(hud_ammo1_bar) * autocvar_cl_vehicles_hudscale; + picloc = '450 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x, picloc_y, picsize_x * ammo1, vid_conheight); + drawpic(hudloc + picloc, hud_ammo1_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picsize = draw_getimagesize(hud_ammo1_ico) * autocvar_cl_vehicles_hudscale; + picloc = '664 60 0' * autocvar_cl_vehicles_hudscale; + if(ammo1 < 0.2) + drawpic(hudloc + picloc, hud_ammo1_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, hud_ammo1_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + + // Rocket ammo bar + picsize = draw_getimagesize(hud_ammo2_bar) * autocvar_cl_vehicles_hudscale; + ammo1 = picsize_x / 8; + picloc = '450 140 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x, hudloc_y + picloc_y, picsize_x * reload2, vid_conheight); + drawpic(hudloc + picloc, hud_ammo2_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + + // .. and icons + pic2size = 0.35 * draw_getimagesize(hud_ammo2_ico) * autocvar_cl_vehicles_hudscale; + picloc_x -= pic2size_x; + picloc_y += pic2size_y * 2.25; + if(ammo2 == 9) + { + for(i = 1; i < 9; ++i) + { + picloc_x += ammo1; + drawpic(hudloc + picloc, hud_ammo2_ico, pic2size, ((8 * reload2 <= i) ? '0 0 0' : '1 1 1'), 0.75, DRAWFLAG_NORMAL); + } + } + else + { + for(i = 1; i < 9; ++i) + { + picloc_x += ammo1; + drawpic(hudloc + picloc, hud_ammo2_ico, pic2size, ((i >= ammo2) ? '1 1 1' : '0 0 0'), 0.75, DRAWFLAG_NORMAL); + } + } + pic2size = draw_getimagesize(hud_ammo2_ico) * autocvar_cl_vehicles_hudscale; + picloc = '664 130 0' * autocvar_cl_vehicles_hudscale; + if(ammo2 == 9) + drawpic(hudloc + picloc, hud_ammo2_ico, pic2size, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, hud_ammo2_ico, pic2size, '1 1 1', 1, DRAWFLAG_NORMAL); + + if (scoreboard_showscores) + HUD_DrawScoreboard(); + else + { + switch(getstati(STAT_VEHICLESTAT_W2MODE)) + { + case SBRM_VOLLY: + spider_xhair = "gfx/vehicles/axh-bracket.tga"; + break; + case SBRM_GUIDE: + spider_xhair = "gfx/vehicles/axh-cross.tga"; + break; + case SBRM_ARTILLERY: + spider_xhair = "gfx/vehicles/axh-tag.tga"; + break; + default: + spider_xhair= "gfx/vehicles/axh-tag.tga"; + } + + picsize = draw_getimagesize(spider_xhair); + picsize_x *= autocvar_cl_vehicle_spiderbot_cross_size; + picsize_y *= autocvar_cl_vehicle_spiderbot_cross_size; + + drawpic('0.5 0 0' * (vid_conwidth - picsize_x) + '0 0.5 0' * (vid_conheight - picsize_y), spider_xhair, picsize, '1 1 1', autocvar_cl_vehicle_spiderbot_cross_alpha, DRAWFLAG_ADDITIVE); + } + + return TRUE; + } + case VR_SETUP: + { + // Minigun1 + AuxiliaryXhair[0].axh_image = "gfx/vehicles/axh-ring.tga"; + AuxiliaryXhair[0].axh_scale = 0.25; + // Minigun2 + AuxiliaryXhair[1].axh_image = "gfx/vehicles/axh-ring.tga"; + AuxiliaryXhair[1].axh_scale = 0.25; + // Rocket + AuxiliaryXhair[2].axh_image = "gfx/vehicles/axh-special1.tga"; + AuxiliaryXhair[2].axh_scale = 0.5; + + return TRUE; + } + case VR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_VEHICLE diff --git a/qcsrc/common/vehicles/unit/tankll48.qc b/qcsrc/common/vehicles/unit/tankll48.qc new file mode 100644 index 000000000..94266d226 --- /dev/null +++ b/qcsrc/common/vehicles/unit/tankll48.qc @@ -0,0 +1,743 @@ +#ifdef REGISTER_VEHICLE +REGISTER_VEHICLE( +/* VEH_##id */ TANKLL48, +/* function */ v_tankll48, +/* spawnflags */ VHF_DMGSHAKE, +/* mins,maxs */ '-185 -185 2', '185 185 136', +/* model */ "models/vehicles/tankll48.md3", +/* head_model */ "models/vehicles/tankll48_turret.md3", +/* hud_model */ "null", +/* tags */ "tag_turret", "tag_camera", "", +/* netname */ "tankll48", +/* fullname */ _("LL48") +); +#else +#ifdef SVQC +float autocvar_g_vehicle_tankll48; + +float autocvar_g_vehicle_tankll48_respawntime; + +float autocvar_g_vehicle_tankll48_speed_stop; +float autocvar_g_vehicle_tankll48_speed_strafe; +float autocvar_g_vehicle_tankll48_speed_walk; +float autocvar_g_vehicle_tankll48_turnspeed; +float autocvar_g_vehicle_tankll48_turnspeed_strafe; +float autocvar_g_vehicle_tankll48_movement_inertia; + +float autocvar_g_vehicle_tankll48_springlength; +float autocvar_g_vehicle_tankll48_springup; +float autocvar_g_vehicle_tankll48_springblend; +float autocvar_g_vehicle_tankll48_tiltlimit; + +float autocvar_g_vehicle_tankll48_head_pitchlimit_down; +float autocvar_g_vehicle_tankll48_head_pitchlimit_up; +float autocvar_g_vehicle_tankll48_head_turnlimit; +float autocvar_g_vehicle_tankll48_head_turnspeed; + +float autocvar_g_vehicle_tankll48_turret_turnlimit; + +float autocvar_g_vehicle_tankll48_health; +float autocvar_g_vehicle_tankll48_health_regen; +float autocvar_g_vehicle_tankll48_health_regen_pause; + +float autocvar_g_vehicle_tankll48_shield; +float autocvar_g_vehicle_tankll48_shield_regen; +float autocvar_g_vehicle_tankll48_shield_regen_pause; + +vector autocvar_g_vehicle_tankll48_bouncepain; + +float autocvar_g_vehicle_tankll48_cannon_damage; +float autocvar_g_vehicle_tankll48_cannon_ammo; +float autocvar_g_vehicle_tankll48_cannon_speed; +float autocvar_g_vehicle_tankll48_cannon_edgedamage; +float autocvar_g_vehicle_tankll48_cannon_force; +float autocvar_g_vehicle_tankll48_cannon_radius; +float autocvar_g_vehicle_tankll48_cannon_damage2; +float autocvar_g_vehicle_tankll48_cannon_speedaccel; +float autocvar_g_vehicle_tankll48_cannon_ammo_max; +float autocvar_g_vehicle_tankll48_cannon_ammo_regen; +float autocvar_g_vehicle_tankll48_cannon_ammo_regen_pause; + +void tankll48_cannon_explode() +{ + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + RadiusDamage (self, self.realowner, autocvar_g_vehicle_tankll48_cannon_damage, autocvar_g_vehicle_tankll48_cannon_edgedamage, autocvar_g_vehicle_tankll48_cannon_radius, world, world, autocvar_g_vehicle_tankll48_cannon_force, self.projectiledeathtype, other); + + remove (self); +} + +void tankll48_cannon_touch() +{ + if(WarpZone_Projectile_Touch()) + if(wasfreed(self)) + return; + + //if(other.solid != SOLID_BSP) + // return; + + tankll48_cannon_explode(); +} + +void tankll48_cannon_think() +{ + if(self.cnt <= time) + { + remove(self); + return; + } + + self.cnt = vlen(self.velocity); + self.wait = self.cnt * sys_frametime; + self.pos1 = normalize(self.velocity); + + tracebox(self.origin, self.mins, self.maxs, self.origin + self.pos1 * (2 * self.wait), MOVE_NORMAL, self); + if(IS_PLAYER(trace_ent)) + Damage (trace_ent, self, self.realowner, autocvar_g_vehicle_tankll48_cannon_damage2, self.projectiledeathtype, self.origin, normalize(self.origin - other.origin) * autocvar_g_vehicle_tankll48_cannon_force); + + self.velocity = self.pos1 * (self.cnt + (autocvar_g_vehicle_tankll48_cannon_speedaccel * sys_frametime)); + + UpdateCSQCProjectile(self); + self.nextthink = time; +} + +float tankll48_frame() +{ + vector ad, vf; + entity player, tank; + float ftmp; + + if(intermission_running) + { + self.vehicle.velocity = '0 0 0'; + self.vehicle.avelocity = '0 0 0'; + return 1; + } + + player = self; + tank = self.vehicle; + self = tank; + + vehicles_painframe(); + + player.BUTTON_ZOOM = 0; + player.BUTTON_CROUCH = 0; + player.switchweapon = 0; + player.vehicle_weapon2mode = tank.vehicle_weapon2mode; + + +#if 1 // 0 to enable per-gun impact aux crosshairs + // Avarage gun impact point's -> aux cross + ad = gettaginfo(tank.tur_head, gettagindex(tank.tur_head, "tag_cannon_pivot")); + vf = v_forward; + ad += gettaginfo(tank.tur_head, gettagindex(tank.tur_head, "tag_cannon_pivot_0")); + vf += v_forward; + ad = ad * 0.5; + v_forward = vf * 0.5; + traceline(ad, ad + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, tank); + UpdateAuxiliaryXhair(player, trace_endpos, ('1 0 0' * player.vehicle_reload1) + ('0 1 0' * (1 - player.vehicle_reload1)), 0); +#else + ad = gettaginfo(tank.gun1, gettagindex(tank.gun1, "barrels")); + traceline(ad, ad + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, tank); + UpdateAuxiliaryXhair(player, trace_endpos, ('1 0 0' * player.vehicle_reload1) + ('0 1 0' * (1 - player.vehicle_reload1)), 0); + vf = ad; + ad = gettaginfo(tank.gun2, gettagindex(tank.gun2, "barrels")); + traceline(ad, ad + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, tank); + UpdateAuxiliaryXhair(player, trace_endpos, ('1 0 0' * player.vehicle_reload1) + ('0 1 0' * (1 - player.vehicle_reload1)), 1); + ad = 0.5 * (ad + vf); +#endif + + crosshair_trace(player); + ad = vectoangles(normalize(trace_endpos - ad)); + ad = AnglesTransform_ToAngles(AnglesTransform_LeftDivide(AnglesTransform_FromAngles(tank.angles), AnglesTransform_FromAngles(ad))) - tank.tur_head.angles; + ad = AnglesTransform_Normalize(ad, TRUE); + //UpdateAuxiliaryXhair(player, trace_endpos, ('1 0 0' * player.vehicle_reload2) + ('0 1 0' * (1 - player.vehicle_reload2)), 2); + + // rotate turret and head + ftmp = autocvar_g_vehicle_tankll48_head_turnspeed * sys_frametime; + ad_y = bound(-ftmp, ad_y, ftmp); + tank.gun3.angles_y = bound(autocvar_g_vehicle_tankll48_turret_turnlimit * -1, tank.gun3.angles_y + ad_y, autocvar_g_vehicle_tankll48_turret_turnlimit); + tank.tur_head.angles_y = bound(autocvar_g_vehicle_tankll48_head_turnlimit * -1, tank.tur_head.angles_y + ad_y, autocvar_g_vehicle_tankll48_head_turnlimit); + + + // Pitch head + ad_x = bound(ftmp * -1, ad_x, ftmp); + tank.tur_head.angles_x = bound(autocvar_g_vehicle_tankll48_head_pitchlimit_down, tank.tur_head.angles_x + ad_x, autocvar_g_vehicle_tankll48_head_pitchlimit_up); + + + //fixedmakevectors(tank.angles); + makevectors(tank.angles + '-2 0 0' * tank.angles_x); + + movelib_groundalign4point(autocvar_g_vehicle_tankll48_springlength, autocvar_g_vehicle_tankll48_springup, autocvar_g_vehicle_tankll48_springblend, autocvar_g_vehicle_tankll48_tiltlimit); + + if(tank.flags & FL_ONGROUND) + { + if(tank.frame == 4 && self.tur_head.wait != 0) + { + tank.frame = 5; + } + + if(vlen(player.movement) == 0) + { + if(self.sound_nexttime < time || self.delay != 3) + { + self.delay = 3; + self.sound_nexttime = time + 6.009; //soundlength("vehicles/tankll48_idle.wav"); + //dprint("tankll48_idle:", ftos(soundlength("vehicles/tankll48_idle.wav")), "\n"); + sound (self, CH_TRIGGER_SINGLE, "machines/generator_loop_pitchdown.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + movelib_beak_simple(autocvar_g_vehicle_tankll48_speed_stop); + tank.frame = 5; + } + else + { + // Turn Body + if(player.movement_x == 0 && player.movement_y != 0) + ftmp = autocvar_g_vehicle_tankll48_turnspeed_strafe * sys_frametime; + else + ftmp = autocvar_g_vehicle_tankll48_turnspeed * sys_frametime; + + ftmp = bound(-ftmp, tank.tur_head.angles_y, ftmp); + tank.angles_y = anglemods(tank.angles_y + ftmp); + tank.tur_head.angles_y -= ftmp; + + if(player.movement_x != 0) + { + if(player.movement_x > 0) + { + player.movement_x = 1; + tank.frame = 0; + } + else if(player.movement_x < 0) + { + player.movement_x = -1; + tank.frame = 1; + } + player.movement_y = 0; + movelib_move_simple(normalize(v_forward * player.movement_x),autocvar_g_vehicle_tankll48_speed_walk,autocvar_g_vehicle_tankll48_movement_inertia); + + if(self.sound_nexttime < time || self.delay != 1) + { + self.delay = 1; + self.sound_nexttime = time + 3.991; //soundlength("vehicles/tankll48_walk.wav"); + sound (self, CH_TRIGGER_SINGLE, "machines/generator_loop_speedup_pitchdown.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + //dprint("tankll48_walk:", ftos(soundlength("vehicles/tankll48_walk.wav")), "\n"); + } + } + else if(player.movement_y != 0) + { + if(player.movement_y < 0) + { + player.movement_y = -1; + tank.frame = 2; + } + else if(player.movement_y > 0) + { + player.movement_y = 1; + tank.frame = 3; + } + + movelib_move_simple(normalize(v_right * player.movement_y),autocvar_g_vehicle_tankll48_speed_strafe,autocvar_g_vehicle_tankll48_movement_inertia); + if(self.sound_nexttime < time || self.delay != 2) + { + self.delay = 2; + self.sound_nexttime = time + 3.991; //soundlength("vehicles/tankll48_strafe.wav"); + sound (self, CH_TRIGGER_SINGLE, "machines/generator_loop_speedup_pitchdown.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + //dprint("tankll48_strafe:", ftos(soundlength("vehicles/tankll48_strafe.wav")), "\n"); + } + } + } + } + + self.angles_x = bound(-autocvar_g_vehicle_tankll48_tiltlimit, self.angles_x, autocvar_g_vehicle_tankll48_tiltlimit); + self.angles_z = bound(-autocvar_g_vehicle_tankll48_tiltlimit, self.angles_z, autocvar_g_vehicle_tankll48_tiltlimit); + + if(!forbidWeaponUse(player)) + if(player.BUTTON_ATCK && tank.vehicle_ammo1 >= autocvar_g_vehicle_tankll48_cannon_ammo && tank.tur_head.attack_finished_single <= time) + { + tank.cnt = time; + entity missile = spawn(); + vector v = gettaginfo(self.gun1, gettagindex(self.gun1, "barrels")); + W_SetupShot_ProjectileSize (player, '-3 -3 -3', '3 3 3', FALSE, 5, W_Sound("campingrifle_fire_morebass"), CH_WEAPON_A, autocvar_g_vehicle_tankll48_cannon_damage); + tank.vehicle_ammo1 -= autocvar_g_vehicle_tankll48_cannon_ammo; + w_shotorg = v; + + Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + PROJECTILE_MAKETRIGGER(missile); + + missile.owner = tank; + missile.realowner = player; + missile.bot_dodge = TRUE; + missile.bot_dodgerating = autocvar_g_vehicle_tankll48_cannon_damage * 2; + + missile.takedamage = DAMAGE_NO; + missile.event_damage = func_null; + missile.damagedbycontents = TRUE; + missile.movetype = MOVETYPE_FLY; + + missile.projectiledeathtype = DEATH_VH_TANKLL48; + setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot + + setorigin (missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point + W_SetupProjVelocity_Basic(missile, autocvar_g_vehicle_tankll48_cannon_speed, 0); + + missile.touch = tankll48_cannon_touch; + + missile.think = tankll48_cannon_think; + missile.cnt = time + 15; + missile.nextthink = time; + missile.flags = FL_PROJECTILE; + missile.pos1 = missile.velocity; + + CSQCProjectile(missile, TRUE, PROJECTILE_CANNONBALL, FALSE); + } + else + vehicles_regen(tank.cnt, vehicle_ammo1, autocvar_g_vehicle_tankll48_cannon_ammo_max, + autocvar_g_vehicle_tankll48_cannon_ammo_regen_pause, + autocvar_g_vehicle_tankll48_cannon_ammo_regen, frametime, FALSE); + + if(self.vehicle_flags & VHF_SHIELDREGEN) + vehicles_regen(tank.dmg_time, vehicle_shield, autocvar_g_vehicle_tankll48_shield, autocvar_g_vehicle_tankll48_shield_regen_pause, autocvar_g_vehicle_tankll48_shield_regen, frametime, TRUE); + + if(self.vehicle_flags & VHF_HEALTHREGEN) + vehicles_regen(tank.dmg_time, vehicle_health, autocvar_g_vehicle_tankll48_health, autocvar_g_vehicle_tankll48_health_regen_pause, autocvar_g_vehicle_tankll48_health_regen, frametime, FALSE); + + player.BUTTON_ATCK = player.BUTTON_ATCK2 = 0; + + if(tank.gun2.cnt <= time) + player.vehicle_reload2 = 100; + else + player.vehicle_reload2 = 100 - ((tank.gun2.cnt - time) / tank.attack_finished_single) * 100; + + setorigin(player, tank.origin + '0 0 1' * tank.maxs_z); + player.velocity = tank.velocity; + + VEHICLE_UPDATE_PLAYER(player, health, tankll48); + + if(self.vehicle_flags & VHF_HASSHIELD) + VEHICLE_UPDATE_PLAYER(player, shield, tankll48); + + self = player; + return 1; +} + +void tankll48_exit(float eject) +{ + entity e; + vector spot; + + e = findchain(classname,"tankll48_rocket"); + while(e) + { + if(e.owner == self.owner) + { + e.realowner = self.owner; + e.owner = world; + } + e = e.chain; + } + + self.think = vehicles_think; + self.nextthink = time; + self.frame = 5; + self.movetype = MOVETYPE_WALK; + + if(!self.owner) + return; + + makevectors(self.angles); + if(eject) + { + spot = self.origin + v_forward * 300 + '0 0 64'; + spot = vehicles_findgoodexit(spot); + setorigin(self.owner , spot); + self.owner.velocity = (v_up + v_forward * 0.25) * 750; + self.owner.oldvelocity = self.owner.velocity; + } + else + { + if(vlen(self.velocity) > autocvar_g_vehicle_tankll48_speed_strafe) + { + self.owner.velocity = normalize(self.velocity) * vlen(self.velocity); + self.owner.velocity_z += 200; + spot = self.origin + v_forward * 328 + '0 0 64'; + spot = vehicles_findgoodexit(spot); + } + else + { + self.owner.velocity = self.velocity * 0.5; + self.owner.velocity_z += 10; + spot = self.origin + v_forward * 356 + '0 0 64'; + spot = vehicles_findgoodexit(spot); + } + self.owner.oldvelocity = self.owner.velocity; + setorigin(self.owner , spot); + } + + antilag_clear(self.owner); + self.owner = world; +} + +void tankll48_headfade() +{ + self.think = tankll48_headfade; + self.nextthink = self.fade_time; + self.alpha = 1 - (time - self.fade_time) * self.fade_rate; + + if(self.cnt < time || self.alpha < 0.1) + { + if(self.alpha > 0.1) + { + sound (self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_EXPLOSION_BIG, self.origin + '0 0 100', '0 0 0', 1); + } + remove(self); + } +} + +void tankll48_blowup() +{ + if(self.cnt > time) + { + if(random() < 0.1) + { + sound (self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_EXPLOSION_SMALL, randomvec() * 80 + (self.origin + '0 0 100'), '0 0 0', 1); + } + self.nextthink = time + 0.1; + return; + } + + entity h, g1, b; + b = spawn(); + h = spawn(); + g1 = spawn(); + + setmodel(b, "models/vehicles/tankll48.md3"); + setmodel(h, "models/vehicles/tankll48_turret.md3"); + setmodel(g1, "models/vehicles/tankll48_cannon.md3"); + + setorigin(b, self.origin); + b.frame = 11; + b.angles = self.angles; + setsize(b, self.mins, self.maxs); + + setorigin(h, gettaginfo(self, gettagindex(self, "tag_head"))); + h.movetype = MOVETYPE_BOUNCE; + h.solid = SOLID_BBOX; + h.velocity = v_up * (500 + random() * 500) + randomvec() * 128; + h.modelflags = MF_ROCKET; + h.effects = EF_FLAME | EF_LOWPRECISION; + h.avelocity = randomvec() * 360; + + h.alpha = 1; + h.cnt = time + (3.5 * random()); + h.fade_rate = 1 / min(self.respawntime, 10); + h.fade_time = time; + h.think = tankll48_headfade; + h.nextthink = time; + + setorigin(g1, gettaginfo(self.tur_head, gettagindex(self.tur_head, "tag_hardpoint01"))); + g1.movetype = MOVETYPE_TOSS; + g1.solid = SOLID_CORPSE; + g1.velocity = v_forward * 700 + (randomvec() * 32); + g1.avelocity = randomvec() * 180; + + h.colormod = b.colormod = g1.colormod = '-2 -2 -2'; + + SUB_SetFade(b, time + 5, min(self.respawntime, 1)); + //SUB_SetFade(h, time, min(self.respawntime, 10)); + SUB_SetFade(g1, time, min(self.respawntime, 10)); + + RadiusDamage (self, self.enemy, 250, 15, 250, world, world, 250, DEATH_VH_TANK_DEATH, world); + + self.alpha = self.tur_head.alpha = self.gun1.alpha = self.gun2.alpha = -1; + self.movetype = MOVETYPE_NONE; + self.deadflag = DEAD_DEAD; + self.solid = SOLID_NOT; + self.tur_head.effects &= ~EF_FLAME; + self.vehicle_hudmodel.viewmodelforclient = self; +} + +void spawnfunc_vehicle_tankll48() +{ + if(!autocvar_g_vehicles_extra) { remove(self); return; } + if(!autocvar_g_vehicle_tankll48) { remove(self); return; } + if(!vehicle_initialize(VEH_TANKLL48, FALSE)) { remove(self); return; } +} + +float v_tankll48(float req) +{ + switch(req) + { + case VR_IMPACT: + { + if(autocvar_g_vehicle_tankll48_bouncepain) + vehicles_impact(autocvar_g_vehicle_tankll48_bouncepain_x, autocvar_g_vehicle_tankll48_bouncepain_y, autocvar_g_vehicle_tankll48_bouncepain_z); + + return TRUE; + } + case VR_ENTER: + { + self.movetype = MOVETYPE_WALK; + CSQCVehicleSetup(self.owner, 0); + self.owner.vehicle_health = (self.vehicle_health / autocvar_g_vehicle_tankll48_health) * 100; + self.owner.vehicle_shield = (self.vehicle_shield / autocvar_g_vehicle_tankll48_shield) * 100; + self.gun2.colormap = self.colormap; + + if(self.owner.flagcarried) + { + setattachment(self.owner.flagcarried, self.tur_head, ""); + setorigin(self.owner.flagcarried, '-20 0 120'); + } + + return TRUE; + } + case VR_THINK: + { + if(self.flags & FL_ONGROUND) + movelib_beak_simple(autocvar_g_vehicle_tankll48_speed_stop); + + return TRUE; + } + case VR_DEATH: + { + self.health = 0; + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + self.touch = func_null; + self.cnt = 3.4 + time + random() * 2; + self.think = tankll48_blowup; + self.nextthink = time; + self.deadflag = DEAD_DYING; + self.frame = 5; + self.tur_head.effects |= EF_FLAME; + self.colormod = self.tur_head.colormod = self.gun1.colormod = self.gun2.colormod = '-1 -1 -1'; + self.frame = 10; + self.movetype = MOVETYPE_TOSS; + + CSQCModel_UnlinkEntity(); // networking the death scene would be a nightmare + + return TRUE; + } + case VR_SPAWN: + { + if(!self.gun1) + { + self.gun3 = spawn(); // Will be an angle stabilizer for the rotating turret + self.gun3.alpha = -1; + setmodel(self.gun3, "null"); + setattachment(self.gun3, self.tur_head, "tag_cannon_pivot"); + + self.gun1 = spawn(); + self.gun2 = spawn(); + setmodel(self.gun1, "null"); + setmodel(self.gun2, "models/vehicles/tankll48_cannon.md3"); + setattachment(self.gun1, self.tur_head, "tag_gunpivot2"); + setattachment(self.gun2, self.tur_head, "tag_cannon_pivot"); + self.gravity = 2; + self.mass = 5000; + } + + self.frame = 5; + self.tur_head.frame = 1; + self.movetype = MOVETYPE_WALK; + self.solid = SOLID_SLIDEBOX; + self.alpha = self.tur_head.alpha = self.gun1.alpha = self.gun2.alpha = 1; + self.colormod = self.tur_head.colormod = self.gun1.colormod = self.gun2.colormod = '1 1 1'; + self.gun2.colormap = self.colormap; + self.tur_head.angles = '0 0 0'; + self.vehicle_exit = tankll48_exit; + + setorigin(self, self.pos1 + '0 0 128'); + self.angles = self.pos2; + self.damageforcescale = 0.03; + self.vehicle_health = autocvar_g_vehicle_tankll48_health; + self.vehicle_shield = autocvar_g_vehicle_tankll48_shield; + + self.PlayerPhysplug = tankll48_frame; + + return TRUE; + } + case VR_SETUP: + { + if(autocvar_g_vehicle_tankll48_shield) + self.vehicle_flags |= VHF_HASSHIELD; + + if(autocvar_g_vehicle_tankll48_shield_regen) + self.vehicle_flags |= VHF_SHIELDREGEN; + + if(autocvar_g_vehicle_tankll48_health_regen) + self.vehicle_flags |= VHF_HEALTHREGEN; + + self.respawntime = autocvar_g_vehicle_tankll48_respawntime; + self.vehicle_health = autocvar_g_vehicle_tankll48_health; + self.vehicle_shield = autocvar_g_vehicle_tankll48_shield; + self.max_health = self.vehicle_health; + self.pushable = TRUE; // tankll48 can use jumppads + setorigin(self.tur_head, '0 0 110'); + setorigin(self.vehicle_hudmodel, '0 0 50'); + + return TRUE; + } + case VR_PRECACHE: + { + precache_model ("models/vhshield.md3"); + precache_model ("models/vehicles/tankll48.md3"); + precache_model ("models/vehicles/tankll48_turret.md3"); + precache_model ( "models/uziflash.md3"); + + precache_model ( "models/vehicles/tankll48_cannon.md3"); + + precache_sound (W_Sound("rocket_impact")); + precache_sound (W_Sound("campingrifle_fire_morebass")); + + precache_sound ( "machines/generator_loop_speedup_pitchdown.wav"); + precache_sound ( "machines/generator_loop_pitchdown.wav"); + + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +#define tank_ico "gfx/vehicles/tankll48.tga" + +float v_tankll48(float req) +{ + switch(req) + { + case VR_HUD: + { + if(autocvar_r_letterbox) + return TRUE; + + vector picsize, hudloc = '0 0 0', pic2size, picloc; + + // Fetch health & ammo stats + HUD_GETVEHICLESTATS + + picsize = draw_getimagesize(hud_bg) * autocvar_cl_vehicles_hudscale; + hudloc_y = vid_conheight - picsize_y; + hudloc_x = vid_conwidth * 0.5 - picsize_x * 0.5; + + drawpic(hudloc, hud_bg, picsize, '1 1 1', autocvar_cl_vehicles_hudalpha, DRAWFLAG_NORMAL); + + ammo1 *= 0.01; + shield *= 0.01; + vh_health *= 0.01; + reload2 *= 0.01; + + pic2size = draw_getimagesize(tank_ico) * (autocvar_cl_vehicles_hudscale * 0.8); + picloc = picsize * 0.5 - pic2size * 0.5; + if(vh_health < 0.25) + drawpic(hudloc + picloc, tank_ico, pic2size, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, tank_ico, pic2size, '1 1 1' * vh_health + '1 0 0' * (1 - vh_health), 1, DRAWFLAG_NORMAL); + //drawpic(hudloc + picloc, tank_rkt, pic2size, '1 1 1' * reload2 + '1 0 0' * (1 - reload2), 1, DRAWFLAG_NORMAL); + //drawpic(hudloc + picloc, tank_mgun, pic2size, '1 1 1' * ammo1 + '1 0 0' * (1 - ammo1), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, hud_sh, pic2size, '1 1 1', shield, DRAWFLAG_NORMAL); + + // Health bar + picsize = draw_getimagesize(hud_hp_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - vh_health)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_hp_bar, picsize, '1 1 1', 1 , DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picsize = draw_getimagesize(hud_hp_ico) * autocvar_cl_vehicles_hudscale; + picloc = '37 65 0' * autocvar_cl_vehicles_hudscale; + if(vh_health < 0.25) + { + if(alarm1time < time) + { + alarm1time = time + 2; + vehicle_alarm(self, CH_PAIN_SINGLE, "vehicles/alarm.wav"); + } + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm1time) + { + vehicle_alarm(self, CH_PAIN_SINGLE, "misc/null.wav"); + alarm1time = 0; + } + } + // Shield bar + picsize = draw_getimagesize(hud_sh_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 140 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - shield)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_sh_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picloc = '40 136 0' * autocvar_cl_vehicles_hudscale; + picsize = draw_getimagesize(hud_sh_ico) * autocvar_cl_vehicles_hudscale; + if(shield < 0.25) + { + if(alarm2time < time) + { + alarm2time = time + 1; + vehicle_alarm(self, CH_PAIN_SINGLE, "vehicles/alarm_shield.wav"); + } + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm2time) + { + vehicle_alarm(self, CH_PAIN_SINGLE, "misc/null.wav"); + alarm2time = 0; + } + } + + // Minigun bar + picsize = draw_getimagesize(hud_ammo1_bar) * autocvar_cl_vehicles_hudscale; + picloc = '450 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x, picloc_y, picsize_x * ammo1, vid_conheight); + drawpic(hudloc + picloc, hud_ammo1_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picsize = draw_getimagesize(hud_ammo2_ico) * autocvar_cl_vehicles_hudscale; + picloc = '664 60 0' * autocvar_cl_vehicles_hudscale; + if(ammo1 < 0.2) + drawpic(hudloc + picloc, hud_ammo2_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, hud_ammo2_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + + if (scoreboard_showscores) + HUD_DrawScoreboard(); + + return TRUE; + } + case VR_SETUP: + { + // Minigun1 + AuxiliaryXhair[0].axh_image = "gfx/vehicles/axh-ring.tga"; + AuxiliaryXhair[0].axh_scale = 0.25; + // Minigun2 + AuxiliaryXhair[1].axh_image = "gfx/vehicles/axh-ring.tga"; + AuxiliaryXhair[1].axh_scale = 0.25; + // Rocket + AuxiliaryXhair[2].axh_image = "gfx/vehicles/axh-special1.tga"; + AuxiliaryXhair[2].axh_scale = 0.5; + + return TRUE; + } + case VR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_VEHICLE diff --git a/qcsrc/common/vehicles/unit/yugo.qc b/qcsrc/common/vehicles/unit/yugo.qc new file mode 100644 index 000000000..7facf7596 --- /dev/null +++ b/qcsrc/common/vehicles/unit/yugo.qc @@ -0,0 +1,661 @@ +#ifdef REGISTER_VEHICLE +REGISTER_VEHICLE( +/* VEH_##id */ YUGO, +/* function */ v_yugo, +/* spawnflags */ 0, +/* mins,maxs */ '-50 -50 -50', '50 50 50', +/* model */ "models/vehicles/yugo.dpm", +/* head_model */ "null", +/* hud_model */ "null", +/* tags */ "", "", "tag_viewport", +/* netname */ "yugo", +/* fullname */ _("Yugo") +); +#else +#ifdef SVQC +float autocvar_g_vehicle_yugo; + +float autocvar_g_vehicle_yugo_speed_afterburn; +float autocvar_g_vehicle_yugo_afterburn_cost; + +var float autocvar_g_vehicle_yugo_cloak_cost = 20; +var float autocvar_g_vehicle_yugo_supercloak_cost = 1000; + +float autocvar_g_vehicle_yugo_anglestabilizer; +float autocvar_g_vehicle_yugo_downforce; + +float autocvar_g_vehicle_yugo_speed_forward; +float autocvar_g_vehicle_yugo_speed_strafe; +float autocvar_g_vehicle_yugo_springlength; +float autocvar_g_vehicle_yugo_upforcedamper; +float autocvar_g_vehicle_yugo_friction; + +float autocvar_g_vehicle_yugo_hovertype; +float autocvar_g_vehicle_yugo_hoverpower; +float autocvar_g_vehicle_yugo_hoverpower_idle; + +float autocvar_g_vehicle_yugo_turnroll; +float autocvar_g_vehicle_yugo_turnspeed; +float autocvar_g_vehicle_yugo_pitchspeed; + +float autocvar_g_vehicle_yugo_energy; +float autocvar_g_vehicle_yugo_energy_regen; +float autocvar_g_vehicle_yugo_energy_regen_pause; + +float autocvar_g_vehicle_yugo_health; +float autocvar_g_vehicle_yugo_health_regen; +float autocvar_g_vehicle_yugo_health_regen_pause; + +float autocvar_g_vehicle_yugo_shield; +float autocvar_g_vehicle_yugo_shield_regen; +float autocvar_g_vehicle_yugo_shield_regen_pause; + +float autocvar_g_vehicle_yugo_respawntime; + +float autocvar_g_vehicle_yugo_blowup_radius; +float autocvar_g_vehicle_yugo_blowup_coredamage; +float autocvar_g_vehicle_yugo_blowup_edgedamage; +float autocvar_g_vehicle_yugo_blowup_forceintensity; + +float autocvar_g_vehicle_yugo_bouncefactor; +float autocvar_g_vehicle_yugo_bouncestop; +vector autocvar_g_vehicle_yugo_bouncepain; + +var vector yugo_force_from_tag(string tag_name, float spring_length, float max_power); + +void yugo_align4point(float _delta) +{ + vector push_vector; + float fl_push, fr_push, bl_push, br_push; + float hoverpower = ((self.owner) ? autocvar_g_vehicle_yugo_hoverpower : autocvar_g_vehicle_yugo_hoverpower_idle); + + push_vector = yugo_force_from_tag("tag_engine_fr", autocvar_g_vehicle_yugo_springlength, hoverpower); + fr_push = force_fromtag_normpower; + //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_yugo_collision_multiplier); + + push_vector += yugo_force_from_tag("tag_engine_fl", autocvar_g_vehicle_yugo_springlength, hoverpower); + fl_push = force_fromtag_normpower; + //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_yugo_collision_multiplier); + + push_vector += yugo_force_from_tag("tag_engine_br", autocvar_g_vehicle_yugo_springlength, hoverpower); + br_push = force_fromtag_normpower; + //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_yugo_collision_multiplier); + + push_vector += yugo_force_from_tag("tag_engine_bl", autocvar_g_vehicle_yugo_springlength, hoverpower); + bl_push = force_fromtag_normpower; + //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_yugo_collision_multiplier); + + self.velocity += push_vector * _delta; + + if(pointcontents(self.origin - '0 0 64') == CONTENT_WATER) + self.velocity_z += 200; + + // Anti ocilation + if(self.velocity_z > 0) + self.velocity_z *= 1 - autocvar_g_vehicle_yugo_upforcedamper * _delta; + + push_vector_x = (fl_push - bl_push); + push_vector_x += (fr_push - br_push); + push_vector_x *= 360; + + push_vector_z = (fr_push - fl_push); + push_vector_z += (br_push - bl_push); + push_vector_z *= 360; + + // Apply angle diffrance + self.angles_z += push_vector_z * _delta; + self.angles_x += push_vector_x * _delta; + + // Apply stabilizer + self.angles_x *= 1 - (autocvar_g_vehicle_yugo_anglestabilizer * _delta); + self.angles_z *= 1 - (autocvar_g_vehicle_yugo_anglestabilizer * _delta); +} + +float yugo_frame() +{ + entity player, yugo; + vector df; + float ftmp; + + if(intermission_running) + { + self.vehicle.velocity = '0 0 0'; + self.vehicle.avelocity = '0 0 0'; + return 1; + } + + player = self; + yugo = self.vehicle; + self = yugo; + + player.BUTTON_ZOOM = player.BUTTON_CROUCH = 0; + + vehicles_painframe(); + + if(yugo.deadflag != DEAD_NO) + { + self = player; + player.BUTTON_ATCK = player.BUTTON_ATCK2 = 0; + return 1; + } + + yugo_align4point(frametime); + + crosshair_trace(player); + + yugo.angles_x *= -1; + + // Yaw + ftmp = autocvar_g_vehicle_yugo_turnspeed * frametime; + ftmp = bound(-ftmp, shortangle_f(player.v_angle_y - yugo.angles_y, yugo.angles_y), ftmp); + yugo.angles_y = anglemods(yugo.angles_y + ftmp); + + // Roll + yugo.angles_z += -ftmp * autocvar_g_vehicle_yugo_turnroll * frametime; + + // Pitch + ftmp = autocvar_g_vehicle_yugo_pitchspeed * frametime; + ftmp = bound(-ftmp, shortangle_f(player.v_angle_x - yugo.angles_x, yugo.angles_x), ftmp); + yugo.angles_x = bound(-30, anglemods(yugo.angles_x + ftmp), 30); + + makevectors(yugo.angles); + yugo.angles_x *= -1; + + //ftmp = yugo.velocity_z; + df = yugo.velocity * -autocvar_g_vehicle_yugo_friction; + //yugo.velocity_z = ftmp; + + if(vlen(player.movement) != 0) + { + if(player.movement_x) + df += v_forward * ((player.movement_x > 0) ? autocvar_g_vehicle_yugo_speed_forward : -autocvar_g_vehicle_yugo_speed_forward); + + if(player.movement_y) + df += v_right * ((player.movement_y > 0) ? autocvar_g_vehicle_yugo_speed_strafe : -autocvar_g_vehicle_yugo_speed_strafe); + + if(self.sound_nexttime < time || self.sounds != 1) + { + self.sounds = 1; + self.sound_nexttime = time + 10.922667; //soundlength("vehicles/yugo_move.wav"); + sound (self, CH_TRIGGER_SINGLE, "vehicles/racer_move.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + } + else + { + if(self.sound_nexttime < time || self.sounds != 0) + { + self.sounds = 0; + self.sound_nexttime = time + 11.888604; //soundlength("vehicles/yugo_idle.wav"); + sound (self, CH_TRIGGER_SINGLE, "vehicles/racer_idle.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + } + + // Afterburn + if (player.BUTTON_JUMP && yugo.vehicle_energy >= (autocvar_g_vehicle_yugo_afterburn_cost * frametime)) + { + if(time - yugo.wait > 0.2) + pointparticles(particleeffectnum("yugo_booster_smoke"), self.origin - v_forward * 32, v_forward * vlen(self.velocity), 1); + + yugo.wait = time; + yugo.vehicle_energy -= autocvar_g_vehicle_yugo_afterburn_cost * frametime; + df += (v_forward * autocvar_g_vehicle_yugo_speed_afterburn); + + if(yugo.invincible_finished < time) + { + traceline(yugo.origin, yugo.origin - '0 0 256', MOVE_NORMAL, self); + if(trace_fraction != 1.0) + pointparticles(particleeffectnum("smoke_small"), trace_endpos, '0 0 0', 1); + + yugo.invincible_finished = time + 0.1 + (random() * 0.1); + } + + if(yugo.strength_finished < time) + { + yugo.strength_finished = time + 10.922667; //soundlength("vehicles/yugo_boost.wav"); + sound (yugo.tur_head, CH_TRIGGER_SINGLE, "vehicles/racer_boost.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + } + else + { + yugo.strength_finished = 0; + sound (yugo.tur_head, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + + if(player.BUTTON_ATCK && time - yugo.pain_finished >= 0.5 && yugo.vehicle_energy >= (autocvar_g_vehicle_yugo_cloak_cost * frametime)) + { + yugo.wait = time; + yugo.vehicle_energy -= autocvar_g_vehicle_yugo_cloak_cost * frametime; + + if(yugo.vehicle_energy <= 50) + yugo.alpha = 0.5; + else if(yugo.vehicle_energy <= 100) + yugo.alpha = 0.3; + else + yugo.alpha = 0.1; + } + else if(player.BUTTON_ATCK2 && time - yugo.pain_finished >= 0.5 && yugo.vehicle_energy >= (autocvar_g_vehicle_yugo_supercloak_cost * frametime)) + { + yugo.wait = time; + yugo.vehicle_energy -= autocvar_g_vehicle_yugo_supercloak_cost * frametime; + + yugo.alpha = -1; + } + else + yugo.alpha = 1; + + df -= v_up * (vlen(yugo.velocity) * autocvar_g_vehicle_yugo_downforce); + player.movement = yugo.velocity += df * frametime; + + player.vehicle_reload1 = bound(0, 100 * ((time - yugo.lip) / (yugo.delay - yugo.lip)), 100); + + if(yugo.vehicle_flags & VHF_SHIELDREGEN) + vehicles_regen(yugo.dmg_time, vehicle_shield, autocvar_g_vehicle_yugo_shield, autocvar_g_vehicle_yugo_shield_regen_pause, autocvar_g_vehicle_yugo_shield_regen, frametime, TRUE); + + if(yugo.vehicle_flags & VHF_HEALTHREGEN) + vehicles_regen(yugo.dmg_time, vehicle_health, autocvar_g_vehicle_yugo_health, autocvar_g_vehicle_yugo_health_regen_pause, autocvar_g_vehicle_yugo_health_regen, frametime, FALSE); + + if(yugo.vehicle_flags & VHF_ENERGYREGEN) + vehicles_regen(yugo.wait, vehicle_energy, autocvar_g_vehicle_yugo_energy, autocvar_g_vehicle_yugo_energy_regen_pause, autocvar_g_vehicle_yugo_energy_regen, frametime, FALSE); + + + VEHICLE_UPDATE_PLAYER(player, health, yugo); + VEHICLE_UPDATE_PLAYER(player, energy, yugo); + + if(yugo.vehicle_flags & VHF_HASSHIELD) + VEHICLE_UPDATE_PLAYER(player, shield, yugo); + + player.BUTTON_ATCK = player.BUTTON_ATCK2 = 0; + setorigin(player,yugo.origin + '0 0 32'); + player.velocity = yugo.velocity; + + self = player; + return 1; +} + +void yugo_think() +{ + self.nextthink = time; + + float pushdeltatime = time - self.lastpushtime; + if (pushdeltatime > 0.15) pushdeltatime = 0; + self.lastpushtime = time; + if(!pushdeltatime) return; + + tracebox(self.origin, self.mins, self.maxs, self.origin - ('0 0 1' * autocvar_g_vehicle_yugo_springlength), MOVE_NOMONSTERS, self); + + vector df = self.velocity * -autocvar_g_vehicle_yugo_friction; + df_z += (1 - trace_fraction) * ((self.owner) ? autocvar_g_vehicle_yugo_hoverpower : autocvar_g_vehicle_yugo_hoverpower_idle) + sin(time * 2) * (autocvar_g_vehicle_yugo_springlength * 2); + + if(pointcontents(self.origin - '0 0 64') == CONTENT_WATER) + self.velocity_z += 200; + + self.velocity += df * pushdeltatime; + if(self.velocity_z > 0) + self.velocity_z *= 1 - autocvar_g_vehicle_yugo_upforcedamper * pushdeltatime; + + self.angles_x *= 1 - (autocvar_g_vehicle_yugo_anglestabilizer * pushdeltatime); + self.angles_z *= 1 - (autocvar_g_vehicle_yugo_anglestabilizer * pushdeltatime); + + CSQCMODEL_AUTOUPDATE(); +} + +void yugo_exit(float eject) +{ + vector spot; + + self.think = yugo_think; + self.nextthink = time; + self.movetype = MOVETYPE_BOUNCE; + self.alpha = 1; + sound (self.tur_head, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + + if(!self.owner) + return; + + makevectors(self.angles); + if(eject) + { + spot = self.origin + v_forward * 100 + '0 0 64'; + spot = vehicles_findgoodexit(spot); + setorigin(self.owner , spot); + self.owner.velocity = (v_up + v_forward * 0.25) * 750; + self.owner.oldvelocity = self.owner.velocity; + } + else + { + if(vlen(self.velocity) > 2 * autocvar_sv_maxairspeed) + { + self.owner.velocity = normalize(self.velocity) * autocvar_sv_maxairspeed * 2; + self.owner.velocity_z += 200; + spot = self.origin + v_forward * 32 + '0 0 32'; + spot = vehicles_findgoodexit(spot); + } + else + { + self.owner.velocity = self.velocity * 0.5; + self.owner.velocity_z += 10; + spot = self.origin - v_forward * 200 + '0 0 32'; + spot = vehicles_findgoodexit(spot); + } + self.owner.oldvelocity = self.owner.velocity; + setorigin(self.owner , spot); + } + antilag_clear(self.owner); + self.owner = world; +} + +void yugo_blowup() +{ + self.deadflag = DEAD_DEAD; + self.vehicle_exit(VHEF_NORMAL); + + RadiusDamage (self, self.enemy, autocvar_g_vehicle_yugo_blowup_coredamage, + autocvar_g_vehicle_yugo_blowup_edgedamage, + autocvar_g_vehicle_yugo_blowup_radius, world, world, + autocvar_g_vehicle_yugo_blowup_forceintensity, + DEATH_VH_WAKI_DEATH, world); + + self.nextthink = time + autocvar_g_vehicle_yugo_respawntime; + self.think = vehicles_spawn; + self.movetype = MOVETYPE_NONE; + self.effects = EF_NODRAW; + + self.colormod = '0 0 0'; + self.avelocity = '0 0 0'; + self.velocity = '0 0 0'; + + setorigin(self, self.pos1); +} + +void yugo_blowup_think() +{ + self.nextthink = time; + + if(time >= self.delay) + yugo_blowup(); + + CSQCMODEL_AUTOUPDATE(); +} + +void yugo_deadtouch() +{ + self.avelocity_x *= 0.7; + self.cnt -= 1; + if(self.cnt <= 0) + yugo_blowup(); +} + +void spawnfunc_vehicle_yugo() +{ + if(!autocvar_g_vehicles_extra) { remove(self); return; } + if(!autocvar_g_vehicle_yugo) { remove(self); return; } + if(!vehicle_initialize(VEH_YUGO, FALSE)) { remove(self); return; } +} + +float v_yugo(float req) +{ + switch(req) + { + case VR_IMPACT: + { + if(autocvar_g_vehicle_yugo_bouncepain) + vehicles_impact(autocvar_g_vehicle_yugo_bouncepain_x, autocvar_g_vehicle_yugo_bouncepain_y, autocvar_g_vehicle_yugo_bouncepain_z); + return TRUE; + } + case VR_ENTER: + { + self.movetype = MOVETYPE_WALK; + self.owner.vehicle_health = (self.vehicle_health / autocvar_g_vehicle_yugo_health) * 100; + self.owner.vehicle_shield = (self.vehicle_shield / autocvar_g_vehicle_yugo_shield) * 100; + + if(self.owner.flagcarried) + setorigin(self.owner.flagcarried, '-190 0 96'); + + return TRUE; + } + case VR_THINK: + { + + return TRUE; + } + case VR_DEATH: + { + self.health = 0; + self.event_damage = func_null; + self.solid = SOLID_CORPSE; + self.takedamage = DAMAGE_NO; + self.deadflag = DEAD_DYING; + self.movetype = MOVETYPE_BOUNCE; + self.wait = time; + self.delay = 2 + time + random() * 3; + self.cnt = 1 + random() * 2; + self.touch = yugo_deadtouch; + + Send_Effect(EFFECT_EXPLOSION_MEDIUM, self.origin, '0 0 0', 1); + + if(random() < 0.5) + self.avelocity_z = 32; + else + self.avelocity_z = -32; + + self.avelocity_x = -vlen(self.velocity) * 0.2; + self.velocity += '0 0 700'; + self.colormod = '-0.5 -0.5 -0.5'; + + self.think = yugo_blowup_think; + self.nextthink = time; + + return TRUE; + } + case VR_SPAWN: + { + if(self.scale != 0.5) + { + if(autocvar_g_vehicle_yugo_hovertype) + yugo_force_from_tag = vehicles_force_fromtag_maglev; + else + yugo_force_from_tag = vehicles_force_fromtag_hover; + + // FIXME: this be hakkz, fix the models insted (scale body, add tag_viewport to the hudmodel). + self.scale = 0.5; + setattachment(self.vehicle_hudmodel, self, ""); + setattachment(self.vehicle_viewport, self, "tag_viewport"); + + self.mass = 900; + } + + self.think = yugo_think; + self.nextthink = time; + self.vehicle_health = autocvar_g_vehicle_yugo_health; + self.vehicle_shield = autocvar_g_vehicle_yugo_shield; + + self.movetype = MOVETYPE_TOSS; + self.solid = SOLID_SLIDEBOX; + self.delay = time; + self.scale = 0.5; + self.alpha = 1; + + self.PlayerPhysplug = yugo_frame; + + self.bouncefactor = autocvar_g_vehicle_yugo_bouncefactor; + self.bouncestop = autocvar_g_vehicle_yugo_bouncestop; + self.damageforcescale = 0.5; + self.vehicle_health = autocvar_g_vehicle_yugo_health; + self.vehicle_shield = autocvar_g_vehicle_yugo_shield; + + return TRUE; + } + case VR_SETUP: + { + if(autocvar_g_vehicle_yugo_energy) + if(autocvar_g_vehicle_yugo_energy_regen) + self.vehicle_flags |= VHF_ENERGYREGEN; + + if(autocvar_g_vehicle_yugo_shield) + self.vehicle_flags |= VHF_HASSHIELD; + + if(autocvar_g_vehicle_yugo_shield_regen) + self.vehicle_flags |= VHF_SHIELDREGEN; + + if(autocvar_g_vehicle_yugo_health_regen) + self.vehicle_flags |= VHF_HEALTHREGEN; + + self.vehicle_exit = yugo_exit; + self.respawntime = autocvar_g_vehicle_yugo_respawntime; + self.vehicle_health = autocvar_g_vehicle_yugo_health; + self.vehicle_shield = autocvar_g_vehicle_yugo_shield; + self.max_health = self.vehicle_health; + + return TRUE; + } + case VR_PRECACHE: + { + precache_sound (W_Sound("lasergun_fire")); + precache_sound (W_Sound("rocket_fire")); + + precache_sound ("vehicles/racer_idle.wav"); + precache_sound ("vehicles/racer_move.wav"); + precache_sound ("vehicles/racer_boost.wav"); + + precache_model ("models/vhshield.md3"); + precache_model ("models/vehicles/yugo.dpm"); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC + +#define yugo_ico "gfx/vehicles/yugo.tga" + +float v_yugo(float req) +{ + switch(req) + { + case VR_HUD: + { + if(autocvar_r_letterbox) + return TRUE; + + vector picsize, hudloc = '0 0 0', pic2size, picloc; + + // Fetch health & ammo stats + HUD_GETVEHICLESTATS + + picsize = draw_getimagesize(hud_bg) * autocvar_cl_vehicles_hudscale; + hudloc_y = vid_conheight - picsize_y; + hudloc_x = vid_conwidth * 0.5 - picsize_x * 0.5; + + drawpic(hudloc, hud_bg, picsize, '1 1 1', autocvar_cl_vehicles_hudalpha, DRAWFLAG_NORMAL); + + shield *= 0.01; + vh_health *= 0.01; + energy *= 0.01; + reload1 *= 0.01; + + pic2size = draw_getimagesize(yugo_ico) * (autocvar_cl_vehicles_hudscale * 0.8); + picloc = picsize * 0.5 - pic2size * 0.5; + if(vh_health < 0.25) + drawpic(hudloc + picloc, yugo_ico, pic2size, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, yugo_ico, pic2size, '1 1 1' * vh_health + '1 0 0' * (1 - vh_health), 1, DRAWFLAG_NORMAL); + //drawpic(hudloc + picloc, yugo_eng, pic2size, '1 1 1' * energy + '1 0 0' * (1 - energy), 1, DRAWFLAG_NORMAL); + //drawpic(hudloc + picloc, yugo_gun, pic2size, '1 1 1' * energy + '1 0 0' * (1 - energy), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, hud_sh, pic2size, '1 1 1', shield, DRAWFLAG_NORMAL); + + // Health bar + picsize = draw_getimagesize(hud_hp_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - vh_health)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_hp_bar, picsize, '1 1 1', 1 , DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picsize = draw_getimagesize(hud_hp_ico) * autocvar_cl_vehicles_hudscale; + picloc = '37 65 0' * autocvar_cl_vehicles_hudscale; + if(vh_health < 0.25) + { + if(alarm1time < time) + { + alarm1time = time + 2; + vehicle_alarm(self, CH_PAIN_SINGLE, "vehicles/alarm.wav"); + } + + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm1time) + { + vehicle_alarm(self, CH_PAIN_SINGLE, "misc/null.wav"); + alarm1time = 0; + } + } + + + // Shield bar + picsize = draw_getimagesize(hud_sh_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 140 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - shield)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_sh_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picloc = '40 136 0' * autocvar_cl_vehicles_hudscale; + picsize = draw_getimagesize(hud_sh_ico) * autocvar_cl_vehicles_hudscale; + if(shield < 0.25) + { + if(alarm2time < time) + { + alarm2time = time + 1; + vehicle_alarm(self, CH_TRIGGER_SINGLE, "vehicles/alarm_shield.wav"); + } + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm2time) + { + vehicle_alarm(self, CH_TRIGGER_SINGLE, "misc/null.wav"); + alarm2time = 0; + } + } + + // Gun bar + picsize = draw_getimagesize(hud_ammo1_bar) * autocvar_cl_vehicles_hudscale; + picloc = '450 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x, picloc_y, picsize_x * energy, vid_conheight); + drawpic(hudloc + picloc, hud_ammo1_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picsize = draw_getimagesize(hud_ammo1_ico) * autocvar_cl_vehicles_hudscale; + picloc = '664 60 0' * autocvar_cl_vehicles_hudscale; + if(energy < 0.2) + drawpic(hudloc + picloc, hud_ammo1_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, hud_ammo1_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + + if (scoreboard_showscores) + HUD_DrawScoreboard(); + return TRUE; + } + case VR_SETUP: + { + AuxiliaryXhair[0].axh_image = "gfx/vehicles/axh-bracket.tga"; + AuxiliaryXhair[0].axh_scale = 0.25; + return TRUE; + } + case VR_PRECACHE: + { + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_VEHICLE diff --git a/qcsrc/common/vehicles/vehicles.qc b/qcsrc/common/vehicles/vehicles.qc new file mode 100644 index 000000000..83fe5e348 --- /dev/null +++ b/qcsrc/common/vehicles/vehicles.qc @@ -0,0 +1,87 @@ +#include "all.qh" + +// VEHICLE PLUGIN SYSTEM +entity vehicle_info[VEH_MAXCOUNT]; +entity dummy_vehicle_info; + +void vehicles_common_initialize() +{ +#ifdef CSQC + precache_model("models/vehicles/bomblet.md3"); + precache_model("models/vehicles/clusterbomb.md3"); + precache_model("models/vehicles/clusterbomb_fragment.md3"); + precache_model("models/vehicles/rocket01.md3"); + precache_model("models/vehicles/rocket02.md3"); + + precache_sound ("vehicles/alarm.wav"); + precache_sound ("vehicles/alarm_shield.wav"); +#endif // CSQC +#ifdef SVQC + precache_sound("onslaught/ons_hit2.wav"); + precache_sound("onslaught/electricity_explode.wav"); + + addstat(STAT_HUD, AS_INT, hud); + addstat(STAT_VEHICLESTAT_HEALTH, AS_INT, vehicle_health); + addstat(STAT_VEHICLESTAT_SHIELD, AS_INT, vehicle_shield); + addstat(STAT_VEHICLESTAT_ENERGY, AS_INT, vehicle_energy); + + addstat(STAT_VEHICLESTAT_W2MODE, AS_INT, vehicle_weapon2mode); + + addstat(STAT_VEHICLESTAT_AMMO1, AS_INT, vehicle_ammo1); + addstat(STAT_VEHICLESTAT_RELOAD1, AS_INT, vehicle_reload1); + + addstat(STAT_VEHICLESTAT_AMMO2, AS_INT, vehicle_ammo2); + addstat(STAT_VEHICLESTAT_RELOAD2, AS_INT, vehicle_reload2); +#endif // SVQC +} + +void register_vehicle(float id, float(float) func, float vehicleflags, vector min_s, vector max_s, string modelname, string headmodelname, string hudmodelname, string headtag, string hudtag, string viewtag, string shortname, string vname) +{ + entity e; + vehicle_info[id - 1] = e = spawn(); + e.classname = "vehicle_info"; + e.vehicleid = id; + e.netname = shortname; + e.vehicle_name = vname; + e.vehicle_func = func; + e.mdl = modelname; + e.spawnflags = vehicleflags; + e.mins = min_s; + e.maxs = max_s; + e.model = modelname; + e.head_model = headmodelname; + e.hud_model = hudmodelname; + e.tag_head = headtag; + e.tag_hud = hudtag; + e.tag_view = viewtag; + + #ifndef MENUQC + vehicles_common_initialize(); + #endif +} +float v_null(float dummy) { return 0; } +void register_vehicles_done() +{ + dummy_vehicle_info = spawn(); + dummy_vehicle_info.classname = "vehicle_info"; + dummy_vehicle_info.vehicleid = 0; // you can recognize dummies by this + dummy_vehicle_info.netname = ""; + dummy_vehicle_info.vehicle_name = "Vehicle"; + dummy_vehicle_info.vehicle_func = v_null; + dummy_vehicle_info.mdl = ""; + dummy_vehicle_info.mins = '-0 -0 -0'; + dummy_vehicle_info.maxs = '0 0 0'; + dummy_vehicle_info.model = ""; + dummy_vehicle_info.head_model = ""; + dummy_vehicle_info.hud_model = ""; +} +entity get_vehicleinfo(float id) +{ + entity m; + if(id < VEH_FIRST || id > VEH_LAST) + return dummy_vehicle_info; + m = vehicle_info[id - 1]; + if(m) + return m; + return dummy_vehicle_info; +} diff --git a/qcsrc/common/vehicles/vehicles.qh b/qcsrc/common/vehicles/vehicles.qh new file mode 100644 index 000000000..db5bb8ddc --- /dev/null +++ b/qcsrc/common/vehicles/vehicles.qh @@ -0,0 +1,89 @@ +// vehicle requests +#define VR_SETUP 1 // (BOTH) setup vehicle data +#define VR_THINK 2 // (SERVER) logic to run every frame +#define VR_DEATH 3 // (SERVER) called when vehicle dies +#define VR_PRECACHE 4 // (BOTH) precaches models/sounds used by this vehicle +#define VR_ENTER 5 // (SERVER) called when a player enters this vehicle +#define VR_SPAWN 6 // (SERVER) called when the vehicle re-spawns +#define VR_IMPACT 7 // (SERVER) called when a vehicle hits something +#define VR_HUD 8 // (CLIENT) logic to run every frame + +// functions: +entity get_vehicleinfo(float id); + +// fields: +.entity tur_head; + +// flags: +.float vehicle_flags; +const float VHF_ISVEHICLE = 2; /// Indicates vehicle +const float VHF_HASSHIELD = 4; /// Vehicle has shileding +const float VHF_SHIELDREGEN = 8; /// Vehicles shield regenerates +const float VHF_HEALTHREGEN = 16; /// Vehicles health regenerates +const float VHF_ENERGYREGEN = 32; /// Vehicles energy regenerates +const float VHF_DEATHEJECT = 64; /// Vehicle ejects pilot upon fatal damage +const float VHF_MOVE_GROUND = 128; /// Vehicle moves on gound +const float VHF_MOVE_HOVER = 256; /// Vehicle hover close to gound +const float VHF_MOVE_FLY = 512; /// Vehicle is airborn +const float VHF_DMGSHAKE = 1024; /// Add random velocity each frame if health < 50% +const float VHF_DMGROLL = 2048; /// Add random angles each frame if health < 50% +const float VHF_DMGHEADROLL = 4096; /// Add random head angles each frame if health < 50% +const float VHF_MULTISLOT = 8192; /// Vehicle has multiple player slots +const float VHF_PLAYERSLOT = 16384; /// This ent is a player slot on a multi-person vehicle + + +// entity properties of vehicleinfo: +.float vehicleid; // VEH_... +.string netname; // short name +.string vehicle_name; // human readable name +.float(float) vehicle_func; // v_... +.string mdl; // currently a copy of the model +.string model; // full name of model +.string head_model; // full name of tur_head model +.string hud_model; // cockpit model +.string tag_head; // tur_head model tag +.string tag_hud; // hud model tag +.string tag_view; // cockpit model tag +.float() PlayerPhysplug; // player physics mod +.float spawnflags; +.vector mins, maxs; // vehicle hitbox size + +// other useful macros +#define VEH_ACTION(vehicletype,mrequest) (get_vehicleinfo(vehicletype)).vehicle_func(mrequest) +#define VEH_NAME(vehicletype) (get_vehicleinfo(vehicletype)).vehicle_name + +// ===================== +// Vehicle Registration +// ===================== + +float v_null(float dummy); +void register_vehicle(float id, float(float) func, float vehicleflags, vector min_s, vector max_s, string modelname, string headmodelname, string hudmodelname, string headtag, string hudtag, string viewtag, string shortname, string vname); +void register_vehicles_done(); + +const float VEH_MAXCOUNT = 24; +#define VEH_FIRST 1 +float VEH_COUNT; +float VEH_LAST; + +#define REGISTER_VEHICLE_2(id,func,vehicleflags,min_s,max_s,modelname,headmodelname,hudmodelname,headtag,hudtag,viewtag,shortname,vname) \ + float id; \ + float func(float); \ + void RegisterVehicles_##id() \ + { \ + VEH_LAST = (id = VEH_FIRST + VEH_COUNT); \ + ++VEH_COUNT; \ + register_vehicle(id,func,vehicleflags,min_s,max_s,modelname,headmodelname,hudmodelname,headtag,hudtag,viewtag,shortname,vname); \ + } \ + ACCUMULATE_FUNCTION(RegisterVehicles, RegisterVehicles_##id) +#ifdef MENUQC +#define REGISTER_VEHICLE(id,func,vehicleflags,min_s,max_s,modelname,headmodelname,hudmodelname,headtag,hudtag,viewtag,shortname,vname) \ + REGISTER_VEHICLE_2(VEH_##id,v_null,vehicleflags,min_s,max_s,modelname,headmodelname,hudmodelname,headtag,hudtag,viewtag,shortname,vname) +#else +#define REGISTER_VEHICLE(id,func,vehicleflags,min_s,max_s,modelname,headmodelname,hudmodelname,headtag,hudtag,viewtag,shortname,vname) \ + REGISTER_VEHICLE_2(VEH_##id,func,vehicleflags,min_s,max_s,modelname,headmodelname,hudmodelname,headtag,hudtag,viewtag,shortname,vname) +#endif + +#include "all.qh" + +#undef REGISTER_VEHICLE +ACCUMULATE_FUNCTION(RegisterVehicles, register_vehicles_done); diff --git a/qcsrc/common/vehicles/vehicles_include.qc b/qcsrc/common/vehicles/vehicles_include.qc new file mode 100644 index 000000000..85b923afa --- /dev/null +++ b/qcsrc/common/vehicles/vehicles_include.qc @@ -0,0 +1,8 @@ +#ifdef CSQC +#include "cl_vehicles.qc" +#include "vehicles.qc" +#endif // CSQC +#ifdef SVQC +#include "sv_vehicles.qc" +#include "vehicles.qc" +#endif // SVQC diff --git a/qcsrc/common/vehicles/vehicles_include.qh b/qcsrc/common/vehicles/vehicles_include.qh new file mode 100644 index 000000000..d37d749a4 --- /dev/null +++ b/qcsrc/common/vehicles/vehicles_include.qh @@ -0,0 +1,8 @@ +#ifdef CSQC +#include "vehicles.qh" +#include "cl_vehicles.qh" +#endif // CSQC +#ifdef SVQC +#include "vehicles.qh" +#include "sv_vehicles.qh" +#endif // SVQC diff --git a/qcsrc/common/weapons/all.qh b/qcsrc/common/weapons/all.qh index 4f4cd2b3d..6576fcd60 100644 --- a/qcsrc/common/weapons/all.qh +++ b/qcsrc/common/weapons/all.qh @@ -27,3 +27,7 @@ #include "w_arc.qc" #include "w_hmg.qc" #include "w_rpc.qc" + +// more other weapons +#include "w_revolver.qc" +#include "w_lightsabre.qc" diff --git a/qcsrc/common/weapons/w_arc.qc b/qcsrc/common/weapons/w_arc.qc index 80fcf5524..2607e47cd 100644 --- a/qcsrc/common/weapons/w_arc.qc +++ b/qcsrc/common/weapons/w_arc.qc @@ -233,12 +233,10 @@ void W_Arc_Beam_Think(void) || (self.owner.WEP_AMMO(ARC) <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) || - self.owner.deadflag != DEAD_NO + Player_Trapped(self.owner) || (!self.owner.BUTTON_ATCK && !burst ) || - self.owner.frozen - || (WEP_CVAR(arc, overheat_max) > 0 && self.beam_heat >= WEP_CVAR(arc, overheat_max)) ) { @@ -264,7 +262,7 @@ void W_Arc_Beam_Think(void) { pointparticles( particleeffectnum("arc_overheat"), self.beam_start, self.beam_wantdir, 1 ); - sound(self, CH_WEAPON_A, "weapons/arc_stop.wav", VOL_BASE, ATTN_NORM); + sound(self, CH_WEAPON_A, W_Sound("arc_stop"), VOL_BASE, ATTN_NORM); } } @@ -576,9 +574,7 @@ void W_Arc_Beam(float burst) // only play fire sound if 1 sec has passed since player let go the fire button if(time - self.beam_prev > 1) - { - sound(self, CH_WEAPON_A, "weapons/arc_fire.wav", VOL_BASE, ATTN_NORM); - } + sound(self, CH_WEAPON_A, W_Sound("arc_fire"), VOL_BASE, ATTN_NORM); entity beam = self.arc_beam = spawn(); beam.classname = "W_Arc_Beam"; @@ -613,7 +609,7 @@ void Arc_Smoke() if ( !self.arc_smoke_sound ) { self.arc_smoke_sound = 1; - sound(self, CH_SHOTS_SINGLE, "weapons/arc_loop_overheat.wav", VOL_BASE, ATTN_NORM); + sound(self, CH_SHOTS_SINGLE, W_Sound("arc_loop_overheat"), VOL_BASE, ATTN_NORM); } } } @@ -697,7 +693,7 @@ float W_Arc(float req) if(self.arc_BUTTON_ATCK_prev != 0) { - sound(self, CH_WEAPON_A, "weapons/arc_stop.wav", VOL_BASE, ATTN_NORM); + sound(self, CH_WEAPON_A, W_Sound("arc_stop"), VOL_BASE, ATTN_NORM); weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready); ATTACK_FINISHED(self) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor(); } @@ -718,13 +714,13 @@ float W_Arc(float req) } case WR_INIT: { - precache_model("models/weapons/g_arc.md3"); - precache_model("models/weapons/v_arc.md3"); - precache_model("models/weapons/h_arc.iqm"); - precache_sound("weapons/arc_fire.wav"); - precache_sound("weapons/arc_loop.wav"); - precache_sound("weapons/arc_stop.wav"); - precache_sound("weapons/arc_loop_overheat.wav"); + precache_model(W_Model("g_arc.md3")); + precache_model(W_Model("v_arc.md3")); + precache_model(W_Model("h_arc.iqm")); + precache_sound(W_Sound("arc_fire")); + precache_sound(W_Sound("arc_loop")); + precache_sound(W_Sound("arc_stop")); + precache_sound(W_Sound("arc_loop_overheat")); if(!arc_shotorigin[0]) { arc_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 1); diff --git a/qcsrc/common/weapons/w_blaster.qc b/qcsrc/common/weapons/w_blaster.qc index aa8d0a89b..fdb0e9e52 100644 --- a/qcsrc/common/weapons/w_blaster.qc +++ b/qcsrc/common/weapons/w_blaster.qc @@ -94,8 +94,8 @@ void W_Blaster_Attack( { vector s_forward = v_forward * cos(atk_shotangle * DEG2RAD) + v_up * sin(atk_shotangle * DEG2RAD); - W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, atk_damage); - pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + W_SetupShot_Dir(self, s_forward, FALSE, 3, W_Sound("lasergun_fire"), CH_WEAPON_B, atk_damage); + Send_Effect(EFFECT_LASER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); entity missile = spawn(); missile.owner = missile.realowner = self; @@ -226,10 +226,10 @@ float W_Blaster(float request) case WR_INIT: { - precache_model("models/weapons/g_laser.md3"); - precache_model("models/weapons/v_laser.md3"); - precache_model("models/weapons/h_laser.iqm"); - precache_sound("weapons/lasergun_fire.wav"); + precache_model(W_Model("g_laser.md3")); + precache_model(W_Model("v_laser.md3")); + precache_model(W_Model("h_laser.iqm")); + precache_sound(W_Sound("lasergun_fire")); BLASTER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } diff --git a/qcsrc/common/weapons/w_crylink.qc b/qcsrc/common/weapons/w_crylink.qc index d17826a92..c0571cc20 100644 --- a/qcsrc/common/weapons/w_crylink.qc +++ b/qcsrc/common/weapons/w_crylink.qc @@ -257,7 +257,7 @@ void W_Crylink_LinkJoinEffect_Think(void) e.projectiledeathtype, other ); - pointparticles(particleeffectnum("crylink_joinexplode"), self.origin, '0 0 0', n); + Send_Effect(EFFECT_CRYLINK_JOINEXPLODE, self.origin, '0 0 0', n); } } } @@ -353,13 +353,13 @@ void W_Crylink_Attack(void) if(WEP_CVAR_PRI(crylink, joinexplode)) maxdmg += WEP_CVAR_PRI(crylink, joinexplode_damage); - W_SetupShot(self, FALSE, 2, "weapons/crylink_fire.wav", CH_WEAPON_A, maxdmg); + W_SetupShot(self, FALSE, 2, W_Sound("crylink_fire"), CH_WEAPON_A, maxdmg); forward = v_forward; right = v_right; up = v_up; shots = WEP_CVAR_PRI(crylink, shots); - pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots); + Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots); proj = prevproj = firstproj = world; for(counter = 0; counter < shots; ++counter) { @@ -462,13 +462,13 @@ void W_Crylink_Attack2(void) if(WEP_CVAR_SEC(crylink, joinexplode)) maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage); - W_SetupShot(self, FALSE, 2, "weapons/crylink_fire2.wav", CH_WEAPON_A, maxdmg); + W_SetupShot(self, FALSE, 2, W_Sound("crylink_fire2"), CH_WEAPON_A, maxdmg); forward = v_forward; right = v_right; up = v_up; shots = WEP_CVAR_SEC(crylink, shots); - pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots); + Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots); proj = prevproj = firstproj = world; for(counter = 0; counter < shots; ++counter) { @@ -637,12 +637,12 @@ float W_Crylink(float req) } case WR_INIT: { - precache_model("models/weapons/g_crylink.md3"); - precache_model("models/weapons/v_crylink.md3"); - precache_model("models/weapons/h_crylink.iqm"); - precache_sound("weapons/crylink_fire.wav"); - precache_sound("weapons/crylink_fire2.wav"); - precache_sound("weapons/crylink_linkjoin.wav"); + precache_model(W_Model("g_crylink.md3")); + precache_model(W_Model("v_crylink.md3")); + precache_model(W_Model("h_crylink.iqm")); + precache_sound(W_Sound("crylink_fire")); + precache_sound(W_Sound("crylink_fire2")); + precache_sound(W_Sound("crylink_linkjoin")); CRYLINK_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } @@ -673,7 +673,7 @@ float W_Crylink(float req) } case WR_RELOAD: { - W_Reload(min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo)), "weapons/reload.wav"); + W_Reload(min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo)), W_Sound("reload")); return TRUE; } case WR_SUICIDEMESSAGE: diff --git a/qcsrc/common/weapons/w_devastator.qc b/qcsrc/common/weapons/w_devastator.qc index cff16702d..7460fe6d2 100644 --- a/qcsrc/common/weapons/w_devastator.qc +++ b/qcsrc/common/weapons/w_devastator.qc @@ -290,9 +290,9 @@ void W_Devastator_Think(void) if(!self.count) { - pointparticles(particleeffectnum("rocket_guide"), self.origin, self.velocity, 1); + Send_Effect(EFFECT_ROCKET_GUIDE, self.origin, self.velocity, 1); // TODO add a better sound here - sound(self.realowner, CH_WEAPON_B, "weapons/rocket_mode.wav", VOL_BASE, ATTN_NORM); + sound(self.realowner, CH_WEAPON_B, W_Sound("rocket_mode"), VOL_BASE, ATTN_NORM); self.count = 1; } } @@ -339,8 +339,8 @@ void W_Devastator_Attack(void) W_DecreaseAmmo(WEP_CVAR(devastator, ammo)); - W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", CH_WEAPON_A, WEP_CVAR(devastator, damage)); - pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', FALSE, 5, W_Sound("rocket_fire"), CH_WEAPON_A, WEP_CVAR(devastator, damage)); + Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); missile = WarpZone_RefSys_SpawnSameRefSys(self); missile.owner = missile.realowner = self; @@ -430,7 +430,7 @@ float W_Devastator(float req) { // aim and decide to fire if appropriate self.BUTTON_ATCK = bot_aim(WEP_CVAR(devastator, speed), 0, WEP_CVAR(devastator, lifetime), FALSE); - if(skill >= 2) // skill 0 and 1 bots won't detonate rockets! + if(bot_skill >= 2) // skill 0 and 1 bots won't detonate rockets! { // decide whether to detonate rockets entity missile, targetlist, targ; @@ -485,7 +485,7 @@ float W_Devastator(float req) } makevectors(missile.v_angle); targ = targetlist; - if(skill > 9) // normal players only do this for the target they are tracking + if(bot_skill > 9) // normal players only do this for the target they are tracking { targ = targetlist; while(targ) @@ -503,7 +503,7 @@ float W_Devastator(float req) if(v_forward * normalize(missile.origin - self.enemy.origin)< 0.1) if(IS_PLAYER(self.enemy)) if(desirabledamage >= 0.1*coredamage) - if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1)) + if(random()/distance*300 > frametime*bound(0,(10-bot_skill)*0.2,1)) self.BUTTON_ATCK2 = TRUE; // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n"); } @@ -514,7 +514,7 @@ float W_Devastator(float req) // but don't fire a new shot at the same time! if(desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events self.BUTTON_ATCK2 = TRUE; - if((skill > 6.5) && (selfdamage > self.health)) + if((bot_skill > 6.5) && (selfdamage > self.health)) self.BUTTON_ATCK2 = FALSE; //if(self.BUTTON_ATCK2 == TRUE) // dprint(ftos(desirabledamage),"\n"); @@ -556,7 +556,7 @@ float W_Devastator(float req) } } if(rockfound) - sound(self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM); + sound(self, CH_WEAPON_B, W_Sound("rocket_det"), VOL_BASE, ATTN_NORM); } } @@ -567,12 +567,12 @@ float W_Devastator(float req) //if(autocvar_sv_precacheweapons) //{ precache_model("models/flash.md3"); - precache_model("models/weapons/g_rl.md3"); - precache_model("models/weapons/v_rl.md3"); - precache_model("models/weapons/h_rl.iqm"); - precache_sound("weapons/rocket_det.wav"); - precache_sound("weapons/rocket_fire.wav"); - precache_sound("weapons/rocket_mode.wav"); + precache_model(W_Model("g_rl.md3")); + precache_model(W_Model("v_rl.md3")); + precache_model(W_Model("h_rl.iqm")); + precache_sound(W_Sound("rocket_det")); + precache_sound(W_Sound("rocket_fire")); + precache_sound(W_Sound("rocket_mode")); //} DEVASTATOR_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; @@ -634,7 +634,7 @@ float W_Devastator(float req) } case WR_RELOAD: { - W_Reload(WEP_CVAR(devastator, ammo), "weapons/reload.wav"); + W_Reload(WEP_CVAR(devastator, ammo), W_Sound("reload")); return TRUE; } case WR_SUICIDEMESSAGE: diff --git a/qcsrc/common/weapons/w_electro.qc b/qcsrc/common/weapons/w_electro.qc index 3ec86c782..c02c6be9f 100644 --- a/qcsrc/common/weapons/w_electro.qc +++ b/qcsrc/common/weapons/w_electro.qc @@ -47,6 +47,7 @@ REGISTER_WEAPON( w_cvar(id, sn, NONE, combo_edgedamage) \ w_cvar(id, sn, NONE, combo_force) \ w_cvar(id, sn, NONE, combo_radius) \ + w_cvar(id, sn, NONE, combo_simple) \ w_cvar(id, sn, NONE, combo_speed) \ w_cvar(id, sn, NONE, combo_safeammocheck) \ w_prop(id, sn, float, reloading_ammo, reload_ammo) \ @@ -68,7 +69,51 @@ void W_Electro_ExplodeCombo(void); #ifdef SVQC void spawnfunc_weapon_electro(void) { weapon_defaultspawnfunc(WEP_ELECTRO); } -void W_Electro_TriggerCombo(vector org, float rad, entity own) +void W_Electro_TriggerCombo_Simple(vector org, float rad, entity own) +{ + entity e; + for(e = world; (e = find(e, classname, "electro_orb")); ) + { + if(vlen(e.origin - org) <= rad) + { + // do we allow thruwall triggering? + if(WEP_CVAR(electro, combo_comboradius_thruwall)) + { + WarpZone_TraceLine(org, e.origin, MOVE_NOMONSTERS, e); + if(trace_fraction != 1) + { + if(vlen(e.origin - org) >= WEP_CVAR(electro, combo_comboradius_thruwall)) + { + // trigger is through a wall and outside of thruwall range, abort + continue; + } + } + } + + // change owner to whoever caused the combo explosion + e.realowner = own; + e.takedamage = DAMAGE_NO; + e.classname = "electro_orb_chain"; + + // now set the next one to trigger as well + e.think = W_Electro_ExplodeCombo; + + // delay combo chains, looks cooler + e.nextthink = + ( + time + + + (WEP_CVAR(electro, combo_speed) ? + (vlen(e.origin - org) / WEP_CVAR(electro, combo_speed)) + : + 0 + ) + ); + } + } +} + +void W_Electro_TriggerCombo_WarpZone(vector org, float rad, entity own) { entity e = WarpZone_FindRadius(org, rad, !WEP_CVAR(electro, combo_comboradius_thruwall)); while(e) @@ -115,6 +160,16 @@ void W_Electro_TriggerCombo(vector org, float rad, entity own) } } +void W_Electro_TriggerCombo(vector org, float rad, entity own) +{ + // TODO: remove when we have unlagged findradius! + + if(WEP_CVAR(electro, combo_simple)) + W_Electro_TriggerCombo_Simple(org, rad, own); + else + W_Electro_TriggerCombo_WarpZone(org, rad, own); +} + void W_Electro_ExplodeCombo(void) { W_Electro_TriggerCombo(self.origin, WEP_CVAR(electro, combo_comboradius), self.realowner); @@ -254,12 +309,12 @@ void W_Electro_Attack_Bolt(void) '0 0 -3', FALSE, 2, - "weapons/electro_fire.wav", + W_Sound("electro_fire"), CH_WEAPON_A, WEP_CVAR_PRI(electro, damage) ); - pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); proj = spawn(); proj.classname = "electro_bolt"; @@ -295,7 +350,7 @@ void W_Electro_Orb_Touch(void) else { //UpdateCSQCProjectile(self); - spamsound(self, CH_SHOTS, "weapons/electro_bounce.wav", VOL_BASE, ATTEN_NORM); + spamsound(self, CH_SHOTS, W_Sound("electro_bounce"), VOL_BASE, ATTEN_NORM); self.projectiledeathtype |= HITTYPE_BOUNCE; } } @@ -352,14 +407,14 @@ void W_Electro_Attack_Orb(void) '0 0 -4', FALSE, 2, - "weapons/electro_fire2.wav", + W_Sound("electro_fire2"), CH_WEAPON_A, WEP_CVAR_SEC(electro, damage) ); w_shotdir = v_forward; // no TrueAim for grenades please - pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); entity proj = spawn(); proj.classname = "electro_orb"; @@ -497,14 +552,14 @@ float W_Electro(float req) } 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"); + precache_model(W_Model("g_electro.md3")); + precache_model(W_Model("v_electro.md3")); + precache_model(W_Model("h_electro.iqm")); + precache_sound(W_Sound("electro_bounce")); + precache_sound(W_Sound("electro_fire")); + precache_sound(W_Sound("electro_fire2")); + precache_sound(W_Sound("electro_impact")); + precache_sound(W_Sound("electro_impact_combo")); ELECTRO_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } @@ -540,7 +595,7 @@ float W_Electro(float req) } case WR_RELOAD: { - W_Reload(min(WEP_CVAR_PRI(electro, ammo), WEP_CVAR_SEC(electro, ammo)), "weapons/reload.wav"); + W_Reload(min(WEP_CVAR_PRI(electro, ammo), WEP_CVAR_SEC(electro, ammo)), W_Sound("reload")); return TRUE; } case WR_SUICIDEMESSAGE: diff --git a/qcsrc/common/weapons/w_fireball.qc b/qcsrc/common/weapons/w_fireball.qc index 80c453a4d..ead6567c8 100644 --- a/qcsrc/common/weapons/w_fireball.qc +++ b/qcsrc/common/weapons/w_fireball.qc @@ -99,7 +99,7 @@ void W_Fireball_Explode(void) 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); + Send_Effect(EFFECT_FIREBALL_BFGDAMAGE, e.origin, -1 * dir, 1); } } @@ -141,8 +141,7 @@ void W_Fireball_LaserPlay(float dt, float dist, float damage, float edgedamage, 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); + Send_Effect(EFFECT_FIREBALL_LASER, self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1); } } @@ -181,9 +180,9 @@ void W_Fireball_Attack1(void) { 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)); + W_SetupShot_ProjectileSize(self, '-16 -16 -16', '16 16 16', FALSE, 2, W_Sound("fireball_fire2"), CH_WEAPON_A, WEP_CVAR_PRI(fireball, damage) + WEP_CVAR_PRI(fireball, bfgdamage)); - pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + Send_Effect(EFFECT_FIREBALL_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); proj = spawn(); proj.classname = "plasma_prim"; @@ -220,7 +219,7 @@ 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); + Send_Effect(EFFECT_FIREBALL_PRE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); } void W_Fireball_Attack1_Frame4(void) @@ -250,7 +249,7 @@ void W_Fireball_Attack1_Frame1(void) void W_Fireball_Attack1_Frame0(void) { W_Fireball_AttackEffect(0, '-1.25 -3.75 0'); - sound(self, CH_WEAPON_SINGLE, "weapons/fireball_prefire2.wav", VOL_BASE, ATTEN_NORM); + sound(self, CH_WEAPON_SINGLE, W_Sound("fireball_prefire2"), VOL_BASE, ATTEN_NORM); weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame1); } @@ -315,11 +314,11 @@ void W_Fireball_Attack2(void) 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)); + W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', FALSE, 2, W_Sound("fireball_fire"), 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); + Send_Effect(EFFECT_FIREBALL_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); proj = spawn(); proj.owner = proj.realowner = self; @@ -398,13 +397,13 @@ float W_Fireball(float req) } 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(W_Model("g_fireball.md3")); + precache_model(W_Model("v_fireball.md3")); + precache_model(W_Model("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"); + precache_sound(W_Sound("fireball_fire")); + precache_sound(W_Sound("fireball_fire2")); + precache_sound(W_Sound("fireball_prefire2")); FIREBALL_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } diff --git a/qcsrc/common/weapons/w_hagar.qc b/qcsrc/common/weapons/w_hagar.qc index fe3abf0a9..9ad4997a9 100644 --- a/qcsrc/common/weapons/w_hagar.qc +++ b/qcsrc/common/weapons/w_hagar.qc @@ -113,7 +113,7 @@ void W_Hagar_Touch2(void) self.use(); } else { self.cnt++; - pointparticles(particleeffectnum("hagar_bounce"), self.origin, self.velocity, 1); + Send_Effect(EFFECT_HAGAR_BOUNCE, self.origin, self.velocity, 1); self.angles = vectoangles(self.velocity); self.owner = world; self.projectiledeathtype |= HITTYPE_BOUNCE; @@ -126,9 +126,9 @@ void W_Hagar_Attack(void) W_DecreaseAmmo(WEP_CVAR_PRI(hagar, ammo)); - W_SetupShot(self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, WEP_CVAR_PRI(hagar, damage)); + W_SetupShot(self, FALSE, 2, W_Sound("hagar_fire"), CH_WEAPON_A, WEP_CVAR_PRI(hagar, damage)); - pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); missile = spawn(); missile.owner = missile.realowner = self; @@ -169,9 +169,9 @@ void W_Hagar_Attack2(void) W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo)); - W_SetupShot(self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage)); + W_SetupShot(self, FALSE, 2, W_Sound("hagar_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage)); - pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); missile = spawn(); missile.owner = missile.realowner = self; @@ -222,8 +222,8 @@ void W_Hagar_Attack2_Load_Release(void) weapon_prepareattack_do(1, WEP_CVAR_SEC(hagar, refire)); - W_SetupShot(self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage)); - pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + W_SetupShot(self, FALSE, 2, W_Sound("hagar_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage)); + Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); forward = v_forward; right = v_right; @@ -314,7 +314,7 @@ void W_Hagar_Attack2_Load(void) self.weaponentity.state = WS_READY; W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo) * self.hagar_load * -1); // give back ammo self.hagar_load = 0; - sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM); + sound(self, CH_WEAPON_A, W_Sound("hagar_beep"), VOL_BASE, ATTN_NORM); // pause until we can load rockets again, once we re-press the alt fire button self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, load_speed) * W_WeaponRateFactor(); @@ -333,7 +333,7 @@ void W_Hagar_Attack2_Load(void) W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo)); self.weaponentity.state = WS_INUSE; self.hagar_load += 1; - sound(self, CH_WEAPON_B, "weapons/hagar_load.wav", VOL_BASE * 0.8, ATTN_NORM); // sound is too loud according to most + sound(self, CH_WEAPON_B, W_Sound("hagar_load"), VOL_BASE * 0.8, ATTN_NORM); // sound is too loud according to most if(self.hagar_load >= WEP_CVAR_SEC(hagar, load_max)) self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, load_hold) * W_WeaponRateFactor(); @@ -344,7 +344,7 @@ void W_Hagar_Attack2_Load(void) else if(!self.hagar_loadbeep && self.hagar_load) // prevents the beep from playing each frame { // if this is the last rocket we can load, play a beep sound to notify the player - sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM); + sound(self, CH_WEAPON_A, W_Sound("hagar_beep"), VOL_BASE, ATTN_NORM); self.hagar_loadbeep = TRUE; } } @@ -363,7 +363,7 @@ void W_Hagar_Attack2_Load(void) if(!self.hagar_warning && self.hagar_load) // prevents the beep from playing each frame { // we're about to automatically release after holding time, play a beep sound to notify the player - sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM); + sound(self, CH_WEAPON_A, W_Sound("hagar_beep"), VOL_BASE, ATTN_NORM); self.hagar_warning = TRUE; } } @@ -445,12 +445,12 @@ float W_Hagar(float req) } case WR_INIT: { - precache_model("models/weapons/g_hagar.md3"); - precache_model("models/weapons/v_hagar.md3"); - precache_model("models/weapons/h_hagar.iqm"); - precache_sound("weapons/hagar_fire.wav"); - precache_sound("weapons/hagar_load.wav"); - precache_sound("weapons/hagar_beep.wav"); + precache_model(W_Model("g_hagar.md3")); + precache_model(W_Model("v_hagar.md3")); + precache_model(W_Model("h_hagar.iqm")); + precache_sound(W_Sound("hagar_fire")); + precache_sound(W_Sound("hagar_load")); + precache_sound(W_Sound("hagar_beep")); HAGAR_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } @@ -499,7 +499,7 @@ float W_Hagar(float req) case WR_RELOAD: { if(!self.hagar_load) // require releasing loaded rockets first - W_Reload(min(WEP_CVAR_PRI(hagar, ammo), WEP_CVAR_SEC(hagar, ammo)), "weapons/reload.wav"); + W_Reload(min(WEP_CVAR_PRI(hagar, ammo), WEP_CVAR_SEC(hagar, ammo)), W_Sound("reload")); return TRUE; } diff --git a/qcsrc/common/weapons/w_hlac.qc b/qcsrc/common/weapons/w_hlac.qc index d3dbed2f9..b34ba4acd 100644 --- a/qcsrc/common/weapons/w_hlac.qc +++ b/qcsrc/common/weapons/w_hlac.qc @@ -76,7 +76,7 @@ void W_HLAC_Attack(void) if(self.crouch) spread = spread * WEP_CVAR_PRI(hlac, spread_crouchmod); - W_SetupShot(self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_A, WEP_CVAR_PRI(hlac, damage)); + W_SetupShot(self, FALSE, 3, W_Sound("lasergun_fire"), CH_WEAPON_A, WEP_CVAR_PRI(hlac, damage)); pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); if(!autocvar_g_norecoil) { @@ -124,8 +124,8 @@ void W_HLAC_Attack2(void) if(self.crouch) spread = spread * WEP_CVAR_SEC(hlac, spread_crouchmod); - W_SetupShot(self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_A, WEP_CVAR_SEC(hlac, damage)); - pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + W_SetupShot(self, FALSE, 3, W_Sound("lasergun_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hlac, damage)); + Send_Effect(EFFECT_LASER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); missile = spawn(); missile.owner = missile.realowner = self; @@ -240,10 +240,10 @@ float W_HLAC(float req) } case WR_INIT: { - precache_model("models/weapons/g_hlac.md3"); - precache_model("models/weapons/v_hlac.md3"); - precache_model("models/weapons/h_hlac.iqm"); - precache_sound("weapons/lasergun_fire.wav"); + precache_model(W_Model("g_hlac.md3")); + precache_model(W_Model("v_hlac.md3")); + precache_model(W_Model("h_hlac.iqm")); + precache_sound(W_Sound("lasergun_fire")); HLAC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } @@ -266,7 +266,7 @@ float W_HLAC(float req) } case WR_RELOAD: { - W_Reload(min(WEP_CVAR_PRI(hlac, ammo), WEP_CVAR_SEC(hlac, ammo)), "weapons/reload.wav"); + W_Reload(min(WEP_CVAR_PRI(hlac, ammo), WEP_CVAR_SEC(hlac, ammo)), W_Sound("reload")); return TRUE; } case WR_SUICIDEMESSAGE: diff --git a/qcsrc/common/weapons/w_hmg.qc b/qcsrc/common/weapons/w_hmg.qc index 69777c4c2..0aef9d82c 100644 --- a/qcsrc/common/weapons/w_hmg.qc +++ b/qcsrc/common/weapons/w_hmg.qc @@ -60,7 +60,7 @@ void W_HeavyMachineGun_Attack_Auto() W_DecreaseAmmo(WEP_CVAR(hmg, ammo)); - W_SetupShot (self, TRUE, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, WEP_CVAR(hmg, damage)); + W_SetupShot (self, TRUE, 0, W_Sound("uzi_fire"), CH_WEAPON_A, WEP_CVAR(hmg, damage)); if(!autocvar_g_norecoil) { @@ -73,7 +73,7 @@ void W_HeavyMachineGun_Attack_Auto() self.misc_bulletcounter = self.misc_bulletcounter + 1; - pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); W_MachineGun_MuzzleFlash(); W_AttachToShotorg(self.muzzle_flash, '5 0 0'); @@ -92,7 +92,7 @@ float W_HeavyMachineGun(float req) { case WR_AIM: { - if(vlen(self.origin-self.enemy.origin) < 3000 - bound(0, skill, 10) * 200) + if(vlen(self.origin-self.enemy.origin) < 3000 - bound(0, bot_skill, 10) * 200) self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); else self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); @@ -118,10 +118,10 @@ float W_HeavyMachineGun(float req) case WR_INIT: { precache_model ("models/uziflash.md3"); - precache_model ("models/weapons/g_ok_hmg.md3"); - precache_model ("models/weapons/v_ok_hmg.md3"); - precache_model ("models/weapons/h_ok_hmg.iqm"); - precache_sound ("weapons/uzi_fire.wav"); + precache_model(W_Model("g_ok_hmg.md3")); + precache_model(W_Model("v_ok_hmg.md3")); + precache_model(W_Model("h_ok_hmg.iqm")); + precache_sound (W_Sound("uzi_fire")); HMG_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } @@ -150,7 +150,7 @@ float W_HeavyMachineGun(float req) } case WR_RELOAD: { - W_Reload(WEP_CVAR(hmg, ammo), "weapons/reload.wav"); + W_Reload(WEP_CVAR(hmg, ammo), W_Sound("reload")); return TRUE; } case WR_SUICIDEMESSAGE: diff --git a/qcsrc/common/weapons/w_hook.qc b/qcsrc/common/weapons/w_hook.qc index 3713ca9c7..f30b74901 100644 --- a/qcsrc/common/weapons/w_hook.qc +++ b/qcsrc/common/weapons/w_hook.qc @@ -34,6 +34,7 @@ REGISTER_WEAPON( w_cvar(id, sn, SEC, speed) \ w_cvar(id, sn, SEC, health) \ w_cvar(id, sn, SEC, damageforcescale) \ + w_cvar(id, sn, NONE, secondary) \ w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ w_prop(id, sn, string, weaponreplace, weaponreplace) \ @@ -133,7 +134,7 @@ void W_Hook_Attack2(void) entity gren; //W_DecreaseAmmo(WEP_CVAR_SEC(hook, ammo)); // WEAPONTODO: Figure out how to handle ammo with hook secondary (gravitybomb) - W_SetupShot(self, FALSE, 4, "weapons/hookbomb_fire.wav", CH_WEAPON_A, WEP_CVAR_SEC(hook, damage)); + W_SetupShot(self, FALSE, 4, W_Sound("hookbomb_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hook, damage)); gren = spawn(); gren.owner = gren.realowner = self; @@ -287,12 +288,12 @@ float W_Hook(float req) } 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"); + precache_model(W_Model("g_hookgun.md3")); + precache_model(W_Model("v_hookgun.md3")); + precache_model(W_Model("h_hookgun.iqm")); + precache_sound(W_Sound("hook_impact")); // done by g_hook.qc + precache_sound(W_Sound("hook_fire")); + precache_sound(W_Sound("hookbomb_fire")); HOOK_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } diff --git a/qcsrc/common/weapons/w_lightsabre.qc b/qcsrc/common/weapons/w_lightsabre.qc new file mode 100644 index 000000000..f10ea5efb --- /dev/null +++ b/qcsrc/common/weapons/w_lightsabre.qc @@ -0,0 +1,337 @@ +#ifndef CHAOS +const float WEP_LIGHTSABRE = 1337; +#else +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ LIGHTSABRE, +/* function */ W_Lightsabre, +/* ammotype */ ammo_none, +/* impulse */ 1, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE, +/* rating */ BOT_PICKUP_RATING_LOW, +/* color */ '1 0.25 0.25', +/* modelname */ "lightsabre", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairlightsabre 0.35", +/* wepimg */ "weaponlightsabre", +/* refname */ "lightsabre", +/* wepname */ _("Lightsabre") +); + +#define LIGHTSABRE_SETTINGS(w_cvar,w_prop) LIGHTSABRE_SETTINGS_LIST(w_cvar, w_prop, LIGHTSABRE, lightsabre) +#define LIGHTSABRE_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, BOTH, animtime) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, BOTH, damage) \ + w_cvar(id, sn, BOTH, force) \ + w_cvar(id, sn, BOTH, melee_time) \ + w_cvar(id, sn, BOTH, melee_no_doubleslap) \ + w_cvar(id, sn, BOTH, melee_traces) \ + w_cvar(id, sn, BOTH, melee_swing_up) \ + w_cvar(id, sn, BOTH, melee_swing_side) \ + w_cvar(id, sn, BOTH, melee_nonplayerdamage) \ + w_cvar(id, sn, BOTH, melee_multihit) \ + w_cvar(id, sn, BOTH, melee_delay) \ + w_cvar(id, sn, BOTH, melee_range) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +LIGHTSABRE_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +#endif +#else +#ifdef SVQC +void spawnfunc_weapon_lightsabre(void) { weapon_defaultspawnfunc(WEP_LIGHTSABRE); } + +.float swing_prev; +.entity swing_alreadyhit; + +float W_Lightsabre_Melee_Block(entity player) +{ + if(!IS_PLAYER(player)) { return FALSE; } + + entity player_melee = world, e; + entity myowner = self.realowner; + + for(e = world; (e = find(e, classname, "melee_temp")); ) + if(e.realowner == player) + { + player_melee = e; + break; + } + + if(!player_melee) { return FALSE; } + + makevectors (player.v_angle); + float dot = normalize (myowner.origin - player.origin) * v_forward; + + if(dot <= 0.3) { return FALSE; } + + //if((myowner.v_angle_x - player.v_angle_x < 70) && (myowner.v_angle_x - player.v_angle_x > -70)) //Look up and down + //if((myowner.v_angle_y - player.v_angle_y > 160) || (myowner.v_angle_y - player.v_angle_y < -160)) //Side to side Facing eachother + // fun stuff + + animdecide_setaction(myowner, ANIMACTION_SHOOT, 1); + string thesound = strcat("lightsabre_hit", ftos(max(1, floor(random() * 4)))); + sound(myowner, CH_WEAPON_A, W_Sound(thesound), VOL_BASE, ATTEN_NORM); + + return TRUE; +} + +void W_Lightsabre_Melee_Think(void) +{ + // declarations + float i, f, swing, swing_factor, swing_damage, meleetime, is_player; + entity target_victim; + vector targpos; + float isprimary = !(self.realowner.BUTTON_ATCK2); + float deathtype = WEP_LIGHTSABRE; + if(!isprimary) + deathtype |= HITTYPE_SECONDARY; + + if(!self.cnt) // set start time of melee + { + self.cnt = time; + } + + makevectors(self.realowner.v_angle); // update values for v_* vectors + + // calculate swing percentage based on time + meleetime = WEP_CVAR_BOTH(lightsabre, isprimary, melee_time) * W_WeaponRateFactor(); + swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); + f = ((1 - swing) * WEP_CVAR_BOTH(lightsabre, isprimary, melee_traces)); + + // check to see if we can still continue, otherwise give up now + if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR_BOTH(lightsabre, isprimary, 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 / WEP_CVAR_BOTH(lightsabre, isprimary, melee_traces))) * 2 - 1); + + targpos = (self.realowner.origin + self.realowner.view_ofs + + (v_forward * WEP_CVAR_BOTH(lightsabre, isprimary, melee_range)) + + (v_up * swing_factor * WEP_CVAR_BOTH(lightsabre, isprimary, melee_swing_up)) + + (v_right * swing_factor * WEP_CVAR_BOTH(lightsabre, isprimary, melee_swing_side))); + + WarpZone_traceline_antilag(self, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self.realowner, 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" || (trace_ent.flags & FL_MONSTER)); + + 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 || WEP_CVAR_BOTH(lightsabre, isprimary, melee_nonplayerdamage))) + { + target_victim = trace_ent; // so it persists through other calls + + if(!W_Lightsabre_Melee_Block(trace_ent)) + { + if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught. + swing_damage = (WEP_CVAR_BOTH(lightsabre, isprimary, damage) * min(1, swing_factor + 1)); + else + swing_damage = (WEP_CVAR_BOTH(lightsabre, isprimary, 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, deathtype, + self.realowner.origin + self.realowner.view_ofs, + v_forward * WEP_CVAR_BOTH(lightsabre, isprimary, force)); + + string thesound = strcat("lightsabre_hit", ftos(max(1, floor(random() * 4)))); + + sound(self.realowner, CH_WEAPON_A, W_Sound(thesound), VOL_BASE, ATTEN_NORM); + + if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_LIGHTSABRE, 0, swing_damage); } + } + + // draw large red flash for debugging + //te_customflash(targpos, 200, 2, '15 0 0'); + + if(WEP_CVAR_BOTH(lightsabre, isprimary, 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_Lightsabre_Attack(void) +{ + float isprimary = !(self.BUTTON_ATCK2); + + sound(self, CH_WEAPON_A, ((isprimary) ? W_Sound("lightsabre_melee2") : W_Sound("lightsabre_melee1")), VOL_BASE, ATTEN_NORM); + weapon_thinkf(((isprimary) ? WFRAME_FIRE2 : WFRAME_FIRE1), WEP_CVAR_BOTH(lightsabre, isprimary, animtime), w_ready); + + entity meleetemp; + meleetemp = spawn(); + meleetemp.classname = "melee_temp"; + meleetemp.realowner = self; + meleetemp.think = W_Lightsabre_Melee_Think; + meleetemp.nextthink = time + WEP_CVAR_BOTH(lightsabre, isprimary, melee_delay) * W_WeaponRateFactor(); + W_SetupShot_Range(self, TRUE, 0, "", 0, WEP_CVAR_BOTH(lightsabre, isprimary, damage), WEP_CVAR_BOTH(lightsabre, isprimary, melee_range)); +} + +.float lightsabre_active; + +void W_LightSabre_SetActive(float newactive, float dosound) +{ + if(newactive) + { + self.lightsabre_active = TRUE; + self.weaponname = "lightsabre_active"; + if(dosound) + sound(self, CH_WEAPON_A, W_Sound("lightsabre_activate"), VOL_BASE, ATTEN_NORM); + } + else + { + self.lightsabre_active = FALSE; + self.weaponname = "lightsabre"; + if(dosound) + sound(self, CH_WEAPON_A, W_Sound("lightsabre_deactivate"), VOL_BASE, ATTEN_NORM); + } +} + +float W_Lightsabre(float req) +{ + switch(req) + { + case WR_AIM: + { + if(random() >= 0.5) + self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); + else + self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); + + return TRUE; + } + case WR_THINK: + { + if(self.BUTTON_ATCK || self.BUTTON_ATCK2) + if(weapon_prepareattack(self.BUTTON_ATCK2, WEP_CVAR_BOTH(lightsabre, self.BUTTON_ATCK2, refire))) + { + if(!self.lightsabre_active) + { + W_LightSabre_SetActive(1, TRUE); + weapon_thinkf(WFRAME_RELOAD, WEP_CVAR_BOTH(lightsabre, self.BUTTON_ATCK2, animtime), w_ready); + } + else + weapon_thinkf(WFRAME_FIRE1, 0, W_Lightsabre_Attack); + } + + return TRUE; + } + case WR_INIT: + { + precache_model("models/uziflash.md3"); + precache_model(W_Model("g_lightsabre.md3")); + precache_model(W_Model("v_lightsabre.md3")); + precache_model(W_Model("h_lightsabre.iqm")); + precache_model(W_Model("v_lightsabre_active.md3")); + precache_model(W_Model("h_lightsabre_active.iqm")); + precache_sound("misc/itempickup.wav"); + precache_sound(W_Sound("lightsabre_melee1")); + precache_sound(W_Sound("lightsabre_melee2")); + precache_sound(W_Sound("lightsabre_activate")); + precache_sound(W_Sound("lightsabre_deactivate")); + precache_sound(W_Sound("lightsabre_hit1")); + precache_sound(W_Sound("lightsabre_hit2")); + precache_sound(W_Sound("lightsabre_hit3")); + LIGHTSABRE_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) + return TRUE; + } + case WR_SETUP: + { + self.ammo_field = ammo_none; + W_LightSabre_SetActive(0, FALSE); + return TRUE; + } + case WR_CHECKAMMO1: + { + return TRUE; + } + case WR_CHECKAMMO2: + { + return TRUE; + } + case WR_CONFIG: + { + LIGHTSABRE_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS) + return TRUE; + } + case WR_RELOAD: + { + W_LightSabre_SetActive(((self.lightsabre_active) ? 0 : 1), TRUE); + return TRUE; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_THINKING_WITH_PORTALS; + } + case WR_KILLMESSAGE: + { + return WEAPON_LIGHTSABRE_MURDER; + } + } + return FALSE; +} +#endif +#ifdef CSQC +.float prevric; +float W_Lightsabre(float req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + return TRUE; + } + case WR_INIT: + { + return TRUE; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return FALSE; + } + } + return FALSE; +} +#endif +#endif +#endif diff --git a/qcsrc/common/weapons/w_machinegun.qc b/qcsrc/common/weapons/w_machinegun.qc index 9c69c8d82..8714aa717 100644 --- a/qcsrc/common/weapons/w_machinegun.qc +++ b/qcsrc/common/weapons/w_machinegun.qc @@ -103,7 +103,7 @@ void W_MachineGun_MuzzleFlash(void) void W_MachineGun_Attack(float deathtype) { - W_SetupShot(self, TRUE, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, ((self.misc_bulletcounter == 1) ? WEP_CVAR(machinegun, first_damage) : WEP_CVAR(machinegun, sustained_damage))); + W_SetupShot(self, TRUE, 0, W_Sound("uzi_fire"), CH_WEAPON_A, ((self.misc_bulletcounter == 1) ? WEP_CVAR(machinegun, first_damage) : WEP_CVAR(machinegun, sustained_damage))); if(!autocvar_g_norecoil) { self.punchangle_x = random() - 0.5; @@ -118,7 +118,7 @@ void W_MachineGun_Attack(float deathtype) else fireBullet(w_shotorg, w_shotdir, WEP_CVAR(machinegun, sustained_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), deathtype, 0); - pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); W_MachineGun_MuzzleFlash(); W_AttachToShotorg(self.muzzle_flash, '5 0 0'); @@ -179,7 +179,7 @@ void W_MachineGun_Attack_Auto(void) W_DecreaseAmmo(WEP_CVAR(machinegun, sustained_ammo)); - W_SetupShot(self, TRUE, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage)); + W_SetupShot(self, TRUE, 0, W_Sound("uzi_fire"), CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage)); if(!autocvar_g_norecoil) { self.punchangle_x = random() - 0.5; @@ -191,7 +191,7 @@ void W_MachineGun_Attack_Auto(void) self.misc_bulletcounter = self.misc_bulletcounter + 1; - pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); W_MachineGun_MuzzleFlash(); W_AttachToShotorg(self.muzzle_flash, '5 0 0'); @@ -205,7 +205,7 @@ void W_MachineGun_Attack_Auto(void) void W_MachineGun_Attack_Burst(void) { - W_SetupShot(self, TRUE, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage)); + W_SetupShot(self, TRUE, 0, W_Sound("uzi_fire"), CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage)); if(!autocvar_g_norecoil) { self.punchangle_x = random() - 0.5; @@ -214,7 +214,7 @@ void W_MachineGun_Attack_Burst(void) fireBullet(w_shotorg, w_shotdir, WEP_CVAR(machinegun, burst_speed), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), WEP_MACHINEGUN, 0); - pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); W_MachineGun_MuzzleFlash(); W_AttachToShotorg(self.muzzle_flash, '5 0 0'); @@ -242,7 +242,7 @@ float W_MachineGun(float req) { case WR_AIM: { - if(vlen(self.origin-self.enemy.origin) < 3000 - bound(0, skill, 10) * 200) + if(vlen(self.origin-self.enemy.origin) < 3000 - bound(0, bot_skill, 10) * 200) self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); else self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); @@ -304,10 +304,10 @@ float W_MachineGun(float req) case WR_INIT: { precache_model("models/uziflash.md3"); - precache_model("models/weapons/g_uzi.md3"); - precache_model("models/weapons/v_uzi.md3"); - precache_model("models/weapons/h_uzi.iqm"); - precache_sound("weapons/uzi_fire.wav"); + precache_model(W_Model("g_uzi.md3")); + precache_model(W_Model("v_uzi.md3")); + precache_model(W_Model("h_uzi.iqm")); + precache_sound(W_Sound("uzi_fire")); MACHINEGUN_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } @@ -350,7 +350,7 @@ float W_MachineGun(float req) } case WR_RELOAD: { - W_Reload(min(max(WEP_CVAR(machinegun, sustained_ammo), WEP_CVAR(machinegun, first_ammo)), WEP_CVAR(machinegun, burst_ammo)), "weapons/reload.wav"); + W_Reload(min(max(WEP_CVAR(machinegun, sustained_ammo), WEP_CVAR(machinegun, first_ammo)), WEP_CVAR(machinegun, burst_ammo)), W_Sound("reload")); return TRUE; } case WR_SUICIDEMESSAGE: diff --git a/qcsrc/common/weapons/w_minelayer.qc b/qcsrc/common/weapons/w_minelayer.qc index 2dac41e1b..b6df9e4a0 100644 --- a/qcsrc/common/weapons/w_minelayer.qc +++ b/qcsrc/common/weapons/w_minelayer.qc @@ -60,7 +60,7 @@ void spawnfunc_weapon_minelayer(void) { weapon_defaultspawnfunc(WEP_MINE_LAYER); void W_MineLayer_Stick(entity to) { - spamsound(self, CH_SHOTS, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("mine_stick"), VOL_BASE, ATTN_NORM); // in order for mines to face properly when sticking to the ground, they must be a server side entity rather than a csqc projectile @@ -224,14 +224,14 @@ void W_MineLayer_Think(void) if((time > self.cnt) && (!self.mine_time) && (self.cnt > 0)) { if(WEP_CVAR(minelayer, lifetime_countdown) > 0) - spamsound(self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("mine_trigger"), VOL_BASE, ATTN_NORM); self.mine_time = time + WEP_CVAR(minelayer, lifetime_countdown); self.mine_explodeanyway = 1; // make the mine super aggressive -- Samual: Rather, make it not care if a team mate is near. } // a player's mines shall explode if he disconnects or dies // TODO: Do this on team change too -- Samual: But isn't a player killed when they switch teams? - if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO || self.realowner.frozen) + if(!IS_PLAYER(self.realowner) || Player_Trapped(self.realowner)) { other = world; self.projectiledeathtype |= HITTYPE_BOUNCE; @@ -243,11 +243,11 @@ void W_MineLayer_Think(void) head = findradius(self.origin, WEP_CVAR(minelayer, proximityradius)); while(head) { - if(IS_PLAYER(head) && head.deadflag == DEAD_NO && !head.frozen) + if(IS_PLAYER(head) && !Player_Trapped(head)) if(head != self.realowner && DIFF_TEAM(head, self.realowner)) // don't trigger for team mates if(!self.mine_time) { - spamsound(self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("mine_trigger"), VOL_BASE, ATTN_NORM); self.mine_time = time + WEP_CVAR(minelayer, time); } head = head.chain; @@ -319,15 +319,15 @@ void W_MineLayer_Attack(void) { // the refire delay keeps this message from being spammed Send_Notification(NOTIF_ONE, self, MSG_MULTI, WEAPON_MINELAYER_LIMIT, WEP_CVAR(minelayer, limit)); - play2(self, "weapons/unavailable.wav"); + play2(self, W_Sound("unavailable")); return; } } W_DecreaseAmmo(WEP_CVAR(minelayer, ammo)); - W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", CH_WEAPON_A, WEP_CVAR(minelayer, damage)); - pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', FALSE, 5, W_Sound("mine_fire"), CH_WEAPON_A, WEP_CVAR(minelayer, damage)); + Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); mine = WarpZone_RefSys_SpawnSameRefSys(self); mine.owner = mine.realowner = self; @@ -413,7 +413,7 @@ float W_MineLayer(float req) self.BUTTON_ATCK = FALSE; else self.BUTTON_ATCK = bot_aim(WEP_CVAR(minelayer, speed), 0, WEP_CVAR(minelayer, lifetime), FALSE); - if(skill >= 2) // skill 0 and 1 bots won't detonate mines! + if(bot_skill >= 2) // skill 0 and 1 bots won't detonate mines! { // decide whether to detonate mines entity targetlist, targ; @@ -468,7 +468,7 @@ float W_MineLayer(float req) } makevectors(mine.v_angle); targ = targetlist; - if(skill > 9) // normal players only do this for the target they are tracking + if(bot_skill > 9) // normal players only do this for the target they are tracking { targ = targetlist; while(targ) @@ -486,7 +486,7 @@ float W_MineLayer(float req) if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1) if(IS_PLAYER(self.enemy)) if(desirabledamage >= 0.1*coredamage) - if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1)) + if(random()/distance*300 > frametime*bound(0,(10-bot_skill)*0.2,1)) self.BUTTON_ATCK2 = TRUE; // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n"); } @@ -497,7 +497,7 @@ float W_MineLayer(float req) // but don't fire a new shot at the same time! if(desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events self.BUTTON_ATCK2 = TRUE; - if((skill > 6.5) && (selfdamage > self.health)) + if((bot_skill > 6.5) && (selfdamage > self.health)) self.BUTTON_ATCK2 = FALSE; //if(self.BUTTON_ATCK2 == TRUE) // dprint(ftos(desirabledamage),"\n"); @@ -526,7 +526,7 @@ float W_MineLayer(float req) if(self.BUTTON_ATCK2) { if(W_MineLayer_PlacedMines(TRUE)) - sound(self, CH_WEAPON_B, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM); + sound(self, CH_WEAPON_B, W_Sound("mine_det"), VOL_BASE, ATTN_NORM); } return TRUE; @@ -535,13 +535,13 @@ float W_MineLayer(float req) { precache_model("models/flash.md3"); precache_model("models/mine.md3"); - precache_model("models/weapons/g_minelayer.md3"); - precache_model("models/weapons/v_minelayer.md3"); - precache_model("models/weapons/h_minelayer.iqm"); - precache_sound("weapons/mine_det.wav"); - precache_sound("weapons/mine_fire.wav"); - precache_sound("weapons/mine_stick.wav"); - precache_sound("weapons/mine_trigger.wav"); + precache_model(W_Model("g_minelayer.md3")); + precache_model(W_Model("v_minelayer.md3")); + precache_model(W_Model("h_minelayer.iqm")); + precache_sound(W_Sound("mine_det")); + precache_sound(W_Sound("mine_fire")); + precache_sound(W_Sound("mine_stick")); + precache_sound(W_Sound("mine_trigger")); MINELAYER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } @@ -575,7 +575,7 @@ float W_MineLayer(float req) } case WR_RELOAD: { - W_Reload(WEP_CVAR(minelayer, ammo), "weapons/reload.wav"); + W_Reload(WEP_CVAR(minelayer, ammo), W_Sound("reload")); return TRUE; } case WR_SUICIDEMESSAGE: diff --git a/qcsrc/common/weapons/w_mortar.qc b/qcsrc/common/weapons/w_mortar.qc index de40fcb39..e531756ef 100644 --- a/qcsrc/common/weapons/w_mortar.qc +++ b/qcsrc/common/weapons/w_mortar.qc @@ -139,24 +139,24 @@ void W_Mortar_Grenade_Touch1(void) float r; r = random() * 6; if(r < 1) - spamsound(self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce1"), VOL_BASE, ATTN_NORM); else if(r < 2) - spamsound(self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce2"), VOL_BASE, ATTN_NORM); else if(r < 3) - spamsound(self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce3"), VOL_BASE, ATTN_NORM); else if(r < 4) - spamsound(self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce4"), VOL_BASE, ATTN_NORM); else if(r < 5) - spamsound(self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce5"), VOL_BASE, ATTN_NORM); else - spamsound(self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM); - pointparticles(particleeffectnum("hagar_bounce"), self.origin, self.velocity, 1); + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce6"), VOL_BASE, ATTN_NORM); + Send_Effect(EFFECT_HAGAR_BOUNCE, self.origin, self.velocity, 1); self.projectiledeathtype |= HITTYPE_BOUNCE; self.gl_bouncecnt += 1; } else if(WEP_CVAR_PRI(mortar, type) == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick { - spamsound(self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("grenade_stick"), VOL_BASE, ATTN_NORM); // let it stick whereever it is self.oldvelocity = self.velocity; @@ -184,18 +184,18 @@ void W_Mortar_Grenade_Touch2(void) float r; r = random() * 6; if(r < 1) - spamsound(self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce1"), VOL_BASE, ATTN_NORM); else if(r < 2) - spamsound(self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce2"), VOL_BASE, ATTN_NORM); else if(r < 3) - spamsound(self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce3"), VOL_BASE, ATTN_NORM); else if(r < 4) - spamsound(self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce4"), VOL_BASE, ATTN_NORM); else if(r < 5) - spamsound(self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce5"), VOL_BASE, ATTN_NORM); else - spamsound(self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM); - pointparticles(particleeffectnum("hagar_bounce"), self.origin, self.velocity, 1); + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce6"), VOL_BASE, ATTN_NORM); + Send_Effect(EFFECT_HAGAR_BOUNCE, self.origin, self.velocity, 1); self.projectiledeathtype |= HITTYPE_BOUNCE; self.gl_bouncecnt += 1; @@ -205,7 +205,7 @@ void W_Mortar_Grenade_Touch2(void) } else if(WEP_CVAR_SEC(mortar, type) == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick { - spamsound(self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM); + spamsound(self, CH_SHOTS, W_Sound("grenade_stick"), VOL_BASE, ATTN_NORM); // let it stick whereever it is self.oldvelocity = self.velocity; @@ -227,10 +227,10 @@ void W_Mortar_Attack(void) W_DecreaseAmmo(WEP_CVAR_PRI(mortar, ammo)); - W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, WEP_CVAR_PRI(mortar, damage)); + W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', FALSE, 4, W_Sound("grenade_fire"), CH_WEAPON_A, WEP_CVAR_PRI(mortar, damage)); w_shotdir = v_forward; // no TrueAim for grenades please - pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); gren = spawn(); gren.owner = gren.realowner = self; @@ -276,10 +276,10 @@ void W_Mortar_Attack2(void) W_DecreaseAmmo(WEP_CVAR_SEC(mortar, ammo)); - W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, WEP_CVAR_SEC(mortar, damage)); + W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', FALSE, 4, W_Sound("grenade_fire"), CH_WEAPON_A, WEP_CVAR_SEC(mortar, damage)); w_shotdir = v_forward; // no TrueAim for grenades please - pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); gren = spawn(); gren.owner = gren.realowner = self; @@ -391,7 +391,7 @@ float W_Mortar(float req) } } if(nadefound) - sound(self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM); + sound(self, CH_WEAPON_B, W_Sound("rocket_det"), VOL_BASE, ATTN_NORM); } else if(weapon_prepareattack(1, WEP_CVAR_SEC(mortar, refire))) { @@ -404,17 +404,17 @@ float W_Mortar(float req) } case WR_INIT: { - precache_model("models/weapons/g_gl.md3"); - precache_model("models/weapons/v_gl.md3"); - precache_model("models/weapons/h_gl.iqm"); - precache_sound("weapons/grenade_bounce1.wav"); - precache_sound("weapons/grenade_bounce2.wav"); - precache_sound("weapons/grenade_bounce3.wav"); - precache_sound("weapons/grenade_bounce4.wav"); - precache_sound("weapons/grenade_bounce5.wav"); - precache_sound("weapons/grenade_bounce6.wav"); - precache_sound("weapons/grenade_stick.wav"); - precache_sound("weapons/grenade_fire.wav"); + precache_model(W_Model("g_gl.md3")); + precache_model(W_Model("v_gl.md3")); + precache_model(W_Model("h_gl.iqm")); + precache_sound(W_Sound("grenade_bounce1")); + precache_sound(W_Sound("grenade_bounce2")); + precache_sound(W_Sound("grenade_bounce3")); + precache_sound(W_Sound("grenade_bounce4")); + precache_sound(W_Sound("grenade_bounce5")); + precache_sound(W_Sound("grenade_bounce6")); + precache_sound(W_Sound("grenade_stick")); + precache_sound(W_Sound("grenade_fire")); MORTAR_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } @@ -437,7 +437,7 @@ float W_Mortar(float req) } case WR_RELOAD: { - W_Reload(min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo)), "weapons/reload.wav"); // WEAPONTODO + W_Reload(min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo)), W_Sound("reload")); // WEAPONTODO return TRUE; } case WR_SUICIDEMESSAGE: diff --git a/qcsrc/common/weapons/w_porto.qc b/qcsrc/common/weapons/w_porto.qc index e1fb82f8f..fb14e9861 100644 --- a/qcsrc/common/weapons/w_porto.qc +++ b/qcsrc/common/weapons/w_porto.qc @@ -240,8 +240,6 @@ void W_Porto_Attack(float type) 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; @@ -259,11 +257,8 @@ void W_Porto_Attack(float type) 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_SetupProjVelocity_Basic(gren, WEP_CVAR_BOTH(porto, (type <= 0), speed) * autocvar_g_balance_powerup_strength_force, 0); - else - W_SetupProjVelocity_Basic(gren, WEP_CVAR_BOTH(porto, (type <= 0), speed), 0); + + W_SetupProjVelocity_Basic(gren, WEP_CVAR_BOTH(porto, (type <= 0), speed), 0); gren.angles = vectoangles(gren.velocity); gren.flags = FL_PROJECTILE; @@ -368,9 +363,9 @@ float W_Porto(float req) } 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(W_Model("g_porto.md3")); + precache_model(W_Model("v_porto.md3")); + precache_model(W_Model("h_porto.iqm")); precache_model("models/portal.md3"); precache_sound("porto/bounce.wav"); precache_sound("porto/create.wav"); diff --git a/qcsrc/common/weapons/w_revolver.qc b/qcsrc/common/weapons/w_revolver.qc new file mode 100644 index 000000000..7f309cb16 --- /dev/null +++ b/qcsrc/common/weapons/w_revolver.qc @@ -0,0 +1,242 @@ +#ifndef CHAOS +const float WEP_REVOLVER = 1337; // abuse +#else +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ REVOLVER, +/* function */ W_Revolver, +/* ammotype */ ammo_shells, +/* impulse */ 2, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_LOW, +/* color */ '1 1 0.3', +/* modelname */ "revolver", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairrevolver 0.4", +/* wepimg */ "weaponrevolver", +/* refname */ "revolver", +/* wepname */ _("Revolver") +); + +#define REVOLVER_SETTINGS(w_cvar,w_prop) REVOLVER_SETTINGS_LIST(w_cvar, w_prop, REVOLVER, revolver) +#define REVOLVER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, NONE, ammo) \ + w_cvar(id, sn, NONE, animtime) \ + w_cvar(id, sn, NONE, refire) \ + w_cvar(id, sn, NONE, damage) \ + w_cvar(id, sn, NONE, force) \ + w_cvar(id, sn, NONE, solidpenetration) \ + w_cvar(id, sn, NONE, spread) \ + w_cvar(id, sn, NONE, load_time) \ + w_cvar(id, sn, NONE, load_refire) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +REVOLVER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +#endif +#else +#ifdef SVQC +void spawnfunc_weapon_revolver(void) { weapon_defaultspawnfunc(WEP_REVOLVER); } + +void W_Revolver_Attack() +{ + entity flash; + + W_DecreaseAmmo(WEP_CVAR(revolver, ammo)); + + W_SetupShot(self, TRUE, 5, W_Sound("revolver_fire"), CH_WEAPON_A, WEP_CVAR(revolver, damage)); + fireBullet(w_shotorg, w_shotdir, WEP_CVAR(revolver, spread), WEP_CVAR(revolver, solidpenetration), WEP_CVAR(revolver, damage), WEP_CVAR(revolver, force), WEP_REVOLVER, 0); + + Send_Effect(EFFECT_SHOTGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, WEP_CVAR(revolver, ammo)); + + // 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 revolver_primarytime; +.float revolver_load; +.float revolver_loadspamtime; + +void W_Revolver_SetLoad(float newload) +{ + if(newload == self.revolver_load) { return; } + + self.revolver_load = newload; + + self.weaponname = ((newload) ? "revolver-cocked" : "revolver"); + + // casing code + if(autocvar_g_casings >= 1) + if(!newload) + 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); +} + +float W_Revolver_CheckLoad() +{ + if(!self.revolver_load) { return TRUE; } + else if(time >= self.revolver_loadspamtime && IS_REAL_CLIENT(self) && self.BUTTON_ATCK && !self.BUTTON_ATCK2) // TODO + { + self.revolver_loadspamtime = time + WEP_CVAR(revolver, refire) * W_WeaponRateFactor(); + play2(self, W_Sound("dryfire")); + sprint(self, "Please use secondary fire to load the revolver!\n"); + return FALSE; + } + + return FALSE; +} + +float W_Revolver(float req) +{ + float ammo_amount; + switch(req) + { + case WR_AIM: + { + if(!W_Revolver_CheckLoad()) + self.BUTTON_ATCK2 = TRUE; + else + self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); + + return TRUE; + } + case WR_THINK: + { + if(WEP_CVAR(revolver, reload_ammo) && self.clip_load < WEP_CVAR(revolver, ammo)) // forced reload + { + WEP_ACTION(self.weapon, WR_RELOAD); + } + else + { + if(self.BUTTON_ATCK) + if(W_Revolver_CheckLoad()) + if(time >= self.revolver_primarytime) // handle refire separately so the secondary can be fired straight after a primary + { + if(weapon_prepareattack(0, WEP_CVAR(revolver, animtime))) + { + W_Revolver_Attack(); + self.revolver_primarytime = time + WEP_CVAR(revolver, refire) * W_WeaponRateFactor(); + self.revolver_loadspamtime = time + self.revolver_primarytime; // just enough to not spam it this frame + W_Revolver_SetLoad(1); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(revolver, animtime), w_ready); + } + } + } + if(self.BUTTON_ATCK2) + if(!W_Revolver_CheckLoad()) + if(weapon_prepareattack(1, WEP_CVAR(revolver, load_refire))) + { + W_Revolver_SetLoad(0); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(revolver, load_time), w_ready); + } + + return TRUE; + } + case WR_INIT: + { + precache_model("models/uziflash.md3"); + precache_model(W_Model("g_revolver.md3")); + precache_model(W_Model("v_revolver.md3")); + precache_model(W_Model("h_revolver.iqm")); + precache_model(W_Model("v_revolver-cocked.md3")); + precache_model(W_Model("h_revolver-cocked.iqm")); + precache_sound("misc/itempickup.wav"); + precache_sound(W_Sound("revolver_fire")); + REVOLVER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) + return TRUE; + } + case WR_SETUP: + { + W_Revolver_SetLoad(0); + return TRUE; + } + case WR_CHECKAMMO1: + { + ammo_amount = self.WEP_AMMO(REVOLVER) >= WEP_CVAR(revolver, ammo); + ammo_amount += self.(weapon_load[WEP_REVOLVER]) >= WEP_CVAR(revolver, ammo); + return ammo_amount; + } + case WR_CHECKAMMO2: + { + ammo_amount = self.WEP_AMMO(REVOLVER) >= WEP_CVAR(revolver, ammo); + ammo_amount += self.(weapon_load[WEP_REVOLVER]) >= WEP_CVAR(revolver, ammo); + if(ammo_amount >= 1) + return !W_Revolver_CheckLoad(); + else + return FALSE; + } + case WR_CONFIG: + { + REVOLVER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS) + return TRUE; + } + case WR_RELOAD: + { + W_Reload(WEP_CVAR(revolver, ammo), W_Sound("reload")); // WEAPONTODO + return TRUE; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_THINKING_WITH_PORTALS; + } + case WR_KILLMESSAGE: + { + return WEAPON_REVOLVER_MURDER; + } + } + return FALSE; +} +#endif +#ifdef CSQC +.float prevric; +float W_Revolver(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, ATTEN_NORM); + else if(w_random < 0.033) + 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, 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; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return FALSE; + } + } + return FALSE; +} +#endif +#endif +#endif diff --git a/qcsrc/common/weapons/w_rifle.qc b/qcsrc/common/weapons/w_rifle.qc index 03c396ceb..7257c6694 100644 --- a/qcsrc/common/weapons/w_rifle.qc +++ b/qcsrc/common/weapons/w_rifle.qc @@ -7,7 +7,7 @@ REGISTER_WEAPON( /* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, /* rating */ BOT_PICKUP_RATING_MID, /* color */ '0.5 1 0', -/* modelname */ "campingrifle", +/* modelname */ "sniperrifle", /* simplemdl */ "foobar", /* crosshair */ "gfx/crosshairrifle 0.5", /* wepimg */ "weaponrifle", @@ -58,7 +58,7 @@ void W_Rifle_FireBullet(float pSpread, float pDamage, float pForce, float pSolid W_SetupShot(self, TRUE, 2, pSound, CH_WEAPON_A, pDamage * pShots); - pointparticles(particleeffectnum("rifle_muzzleflash"), w_shotorg, w_shotdir * 2000, 1); + Send_Effect(EFFECT_RIFLE_MUZZLEFLASH, w_shotorg, w_shotdir * 2000, 1); if(self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) // if zoomed, shoot from the eye { @@ -75,12 +75,12 @@ void W_Rifle_FireBullet(float pSpread, float pDamage, float pForce, float pSolid void W_Rifle_Attack(void) { - W_Rifle_FireBullet(WEP_CVAR_PRI(rifle, spread), WEP_CVAR_PRI(rifle, damage), WEP_CVAR_PRI(rifle, force), WEP_CVAR_PRI(rifle, solidpenetration), WEP_CVAR_PRI(rifle, ammo), WEP_RIFLE, WEP_CVAR_PRI(rifle, tracer), WEP_CVAR_PRI(rifle, shots), "weapons/campingrifle_fire.wav"); + W_Rifle_FireBullet(WEP_CVAR_PRI(rifle, spread), WEP_CVAR_PRI(rifle, damage), WEP_CVAR_PRI(rifle, force), WEP_CVAR_PRI(rifle, solidpenetration), WEP_CVAR_PRI(rifle, ammo), WEP_RIFLE, WEP_CVAR_PRI(rifle, tracer), WEP_CVAR_PRI(rifle, shots), W_Sound("campingrifle_fire")); } void W_Rifle_Attack2(void) { - W_Rifle_FireBullet(WEP_CVAR_SEC(rifle, spread), WEP_CVAR_SEC(rifle, damage), WEP_CVAR_SEC(rifle, force), WEP_CVAR_SEC(rifle, solidpenetration), WEP_CVAR_SEC(rifle, ammo), WEP_RIFLE | HITTYPE_SECONDARY, WEP_CVAR_SEC(rifle, tracer), WEP_CVAR_SEC(rifle, shots), "weapons/campingrifle_fire2.wav"); + W_Rifle_FireBullet(WEP_CVAR_SEC(rifle, spread), WEP_CVAR_SEC(rifle, damage), WEP_CVAR_SEC(rifle, force), WEP_CVAR_SEC(rifle, solidpenetration), WEP_CVAR_SEC(rifle, ammo), WEP_RIFLE | HITTYPE_SECONDARY, WEP_CVAR_SEC(rifle, tracer), WEP_CVAR_SEC(rifle, shots), W_Sound("campingrifle_fire2")); } .void(void) rifle_bullethail_attackfunc; @@ -203,11 +203,11 @@ float W_Rifle(float req) } case WR_INIT: { - precache_model("models/weapons/g_campingrifle.md3"); - precache_model("models/weapons/v_campingrifle.md3"); - precache_model("models/weapons/h_campingrifle.iqm"); - precache_sound("weapons/campingrifle_fire.wav"); - precache_sound("weapons/campingrifle_fire2.wav"); + precache_model(W_Model("g_sniperrifle.md3")); + precache_model(W_Model("v_sniperrifle.md3")); + precache_model(W_Model("h_sniperrifle.iqm")); + precache_sound(W_Sound("campingrifle_fire")); + precache_sound(W_Sound("campingrifle_fire2")); RIFLE_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } @@ -235,7 +235,7 @@ float W_Rifle(float req) } case WR_RELOAD: { - W_Reload(min(WEP_CVAR_PRI(rifle, ammo), WEP_CVAR_SEC(rifle, ammo)), "weapons/reload.wav"); + W_Reload(min(WEP_CVAR_PRI(rifle, ammo), WEP_CVAR_SEC(rifle, ammo)), W_Sound("reload")); return TRUE; } case WR_SUICIDEMESSAGE: diff --git a/qcsrc/common/weapons/w_rpc.qc b/qcsrc/common/weapons/w_rpc.qc index 81e114443..d624c197f 100644 --- a/qcsrc/common/weapons/w_rpc.qc +++ b/qcsrc/common/weapons/w_rpc.qc @@ -107,8 +107,8 @@ void W_RocketPropelledChainsaw_Attack (void) entity flash = spawn (); W_DecreaseAmmo(WEP_CVAR(rpc, ammo)); - W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", CH_WEAPON_A, WEP_CVAR(rpc, damage)); - pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 5, W_Sound("rocket_fire"), CH_WEAPON_A, WEP_CVAR(rpc, damage)); + Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); PROJECTILE_MAKETRIGGER(missile); missile.owner = missile.realowner = self; @@ -182,10 +182,10 @@ float W_RocketPropelledChainsaw(float req) case WR_INIT: { precache_model ("models/flash.md3"); - precache_model("models/weapons/h_ok_rl.iqm"); - precache_model("models/weapons/v_ok_rl.md3"); - precache_model("models/weapons/g_ok_rl.md3"); - precache_sound ("weapons/rocket_fire.wav"); + precache_model(W_Model("g_ok_rl.md3")); + precache_model(W_Model("v_ok_rl.md3")); + precache_model(W_Model("h_ok_rl.iqm")); + precache_sound (W_Sound("rocket_fire")); RPC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } @@ -206,7 +206,7 @@ float W_RocketPropelledChainsaw(float req) } case WR_RELOAD: { - W_Reload(WEP_CVAR(rpc, ammo), "weapons/reload.wav"); + W_Reload(WEP_CVAR(rpc, ammo), W_Sound("reload")); return TRUE; } case WR_SUICIDEMESSAGE: diff --git a/qcsrc/common/weapons/w_seeker.qc b/qcsrc/common/weapons/w_seeker.qc index d4c4d3394..dac77797b 100644 --- a/qcsrc/common/weapons/w_seeker.qc +++ b/qcsrc/common/weapons/w_seeker.qc @@ -249,9 +249,9 @@ void W_Seeker_Fire_Missile(vector f_diff, entity m_target) W_DecreaseAmmo(WEP_CVAR(seeker, missile_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_SetupShot_ProjectileSize(self, '-2 -2 -2', '2 2 2', FALSE, 2, W_Sound("seeker_fire.wav"), CH_WEAPON_A, 0); w_shotorg += f_diff; - pointparticles(particleeffectnum("seeker_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + Send_Effect(EFFECT_SEEKER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); //self.detornator = FALSE; @@ -340,10 +340,10 @@ void W_Seeker_Fire_Flac(void) 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_SetupShot_ProjectileSize(self, '-2 -2 -2', '2 2 2', FALSE, 2, W_Sound("flac_fire"), CH_WEAPON_A, WEP_CVAR(seeker, flac_damage)); w_shotorg += f_diff; - pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); missile = spawn(); missile.owner = missile.realowner = self; @@ -559,7 +559,7 @@ void W_Seeker_Fire_Tag(void) entity missile; W_DecreaseAmmo(WEP_CVAR(seeker, tag_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)); + W_SetupShot_ProjectileSize(self, '-2 -2 -2', '2 2 2', FALSE, 2, W_Sound("tag_fire"), CH_WEAPON_A, WEP_CVAR(seeker, missile_damage) * WEP_CVAR(seeker, missile_count)); missile = spawn(); missile.owner = missile.realowner = self; @@ -662,12 +662,12 @@ float W_Seeker(float req) } 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"); + precache_model(W_Model("g_seeker.md3")); + precache_model(W_Model("v_seeker.md3")); + precache_model(W_Model("h_seeker.iqm")); + precache_sound(W_Sound("tag_fire")); + precache_sound(W_Sound("flac_fire")); + precache_sound(W_Sound("seeker_fire")); SEEKER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } @@ -706,7 +706,7 @@ float W_Seeker(float req) } case WR_RELOAD: { - W_Reload(min(WEP_CVAR(seeker, missile_ammo), WEP_CVAR(seeker, tag_ammo)), "weapons/reload.wav"); + W_Reload(min(WEP_CVAR(seeker, missile_ammo), WEP_CVAR(seeker, tag_ammo)), W_Sound("reload")); return TRUE; } case WR_SUICIDEMESSAGE: diff --git a/qcsrc/common/weapons/w_shockwave.qc b/qcsrc/common/weapons/w_shockwave.qc index 759dc35ea..abf7afa85 100644 --- a/qcsrc/common/weapons/w_shockwave.qc +++ b/qcsrc/common/weapons/w_shockwave.qc @@ -122,7 +122,6 @@ void W_Shockwave_Melee_Think(void) if(!self.cnt) { self.cnt = time; - W_PlayStrengthSound(self.realowner); } // update values for v_* vectors @@ -148,7 +147,7 @@ void W_Shockwave_Melee_Think(void) (self.realowner.origin + self.realowner.view_ofs), targpos, FALSE, - self.realowner, + world, //self.realowner, ANTILAG_LATENCY(self.realowner) ); @@ -228,7 +227,7 @@ void W_Shockwave_Melee_Think(void) void W_Shockwave_Melee(void) { - sound(self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM); + sound(self, CH_WEAPON_A, W_Sound("shotgun_melee"), VOL_BASE, ATTN_NORM); weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(shockwave, melee_animtime), w_ready); entity meleetemp; @@ -359,7 +358,7 @@ void W_Shockwave_Attack(void) float i, queue = 0; // set up the shot direction - W_SetupShot(self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, WEP_CVAR(shockwave, blast_damage)); + W_SetupShot(self, FALSE, 3, W_Sound("lasergun_fire"), CH_WEAPON_B, WEP_CVAR(shockwave, blast_damage)); vector attack_endpos = (w_shotorg + (w_shotdir * WEP_CVAR(shockwave, blast_distance))); WarpZone_TraceLine(w_shotorg, attack_endpos, MOVE_NOMONSTERS, self); vector attack_hitpos = trace_endpos; @@ -710,12 +709,12 @@ float W_Shockwave(float req) 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_model(W_Model("g_shotgun.md3")); + precache_model(W_Model("v_shotgun.md3")); + precache_model(W_Model("h_shotgun.iqm")); precache_sound("misc/itempickup.wav"); - precache_sound("weapons/lasergun_fire.wav"); - precache_sound("weapons/shotgun_melee.wav"); + precache_sound(W_Sound("lasergun_fire")); + precache_sound(W_Sound("shotgun_melee")); SHOCKWAVE_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } diff --git a/qcsrc/common/weapons/w_shotgun.qc b/qcsrc/common/weapons/w_shotgun.qc index 9227cdab4..d6cd41434 100644 --- a/qcsrc/common/weapons/w_shotgun.qc +++ b/qcsrc/common/weapons/w_shotgun.qc @@ -60,11 +60,11 @@ void W_Shotgun_Attack(float isprimary) W_DecreaseAmmo(WEP_CVAR_PRI(shotgun, ammo)); - W_SetupShot(self, TRUE, 5, "weapons/shotgun_fire.wav", ((isprimary) ? CH_WEAPON_A : CH_WEAPON_SINGLE), WEP_CVAR_PRI(shotgun, damage) * WEP_CVAR_PRI(shotgun, bullets)); + W_SetupShot(self, TRUE, 5, W_Sound("shotgun_fire"), ((isprimary) ? CH_WEAPON_A : CH_WEAPON_SINGLE), WEP_CVAR_PRI(shotgun, damage) * WEP_CVAR_PRI(shotgun, bullets)); for(sc = 0;sc < WEP_CVAR_PRI(shotgun, bullets);sc = sc + 1) fireBullet(w_shotorg, w_shotdir, WEP_CVAR_PRI(shotgun, spread), WEP_CVAR_PRI(shotgun, solidpenetration), WEP_CVAR_PRI(shotgun, damage), WEP_CVAR_PRI(shotgun, force), WEP_SHOTGUN, 0); - pointparticles(particleeffectnum("shotgun_muzzleflash"), w_shotorg, w_shotdir * 1000, WEP_CVAR_PRI(shotgun, ammo)); + Send_Effect(EFFECT_SHOTGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, WEP_CVAR_PRI(shotgun, ammo)); // casing code if(autocvar_g_casings >= 1) @@ -92,7 +92,6 @@ void W_Shotgun_Melee_Think(void) 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 @@ -119,7 +118,7 @@ void W_Shotgun_Melee_Think(void) + (v_up * swing_factor * WEP_CVAR_SEC(shotgun, melee_swing_up)) + (v_right * swing_factor * WEP_CVAR_SEC(shotgun, melee_swing_side))); - WarpZone_traceline_antilag(self, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self, ANTILAG_LATENCY(self.realowner)); + WarpZone_traceline_antilag(self, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self.realowner, 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); @@ -180,7 +179,7 @@ void W_Shotgun_Melee_Think(void) void W_Shotgun_Attack2(void) { - sound(self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTEN_NORM); + sound(self, CH_WEAPON_A, W_Sound("shotgun_melee"), VOL_BASE, ATTEN_NORM); weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(shotgun, animtime), w_ready); entity meleetemp; @@ -286,12 +285,12 @@ float W_Shotgun(float req) 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_model(W_Model("g_shotgun.md3")); + precache_model(W_Model("v_shotgun.md3")); + precache_model(W_Model("h_shotgun.iqm")); precache_sound("misc/itempickup.wav"); - precache_sound("weapons/shotgun_fire.wav"); - precache_sound("weapons/shotgun_melee.wav"); + precache_sound(W_Sound("shotgun_fire")); + precache_sound(W_Sound("shotgun_melee")); SHOTGUN_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } @@ -330,7 +329,7 @@ float W_Shotgun(float req) } case WR_RELOAD: { - W_Reload(WEP_CVAR_PRI(shotgun, ammo), "weapons/reload.wav"); // WEAPONTODO + W_Reload(WEP_CVAR_PRI(shotgun, ammo), W_Sound("reload")); // WEAPONTODO return TRUE; } case WR_SUICIDEMESSAGE: diff --git a/qcsrc/common/weapons/w_tuba.qc b/qcsrc/common/weapons/w_tuba.qc index e08cf676f..9a327a679 100644 --- a/qcsrc/common/weapons/w_tuba.qc +++ b/qcsrc/common/weapons/w_tuba.qc @@ -365,7 +365,7 @@ void W_Tuba_NoteOn(float hittype) o = gettaginfo(self.exteriorweaponentity, 0); if(time > self.tuba_smoketime) { - pointparticles(particleeffectnum("smoke_ring"), o + v_up * 45 + v_right * -6 + v_forward * 8, v_up * 100, 1); + Send_Effect(EFFECT_SMOKE_RING, o + v_up * 45 + v_right * -6 + v_forward * 8, v_up * 100, 1); self.tuba_smoketime = time + 0.25; } } @@ -419,13 +419,13 @@ float W_Tuba(float req) } case WR_INIT: { - precache_model("models/weapons/g_tuba.md3"); - precache_model("models/weapons/v_tuba.md3"); - precache_model("models/weapons/h_tuba.iqm"); - precache_model("models/weapons/v_akordeon.md3"); - precache_model("models/weapons/h_akordeon.iqm"); - precache_model("models/weapons/v_kleinbottle.md3"); - precache_model("models/weapons/h_kleinbottle.iqm"); + precache_model(W_Model("g_tuba.md3")); + precache_model(W_Model("v_tuba.md3")); + precache_model(W_Model("h_tuba.iqm")); + precache_model(W_Model("v_akordeon.md3")); + precache_model(W_Model("h_akordeon.iqm")); + precache_model(W_Model("v_kleinbottle.md3")); + precache_model(W_Model("h_kleinbottle.iqm")); TUBA_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } @@ -456,7 +456,7 @@ float W_Tuba(float req) break; } W_SetupShot(self, FALSE, 0, "", 0, 0); - pointparticles(particleeffectnum("teleport"), w_shotorg, '0 0 0', 1); + Send_Effect(EFFECT_TELEPORT, w_shotorg, '0 0 0', 1); self.weaponentity.state = WS_INUSE; weapon_thinkf(WFRAME_RELOAD, 0.5, w_ready); } diff --git a/qcsrc/common/weapons/w_vaporizer.qc b/qcsrc/common/weapons/w_vaporizer.qc index 90ea15d5c..37aae84a3 100644 --- a/qcsrc/common/weapons/w_vaporizer.qc +++ b/qcsrc/common/weapons/w_vaporizer.qc @@ -19,6 +19,7 @@ REGISTER_WEAPON( #define VAPORIZER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ w_cvar(id, sn, PRI, ammo) \ w_cvar(id, sn, PRI, animtime) \ + w_cvar(id, sn, PRI, damage) \ w_cvar(id, sn, PRI, refire) \ w_cvar(id, sn, SEC, ammo) \ w_cvar(id, sn, SEC, animtime) \ @@ -32,6 +33,34 @@ REGISTER_WEAPON( w_cvar(id, sn, SEC, shotangle) \ w_cvar(id, sn, SEC, speed) \ w_cvar(id, sn, SEC, spread) \ + w_cvar(id, sn, PRI, charge) \ + w_cvar(id, sn, PRI, charge_distance) \ + w_cvar(id, sn, PRI, charge_force) \ + w_cvar(id, sn, PRI, charge_force_forwardbias) \ + w_cvar(id, sn, PRI, charge_force_zscale) \ + w_cvar(id, sn, PRI, charge_multiplier_accuracy) \ + w_cvar(id, sn, PRI, charge_multiplier_distance) \ + w_cvar(id, sn, PRI, charge_multiplier_min) \ + w_cvar(id, sn, PRI, charge_jump_damage) \ + w_cvar(id, sn, PRI, charge_jump_edgedamage) \ + w_cvar(id, sn, PRI, charge_jump_force) \ + w_cvar(id, sn, PRI, charge_jump_force_velocitybias) \ + w_cvar(id, sn, PRI, charge_jump_force_zscale) \ + w_cvar(id, sn, PRI, charge_jump_multiplier_accuracy) \ + w_cvar(id, sn, PRI, charge_jump_multiplier_distance) \ + w_cvar(id, sn, PRI, charge_jump_multiplier_min) \ + w_cvar(id, sn, PRI, charge_jump_radius) \ + w_cvar(id, sn, PRI, charge_spread_max) \ + w_cvar(id, sn, PRI, charge_spread_min) \ + w_cvar(id, sn, PRI, charge_splash_damage) \ + w_cvar(id, sn, PRI, charge_splash_edgedamage) \ + w_cvar(id, sn, PRI, charge_splash_force) \ + w_cvar(id, sn, PRI, charge_splash_force_forwardbias) \ + w_cvar(id, sn, PRI, charge_splash_force_zscale) \ + w_cvar(id, sn, PRI, charge_splash_multiplier_accuracy) \ + w_cvar(id, sn, PRI, charge_splash_multiplier_distance) \ + w_cvar(id, sn, PRI, charge_splash_multiplier_min) \ + w_cvar(id, sn, PRI, charge_splash_radius) \ w_prop(id, sn, float, reloading_ammo, reload_ammo) \ w_prop(id, sn, float, reloading_time, reload_time) \ w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ @@ -45,25 +74,197 @@ REGISTER_WEAPON( VAPORIZER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) .float vaporizer_lasthit; .float jump_interval; +.float jump_interval2; +.float held_down; +.float vaporizer_held_down; +.float vaporizer_jumpinterval; +.float superrocket_lasthit; +.float rm_force; +.float rm_damage; +.float rm_edmg; +#endif +#ifdef CSQC +void Net_ReadSuperBlastParticle(); #endif #else #ifdef SVQC void spawnfunc_weapon_vaporizer(void) { weapon_defaultspawnfunc(WEP_VAPORIZER); } void spawnfunc_weapon_minstanex(void) { spawnfunc_weapon_vaporizer(); } +void SendCSQCSuperBlastParticle(vector endpos) +{ + //endpos = WarpZone_UnTransformOrigin(transform, endpos); + + WriteByte(MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte(MSG_BROADCAST, TE_CSQC_SUPERBLASTPARTICLE); + WriteCoord(MSG_BROADCAST, w_shotorg_x); + WriteCoord(MSG_BROADCAST, w_shotorg_y); + WriteCoord(MSG_BROADCAST, w_shotorg_z); + WriteCoord(MSG_BROADCAST, endpos_x); + WriteCoord(MSG_BROADCAST, endpos_y); + WriteCoord(MSG_BROADCAST, endpos_z); + WriteByte(MSG_BROADCAST, bound(0, WEP_CVAR_PRI(vaporizer, charge_spread_max), 255)); + WriteByte(MSG_BROADCAST, bound(0, WEP_CVAR_PRI(vaporizer, charge_spread_min), 255)); + WriteByte(MSG_BROADCAST, num_for_edict(self)); +} + +float W_Vaporizer_SuperBlast_CheckSpread(vector targetorg, vector nearest_on_line, vector sw_shotorg, vector attack_endpos) +{ + float spreadlimit; + float distance_of_attack = vlen(sw_shotorg - attack_endpos); + float distance_from_line = vlen(targetorg - nearest_on_line); + + spreadlimit = (distance_of_attack ? min(1, (vlen(sw_shotorg - nearest_on_line) / distance_of_attack)) : 1); + spreadlimit = (WEP_CVAR_PRI(vaporizer, charge_spread_min) * (1 - spreadlimit) + WEP_CVAR_PRI(vaporizer, charge_spread_max) * spreadlimit); + + if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(targetorg - sw_shotorg) - normalize(attack_endpos - sw_shotorg)) * RAD2DEG) <= 90)) + return bound(0, (distance_from_line / spreadlimit), 1); + else + return FALSE; +} + +float W_Vaporizer_SuperBlast_IsVisible(entity head, vector nearest_on_line, vector sw_shotorg, vector attack_endpos) +{ + vector nearest_to_attacker = head.WarpZone_findradius_nearest; + vector center = (head.origin + (head.mins + head.maxs) * 0.5); + vector corner; + float i; + + // STEP ONE: Check if the nearest point is clear + if(W_Vaporizer_SuperBlast_CheckSpread(nearest_to_attacker, nearest_on_line, sw_shotorg, attack_endpos)) + { + WarpZone_TraceLine(sw_shotorg, nearest_to_attacker, MOVE_NOMONSTERS, self); + if(trace_fraction == 1) { return TRUE; } // yes, the nearest point is clear and we can allow the damage + } + + // STEP TWO: Check if shotorg to center point is clear + if(W_Vaporizer_SuperBlast_CheckSpread(center, nearest_on_line, sw_shotorg, attack_endpos)) + { + WarpZone_TraceLine(sw_shotorg, center, MOVE_NOMONSTERS, self); + if(trace_fraction == 1) { return TRUE; } // yes, the center point is clear and we can allow the damage + } + + // STEP THREE: Check each corner to see if they are clear + for(i=1; i<=8; ++i) + { + corner = get_corner_position(head, i); + if(W_Vaporizer_SuperBlast_CheckSpread(corner, nearest_on_line, sw_shotorg, attack_endpos)) + { + WarpZone_TraceLine(sw_shotorg, corner, MOVE_NOMONSTERS, self); + if(trace_fraction == 1) { return TRUE; } // yes, this corner is clear and we can allow the damage + } + } + + return FALSE; +} + +entity shockwave_hit[32]; +float shockwave_hit_damage[32]; +vector shockwave_hit_force[32]; + +float W_Vaporizer_SuperBlast_CheckHit(float queue, entity head, vector final_force, float final_damage) +{ + if(!head) { return FALSE; } + float i; + + ++queue; + + for(i = 1; i <= queue; ++i) + { + if(shockwave_hit[i] == head) + { + if(vlen(final_force) > vlen(shockwave_hit_force[i])) { shockwave_hit_force[i] = final_force; } + if(final_damage > shockwave_hit_damage[i]) { shockwave_hit_damage[i] = final_damage; } + return FALSE; + } + } + + shockwave_hit[queue] = head; + shockwave_hit_force[queue] = final_force; + shockwave_hit_damage[queue] = final_damage; + return TRUE; +} + +void W_RocketMinsta_Explosion(vector loc) +{ + Send_Effect(EFFECT_ROCKET_EXPLODE, loc, '0 0 0', 1); + + if(accuracy_canbegooddamage(self)) + accuracy_add(self, WEP_DEVASTATOR, autocvar_g_rm_damage, 0); + + // declarations + float multiplier, multiplier_from_accuracy; + float final_damage = 0; //, final_spread; + vector final_force, center; + entity head, next; + + float i, queue = 0; + + // splash damage/jumping trace + head = WarpZone_FindRadius(loc, autocvar_g_rm_radius, FALSE); + while(head) + { + next = head.chain; + + if(head.takedamage) + { + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + //center = CENTER_OR_VIEWOFS(head); + center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head)); + + float distance_to_head = vlen(loc - head.WarpZone_findradius_nearest); + + if (distance_to_head <= autocvar_g_rm_radius || head == self) + { + multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / autocvar_g_rm_radius)) : 0)); + multiplier = max(autocvar_g_rm_damage_multiplier_min, multiplier_from_accuracy * autocvar_g_rm_damage_multiplier_accuracy); + + final_force = normalize(center - loc); + if(head == self) + final_force = (final_force * autocvar_g_rm_force); + else + final_force = ((final_force * autocvar_g_rm_force) * multiplier); + + final_damage = (autocvar_g_rm_damage * multiplier + autocvar_g_rm_edgedamage * (1 - multiplier)); + + if(W_Vaporizer_SuperBlast_CheckHit(queue, head, final_force, final_damage)) { ++queue; } + } + } + head = next; + } + + for(i = 1; i <= queue; ++i) + { + head = shockwave_hit[i]; + final_force = shockwave_hit_force[i]; + final_damage = shockwave_hit_damage[i]; + + if(accuracy_isgooddamage(self, head)) + accuracy_add(self, WEP_DEVASTATOR, 0, final_damage); + + Damage(head, self, self, final_damage, (WEP_DEVASTATOR | HITTYPE_SPLASH), head.origin, final_force); + //print("SHOCKWAVE by ", self.netname, ": damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force)), ".\n"); + + shockwave_hit[i] = world; + shockwave_hit_force[i] = '0 0 0'; + shockwave_hit_damage[i] = 0; + } +} + void W_Vaporizer_Attack(void) { - float flying; + float flying, vaporizer_damage; flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last + vaporizer_damage = ((WEP_CVAR_PRI(vaporizer, damage) > 0) ? WEP_CVAR_PRI(vaporizer, damage) : 10000); - W_SetupShot(self, TRUE, 0, "", CH_WEAPON_A, 10000); + W_SetupShot(self, TRUE, 0, "", CH_WEAPON_A, vaporizer_damage); // handle sound separately so we can change the volume // added bonus: no longer plays the strength sound (strength gives no bonus to instakill anyway) - sound (self, CH_WEAPON_A, "weapons/minstanexfire.wav", VOL_BASE * 0.8, ATTEN_NORM); + sound (self, CH_WEAPON_A, W_Sound("minstanexfire"), VOL_BASE * 0.8, ATTEN_NORM); yoda = 0; damage_goodhits = 0; - FireRailgunBullet(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, 10000, 800, 0, 0, 0, 0, WEP_VAPORIZER); + FireRailgunBullet(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, vaporizer_damage, 800, 0, 0, 0, 0, WEP_VAPORIZER); if(yoda && flying) Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA); @@ -75,52 +276,404 @@ void W_Vaporizer_Attack(void) self.vaporizer_lasthit = damage_goodhits; - pointparticles(particleeffectnum("nex_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + Send_Effect(EFFECT_VORTEX_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); // teamcolor / hit beam effect vector v; v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); + float f; switch(self.team) { - case NUM_TEAM_1: // Red - if(damage_goodhits) - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3RED_HIT"), w_shotorg, v); - else - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3RED"), w_shotorg, v); - break; - case NUM_TEAM_2: // Blue - if(damage_goodhits) - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3BLUE_HIT"), w_shotorg, v); - else - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3BLUE"), w_shotorg, v); - break; - case NUM_TEAM_3: // Yellow - if(damage_goodhits) - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3YELLOW_HIT"), w_shotorg, v); - else - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3YELLOW"), w_shotorg, v); - break; - case NUM_TEAM_4: // Pink - if(damage_goodhits) - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3PINK_HIT"), w_shotorg, v); - else - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3PINK"), w_shotorg, v); - break; - default: - if(damage_goodhits) - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3_HIT"), w_shotorg, v); - else - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3"), w_shotorg, v); - break; + case NUM_TEAM_1: f = (damage_goodhits) ? EFFECT_VAPORIZER_RED_HIT : EFFECT_VAPORIZER_RED; break; + case NUM_TEAM_2: f = (damage_goodhits) ? EFFECT_VAPORIZER_BLUE_HIT : EFFECT_VAPORIZER_BLUE; break; + case NUM_TEAM_3: f = (damage_goodhits) ? EFFECT_VAPORIZER_YELLOW_HIT : EFFECT_VAPORIZER_YELLOW; break; + case NUM_TEAM_4: f = (damage_goodhits) ? EFFECT_VAPORIZER_PINK_HIT : EFFECT_VAPORIZER_PINK; break; + default: f = (damage_goodhits) ? EFFECT_VAPORIZER_NEUTRAL_HIT : EFFECT_VAPORIZER_NEUTRAL; break; } + if(autocvar_g_rm) + if(!(trace_dphitq3surfaceflags & (Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT))) + W_RocketMinsta_Explosion(trace_endpos); + + Send_Effect(f, w_shotorg, v, 0); + W_DecreaseAmmo(((g_instagib) ? 1 : WEP_CVAR_PRI(vaporizer, ammo))); } +void W_Vaporizer_SuperBlast() +{ + // declarations + float multiplier, multiplier_from_accuracy, multiplier_from_distance; + float final_damage; //, final_spread; + vector final_force, center, vel; + entity head, next; + + float i, queue = 0; + + // set up the shot direction + W_SetupShot(self, FALSE, 3, W_Sound("minstanex_charge2"), CH_WEAPON_B, WEP_CVAR_PRI(vaporizer, damage)); + vector attack_endpos = (w_shotorg + (w_shotdir * WEP_CVAR_PRI(vaporizer, charge_distance))); + WarpZone_TraceLine(w_shotorg, attack_endpos, MOVE_NOMONSTERS, self); + vector attack_hitpos = trace_endpos; + float distance_to_end = vlen(w_shotorg - attack_endpos); + float distance_to_hit = vlen(w_shotorg - attack_hitpos); + //entity transform = WarpZone_trace_transform; + + // do the firing effect now + SendCSQCSuperBlastParticle(attack_endpos); + Damage_DamageInfo(attack_hitpos, WEP_CVAR_PRI(vaporizer, charge_splash_damage), WEP_CVAR_PRI(vaporizer, charge_splash_edgedamage), WEP_CVAR_PRI(vaporizer, charge_splash_radius), w_shotdir * WEP_CVAR_PRI(vaporizer, charge_splash_force), (WEP_VAPORIZER | HITTYPE_SPLASH), 0, self); + + // splash damage/jumping trace + head = WarpZone_FindRadius(attack_hitpos, max(WEP_CVAR_PRI(vaporizer, charge_splash_radius), WEP_CVAR_PRI(vaporizer, charge_jump_radius)), FALSE); + while(head) + { + next = head.chain; + + if(head.takedamage) + { + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + center = CENTER_OR_VIEWOFS(head); + + float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest); + + if((head == self) && (distance_to_head <= WEP_CVAR_PRI(vaporizer, charge_jump_radius))) + { + multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / WEP_CVAR_PRI(vaporizer, charge_jump_radius))) : 0)); + multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_hit / distance_to_end)) : 0)); + multiplier = max(WEP_CVAR_PRI(vaporizer, charge_jump_multiplier_min), ((multiplier_from_accuracy * WEP_CVAR_PRI(vaporizer, charge_jump_multiplier_accuracy)) + (multiplier_from_distance * WEP_CVAR_PRI(vaporizer, charge_jump_multiplier_distance)))); + + final_force = ((normalize(center - attack_hitpos) * WEP_CVAR_PRI(vaporizer, charge_jump_force)) * multiplier); + vel = head.velocity; vel_z = 0; + vel = normalize(vel) * bound(0, vlen(vel) / autocvar_sv_maxspeed, 1) * WEP_CVAR_PRI(vaporizer, charge_jump_force_velocitybias); + final_force = (vlen(final_force) * normalize(normalize(final_force) + vel)); + final_force_z *= WEP_CVAR_PRI(vaporizer, charge_jump_force_zscale); + final_damage = (WEP_CVAR_PRI(vaporizer, charge_jump_damage) * multiplier + WEP_CVAR_PRI(vaporizer, charge_jump_edgedamage) * (1 - multiplier)); + + Damage(head, self, self, final_damage, (WEP_VAPORIZER | HITTYPE_SPLASH), head.origin, final_force); + //print("SELF HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); + } + else if (distance_to_head <= WEP_CVAR_PRI(vaporizer, charge_splash_radius)) + { + multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / WEP_CVAR_PRI(vaporizer, charge_splash_radius))) : 0)); + multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_hit / distance_to_end)) : 0)); + multiplier = max(WEP_CVAR_PRI(vaporizer, charge_splash_multiplier_min), ((multiplier_from_accuracy * WEP_CVAR_PRI(vaporizer, charge_splash_multiplier_accuracy)) + (multiplier_from_distance * WEP_CVAR_PRI(vaporizer, charge_splash_multiplier_distance)))); + + final_force = normalize(center - (attack_hitpos - (w_shotdir * WEP_CVAR_PRI(vaporizer, charge_splash_force_forwardbias)))); + //te_lightning2(world, attack_hitpos, (attack_hitpos + (final_force * 200))); + final_force = ((final_force * WEP_CVAR_PRI(vaporizer, charge_splash_force)) * multiplier); + final_force_z *= WEP_CVAR_PRI(vaporizer, charge_splash_force_zscale); + final_damage = (WEP_CVAR_PRI(vaporizer, charge_splash_damage) * multiplier + WEP_CVAR_PRI(vaporizer, charge_splash_edgedamage) * (1 - multiplier)); + + if(W_Vaporizer_SuperBlast_CheckHit(queue, head, final_force, final_damage)) { ++queue; } + //print("SPLASH HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); + } + } + head = next; + } + + // cone damage trace + head = WarpZone_FindRadius(w_shotorg, WEP_CVAR_PRI(vaporizer, charge_distance), FALSE); + while(head) + { + next = head.chain; + + if((head != self) && head.takedamage) + { + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + center = CENTER_OR_VIEWOFS(head); + + // find the closest point on the enemy to the center of the attack + float ang; // angle between shotdir and h + float h; // hypotenuse, which is the distance between attacker to head + float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin + + h = vlen(center - self.origin); + ang = acos(dotproduct(normalize(center - self.origin), w_shotdir)); + a = h * cos(ang); + + vector nearest_on_line = (w_shotorg + a * w_shotdir); + vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line); + float distance_to_target = vlen(w_shotorg - nearest_to_attacker); // todo: use the findradius function for this + + if((distance_to_target <= WEP_CVAR_PRI(vaporizer, charge_distance)) + && (W_Vaporizer_SuperBlast_IsVisible(head, nearest_on_line, w_shotorg, attack_endpos))) + { + multiplier_from_accuracy = (1 - W_Vaporizer_SuperBlast_CheckSpread(nearest_to_attacker, nearest_on_line, w_shotorg, attack_endpos)); + multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_target / distance_to_end)) : 0)); + multiplier = max(WEP_CVAR_PRI(vaporizer, charge_multiplier_min), ((multiplier_from_accuracy * WEP_CVAR_PRI(vaporizer, charge_multiplier_accuracy)) + (multiplier_from_distance * WEP_CVAR_PRI(vaporizer, charge_multiplier_distance)))); + + final_force = normalize(center - (nearest_on_line - (w_shotdir * WEP_CVAR_PRI(vaporizer, charge_force_forwardbias)))); + //te_lightning2(world, nearest_on_line, (attack_hitpos + (final_force * 200))); + final_force = ((final_force * WEP_CVAR_PRI(vaporizer, charge_force)) * multiplier); + final_force_z *= WEP_CVAR_PRI(vaporizer, charge_force_zscale); + final_damage = (WEP_CVAR_PRI(vaporizer, damage) * multiplier + WEP_CVAR_PRI(vaporizer, damage) * (1 - multiplier)); + + if(W_Vaporizer_SuperBlast_CheckHit(queue, head, final_force, final_damage)) { ++queue; } + //print("CONE HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); + } + } + head = next; + } + + for(i = 1; i <= queue; ++i) + { + head = shockwave_hit[i]; + final_force = shockwave_hit_force[i]; + final_damage = shockwave_hit_damage[i]; + + Damage(head, self, self, final_damage, (WEP_VAPORIZER | HITTYPE_SPLASH), head.origin, final_force); + + if(accuracy_isgooddamage(self, head)) + accuracy_add(self, WEP_VAPORIZER, 1, final_damage); + //print("SHOCKWAVE by ", self.netname, ": damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force)), ".\n"); + + shockwave_hit[i] = world; + shockwave_hit_force[i] = '0 0 0'; + shockwave_hit_damage[i] = 0; + } + //print("queue was ", ftos(queue), ".\n\n"); + + self.ammo_supercells -= 1; +} + +float W_Vaporizer_SuperBlast_CheckAmmo() +{ + if(WEP_CVAR_PRI(vaporizer, charge) == 3) + return TRUE; // forced + float ammo_amount; + ammo_amount = self.ammo_supercells >= 1; + ammo_amount += self.(weapon_load[WEP_VAPORIZER]) >= 1; + return ammo_amount; +} + +void W_RocketMinsta_SuperRocket_Explode() +{ + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + RadiusDamage (self, self.realowner, 250, 250, 250, world, world, 250, self.projectiledeathtype, other); + + remove (self); +} + +void W_RocketMinsta_SuperRocket_Think (void) +{ + self.nextthink = time; + makevectors(self.angles); + self.velocity = v_forward * autocvar_g_rm_superrocket_speed; + if (time > self.cnt) + { + other = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + W_RocketMinsta_SuperRocket_Explode (); + return; + } +} + +void W_RocketMinsta_SuperRocket_Touch (void) +{ + if(other.takedamage == DAMAGE_AIM && other.classname != "object" && time > other.superrocket_lasthit) + { + float vaporizer_damage = ((WEP_CVAR_PRI(vaporizer, damage)) ? WEP_CVAR_PRI(vaporizer, damage) : 10000); + Damage(other, self, self.realowner, vaporizer_damage, WEP_DEVASTATOR, other.origin, '0 0 0'); + other.superrocket_lasthit = time + 1; + return; + } + W_RocketMinsta_SuperRocket_Explode(); +} + +void W_RocketMinsta_SuperRocket_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; + self.angles = vectoangles(self.velocity); + + if (self.health <= 0) + W_PrepareExplosionByDamage(attacker, W_RocketMinsta_SuperRocket_Explode); +} + +void W_RocketMinsta_SuperRocket() +{ + entity missile; + float vaporizer_damage = ((WEP_CVAR_PRI(vaporizer, damage)) ? WEP_CVAR_PRI(vaporizer, damage) : 10000); + + makevectors(self.angles); + + W_SetupShot_ProjectileSize (self, '-32 -32 -32', '32 32 32', FALSE, 5, W_Sound("minstanex_charge2"), CH_WEAPON_A, vaporizer_damage); + Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + missile = WarpZone_RefSys_SpawnSameRefSys(self); + missile.owner = missile.realowner = self; + missile.classname = "rocket"; + missile.bot_dodge = TRUE; + missile.bot_dodgerating = vaporizer_damage; + + missile.takedamage = DAMAGE_YES; + missile.damageforcescale = 0; + missile.health = 350; + missile.event_damage = W_RocketMinsta_SuperRocket_Damage; + missile.damagedbycontents = TRUE; + + missile.movetype = MOVETYPE_FLYMISSILE; + PROJECTILE_MAKETRIGGER(missile); + missile.projectiledeathtype = WEP_DEVASTATOR; + setsize (missile, '-32 -32 -32', '32 32 32'); // give it some size so it can be shot + + setorigin (missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point + W_SetupProjVelocity_Basic(missile, autocvar_g_rm_superrocket_speed, 0); + missile.angles = self.v_angle; + + missile.touch = W_RocketMinsta_SuperRocket_Touch; + missile.think = W_RocketMinsta_SuperRocket_Think; + missile.nextthink = time; + missile.cnt = time + 10; + missile.flags = FL_PROJECTILE; + //missile.missile_flags = MIF_SPLASH; + + CSQCProjectile(missile, TRUE, PROJECTILE_SUPERROCKET, FALSE); // because of fly sound + + self.ammo_supercells -= 1; +} + +void W_RocketMinsta_Laser_Explode (void) +{ + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(DIFF_TEAM(self.realowner, other)) + if(other.deadflag == DEAD_NO) + if(IsFlying(other)) + Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + RadiusDamage (self, self.realowner, self.rm_damage, self.rm_edmg, autocvar_g_rm_laser_radius, world, world, self.rm_force, self.projectiledeathtype, other); + remove(self); +} + +void W_RocketMinsta_Laser_Touch (void) +{ + PROJECTILE_TOUCH; + //W_RocketMinsta_Laser_Explode (); + RadiusDamage (self, self.realowner, self.rm_damage, self.rm_edmg, autocvar_g_rm_laser_radius, world, world, self.rm_force, self.projectiledeathtype, other); + remove(self); +} + +void W_RocketMinsta_Attack2(void) +{ + makevectors(self.v_angle); + + entity proj; + float counter = 0; + float total = autocvar_g_rm_laser_count; + float spread = autocvar_g_rm_laser_spread; + float rndspread = autocvar_g_rm_laser_spread_random; + + float w = self.weapon; + self.weapon = WEP_ELECTRO; + W_SetupShot_ProjectileSize (self, '0 0 -3', '0 0 -3', FALSE, 2, W_Sound("crylink_fire"), CH_WEAPON_A, autocvar_g_rm_laser_damage); + self.weapon = w; + + Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + while(counter < total) + { + proj = spawn (); + proj.classname = "plasma_prim"; + proj.owner = proj.realowner = self; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = autocvar_g_rm_laser_damage; + proj.use = W_RocketMinsta_Laser_Explode; + proj.think = adaptor_think2use_hittype_splash; + proj.nextthink = time + autocvar_g_rm_laser_lifetime; + PROJECTILE_MAKETRIGGER(proj); + proj.projectiledeathtype = WEP_ELECTRO; + setorigin(proj, w_shotorg); + + proj.rm_force = autocvar_g_rm_laser_force / total; + proj.rm_damage = autocvar_g_rm_laser_damage / total; + proj.rm_edmg = proj.rm_damage; + + //W_SetupProjectileVelocity(proj, autocvar_g_rm_laser_speed, spread * (rndspread ? random() : 1) * autocvar_g_rm_laser_speed); + + proj.movetype = MOVETYPE_BOUNCEMISSILE; + //W_SETUPPROJECTILEVELOCITY(proj, g_balance_minstanex_laser); + proj.velocity = (w_shotdir + (((counter + 0.5) / total) * 2 - 1) * v_right * (spread * (rndspread ? random() : 1))) * cvar("g_rm_laser_speed"); + proj.velocity_z = proj.velocity_z + cvar("g_rm_laser_zspread") * (random() - 0.5); + proj.velocity = W_CalculateProjectileVelocity(proj.realowner.velocity, proj.velocity, TRUE); + proj.angles = vectoangles(proj.velocity); + proj.touch = W_RocketMinsta_Laser_Touch; + setsize(proj, '0 0 -3', '0 0 -3'); + proj.flags = FL_PROJECTILE; + proj.missile_flags = MIF_SPLASH; + + CSQCProjectile(proj, TRUE, PROJECTILE_ROCKETMINSTA_LASER, TRUE); + + other = proj; MUTATOR_CALLHOOK(EditProjectile); + counter++; + } +} + +void W_RocketMinsta_Attack3 (void) +{ + makevectors(self.v_angle); + + entity proj; + float counter = 0; + float total = 1; + + float w = self.weapon; + self.weapon = WEP_ELECTRO; + W_SetupShot_ProjectileSize (self, '0 0 -3', '0 0 -3', FALSE, 2, W_Sound("electro_fire2"), CH_WEAPON_A, autocvar_g_rm_laser_damage); + self.weapon = w; + + Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + while(counter < total) + { + proj = spawn (); + proj.classname = "plasma_prim"; + proj.owner = proj.realowner = self; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = autocvar_g_rm_laser_damage; + proj.use = W_RocketMinsta_Laser_Explode; + proj.think = adaptor_think2use_hittype_splash; + proj.nextthink = time + autocvar_g_rm_laser_lifetime; + PROJECTILE_MAKETRIGGER(proj); + proj.projectiledeathtype = WEP_ELECTRO; + setorigin(proj, w_shotorg); + + proj.rm_force = autocvar_g_rm_laser_force / total; + proj.rm_damage = autocvar_g_rm_laser_damage / total; + proj.rm_edmg = proj.rm_damage; + + //W_SetupProjectileVelocity(proj, autocvar_g_rm_laser_speed, spread * (rndspread ? random() : 1) * autocvar_g_rm_laser_speed); + + proj.movetype = MOVETYPE_BOUNCEMISSILE; + proj.velocity = w_shotdir * autocvar_g_rm_laser_speed; + proj.velocity = W_CalculateProjectileVelocity(proj.realowner.velocity, proj.velocity, TRUE); + proj.angles = vectoangles(proj.velocity); + proj.touch = W_RocketMinsta_Laser_Touch; + setsize(proj, '0 0 -3', '0 0 -3'); + proj.flags = FL_PROJECTILE; + proj.missile_flags = MIF_SPLASH; + + CSQCProjectile(proj, TRUE, PROJECTILE_ROCKETMINSTA_LASER, TRUE); + + other = proj; MUTATOR_CALLHOOK(EditProjectile); + counter++; + } +} + float W_Vaporizer(float req) { float ammo_amount; float vaporizer_ammo; + float rapid = autocvar_g_rm_laser_rapid; // now multiple WR_s use this vaporizer_ammo = ((g_instagib) ? 1 : WEP_CVAR_PRI(vaporizer, ammo)); @@ -143,18 +696,74 @@ float W_Vaporizer(float req) WEP_ACTION(self.weapon, WR_RELOAD); else if(WEP_CVAR(vaporizer, reload_ammo) && self.clip_load < vaporizer_ammo) // forced reload WEP_ACTION(self.weapon, WR_RELOAD); - else if(self.BUTTON_ATCK) + if (self.BUTTON_ATCK && ((self.ammo_cells && autocvar_g_rm) || !autocvar_g_rm) && !forbidWeaponUse(self)) { - if(weapon_prepareattack(0, WEP_CVAR_PRI(vaporizer, refire))) + if (!self.vaporizer_held_down) + { + if(weapon_prepareattack(0, WEP_CVAR_PRI(vaporizer, refire))) + { + W_Vaporizer_Attack(); + if(time >= self.vaporizer_jumpinterval) + if(WEP_CVAR_PRI(vaporizer, charge) == 1 || (WEP_CVAR_PRI(vaporizer, charge) == 2 && self.cvar_cl_charge) || WEP_CVAR_PRI(vaporizer, charge) >= 3) + if(W_Vaporizer_SuperBlast_CheckAmmo()) + self.vaporizer_held_down = 1; + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(vaporizer, animtime), w_ready); + } + } + else if(self.vaporizer_held_down == 1) { - W_Vaporizer_Attack(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(vaporizer, animtime), w_ready); + if(weapon_prepareattack(0, WEP_CVAR_PRI(vaporizer, refire))) + { + sound(self, CH_SHOTS_SINGLE, W_Sound("minstanex_charge_new"), VOL_BASE, ATTN_NORM); + self.vaporizer_held_down = 2; + self.vaporizer_jumpinterval = time + 1.822; + weapon_thinkf(WFRAME_DONTCHANGE, WEP_CVAR_PRI(vaporizer, refire), w_ready); // update frame so we dont get stuck + } + } + else if(time >= self.vaporizer_jumpinterval && self.vaporizer_held_down == 2) + { + if(weapon_prepareattack(0, WEP_CVAR_PRI(vaporizer, refire))) + { + stopsound(self, CH_SHOTS_SINGLE); + if((autocvar_g_rm && autocvar_g_rm_superrocket) || autocvar_g_rm_superrocket == 2) + W_RocketMinsta_SuperRocket(); + else + W_Vaporizer_SuperBlast(); + + self.vaporizer_held_down = 0; + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(vaporizer, animtime), w_ready); + } } } - else if(self.BUTTON_ATCK2) + else { - if(self.jump_interval <= time) - if(weapon_prepareattack(1, -1)) + if(self.vaporizer_held_down >= 2) + stopsound(self, CH_SHOTS_SINGLE); + self.vaporizer_held_down = FALSE; + } + + if (self.BUTTON_ATCK2 || (self.BUTTON_ATCK && !self.ammo_cells && autocvar_g_rm)) + { + if((autocvar_g_rm && autocvar_g_rm_laser) || autocvar_g_rm_laser == 2) + { + if(self.jump_interval <= time && !self.held_down) + { + if(rapid) + self.held_down = TRUE; + self.jump_interval = time + autocvar_g_rm_laser_refire; + self.jump_interval2 = time + autocvar_g_rm_laser_rapid_delay; + damage_goodhits = 0; + W_RocketMinsta_Attack2(); + } + else if(rapid && self.jump_interval2 <= time && self.held_down) + { + self.jump_interval2 = time + autocvar_g_rm_laser_rapid_refire; + damage_goodhits = 0; + W_RocketMinsta_Attack3(); + //weapon_thinkf(WFRAME_FIRE2, autocvar_g_rm_laser_rapid_animtime, w_ready); + } + } + else if (self.jump_interval <= time) { // handle refire manually, so that primary and secondary can be fired without conflictions (important for instagib) self.jump_interval = time + WEP_CVAR_SEC(vaporizer, refire) * W_WeaponRateFactor(); @@ -181,19 +790,23 @@ float W_Vaporizer(float req) weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(vaporizer, animtime), w_ready); } } + else + self.held_down = FALSE; return TRUE; } case WR_INIT: { precache_model("models/nexflash.md3"); - precache_model("models/weapons/g_minstanex.md3"); - precache_model("models/weapons/v_minstanex.md3"); - precache_model("models/weapons/h_minstanex.iqm"); - precache_sound("weapons/minstanexfire.wav"); - precache_sound("weapons/nexwhoosh1.wav"); - precache_sound("weapons/nexwhoosh2.wav"); - precache_sound("weapons/nexwhoosh3.wav"); + precache_model(W_Model("g_minstanex.md3")); + precache_model(W_Model("v_minstanex.md3")); + precache_model(W_Model("h_minstanex.iqm")); + precache_sound (W_Sound("minstanex_charge_new")); + precache_sound (W_Sound("minstanex_charge2")); + precache_sound(W_Sound("minstanexfire")); + precache_sound(W_Sound("nexwhoosh1")); + precache_sound(W_Sound("nexwhoosh2")); + precache_sound(W_Sound("nexwhoosh3")); //W_Blaster(WR_INIT); // Samual: Is this really the proper thing to do? Didn't we already run this previously? VAPORIZER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; @@ -236,22 +849,146 @@ float W_Vaporizer(float req) else used_ammo = vaporizer_ammo; - W_Reload(used_ammo, "weapons/reload.wav"); + W_Reload(used_ammo, W_Sound("reload")); return TRUE; } case WR_SUICIDEMESSAGE: { - return WEAPON_THINKING_WITH_PORTALS; + if(w_deathtype & HITTYPE_SPLASH) + return WEAPON_VAPORIZER_SUICIDE; + else + return WEAPON_THINKING_WITH_PORTALS; } case WR_KILLMESSAGE: { - return WEAPON_VAPORIZER_MURDER; + if(w_deathtype & HITTYPE_SPLASH) + return WEAPON_VAPORIZER_MURDER_CHARGE; + else + return WEAPON_VAPORIZER_MURDER; } } return FALSE; } #endif #ifdef CSQC + +.vector sb_shotorg; +.vector sb_endpos; +.float sb_spread_max; +.float sb_spread_min; +.float sb_time; + +void Draw_SuperBlast() +{ + float a = bound(0, (0.5 - ((time - self.sb_time) / 0.4)), 0.5); + + if(!a) { remove(self); } + + vector deviation, angle; + + vector sb_color = getcsqcplayercolor(self.sv_entnum); // GetTeamRGB(GetPlayerColor(self.sv_entnum)); + + vector first_min_end = '0 0 0', prev_min_end = '0 0 0', new_min_end = '0 0 0'; + vector first_max_end = '0 0 0', prev_max_end = '0 0 0', new_max_end = '0 0 0'; + + float new_max_dist, new_min_dist; + + vector shotdir = normalize(self.sb_endpos - self.sb_shotorg); + vectorvectors(shotdir); + vector right = v_right; + vector up = v_up; + + float counter, dist_before_normal = 200, shots = 20; + + vector min_end = ((self.sb_shotorg + (shotdir * dist_before_normal)) + (up * self.sb_spread_min)); + vector max_end = (self.sb_endpos + (up * self.sb_spread_max)); + + float spread_to_min = vlen(normalize(min_end - self.sb_shotorg) - shotdir); + float spread_to_max = vlen(normalize(max_end - min_end) - shotdir); + + for(counter = 0; counter < shots; ++counter) + { + // perfect circle effect lines + angle = '0 0 0'; + makevectors('0 360 0' * (0.75 + (counter - 0.5) / shots)); + angle_y = v_forward_x; + angle_z = v_forward_y; + + // first do the spread_to_min effect + deviation = angle * spread_to_min; + deviation = ((shotdir + (right * deviation_y) + (up * deviation_z))); + new_min_dist = dist_before_normal; + new_min_end = (self.sb_shotorg + (deviation * new_min_dist)); + //te_lightning2(world, new_min_end, self.sb_shotorg); + + // then calculate spread_to_max effect + deviation = angle * spread_to_max; + deviation = ((shotdir + (right * deviation_y) + (up * deviation_z))); + new_max_dist = vlen(new_min_end - self.sb_endpos); + new_max_end = (new_min_end + (deviation * new_max_dist)); + //te_lightning2(world, new_end, prev_min_end); + + + if(counter == 0) + { + first_min_end = new_min_end; + first_max_end = new_max_end; + } + + if(counter >= 1) + { + R_BeginPolygon("", DRAWFLAG_NORMAL); + R_PolygonVertex(prev_min_end, '0 0 0', sb_color, a); + R_PolygonVertex(new_min_end, '0 0 0', sb_color, a); + R_PolygonVertex(self.sb_shotorg, '0 0 0', sb_color, a); + R_EndPolygon(); + + R_BeginPolygon("", DRAWFLAG_NORMAL); + R_PolygonVertex(new_min_end, '0 0 0', sb_color, a); + R_PolygonVertex(prev_min_end, '0 0 0', sb_color, a); + R_PolygonVertex(prev_max_end, '0 0 0', sb_color, a); + R_PolygonVertex(new_max_end, '0 0 0', sb_color, a); + R_EndPolygon(); + } + + prev_min_end = new_min_end; + prev_max_end = new_max_end; + + if((counter + 1) == shots) + { + R_BeginPolygon("", DRAWFLAG_NORMAL); + R_PolygonVertex(prev_min_end, '0 0 0', sb_color, a); + R_PolygonVertex(first_min_end, '0 0 0', sb_color, a); + R_PolygonVertex(self.sb_shotorg, '0 0 0', sb_color, a); + R_EndPolygon(); + + R_BeginPolygon("", DRAWFLAG_NORMAL); + R_PolygonVertex(first_min_end, '0 0 0', sb_color, a); + R_PolygonVertex(prev_min_end, '0 0 0', sb_color, a); + R_PolygonVertex(prev_max_end, '0 0 0', sb_color, a); + R_PolygonVertex(first_max_end, '0 0 0', sb_color, a); + R_EndPolygon(); + } + } +} + +void Net_ReadSuperBlastParticle() +{ + entity shockwave; + shockwave = spawn(); + shockwave.draw = Draw_SuperBlast; + + shockwave.sb_shotorg_x = ReadCoord(); shockwave.sb_shotorg_y = ReadCoord(); shockwave.sb_shotorg_z = ReadCoord(); + shockwave.sb_endpos_x = ReadCoord(); shockwave.sb_endpos_y = ReadCoord(); shockwave.sb_endpos_z = ReadCoord(); + + shockwave.sb_spread_max = ReadByte(); + shockwave.sb_spread_min = ReadByte(); + + shockwave.sv_entnum = ReadByte(); + + shockwave.sb_time = time; +} + float W_Vaporizer(float req) { switch(req) @@ -260,6 +997,7 @@ float W_Vaporizer(float req) { vector org2; org2 = w_org + w_backoff * 6; + if(!(w_deathtype & HITTYPE_SPLASH)) if(w_deathtype & HITTYPE_SECONDARY) { pointparticles(particleeffectnum("laser_impact"), org2, w_backoff * 1000, 1); diff --git a/qcsrc/common/weapons/w_vortex.qc b/qcsrc/common/weapons/w_vortex.qc index 6512d0430..c61cd75bd 100644 --- a/qcsrc/common/weapons/w_vortex.qc +++ b/qcsrc/common/weapons/w_vortex.qc @@ -100,10 +100,10 @@ void W_Vortex_Attack(float issecondary) mydmg *= charge; myforce *= charge; - W_SetupShot(self, TRUE, 5, "weapons/nexfire.wav", CH_WEAPON_A, mydmg); - if(charge > WEP_CVAR(vortex, charge_animlimit) && WEP_CVAR(vortex, charge_animlimit)) // if the Vortex is overcharged, we play an extra sound + W_SetupShot(self, TRUE, 5, W_Sound("nexfire"), CH_WEAPON_A, mydmg); + if(WEP_CVAR(vortex, charge) && charge > WEP_CVAR(vortex, charge_animlimit) && WEP_CVAR(vortex, charge_animlimit)) // if the Vortex is overcharged, we play an extra sound { - sound(self, CH_WEAPON_B, "weapons/nexcharge.wav", VOL_BASE * (charge - 0.5 * WEP_CVAR(vortex, charge_animlimit)) / (1 - 0.5 * WEP_CVAR(vortex, charge_animlimit)), ATTN_NORM); + sound(self, CH_WEAPON_B, W_Sound("nexcharge"), VOL_BASE * (charge - 0.5 * WEP_CVAR(vortex, charge_animlimit)) / (1 - 0.5 * WEP_CVAR(vortex, charge_animlimit)), ATTN_NORM); } yoda = 0; @@ -243,14 +243,14 @@ float W_Vortex(float req) case WR_INIT: { precache_model("models/nexflash.md3"); - precache_model("models/weapons/g_nex.md3"); - precache_model("models/weapons/v_nex.md3"); - precache_model("models/weapons/h_nex.iqm"); - precache_sound("weapons/nexfire.wav"); - precache_sound("weapons/nexcharge.wav"); - precache_sound("weapons/nexwhoosh1.wav"); - precache_sound("weapons/nexwhoosh2.wav"); - precache_sound("weapons/nexwhoosh3.wav"); + precache_model(W_Model("g_nex.md3")); + precache_model(W_Model("v_nex.md3")); + precache_model(W_Model("h_nex.iqm")); + precache_sound(W_Sound("nexfire")); + precache_sound(W_Sound("nexcharge")); + precache_sound(W_Sound("nexwhoosh1")); + precache_sound(W_Sound("nexwhoosh2")); + precache_sound(W_Sound("nexwhoosh3")); VORTEX_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) return TRUE; } @@ -281,7 +281,7 @@ float W_Vortex(float req) } case WR_RELOAD: { - W_Reload(min(WEP_CVAR_PRI(vortex, ammo), WEP_CVAR_SEC(vortex, ammo)), "weapons/reload.wav"); + W_Reload(min(WEP_CVAR_PRI(vortex, ammo), WEP_CVAR_SEC(vortex, ammo)), W_Sound("reload")); return TRUE; } case WR_SUICIDEMESSAGE: @@ -306,7 +306,7 @@ float W_Vortex(float req) { vector org2; org2 = w_org + w_backoff * 6; - pointparticles(particleeffectnum("nex_impact"), org2, '0 0 0', 1); + pointparticles(particleeffectnum(((autocvar_cl_particles_newvortexbeam && (getstati(STAT_ALLOW_OLDVORTEXBEAM) || isdemo())) ? "nex_impact_new" : "nex_impact")), org2, '0 0 0', 1); if(!w_issilent) sound(self, CH_SHOTS, "weapons/neximpact.wav", VOL_BASE, ATTN_NORM); diff --git a/qcsrc/common/weapons/weapons.qc b/qcsrc/common/weapons/weapons.qc index 284d9811d..90287b789 100644 --- a/qcsrc/common/weapons/weapons.qc +++ b/qcsrc/common/weapons/weapons.qc @@ -38,6 +38,16 @@ void WepSet_AddStat() #endif #endif } +void WepSet_AddStat_InMap() +{ + addstat(STAT_WEAPONSINMAP, AS_INT, weaponsinmap_x); +#if WEP_MAXCOUNT > 24 + addstat(STAT_WEAPONSINMAP2, AS_INT, weaponsinmap_y); +#if WEP_MAXCOUNT > 48 + addstat(STAT_WEAPONSINMAP3, AS_INT, weaponsinmap_z); +#endif +#endif +} void WriteWepSet(float dst, WepSet w) { #if WEP_MAXCOUNT > 48 @@ -62,6 +72,18 @@ WepSet WepSet_GetFromStat() #endif return w; } +WepSet WepSet_GetFromStat_InMap() +{ + WepSet w = '0 0 0'; + w_x = getstati(STAT_WEAPONSINMAP); +#if WEP_MAXCOUNT > 24 + w_y = getstati(STAT_WEAPONSINMAP2); +#if WEP_MAXCOUNT > 48 + w_z = getstati(STAT_WEAPONSINMAP3); +#endif +#endif + return w; +} WepSet ReadWepSet() { #if WEP_MAXCOUNT > 48 @@ -103,7 +125,11 @@ void register_weapon( e.wpcolor = clr; e.wpmodel = strzone(strcat("wpn-", ftos(id))); e.mdl = modelname; +#ifdef CSQC e.model = strzone(strcat("models/weapons/g_", modelname, ".md3")); +#elif defined(SVQC) + e.model = strzone(W_Model(strcat("g_", modelname, ".md3"))); +#endif e.w_simplemdl = strzone(simplemdl); // simpleitems weapon model/image e.w_crosshair = strzone(car(crosshair)); string s = cdr(crosshair); @@ -285,6 +311,7 @@ string GetAmmoPicture(.float ammotype) case ammo_rockets: return "ammo_rockets"; case ammo_cells: return "ammo_cells"; case ammo_plasma: return "ammo_cells"; + case ammo_supercells: return "ammo_supercells"; case ammo_fuel: return "ammo_fuel"; default: return ""; // wtf, no ammo type? } @@ -300,7 +327,8 @@ string GetAmmoPicture(.float ammotype) case 2: return ammo_rockets; case 3: return ammo_cells; case 4: return ammo_plasma; - case 5: return ammo_fuel; + case 5: return ammo_supercells; + case 6: return ammo_fuel; default: return ammo_none; } } @@ -314,8 +342,37 @@ float GetAmmoStat(.float ammotype) case ammo_rockets: return STAT_ROCKETS; case ammo_cells: return STAT_CELLS; case ammo_plasma: return STAT_PLASMA; + case ammo_supercells: return STAT_SUPERCELLS; case ammo_fuel: return STAT_FUEL; default: return -1; } } #endif + +#ifdef SVQC +string W_Sound(string w_snd) +{ + if(autocvar_sv_weapons_sounddir != "" && autocvar_sv_weapons_sounddir != "default") + { + string thesnd = sprintf("weapons_%s/%s", autocvar_sv_weapons_sounddir, w_snd); + float globhandle = search_begin(strcat("sound/", thesnd, ".*"), TRUE, FALSE); + if(globhandle >= 0) + { + search_end(globhandle); + return strcat(thesnd, ".wav"); + } + } + return strcat("weapons/", w_snd, ".wav"); +} + +string W_Model(string w_mdl) +{ + if(autocvar_sv_weapons_modeloverride != "" && autocvar_sv_weapons_modeloverride != "default") + { + string themdl = sprintf("models/weapons_%s/%s", autocvar_sv_weapons_modeloverride, w_mdl); + if(fexists(themdl)) + return themdl; + } + return strcat("models/weapons/", w_mdl); +} +#endif diff --git a/qcsrc/common/weapons/weapons.qh b/qcsrc/common/weapons/weapons.qh index dca226f42..99ad7eb51 100644 --- a/qcsrc/common/weapons/weapons.qh +++ b/qcsrc/common/weapons/weapons.qh @@ -48,10 +48,12 @@ typedef vector WepSet; WepSet WepSet_FromWeapon(float a); #ifdef SVQC void WepSet_AddStat(); +void WepSet_AddStat_InMap(); void WriteWepSet(float dest, WepSet w); #endif #ifdef CSQC WepSet WepSet_GetFromStat(); +WepSet WepSet_GetFromStat_InMap(); WepSet ReadWepSet(); #endif @@ -80,11 +82,17 @@ string GetAmmoPicture(.float ammotype); float GetAmmoStat(.float ammotype); #endif +#ifdef SVQC +string W_Sound(string w_snd); +string W_Model(string w_mdl); +#endif + // ammo types .float ammo_shells; .float ammo_nails; .float ammo_rockets; .float ammo_cells; +.float ammo_supercells; .float ammo_plasma; .float ammo_fuel; .float ammo_none; diff --git a/qcsrc/csqcmodellib/cl_player.qc b/qcsrc/csqcmodellib/cl_player.qc index 63db0ac14..c8a821be1 100644 --- a/qcsrc/csqcmodellib/cl_player.qc +++ b/qcsrc/csqcmodellib/cl_player.qc @@ -241,13 +241,14 @@ void CSQCPlayer_SetCamera() } CSQCPlayer_PredictTo(clientcommandframe + 1, TRUE); -#ifdef CSQCMODEL_SERVERSIDE_CROUCH +//#ifdef CSQCMODEL_SERVERSIDE_CROUCH // get crouch state from the server (LAG) + if(!autocvar_cl_crouch) if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z) self.pmove_flags &= ~PMF_DUCKED; else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z) self.pmove_flags |= PMF_DUCKED; -#endif +//#endif CSQCPlayer_SetMinsMaxs(); diff --git a/qcsrc/dpdefs/csprogsdefs.qc b/qcsrc/dpdefs/csprogsdefs.qc index 8f4ec8b41..6190e30b7 100644 --- a/qcsrc/dpdefs/csprogsdefs.qc +++ b/qcsrc/dpdefs/csprogsdefs.qc @@ -421,6 +421,7 @@ float( float b, ... ) max = #95; float(float minimum, float val, float maximum) bound = #96; float(float f, float f) pow = #97; entity(entity start, .float fld, float match) findfloat = #98; +entity(entity start, .entity fld, entity match) findentity = #98; float(string s) checkextension = #99; // FrikaC and Telejano range #100-#199 diff --git a/qcsrc/dpdefs/menudefs.qc b/qcsrc/dpdefs/menudefs.qc index 0d6c25370..1bc577097 100644 --- a/qcsrc/dpdefs/menudefs.qc +++ b/qcsrc/dpdefs/menudefs.qc @@ -447,6 +447,39 @@ float(float bufhandle, string str, float order) bufstr_add = #448; void(float bufhandle, float string_index) bufstr_free = #449; void(float bufhandle, string pattern, string antipattern) buf_cvarlist = #517; +//DP_QC_ASINACOSATANATAN2TAN +//idea: Urre +//darkplaces implementation: LordHavoc +//constant definitions: +float DEG2RAD = 0.0174532925199432957692369076848861271344287188854172545609719144; +float RAD2DEG = 57.2957795130823208767981548141051703324054724665643215491602438612; +float PI = 3.1415926535897932384626433832795028841971693993751058209749445923; +//builtin definitions: +float(float s) asin = #471; // returns angle in radians for a given sin() value, the result is in the range -PI*0.5 to PI*0.5 +float(float c) acos = #472; // returns angle in radians for a given cos() value, the result is in the range 0 to PI +float(float t) atan = #473; // returns angle in radians for a given tan() value, the result is in the range -PI*0.5 to PI*0.5 +float(float c, float s) atan2 = #474; // returns angle in radians for a given cos() and sin() value pair, the result is in the range -PI to PI (this is identical to vectoyaw except it returns radians rather than degrees) +float(float a) tan = #475; // returns tangent value (which is simply sin(a)/cos(a)) for the given angle in radians, the result is in the range -infinity to +infinity +//description: +//useful math functions for analyzing vectors, note that these all use angles in radians (just like the cos/sin functions) not degrees unlike makevectors/vectoyaw/vectoangles, so be sure to do the appropriate conversions (multiply by DEG2RAD or RAD2DEG as needed). +//note: atan2 can take unnormalized vectors (just like vectoyaw), and the function was included only for completeness (more often you want vectoyaw or vectoangles), atan2(v_x,v_y) * RAD2DEG gives the same result as vectoyaw(v) + +//DP_QC_NUM_FOR_EDICT +//idea: Blub\0 +//darkplaces implementation: Blub\0 +//Function to get the number of an entity - a clean way. +float(entity num) num_for_edict = #512; + +//DP_QC_EDICT_NUM +//idea: 515 +//DarkPlaces implementation: LordHavoc +//builtin definitions: +entity(float entnum) edict_num = #459; +float(entity ent) wasfreed = #353; // same as in EXT_CSQC extension +//description: +//edict_num returns the entity corresponding to a given number, this works even for freed entities, but you should call wasfreed(ent) to see if is currently active. +//wasfreed returns whether an entity slot is currently free (entities that have never spawned are free, entities that have had remove called on them are also free). + //DP_QC_STRING_CASE_FUNCTIONS //idea: Dresk //darkplaces implementation: LordHavoc / Dresk diff --git a/qcsrc/menu/progs.src b/qcsrc/menu/progs.src index 79b0f9023..0e0378790 100644 --- a/qcsrc/menu/progs.src +++ b/qcsrc/menu/progs.src @@ -25,6 +25,7 @@ oo/base.h ../common/command/rpn.qh ../common/command/generic.qh ../common/command/shared_defs.qh +../common/command/script.qh ../common/urllib.qh ../common/monsters/monsters.qh @@ -45,6 +46,7 @@ oo/implementation.h ../common/command/markup.qc ../common/command/rpn.qc ../common/command/generic.qc +../common/command/script.qc command/menu_cmd.qc menu.qc draw.qc diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index 168045bd0..808d5a929 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -70,6 +70,7 @@ float autocvar_ekg; float autocvar_g_allow_oldvortexbeam; float autocvar_g_antilag; float autocvar_g_antilag_nudge; +float autocvar_g_balance_armor_block_bycount; float autocvar_g_balance_armor_blockpercent; float autocvar_g_balance_armor_limit; float autocvar_g_balance_armor_regen; @@ -100,14 +101,18 @@ float autocvar_g_balance_fuel_regenstable; float autocvar_g_balance_fuel_rot; float autocvar_g_balance_fuel_rotlinear; float autocvar_g_balance_fuel_rotstable; +var float autocvar_g_balance_grapplehook_piggybackfriction = 1; float autocvar_g_balance_grapplehook_airfriction; float autocvar_g_balance_grapplehook_force_rubber; float autocvar_g_balance_grapplehook_force_rubber_overstretch; +float autocvar_g_balance_grapplehook_gravity; float autocvar_g_balance_grapplehook_health; float autocvar_g_balance_grapplehook_length_min; float autocvar_g_balance_grapplehook_speed_fly; float autocvar_g_balance_grapplehook_speed_pull; +float autocvar_g_balance_grapplehook_pull_frozen; float autocvar_g_balance_grapplehook_stretch; +float autocvar_g_balance_grapplehook_crouchslide; float autocvar_g_balance_grapplehook_damagedbycontents; float autocvar_g_balance_grapplehook_refire; float autocvar_g_balance_health_limit; @@ -117,21 +122,6 @@ float autocvar_g_balance_health_regenstable; float autocvar_g_balance_health_rot; float autocvar_g_balance_health_rotlinear; float autocvar_g_balance_health_rotstable; -float autocvar_g_balance_keyhunt_damageforcescale; -float autocvar_g_balance_keyhunt_delay_collect; -float autocvar_g_balance_keyhunt_delay_return; -float autocvar_g_balance_keyhunt_delay_round; -float autocvar_g_balance_keyhunt_delay_tracking; -float autocvar_g_balance_keyhunt_dropvelocity; -float autocvar_g_balance_keyhunt_maxdist; -float autocvar_g_balance_keyhunt_protecttime; -float autocvar_g_balance_keyhunt_score_capture; -float autocvar_g_balance_keyhunt_score_carrierfrag; -float autocvar_g_balance_keyhunt_score_collect; -float autocvar_g_balance_keyhunt_score_destroyed; -float autocvar_g_balance_keyhunt_score_destroyed_ownfactor; -float autocvar_g_balance_keyhunt_score_push; -float autocvar_g_balance_keyhunt_throwvelocity; float autocvar_g_balance_kill_delay; float autocvar_g_balance_kill_antispam; float autocvar_g_balance_nexball_primary_animtime; @@ -167,13 +157,6 @@ float autocvar_g_balance_pause_health_rot; float autocvar_g_balance_pause_health_rot_spawn; float autocvar_g_balance_portal_health; float autocvar_g_balance_portal_lifetime; -float autocvar_g_balance_powerup_invincible_takedamage; -float autocvar_g_balance_powerup_invincible_time; -float autocvar_g_balance_powerup_strength_damage; -float autocvar_g_balance_powerup_strength_force; -float autocvar_g_balance_powerup_strength_selfdamage; -float autocvar_g_balance_powerup_strength_selfforce; -float autocvar_g_balance_powerup_strength_time; float autocvar_g_balance_superweapons_time; float autocvar_g_balance_selfdamagepercent; float autocvar_g_balance_teams; @@ -191,7 +174,6 @@ float autocvar_g_ban_sync_trusted_servers_verify; string autocvar_g_ban_sync_uri; string autocvar_g_banned_list; float autocvar_g_banned_list_idmode; -float autocvar_g_bastet; float autocvar_g_botclip_collisions; float autocvar_g_bugrigs; float autocvar_g_ca_damage2score_multiplier; @@ -230,11 +212,11 @@ float autocvar_g_ctf_throw_angle_min; float autocvar_g_ctf_throw_punish_count; float autocvar_g_ctf_throw_punish_delay; float autocvar_g_ctf_throw_punish_time; -float autocvar_g_ctf_throw_strengthmultiplier; float autocvar_g_ctf_throw_velocity_forward; float autocvar_g_ctf_throw_velocity_up; float autocvar_g_ctf_drop_velocity_up; float autocvar_g_ctf_drop_velocity_side; +float autocvar_g_ctf_oneflag_reverse; float autocvar_g_ctf_portalteleport; float autocvar_g_ctf_pass; float autocvar_g_ctf_pass_arc; @@ -256,12 +238,21 @@ float autocvar_g_ctf_flag_dropped_waypoint; float autocvar_g_ctf_flag_dropped_floatinwater; float autocvar_g_ctf_flag_glowtrails; float autocvar_g_ctf_flag_health; +string autocvar_g_ctf_flag_neutral_model; +float autocvar_g_ctf_flag_neutral_skin; +string autocvar_g_ctf_flag_pink_model; +float autocvar_g_ctf_flag_pink_skin; string autocvar_g_ctf_flag_red_model; float autocvar_g_ctf_flag_red_skin; +float autocvar_g_ctf_flag_return; +float autocvar_g_ctf_flag_return_carried; float autocvar_g_ctf_flag_return_time; float autocvar_g_ctf_flag_return_when_unreachable; float autocvar_g_ctf_flag_return_damage; +float autocvar_g_ctf_flag_return_damage_delay; float autocvar_g_ctf_flag_return_dropped; +string autocvar_g_ctf_flag_yellow_model; +float autocvar_g_ctf_flag_yellow_skin; float autocvar_g_ctf_flagcarrier_auto_helpme_damage; float autocvar_g_ctf_flagcarrier_auto_helpme_time; float autocvar_g_ctf_flagcarrier_selfdamagefactor; @@ -305,22 +296,28 @@ float autocvar_g_domination_warmup; #define autocvar_g_domination_point_limit cvar("g_domination_point_limit") float autocvar_g_domination_point_rate; float autocvar_g_domination_teams_override; +float autocvar_g_domination_controlpoint_unlock_speed; +float autocvar_g_domination_controlpoint_unlock_damage_pushback; +float autocvar_g_domination_controlpoint_idletime_neutral_initial; +float autocvar_g_domination_controlpoint_idletime_initial; +float autocvar_g_domination_controlpoint_idletime_neutral; +float autocvar_g_domination_controlpoint_idletime_neutral_power; +float autocvar_g_domination_controlpoint_idletime_neutral_factor; +float autocvar_g_domination_controlpoint_idletime; +float autocvar_g_domination_controlpoint_idletime_power; +float autocvar_g_domination_controlpoint_idletime_factor; float autocvar_g_forced_respawn; +float autocvar_g_respawn_delay_max; string autocvar_g_forced_team_blue; string autocvar_g_forced_team_otherwise; string autocvar_g_forced_team_pink; string autocvar_g_forced_team_red; string autocvar_g_forced_team_yellow; -float autocvar_g_freezetag_frozen_damage_trigger; -float autocvar_g_freezetag_frozen_force; float autocvar_g_freezetag_frozen_maxtime; -float autocvar_g_freezetag_revive_falldamage; -float autocvar_g_freezetag_revive_falldamage_health; -float autocvar_g_freezetag_revive_nade; -float autocvar_g_freezetag_revive_nade_health; float autocvar_g_freezetag_point_leadlimit; float autocvar_g_freezetag_point_limit; float autocvar_g_freezetag_revive_extra_size; +var float autocvar_g_freezetag_revive_health = 100; float autocvar_g_freezetag_revive_speed; float autocvar_g_freezetag_revive_clearspeed; float autocvar_g_freezetag_round_timelimit; @@ -338,12 +335,14 @@ float autocvar_g_fullbrightplayers; float autocvar_g_grappling_hook_tarzan; float autocvar_g_hitplots; string autocvar_g_hitplots_individuals; +float autocvar_g_jetpack_reverse_thrust; float autocvar_g_jetpack_acceleration_side; float autocvar_g_jetpack_acceleration_up; float autocvar_g_jetpack_antigravity; float autocvar_g_jetpack_fuel; float autocvar_g_jetpack_maxspeed_side; float autocvar_g_jetpack_maxspeed_up; +float autocvar_g_keepaway_point_limit; float autocvar_g_keepaway_ballcarrier_effects; float autocvar_g_keepaway_ballcarrier_damage; float autocvar_g_keepaway_ballcarrier_force; @@ -363,10 +362,64 @@ float autocvar_g_keepawayball_damageforcescale; float autocvar_g_keepawayball_effects; float autocvar_g_keepawayball_respawntime; float autocvar_g_keepawayball_trail_color; +float autocvar_g_keyhunt_allow_vehicle_carry; +float autocvar_g_keyhunt_allow_vehicle_touch; +float autocvar_g_keyhunt_capture_radius; +float autocvar_g_keyhunt_throw; +float autocvar_g_keyhunt_throw_angle_max; +float autocvar_g_keyhunt_throw_angle_min; +float autocvar_g_keyhunt_throw_punish_count; +float autocvar_g_keyhunt_throw_punish_delay; +float autocvar_g_keyhunt_throw_punish_time; +float autocvar_g_keyhunt_throw_velocity_forward; +float autocvar_g_keyhunt_throw_velocity_up; +float autocvar_g_keyhunt_drop_velocity_up; +float autocvar_g_keyhunt_drop_velocity_side; +float autocvar_g_keyhunt_portalteleport; +float autocvar_g_keyhunt_pass; +float autocvar_g_keyhunt_pass_arc; +float autocvar_g_keyhunt_pass_arc_max; +float autocvar_g_keyhunt_pass_directional_max; +float autocvar_g_keyhunt_pass_directional_min; +float autocvar_g_keyhunt_pass_radius; +float autocvar_g_keyhunt_pass_wait; +float autocvar_g_keyhunt_pass_request; +float autocvar_g_keyhunt_pass_turnrate; +float autocvar_g_keyhunt_pass_timelimit; +float autocvar_g_keyhunt_pass_velocity; +float autocvar_g_keyhunt_dynamiclights; +float autocvar_g_keyhunt_key_collect_delay; +float autocvar_g_keyhunt_key_damageforcescale; +float autocvar_g_keyhunt_key_dropped_waypoint; +float autocvar_g_keyhunt_key_dropped_floatinwater; +float autocvar_g_keyhunt_key_glowtrails; +float autocvar_g_keyhunt_key_health; +float autocvar_g_keyhunt_key_return_time; +float autocvar_g_keyhunt_key_return_tokiller; +float autocvar_g_keyhunt_key_return_toenemy; +float autocvar_g_keyhunt_key_return_when_unreachable; +float autocvar_g_keyhunt_key_return_damage; +float autocvar_g_keyhunt_keycarrier_auto_helpme_damage; +float autocvar_g_keyhunt_keycarrier_auto_helpme_time; +float autocvar_g_keyhunt_keycarrier_selfdamagefactor; +float autocvar_g_keyhunt_keycarrier_selfforcefactor; +float autocvar_g_keyhunt_keycarrier_damagefactor; +float autocvar_g_keyhunt_keycarrier_forcefactor; +float autocvar_g_keyhunt_fullbrightkeys; +float autocvar_g_keyhunt_ignore_frags; +float autocvar_g_keyhunt_score_capture; +float autocvar_g_keyhunt_score_capture_assist; +float autocvar_g_keyhunt_score_kill; +float autocvar_g_keyhunt_score_penalty_drop; +float autocvar_g_keyhunt_score_pickup_dropped_early; +float autocvar_g_keyhunt_score_pickup_dropped_late; +float autocvar_g_keyhunt_team_spawns; float autocvar_g_keyhunt_point_leadlimit; #define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit") float autocvar_g_keyhunt_teams; float autocvar_g_keyhunt_teams_override; +float autocvar_g_keyhunt_warmup; +float autocvar_g_keyhunt_round_timelimit; float autocvar_g_lms_extra_lives; float autocvar_g_lms_join_anytime; float autocvar_g_lms_last_join; @@ -393,10 +446,13 @@ float autocvar_g_maxpushtime; float autocvar_g_maxspeed; float autocvar_g_midair_shieldtime; #define autocvar_g_instagib cvar("g_instagib") +float autocvar_g_instagib_use_normal_ammo; float autocvar_g_instagib_ammo_drop; +float autocvar_g_instagib_ammo_rockets; float autocvar_g_instagib_extralives; -float autocvar_g_instagib_speed_highspeed; -float autocvar_g_instagib_invis_alpha; +float autocvar_g_instagib_damagedbycontents; +float autocvar_g_instagib_blaster_keepdamage; +float autocvar_g_instagib_blaster_keepforce; #define autocvar_g_mirrordamage cvar("g_mirrordamage") #define autocvar_g_mirrordamage_virtual cvar("g_mirrordamage_virtual") @@ -404,6 +460,7 @@ var float autocvar_g_movement_highspeed = 1; float autocvar_g_multijump; float autocvar_g_multijump_add; float autocvar_g_multijump_speed; +float autocvar_g_multijump_maxspeed; string autocvar_g_mutatormsg; float autocvar_g_nexball_basketball_bouncefactor; float autocvar_g_nexball_basketball_bouncestop; @@ -427,15 +484,9 @@ float autocvar_g_nick_flood_penalty_yellow; //float autocvar_g_nick_flood_timeout; float autocvar_g_nix_with_healtharmor; float autocvar_g_nix_with_blaster; -float autocvar_g_nix_with_powerups; float autocvar_g_nodepthtestitems; float autocvar_g_nodepthtestplayers; float autocvar_g_norecoil; -float autocvar_g_onslaught_cp_buildhealth; -float autocvar_g_onslaught_cp_buildtime; -float autocvar_g_onslaught_cp_health; -float autocvar_g_onslaught_cp_regen; -float autocvar_g_onslaught_gen_health; float autocvar_g_pickup_cells_max; float autocvar_g_pickup_plasma_max; float autocvar_g_pickup_fuel_max; @@ -446,7 +497,6 @@ float autocvar_g_pickup_shells_max; float autocvar_g_player_alpha; float autocvar_g_player_brightness; float autocvar_g_playerclip_collisions; -float autocvar_g_powerups; float autocvar_g_projectiles_damage; float autocvar_g_projectiles_keep_owner; float autocvar_g_projectiles_newton_style; @@ -457,11 +507,13 @@ float autocvar_g_projectiles_spread_style; float autocvar_g_race_qualifying_timelimit; float autocvar_g_race_qualifying_timelimit_override; float autocvar_g_race_teams; -float autocvar_g_respawn_delay_small; +var float autocvar_g_race_team_spawns = 1; +var float autocvar_g_respawn_delay_small = 2; float autocvar_g_respawn_delay_small_count; -float autocvar_g_respawn_delay_large; +var float autocvar_g_respawn_delay_large = 2; float autocvar_g_respawn_delay_large_count; float autocvar_g_respawn_delay_max; +float autocvar_g_respawn_delay_forced; float autocvar_g_respawn_ghosts; float autocvar_g_respawn_ghosts_maxtime; float autocvar_g_respawn_ghosts_speed; @@ -477,6 +529,7 @@ float autocvar_g_spawn_furthest; float autocvar_g_spawn_useallspawns; float autocvar_g_spawnpoints_auto_move_out_of_solid; #define autocvar_g_spawnshieldtime cvar("g_spawnshieldtime") +float autocvar_g_spawnshield_nodamage; float autocvar_g_tdm_team_spawns; float autocvar_g_tdm_point_limit; float autocvar_g_tdm_point_leadlimit; @@ -507,41 +560,6 @@ float autocvar_g_turrets_nofire; float autocvar_g_turrets_reloadcvars; float autocvar_g_turrets_targetscan_maxdelay; float autocvar_g_turrets_targetscan_mindelay; -float autocvar_g_turrets_unit_ewheel_speed_fast; -float autocvar_g_turrets_unit_ewheel_speed_slow; -float autocvar_g_turrets_unit_ewheel_speed_slower; -float autocvar_g_turrets_unit_ewheel_speed_stop; -float autocvar_g_turrets_unit_ewheel_turnrate; -float autocvar_g_turrets_unit_hellion_std_shot_speed_gain; -float autocvar_g_turrets_unit_hellion_std_shot_speed_max; -float autocvar_g_turrets_unit_hk_std_shot_speed; -float autocvar_g_turrets_unit_hk_std_shot_speed_accel; -float autocvar_g_turrets_unit_hk_std_shot_speed_accel2; -float autocvar_g_turrets_unit_hk_std_shot_speed_decel; -float autocvar_g_turrets_unit_hk_std_shot_speed_max; -float autocvar_g_turrets_unit_hk_std_shot_speed_turnrate; -float autocvar_g_turrets_unit_walker_speed_jump; -float autocvar_g_turrets_unit_walker_speed_roam; -float autocvar_g_turrets_unit_walker_speed_run; -float autocvar_g_turrets_unit_walker_speed_stop; -float autocvar_g_turrets_unit_walker_speed_swim; -float autocvar_g_turrets_unit_walker_speed_walk; -float autocvar_g_turrets_unit_walker_std_meele_dmg; -float autocvar_g_turrets_unit_walker_std_meele_force; -float autocvar_g_turrets_unit_walker_std_meele_range; -float autocvar_g_turrets_unit_walker_std_rocket_dmg; -float autocvar_g_turrets_unit_walker_std_rocket_force; -float autocvar_g_turrets_unit_walker_std_rocket_radius; -float autocvar_g_turrets_unit_walker_std_rocket_refire; -float autocvar_g_turrets_unit_walker_std_rocket_speed; -float autocvar_g_turrets_unit_walker_std_rocket_turnrate; -float autocvar_g_turrets_unit_walker_std_rockets_range; -float autocvar_g_turrets_unit_walker_std_rockets_range_min; -float autocvar_g_turrets_unit_walker_turn; -float autocvar_g_turrets_unit_walker_turn_walk; -float autocvar_g_turrets_unit_walker_turn_run; -float autocvar_g_turrets_unit_walker_turn_strafe; -float autocvar_g_turrets_unit_walker_turn_swim; float autocvar_g_use_ammunition; float autocvar_g_waypointeditor; float autocvar_g_waypointeditor_auto; @@ -582,20 +600,8 @@ float autocvar_skill_auto; float autocvar_snd_soundradius; float autocvar_spawn_debug; float autocvar_speedmeter; -float autocvar_sv_accelerate; var float autocvar_sv_accuracy_data_share = 1; string autocvar_sv_adminnick; -float autocvar_sv_airaccel_qw; -float autocvar_sv_airaccel_qw_stretchfactor; -float autocvar_sv_airaccel_sideways_friction; -float autocvar_sv_airaccelerate; -float autocvar_sv_aircontrol; -float autocvar_sv_aircontrol_penalty; -float autocvar_sv_aircontrol_power; -float autocvar_sv_airspeedlimit_nonqw; -float autocvar_sv_airstopaccelerate; -float autocvar_sv_airstrafeaccel_qw; -float autocvar_sv_airstrafeaccelerate; float autocvar_sv_autoscreenshot; float autocvar_sv_cheats; float autocvar_sv_clientcommand_antispam_time; @@ -629,7 +635,6 @@ float autocvar_sv_eventlog_files_counter; string autocvar_sv_eventlog_files_nameprefix; string autocvar_sv_eventlog_files_namesuffix; float autocvar_sv_eventlog_files_timestamps; -float autocvar_sv_friction; float autocvar_sv_friction_on_land; float autocvar_sv_gameplayfix_q2airaccelerate; float autocvar_sv_gentle; @@ -645,7 +650,6 @@ float autocvar_sv_logscores_file; string autocvar_sv_logscores_filename; float autocvar_sv_mapchange_delay; float autocvar_sv_maxairspeed; -float autocvar_sv_maxairstrafespeed; float autocvar_sv_maxspeed; string autocvar_sv_motd; float autocvar_sv_precacheplayermodels; @@ -659,9 +663,6 @@ float autocvar_sv_spectate; float autocvar_sv_spectator_speed_multiplier; float autocvar_sv_status_privacy; float autocvar_sv_stepheight; -float autocvar_sv_stopspeed; -float autocvar_sv_strengthsound_antispam_refire_threshold; -float autocvar_sv_strengthsound_antispam_time; float autocvar_sv_teamnagger; float autocvar_sv_timeout; float autocvar_sv_timeout_leadtime; @@ -669,6 +670,7 @@ float autocvar_sv_timeout_length; float autocvar_sv_timeout_number; float autocvar_sv_timeout_resumetime; float autocvar_sv_vote_call; +float autocvar_sv_vote_auto; float autocvar_sv_vote_change; string autocvar_sv_vote_commands; float autocvar_sv_vote_gametype; @@ -693,11 +695,6 @@ float autocvar_sv_vote_stop; float autocvar_sv_vote_timeout; float autocvar_sv_vote_wait; float autocvar_sv_vote_gamestart; -float autocvar_sv_warsowbunny_accel; -float autocvar_sv_warsowbunny_airforwardaccel; -float autocvar_sv_warsowbunny_backtosideratio; -float autocvar_sv_warsowbunny_topspeed; -float autocvar_sv_warsowbunny_turnaccel; float autocvar_sv_waypointsprite_deadlifetime; float autocvar_sv_waypointsprite_deployed_lifetime; float autocvar_sv_waypointsprite_limitedrange; @@ -722,6 +719,7 @@ float autocvar_g_trueaim_minrange; float autocvar_g_debug_defaultsounds; float autocvar_g_grab_range; float autocvar_g_sandbox_info; +float autocvar_g_sandbox_snaptogrid; float autocvar_g_sandbox_readonly; string autocvar_g_sandbox_storage_name; float autocvar_g_sandbox_storage_autosave; @@ -735,17 +733,68 @@ float autocvar_g_sandbox_object_scale_min; float autocvar_g_sandbox_object_scale_max; float autocvar_g_sandbox_object_material_velocity_min; float autocvar_g_sandbox_object_material_velocity_factor; +float autocvar_g_sandbox_allow_bspsolid; float autocvar_g_max_info_autoscreenshot; float autocvar_physics_ode; float autocvar_g_physical_items; float autocvar_g_physical_items_damageforcescale; float autocvar_g_physical_items_reset; +float autocvar_g_rm; +float autocvar_g_rm_damage; +float autocvar_g_rm_edgedamage; +float autocvar_g_rm_damage_multiplier_accuracy; +float autocvar_g_rm_damage_multiplier_min; +float autocvar_g_rm_force; +float autocvar_g_rm_radius; +float autocvar_g_rm_laser; +float autocvar_g_rm_laser_count; +float autocvar_g_rm_laser_speed; +float autocvar_g_rm_laser_spread; +float autocvar_g_rm_laser_spread_random; +float autocvar_g_rm_laser_lifetime; +float autocvar_g_rm_laser_damage; +float autocvar_g_rm_laser_refire; +float autocvar_g_rm_laser_rapid; +float autocvar_g_rm_laser_rapid_refire; +float autocvar_g_rm_laser_rapid_delay; +float autocvar_g_rm_laser_radius; +float autocvar_g_rm_laser_force; +float autocvar_g_rm_superrocket; +float autocvar_g_rm_superrocket_speed; +float autocvar_g_rm_hook_damage; +float autocvar_g_rm_hook_damage_always; +float autocvar_g_rm_hook_team; +float autocvar_g_rm_hook_damagefactor; +float autocvar_g_rm_hook_breakable; +float autocvar_g_rm_hook_breakable_owner; +float autocvar_g_rm_hook_damage_health; float autocvar_g_monsters; float autocvar_g_monsters_edit; float autocvar_g_monsters_sounds; float autocvar_g_monsters_think_delay; float autocvar_g_monsters_max; float autocvar_g_monsters_max_perplayer; +var float autocvar_g_monsters_damageforcescale = 0.8; +float autocvar_g_player_gib_always; +float autocvar_g_player_crush; +var float autocvar_g_player_crush_simple = 1; +var float autocvar_g_player_crush_damage = 200; +var float autocvar_g_player_crush_bounce = 300; +var float autocvar_g_player_crush_bounce_jump = 600; +float autocvar_g_player_crush_headheight; +float autocvar_g_freeze_revive_speed; +float autocvar_g_freeze_revive_speed_random; +float autocvar_g_freeze_noauto; +float autocvar_g_freeze_norespawn; +float autocvar_g_freeze_revive_minhealth; +float autocvar_g_freeze_revive_falldamage; +float autocvar_g_freeze_revive_falldamage_health; +float autocvar_g_freeze_revive_nade; +float autocvar_g_freeze_revive_nade_health; +float autocvar_g_freeze_frozen_force; +float autocvar_g_freeze_frozen_damage_trigger; +float autocvar_g_freeze_frozen_maxtime; +float autocvar_g_freeze_respawn_time; float autocvar_g_monsters_target_range; float autocvar_g_monsters_target_infront; float autocvar_g_monsters_attack_range; @@ -761,10 +810,24 @@ float autocvar_g_monsters_teams; float autocvar_g_monsters_respawn_delay; float autocvar_g_monsters_respawn; float autocvar_g_monsters_armor_blockpercent; +float autocvar_g_monsters_healthbars; +float autocvar_g_monsters_lineofsight; float autocvar_g_touchexplode_radius; float autocvar_g_touchexplode_damage; float autocvar_g_touchexplode_edgedamage; float autocvar_g_touchexplode_force; +float autocvar_g_vip_teams; +float autocvar_g_vip_warmup; +float autocvar_g_vip_round_timelimit; +float autocvar_g_vip_point_limit; +float autocvar_g_vip_point_leadlimit; +float autocvar_g_vip_drop; +float autocvar_g_vip_drop_punish_delay; +float autocvar_g_vip_drop_punish_count; +float autocvar_g_vip_drop_punish_time; +float autocvar_g_vip_pickup_wait; +float autocvar_sv_allow_customplayermodels; +string autocvar_sv_allow_customplayermodels_idlist; float autocvar_g_invasion_round_timelimit; float autocvar_g_invasion_teams; float autocvar_g_invasion_team_spawns; @@ -812,7 +875,7 @@ float autocvar_g_nades_napalm_selfdamage; float autocvar_g_nades_nade_type; float autocvar_g_nades_bonus_type; float autocvar_g_nades_bonus; -float autocvar_g_nades_bonus_onstrength; +float autocvar_g_nades_bonus_only; float autocvar_g_nades_bonus_client_select; float autocvar_g_nades_bonus_max; float autocvar_g_nades_bonus_score_max; @@ -837,19 +900,111 @@ float autocvar_g_campcheck_damage; float autocvar_g_campcheck_distance; float autocvar_g_campcheck_interval; float autocvar_g_jump_grunt; -float autocvar_g_overkill_powerups_replace; +float autocvar_g_za; +float autocvar_g_za_max_monsters; +string autocvar_g_za_spawnmonster; +float autocvar_g_za_spawn_delay; float autocvar_g_overkill_superguns_respawn_time; float autocvar_g_overkill_100h_anyway; float autocvar_g_overkill_100a_anyway; float autocvar_g_overkill_ammo_charge; float autocvar_g_overkill_ammo_charge_notice; float autocvar_g_overkill_ammo_charge_limit; +float autocvar_g_overkill_ammo_charge_attack; float autocvar_g_spawn_near_teammate_distance; float autocvar_g_spawn_near_teammate_ignore_spawnpoint; float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death; float autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health; float autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath; +float autocvar_g_conquest_spawn_close_to_death; +float autocvar_g_conquest_capture_distance_default; +float autocvar_g_conquest_capture_sps; +float autocvar_g_conquest_controlpoint_health_default; +float autocvar_g_conquest_spawn_choose; +float autocvar_g_conquest_teleport_radius; +float autocvar_g_conquest_teleport_wait; +float autocvar_g_conquest_click_radius; +float autocvar_g_conquest_teams; +float autocvar_g_conquest_teams_override; +float autocvar_g_conquest_warmup; +float autocvar_g_conquest_round_timelimit; +float autocvar_g_conquest_point_limit; +float autocvar_g_walljump; +float autocvar_g_walljump_delay; +float autocvar_g_walljump_force; +float autocvar_g_walljump_velocity_xy_factor; +float autocvar_g_walljump_velocity_z_factor; +float autocvar_g_infection_teams; +float autocvar_g_infection_warmup; +float autocvar_g_infection_round_timelimit; +float autocvar_g_infection_point_limit; +float autocvar_g_infection_point_leadlimit; +float autocvar_g_jailbreak_warmup; +float autocvar_g_jailbreak_round_timelimit; +float autocvar_g_jailbreak_point_limit; +float autocvar_g_jailbreak_point_leadlimit; +float autocvar_g_jailbreak_controlpoint_unlock_damage_pushback; +float autocvar_g_jailbreak_jail_deathmatch; +float autocvar_g_jailbreak_score_jbreak_neutralmultiplier; +float autocvar_g_jailbreak_score_jbreak_perplayer; +float autocvar_g_jailbreak_score_jbreak; +float autocvar_g_jailbreak_controlpoint_claim_noneutral; +float autocvar_g_jailbreak_controlpoint_unlock_speed; +float autocvar_g_jailbreak_controlpoint_idletime_global_own; +float autocvar_g_jailbreak_controlpoint_idletime_global; +float autocvar_g_jailbreak_controlpoint_claim_allneutral; +float autocvar_g_jailbreak_penalty_death; +float autocvar_g_jailbreak_score_imprison; +float autocvar_g_jailbreak_score_defense; +float autocvar_g_jailbreak_penalty_teamkill; +float autocvar_g_jailbreak_controlpoint_claim; +float autocvar_g_jailbreak_nonjb_openjails; +float autocvar_g_jailbreak_prisoner_health; +float autocvar_g_jailbreak_prisoner_armor; +float autocvar_g_jailbreak_controlpoint_idletime_neutral_initial; +float autocvar_g_jailbreak_controlpoint_idletime_initial; +float autocvar_g_jailbreak_controlpoint_idletime_neutral; +float autocvar_g_jailbreak_controlpoint_idletime_neutral_power; +float autocvar_g_jailbreak_controlpoint_idletime_neutral_factor; +float autocvar_g_jailbreak_controlpoint_idletime; +float autocvar_g_jailbreak_controlpoint_idletime_power; +float autocvar_g_jailbreak_controlpoint_idletime_factor; +float autocvar_g_jailbreak_defense_range; +float autocvar_g_jailbreak_debug; +float autocvar_g_jailbreak_teams; +float autocvar_g_jailbreak_teams_override; +float autocvar_sv_headshot; +var float autocvar_sv_headshot_damage = 1.5; +float autocvar_g_piggyback_ride_enemies; +var float autocvar_g_assault_round_limit = 1; +var float autocvar_g_assault_repair_amount = 40; +float autocvar_g_onslaught_debug; +float autocvar_g_onslaught_teleport_wait; +float autocvar_g_onslaught_spawn_at_controlpoints; +var float autocvar_g_onslaught_spawn_at_controlpoints_chance = 0.5; +float autocvar_g_onslaught_spawn_at_controlpoints_random; +float autocvar_g_onslaught_spawn_at_generator; +float autocvar_g_onslaught_spawn_at_generator_chance; +float autocvar_g_onslaught_spawn_at_generator_random; +float autocvar_g_onslaught_cp_proxydecap; +var float autocvar_g_onslaught_cp_proxydecap_distance = 512; +var float autocvar_g_onslaught_cp_proxydecap_dps = 100; +float autocvar_g_onslaught_cp_buildhealth; +float autocvar_g_onslaught_cp_buildtime; +float autocvar_g_onslaught_cp_health; +float autocvar_g_onslaught_cp_regen; +float autocvar_g_onslaught_gen_health; +var float autocvar_g_onslaught_shield_force = 100; +float autocvar_g_onslaught_allow_vehicle_touch; +float autocvar_g_onslaught_round_timelimit; +float autocvar_g_onslaught_point_limit; +float autocvar_g_onslaught_warmup; +float autocvar_g_onslaught_teleport_radius; +float autocvar_g_onslaught_spawn_choose; +float autocvar_g_onslaught_click_radius; +float autocvar_g_observer_glowtrails; +float autocvar_g_riflearena_withlaser; float autocvar_g_buffs_waypoint_distance; float autocvar_g_buffs_randomize; float autocvar_g_buffs_random_lifetime; @@ -879,4 +1034,19 @@ float autocvar_g_buffs_vampire_damage_steal; float autocvar_g_buffs_invisible_alpha; float autocvar_g_buffs_flight_gravity; float autocvar_g_buffs_jump_height; - +// TODO float autocvar_g_buffs_replace_flags; +float autocvar_g_physics_clientselect; +string autocvar_g_physics_clientselect_options; +string autocvar_sv_announcer; +float autocvar_sv_minigames; +float autocvar_sv_minigames_observer; +float autocvar_g_itemeditor_spawn_distance; +float autocvar_g_itemeditor_storage_autosave; +float autocvar_g_itemeditor_storage_autoload; +float autocvar_g_itemeditor_readonly; +float autocvar_g_itemeditor_max; +float autocvar_g_itemeditor_debug; +var string autocvar_g_itemeditor_storage_name = "default"; +string autocvar_sv_weapons_modeloverride; +string autocvar_sv_weapons_sounddir; +string autocvar_sv_items_modeloverride; diff --git a/qcsrc/server/bot/aim.qc b/qcsrc/server/bot/aim.qc index 0ae331247..96165fcca 100644 --- a/qcsrc/server/bot/aim.qc +++ b/qcsrc/server/bot/aim.qc @@ -114,12 +114,6 @@ float bot_shouldattack(entity e) if(e.frozen) return FALSE; - // If neither player has ball then don't attack unless the ball is on the - // ground. - if (g_keepaway) - if (!e.ballcarried && !self.ballcarried && ka_ball.owner) - return FALSE; - if(teamplay) { if(e.team==0) @@ -137,8 +131,10 @@ float bot_shouldattack(entity e) return FALSE; if(e.flags & FL_NOTARGET) return FALSE; + if(e.alpha <= 0.5 && e.alpha != 0) + return FALSE; // invisible - checkentity = e; + other = e; if(MUTATOR_CALLHOOK(BotShouldAttack)) return FALSE; @@ -158,11 +154,11 @@ void bot_lagfunc(float t, float f1, float f2, entity e1, vector v1, vector v2, v self.bot_aimselfvelocity = v2; self.bot_aimtargorigin = v3; self.bot_aimtargvelocity = v4; - if(skill <= 0) + if(bot_skill <= 0) self.bot_canfire = (random() < 0.8); - else if(skill <= 1) + else if(bot_skill <= 1) self.bot_canfire = (random() < 0.9); - else if(skill <= 2) + else if(bot_skill <= 2) self.bot_canfire = (random() < 0.95); else self.bot_canfire = 1; @@ -185,7 +181,7 @@ float bot_aimdir(vector v, float maxfiredeviation) if (time >= self.bot_badaimtime) { self.bot_badaimtime = max(self.bot_badaimtime + 0.3, time); - self.bot_badaimoffset = randomvec() * bound(0, 5 - 0.5 * (skill+self.bot_offsetskill), 5) * autocvar_bot_ai_aimskill_offset; + self.bot_badaimoffset = randomvec() * bound(0, 5 - 0.5 * (bot_skill+self.bot_offsetskill), 5) * autocvar_bot_ai_aimskill_offset; } desiredang = vectoangles(v) + self.bot_badaimoffset; //dprint(" desired:", vtos(desiredang)); @@ -226,7 +222,7 @@ float bot_aimdir(vector v, float maxfiredeviation) + (self.bot_4th_order_aimfilter - self.bot_5th_order_aimfilter) * bound(0, autocvar_bot_ai_aimskill_order_filter_5th,1); //blend = (bound(0,skill,10)*0.1)*pow(1-bound(0,skill,10)*0.05,2.5)*5.656854249; //Plot formule before changing ! - blend = bound(0,skill+self.bot_aimskill,10)*0.1; + blend = bound(0,bot_skill+self.bot_aimskill,10)*0.1; desiredang = desiredang + blend * ( self.bot_1st_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_1st @@ -246,11 +242,11 @@ float bot_aimdir(vector v, float maxfiredeviation) if (time >= self.bot_aimthinktime) { - self.bot_aimthinktime = max(self.bot_aimthinktime + 0.5 - 0.05*(skill+self.bot_thinkskill), time); - self.bot_mouseaim = self.bot_mouseaim + diffang * (1-random()*0.1*bound(1,10-(skill+self.bot_thinkskill),10)); + self.bot_aimthinktime = max(self.bot_aimthinktime + 0.5 - 0.05*(bot_skill+self.bot_thinkskill), time); + self.bot_mouseaim = self.bot_mouseaim + diffang * (1-random()*0.1*bound(1,10-(bot_skill+self.bot_thinkskill),10)); } - //self.v_angle = self.v_angle + diffang * bound(0, r * frametime * (skill * 0.5 + 2), 1); + //self.v_angle = self.v_angle + diffang * bound(0, r * frametime * (bot_skill * 0.5 + 2), 1); diffang = self.bot_mouseaim - desiredang; // wrap yaw turn @@ -269,7 +265,7 @@ float bot_aimdir(vector v, float maxfiredeviation) // jitter tracking dist = vlen(diffang); - //diffang = diffang + randomvec() * (dist * 0.05 * (3.5 - bound(0, skill, 3))); + //diffang = diffang + randomvec() * (dist * 0.05 * (3.5 - bound(0, bot_skill, 3))); // turn float r, fixedrate, blendrate; @@ -277,9 +273,9 @@ float bot_aimdir(vector v, float maxfiredeviation) blendrate = autocvar_bot_ai_aimskill_blendrate; r = max(fixedrate, blendrate); //self.v_angle = self.v_angle + diffang * bound(frametime, r * frametime * (2+skill*skill*0.05-random()*0.05*(10-skill)), 1); - self.v_angle = self.v_angle + diffang * bound(delta_t, r * delta_t * (2+pow(skill+self.bot_mouseskill,3)*0.005-random()), 1); + self.v_angle = self.v_angle + diffang * bound(delta_t, r * delta_t * (2+pow(bot_skill+self.bot_mouseskill,3)*0.005-random()), 1); self.v_angle = self.v_angle * bound(0,autocvar_bot_ai_aimskill_mouse,1) + desiredang * bound(0,(1-autocvar_bot_ai_aimskill_mouse),1); - //self.v_angle = self.v_angle + diffang * bound(0, r * frametime * (skill * 0.5 + 2), 1); + //self.v_angle = self.v_angle + diffang * bound(0, r * frametime * (bot_skill * 0.5 + 2), 1); //self.v_angle = self.v_angle + diffang * (1/ blendrate); self.v_angle_z = 0; self.v_angle_y = self.v_angle_y - floor(self.v_angle_y / 360) * 360; @@ -304,8 +300,8 @@ float bot_aimdir(vector v, float maxfiredeviation) // note the maxfiredeviation is in degrees so this has to convert to radians first //if ((normalize(v) * shotdir) >= cos(maxfiredeviation * (3.14159265358979323846 / 180))) if ((normalize(v) * shotdir) >= cos(maxfiredeviation * (3.14159265358979323846 / 180))) - if (vlen(trace_endpos-shotorg) < 500+500*bound(0, skill+self.bot_aggresskill, 10) || random()*random()>bound(0,(skill+self.bot_aggresskill)*0.05,1)) - self.bot_firetimer = time + bound(0.1, 0.5-(skill+self.bot_aggresskill)*0.05, 0.5); + if (vlen(trace_endpos-shotorg) < 500+500*bound(0, bot_skill+self.bot_aggresskill, 10) || random()*random()>bound(0,(bot_skill+self.bot_aggresskill)*0.05,1)) + self.bot_firetimer = time + bound(0.1, 0.5-(bot_skill+self.bot_aggresskill)*0.05, 0.5); //traceline(shotorg,shotorg+shotdir*1000,FALSE,world); //dprint(ftos(maxfiredeviation),"\n"); //dprint(" diff:", vtos(diffang), "\n"); @@ -351,7 +347,7 @@ float bot_aim(float shotspeed, float shotspeedupward, float maxshottime, float a shotorg = self.origin + self.view_ofs; shotdir = v_forward; v = bot_shotlead(self.bot_aimtargorigin, self.bot_aimtargvelocity, shotspeed, self.bot_aimlatency); - distanceratio = sqrt(bound(0,skill,10000))*0.3*(vlen(v-shotorg)-100)/autocvar_bot_ai_aimskill_firetolerance_distdegrees; + distanceratio = sqrt(bound(0,bot_skill,10000))*0.3*(vlen(v-shotorg)-100)/autocvar_bot_ai_aimskill_firetolerance_distdegrees; distanceratio = bound(0,distanceratio,1); r = (autocvar_bot_ai_aimskill_firetolerance_maxdegrees-autocvar_bot_ai_aimskill_firetolerance_mindegrees) * (1-distanceratio) + autocvar_bot_ai_aimskill_firetolerance_mindegrees; diff --git a/qcsrc/server/bot/bot.qc b/qcsrc/server/bot/bot.qc index 40b769ddd..7114d004c 100644 --- a/qcsrc/server/bot/bot.qc +++ b/qcsrc/server/bot/bot.qc @@ -64,7 +64,7 @@ void bot_think() // (simulated network latency + naturally delayed reflexes) //self.ping = 0.7 - bound(0, 0.05 * skill, 0.5); // moved the reflexes to bot_aimdir (under the name 'think') // minimum ping 20+10 random - self.ping = bound(0,0.07 - bound(0, (skill + self.bot_pingskill) * 0.005,0.05)+random()*0.01,0.65); // Now holds real lag to server, and higer skill players take a less laggy server + self.ping = bound(0,0.07 - bound(0, (bot_skill + self.bot_pingskill) * 0.005,0.05)+random()*0.01,0.65); // Now holds real lag to server, and higer skill players take a less laggy server // skill 10 = ping 0.2 (adrenaline) // skill 0 = ping 0.7 (slightly drunk) @@ -392,6 +392,7 @@ void bot_clientconnect() self.bot_nextthink = time - random(); self.lag_func = bot_lagfunc; self.isbot = TRUE; + self.clientfov = 90; self.createdtime = self.bot_nextthink; if(!self.bot_config_loaded) // This is needed so team overrider doesn't break between matches diff --git a/qcsrc/server/bot/bot.qh b/qcsrc/server/bot/bot.qh index 043f8332c..e677e0cf1 100644 --- a/qcsrc/server/bot/bot.qh +++ b/qcsrc/server/bot/bot.qh @@ -19,7 +19,7 @@ const float AI_STATUS_STUCK = 2048; // Cannot reach any goal .float aistatus; // Skill system -float skill; +float bot_skill; float autoskill_nextthink; // havocbot_keyboardskill // keyboard movement @@ -111,8 +111,4 @@ void bot_serverframe(); void() havocbot_setupbot; -float c1, c2, c3, c4; -void CheckAllowedTeams(entity for_whom); void GetTeamCounts(entity other); -float JoinBestTeam(entity pl, float only_return_best, float forcebestteam); - void bot_calculate_stepheightvec(void); diff --git a/qcsrc/server/bot/havocbot/havocbot.qc b/qcsrc/server/bot/havocbot/havocbot.qc index e58e67097..9a0a70405 100644 --- a/qcsrc/server/bot/havocbot/havocbot.qc +++ b/qcsrc/server/bot/havocbot/havocbot.qc @@ -1,6 +1,4 @@ #include "havocbot.qh" -#include "role_onslaught.qc" -#include "role_keyhunt.qc" #include "roles.qc" void havocbot_ai() @@ -127,12 +125,12 @@ void havocbot_ai() ) next = ((self.goalstack01.absmin + self.goalstack01.absmax) * 0.5) - (self.origin + self.view_ofs); - skillblend=bound(0,(skill+self.bot_moveskill-2.5)*0.5,1); //lower skill player can't preturn + skillblend=bound(0,(bot_skill+self.bot_moveskill-2.5)*0.5,1); //lower skill player can't preturn distanceblend=bound(0,aimdistance/autocvar_bot_ai_keyboard_distance,1); blend = skillblend * (1-distanceblend); - //v = (now * (distanceblend) + next * (1-distanceblend)) * (skillblend) + now * (1-skillblend); - //v = now * (distanceblend) * (skillblend) + next * (1-distanceblend) * (skillblend) + now * (1-skillblend); - //v = now * ((1-skillblend) + (distanceblend) * (skillblend)) + next * (1-distanceblend) * (skillblend); + //v = (now * (distanceblend) + next * (1-distanceblend)) * (bot_skillblend) + now * (1-skillblend); + //v = now * (distanceblend) * (bot_skillblend) + next * (1-distanceblend) * (bot_skillblend) + now * (1-skillblend); + //v = now * ((1-skillblend) + (distanceblend) * (bot_skillblend)) + next * (1-distanceblend) * (bot_skillblend); v = now + blend * (next - now); //dprint(etos(self), " "); //dprint(vtos(now), ":", vtos(next), "=", vtos(v), " (blend ", ftos(blend), ")\n"); @@ -152,13 +150,13 @@ void havocbot_ai() entity e; // we are currently holding a weapon that's not fully loaded, reload it - if(skill >= 2) // bots can only reload the held weapon on purpose past this skill + if(bot_skill >= 2) // bots can only reload the held weapon on purpose past this skill if(self.clip_load < self.clip_size) self.impulse = 20; // "press" the reload button, not sure if this is done right // if we're not reloading a weapon, switch to any weapon in our invnetory that's not fully loaded to reload it next // the code above executes next frame, starting the reloading then - if(skill >= 5) // bots can only look for unloaded weapons past this skill + if(bot_skill >= 5) // bots can only look for unloaded weapons past this skill if(self.clip_load >= 0) // only if we're not reloading a weapon already { for(i = WEP_FIRST; i <= WEP_LAST; ++i) @@ -177,7 +175,7 @@ void havocbot_keyboard_movement(vector destorg) float blend, maxspeed; float sk; - sk = skill + self.bot_moveskill; + sk = bot_skill + self.bot_moveskill; maxspeed = autocvar_sv_maxspeed; @@ -188,7 +186,7 @@ void havocbot_keyboard_movement(vector destorg) max( self.havocbot_keyboardtime + 0.05/max(1, sk+self.havocbot_keyboardskill) - + random()*0.025/max(0.00025, skill+self.havocbot_keyboardskill) + + random()*0.025/max(0.00025, bot_skill+self.havocbot_keyboardskill) , time); keyboard = self.movement * (1.0 / maxspeed); @@ -545,7 +543,7 @@ void havocbot_movetogoal() self.aistatus &= ~AI_STATUS_OUT_JUMPPAD; // If there is a trigger_hurt right below try to use the jetpack or make a rocketjump - if(skill>6) + if(bot_skill>6) if (!(self.flags & FL_ONGROUND)) { tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 -65536', MOVE_NOMONSTERS, self); @@ -581,7 +579,7 @@ void havocbot_movetogoal() { self.movement_x = dir * v_forward * maxspeed; self.movement_y = dir * v_right * maxspeed; - if (skill < 10) + if (bot_skill < 10) havocbot_keyboard_movement(self.origin + dir * 100); } } @@ -799,11 +797,11 @@ void havocbot_movetogoal() } dodge = havocbot_dodge(); - dodge = dodge * bound(0,0.5+(skill+self.bot_dodgeskill)*0.1,1); - evadelava = evadelava * bound(1,3-(skill+self.bot_dodgeskill),3); //Noobs fear lava a lot and take more distance from it + dodge = dodge * bound(0,0.5+(bot_skill+self.bot_dodgeskill)*0.1,1); + evadelava = evadelava * bound(1,3-(bot_skill+self.bot_dodgeskill),3); //Noobs fear lava a lot and take more distance from it traceline(self.origin, ( ( self.enemy.absmin + self.enemy.absmax ) * 0.5 ), TRUE, world); if(IS_PLAYER(trace_ent)) - dir = dir * bound(0,(skill+self.bot_dodgeskill)/7,1); + dir = dir * bound(0,(bot_skill+self.bot_dodgeskill)/7,1); dir = normalize(dir + dodge + evadeobstacle + evadelava); // self.bot_dodgevector = dir; @@ -832,18 +830,18 @@ void havocbot_movetogoal() self.movement_z = dir * v_up * maxspeed; // Emulate keyboard interface - if (skill < 10) + if (bot_skill < 10) havocbot_keyboard_movement(destorg); // Bunnyhop! // if(self.aistatus & AI_STATUS_ROAMING) if(self.goalcurrent) - if(skill+self.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset) + if(bot_skill+self.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset) havocbot_bunnyhop(dir); if ((dir * v_up) >= autocvar_sv_jumpvelocity*0.5 && (self.flags & FL_ONGROUND)) self.BUTTON_JUMP=1; - if (((dodge * v_up) > 0) && random()*frametime >= 0.2*bound(0,(10-skill-self.bot_dodgeskill)*0.1,1)) self.BUTTON_JUMP=TRUE; - if (((dodge * v_up) < 0) && random()*frametime >= 0.5*bound(0,(10-skill-self.bot_dodgeskill)*0.1,1)) self.havocbot_ducktime=time+0.3/bound(0.1,skill+self.bot_dodgeskill,10); + if (((dodge * v_up) > 0) && random()*frametime >= 0.2*bound(0,(10-bot_skill-self.bot_dodgeskill)*0.1,1)) self.BUTTON_JUMP=TRUE; + if (((dodge * v_up) < 0) && random()*frametime >= 0.5*bound(0,(10-bot_skill-self.bot_dodgeskill)*0.1,1)) self.havocbot_ducktime=time+0.3/bound(0.1,bot_skill+self.bot_dodgeskill,10); } void havocbot_chooseenemy() @@ -946,7 +944,7 @@ float havocbot_chooseweapon_checkreload(float new_weapon) // bots under this skill cannot find unloaded weapons to reload idly when not in combat, // so skip this for them, or they'll never get to reload their weapons at all. // this also allows bots under this skill to be more stupid, and reload more often during combat :) - if(skill < 5) + if(bot_skill < 5) return FALSE; // if this weapon is scheduled for reloading, don't switch to it during combat @@ -1009,7 +1007,7 @@ void havocbot_chooseweapon() // Bots with no skill will be 4 times more slower than "godlike" bots when doing weapon combos // Ideally this 4 should be calculated as longest_weapon_refire / bot_ai_weapon_combo_threshold - combo_time = time + ct + (ct * ((-0.3*(skill+self.bot_weaponskill))+3)); + combo_time = time + ct + (ct * ((-0.3*(bot_skill+self.bot_weaponskill))+3)); combo = FALSE; diff --git a/qcsrc/server/bot/havocbot/role_keyhunt.qc b/qcsrc/server/bot/havocbot/role_keyhunt.qc deleted file mode 100644 index 3b641d9ce..000000000 --- a/qcsrc/server/bot/havocbot/role_keyhunt.qc +++ /dev/null @@ -1,212 +0,0 @@ -void() havocbot_role_kh_carrier; -void() havocbot_role_kh_defense; -void() havocbot_role_kh_offense; -void() havocbot_role_kh_freelancer; - -entity kh_worldkeylist; -.entity kh_worldkeynext; - -void havocbot_goalrating_kh(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy) -{ - entity head; - for (head = kh_worldkeylist; head; head = head.kh_worldkeynext) - { - if(head.owner == self) - continue; - if(!kh_tracking_enabled) - { - // if it's carried by our team we know about it - // otherwise we have to see it to know about it - if(!head.owner || head.team != self.team) - { - traceline(self.origin + self.view_ofs, head.origin, MOVE_NOMONSTERS, self); - if (trace_fraction < 1 && trace_ent != head) - continue; // skip what I can't see - } - } - if(!head.owner) - navigation_routerating(head, ratingscale_dropped * BOT_PICKUP_RATING_HIGH, 100000); - else if(head.team == self.team) - navigation_routerating(head.owner, ratingscale_team * BOT_PICKUP_RATING_HIGH, 100000); - else - navigation_routerating(head.owner, ratingscale_enemy * BOT_PICKUP_RATING_HIGH, 100000); - } - - havocbot_goalrating_items(1, self.origin, 10000); -} - -void havocbot_role_kh_carrier() -{ - if(self.deadflag != DEAD_NO) - return; - - if (!(self.kh_next)) - { - dprint("changing role to freelancer\n"); - self.havocbot_role = havocbot_role_kh_freelancer; - self.havocbot_role_timeout = 0; - return; - } - - if (self.bot_strategytime < time) - { - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - - if(kh_Key_AllOwnedByWhichTeam() == self.team) - havocbot_goalrating_kh(10, 0.1, 0.1); // bring home - else - havocbot_goalrating_kh(4, 4, 1); // play defensively - - navigation_goalrating_end(); - } -} - -void havocbot_role_kh_defense() -{ - if(self.deadflag != DEAD_NO) - return; - - if (self.kh_next) - { - dprint("changing role to carrier\n"); - self.havocbot_role = havocbot_role_kh_carrier; - self.havocbot_role_timeout = 0; - return; - } - - if (!self.havocbot_role_timeout) - self.havocbot_role_timeout = time + random() * 10 + 20; - if (time > self.havocbot_role_timeout) - { - dprint("changing role to freelancer\n"); - self.havocbot_role = havocbot_role_kh_freelancer; - self.havocbot_role_timeout = 0; - return; - } - - if (self.bot_strategytime < time) - { - float key_owner_team; - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - - key_owner_team = kh_Key_AllOwnedByWhichTeam(); - if(key_owner_team == self.team) - havocbot_goalrating_kh(10, 0.1, 0.1); // defend key carriers - else if(key_owner_team == -1) - havocbot_goalrating_kh(4, 1, 0.1); // play defensively - else - havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY - - navigation_goalrating_end(); - } -} - -void havocbot_role_kh_offense() -{ - if(self.deadflag != DEAD_NO) - return; - - if (self.kh_next) - { - dprint("changing role to carrier\n"); - self.havocbot_role = havocbot_role_kh_carrier; - self.havocbot_role_timeout = 0; - return; - } - - if (!self.havocbot_role_timeout) - self.havocbot_role_timeout = time + random() * 10 + 20; - if (time > self.havocbot_role_timeout) - { - dprint("changing role to freelancer\n"); - self.havocbot_role = havocbot_role_kh_freelancer; - self.havocbot_role_timeout = 0; - return; - } - - if (self.bot_strategytime < time) - { - float key_owner_team; - - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - - key_owner_team = kh_Key_AllOwnedByWhichTeam(); - if(key_owner_team == self.team) - havocbot_goalrating_kh(10, 0.1, 0.1); // defend anyway - else if(key_owner_team == -1) - havocbot_goalrating_kh(0.1, 1, 4); // play offensively - else - havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK! EMERGENCY! - - navigation_goalrating_end(); - } -} - -void havocbot_role_kh_freelancer() -{ - if(self.deadflag != DEAD_NO) - return; - - if (self.kh_next) - { - dprint("changing role to carrier\n"); - self.havocbot_role = havocbot_role_kh_carrier; - self.havocbot_role_timeout = 0; - return; - } - - if (!self.havocbot_role_timeout) - self.havocbot_role_timeout = time + random() * 10 + 10; - if (time > self.havocbot_role_timeout) - { - if (random() < 0.5) - { - dprint("changing role to offense\n"); - self.havocbot_role = havocbot_role_kh_offense; - } - else - { - dprint("changing role to defense\n"); - self.havocbot_role = havocbot_role_kh_defense; - } - self.havocbot_role_timeout = 0; - return; - } - - if (self.bot_strategytime < time) - { - float key_owner_team; - - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - - key_owner_team = kh_Key_AllOwnedByWhichTeam(); - if(key_owner_team == self.team) - havocbot_goalrating_kh(10, 0.1, 0.1); // defend anyway - else if(key_owner_team == -1) - havocbot_goalrating_kh(1, 10, 4); // prefer dropped keys - else - havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY - - navigation_goalrating_end(); - } -} - -void havocbot_chooserole_kh() -{ - float r; - - if(self.deadflag != DEAD_NO) - return; - - r = random() * 3; - if (r < 1) - self.havocbot_role = havocbot_role_kh_offense; - else if (r < 2) - self.havocbot_role = havocbot_role_kh_defense; - else - self.havocbot_role = havocbot_role_kh_freelancer; -} diff --git a/qcsrc/server/bot/havocbot/role_onslaught.qc b/qcsrc/server/bot/havocbot/role_onslaught.qc deleted file mode 100644 index dc942a382..000000000 --- a/qcsrc/server/bot/havocbot/role_onslaught.qc +++ /dev/null @@ -1,369 +0,0 @@ -#define HAVOCBOT_ONS_ROLE_NONE 0 -#define HAVOCBOT_ONS_ROLE_DEFENSE 2 -#define HAVOCBOT_ONS_ROLE_ASSISTANT 4 -#define HAVOCBOT_ONS_ROLE_OFFENSE 8 - -.float havocbot_role_flags; -.float havocbot_attack_time; - -.void() havocbot_role; -.void() havocbot_previous_role; - -void() havocbot_role_ons_defense; -void() havocbot_role_ons_offense; -void() havocbot_role_ons_assistant; - -void(entity bot) havocbot_ons_reset_role; -void(float ratingscale, vector org, float sradius) havocbot_goalrating_items; -void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers; - -.float isshielded; -.float iscaptured; -.float islinked; -.float isgenneighbor_blue, iscpneighbor_blue; -.float isgenneighbor_red, iscpneighbor_red; - -.entity havocbot_ons_target; - -void havocbot_goalrating_ons_offenseitems(float ratingscale, vector org, float sradius) -{ - entity head; - float t, i, c, needarmor = FALSE, needweapons = FALSE; - - // Needs armor/health? - if(self.health<100) - needarmor = TRUE; - - // Needs weapons? - c = 0; - for(i = WEP_FIRST; i <= WEP_LAST ; ++i) - { - // Find weapon - if(self.weapons & WepSet_FromWeapon(i)) - if(++c>=4) - break; - } - - if(c<4) - needweapons = TRUE; - - if(!needweapons && !needarmor) - return; - -// dprint(self.netname, " needs weapons ", ftos(needweapons) , "\n"); -// dprint(self.netname, " needs armor ", ftos(needarmor) , "\n"); - - // See what is around - head = findchainfloat(bot_pickup, TRUE); - while (head) - { - // gather health and armor only - if (head.solid) - if ( ((head.health || head.armorvalue) && needarmor) || (head.weapons && needweapons ) ) - if (vlen(head.origin - org) < sradius) - { - t = head.bot_pickupevalfunc(self, head); - if (t > 0) - navigation_routerating(head, t * ratingscale, 500); - } - head = head.chain; - } -} - -void havocbot_role_ons_setrole(entity bot, float role) -{ - dprint(strcat(bot.netname," switched to ")); - switch(role) - { - case HAVOCBOT_ONS_ROLE_DEFENSE: - dprint("defense"); - bot.havocbot_role = havocbot_role_ons_defense; - bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE; - bot.havocbot_role_timeout = 0; - break; - case HAVOCBOT_ONS_ROLE_ASSISTANT: - dprint("assistant"); - bot.havocbot_role = havocbot_role_ons_assistant; - bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT; - bot.havocbot_role_timeout = 0; - break; - case HAVOCBOT_ONS_ROLE_OFFENSE: - dprint("offense"); - bot.havocbot_role = havocbot_role_ons_offense; - bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE; - bot.havocbot_role_timeout = 0; - break; - } - dprint("\n"); -} - -float havocbot_ons_teamcount(entity bot, float role) -{ - float c = 0; - entity head; - - FOR_EACH_PLAYER(head) - if(head.team==self.team) - if(head.havocbot_role_flags & role) - ++c; - - return c; -} - -void havocbot_goalrating_ons_controlpoints_attack(float ratingscale) -{ - entity cp, cp1, cp2, best, pl, wp; - float radius, found, bestvalue, c; - - cp1 = cp2 = findchain(classname, "onslaught_controlpoint"); - - // Filter control points - for (; cp2; cp2 = cp2.chain) - { - cp2.wpcost = c = 0; - cp2.wpconsidered = FALSE; - - if(cp2.isshielded) - continue; - - // Ignore owned controlpoints - if(self.team == NUM_TEAM_1) - { - if( (cp2.isgenneighbor_blue || cp2.iscpneighbor_blue) && !(cp2.isgenneighbor_red || cp2.iscpneighbor_red) ) - continue; - } - else if(self.team == NUM_TEAM_2) - { - if( (cp2.isgenneighbor_red || cp2.iscpneighbor_red) && !(cp2.isgenneighbor_blue || cp2.iscpneighbor_blue) ) - continue; - } - - // Count team mates interested in this control point - // (easier and cleaner than keeping counters per cp and teams) - FOR_EACH_PLAYER(pl) - if(pl.team==self.team) - if(pl.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE) - if(pl.havocbot_ons_target==cp2) - ++c; - - // NOTE: probably decrease the cost of attackable control points - cp2.wpcost = c; - cp2.wpconsidered = TRUE; - } - - // We'll consider only the best case - bestvalue = 99999999999; - cp = world; - for (; cp1; cp1 = cp1.chain) - { - if (!cp1.wpconsidered) - continue; - - if(cp1.wpcost self.havocbot_role_timeout) - { - havocbot_ons_reset_role(self); - return; - } - - if(self.havocbot_attack_time>time) - return; - - if (self.bot_strategytime < time) - { - navigation_goalrating_start(); - havocbot_goalrating_enemyplayers(20000, self.origin, 650); - if(!havocbot_goalrating_ons_generator_attack(20000)) - havocbot_goalrating_ons_controlpoints_attack(20000); - havocbot_goalrating_ons_offenseitems(10000, self.origin, 10000); - navigation_goalrating_end(); - - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - } -} - -void havocbot_role_ons_assistant() -{ - havocbot_ons_reset_role(self); -} - -void havocbot_role_ons_defense() -{ - havocbot_ons_reset_role(self); -} - -void havocbot_ons_reset_role(entity bot) -{ - entity head; - float c; - - if(self.deadflag != DEAD_NO) - return; - - bot.havocbot_ons_target = world; - - // TODO: Defend control points or generator if necessary - - // if there is only me on the team switch to offense - c = 0; - FOR_EACH_PLAYER(head) - if(head.team==self.team) - ++c; - - if(c==1) - { - havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE); - return; - } - - havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE); -} - -void havocbot_chooserole_ons() -{ - havocbot_ons_reset_role(self); -} diff --git a/qcsrc/server/bot/havocbot/roles.qc b/qcsrc/server/bot/havocbot/roles.qc index 7e3ddbb43..59ca5274f 100644 --- a/qcsrc/server/bot/havocbot/roles.qc +++ b/qcsrc/server/bot/havocbot/roles.qc @@ -1,4 +1,3 @@ - .float max_armorvalue; .float havocbot_role_timeout; @@ -203,12 +202,9 @@ void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradi } } -// choose a role according to the situation -void havocbot_role_dm(); - -//DM: -//go to best items -void havocbot_role_dm() +// legacy bot role for standard gamemodes +// go to best items +void havocbot_role_generic() { if(self.deadflag != DEAD_NO) return; @@ -224,21 +220,13 @@ void havocbot_role_dm() } } -void havocbot_chooserole_dm() -{ - self.havocbot_role = havocbot_role_dm; -} - void havocbot_chooserole() { dprint("choosing a role...\n"); self.bot_strategytime = 0; if (MUTATOR_CALLHOOK(HavocBot_ChooseRole)) return; - else if (g_keyhunt) - havocbot_chooserole_kh(); - else if (g_onslaught) - havocbot_chooserole_ons(); - else // assume anything else is deathmatch - havocbot_chooserole_dm(); + + // assume anything else is generic + self.havocbot_role = havocbot_role_generic; } diff --git a/qcsrc/server/cheats.qc b/qcsrc/server/cheats.qc index c1a637514..003e3bc92 100644 --- a/qcsrc/server/cheats.qc +++ b/qcsrc/server/cheats.qc @@ -141,6 +141,7 @@ float CheatImpulse(float i) self.personal.ammo_nails = self.ammo_nails; self.personal.ammo_cells = self.ammo_cells; self.personal.ammo_plasma = self.ammo_plasma; + self.personal.ammo_supercells = self.ammo_supercells; self.personal.ammo_shells = self.ammo_shells; self.personal.ammo_fuel = self.ammo_fuel; self.personal.health = self.health; @@ -151,8 +152,6 @@ float CheatImpulse(float i) self.personal.pauserothealth_finished = self.pauserothealth_finished; self.personal.pauserotfuel_finished = self.pauserotfuel_finished; self.personal.pauseregen_finished = self.pauseregen_finished; - self.personal.strength_finished = self.strength_finished; - self.personal.invincible_finished = self.invincible_finished; self.personal.teleport_time = time; break; // this part itself doesn't cheat, so let's not count this case CHIMPULSE_CLONE_MOVING: @@ -199,6 +198,7 @@ float CheatImpulse(float i) self.ammo_nails = self.personal.ammo_nails; self.ammo_cells = self.personal.ammo_cells; self.ammo_plasma = self.personal.ammo_plasma; + self.ammo_supercells = self.personal.ammo_supercells; self.ammo_shells = self.personal.ammo_shells; self.ammo_fuel = self.personal.ammo_fuel; self.health = self.personal.health; @@ -209,8 +209,6 @@ float CheatImpulse(float i) self.pauserothealth_finished = time + self.personal.pauserothealth_finished - self.personal.teleport_time; self.pauserotfuel_finished = time + self.personal.pauserotfuel_finished - self.personal.teleport_time; self.pauseregen_finished = time + self.personal.pauseregen_finished - self.personal.teleport_time; - self.strength_finished = time + self.personal.strength_finished - self.personal.teleport_time; - self.invincible_finished = time + self.personal.invincible_finished - self.personal.teleport_time; DID_CHEAT(); break; @@ -261,8 +259,8 @@ float CheatImpulse(float i) else e = self; - pointparticles(particleeffectnum("rocket_explode"), e.origin, '0 0 0', 1); - sound(e, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_ROCKET_EXPLODE, e.origin, '0 0 0', 1); + sound(e, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); e2 = spawn(); setorigin(e2, e.origin); @@ -1012,7 +1010,16 @@ void Drag_Update(entity dragger) draggee.ltime = max(servertime + serverframetime, draggee.ltime); // fixes func_train breakage - te_lightning1(dragger, dragger.origin + dragger.view_ofs, curorigin); + //te_lightning1(dragger, dragger.origin + dragger.view_ofs, curorigin); + + vector vecs, dv = '0 0 0'; + if(dragger.weaponentity.movedir_x > 0) + vecs = dragger.weaponentity.movedir; + else + vecs = '0 0 0'; + + dv = v_right * -vecs_y + v_up * vecs_z; + Send_Effect(EFFECT_LASER_BEAM_FAST, dragger.origin + dragger.view_ofs + dv, curorigin, 0); } float Drag_CanDrag(entity dragger) diff --git a/qcsrc/server/cl_client.qc b/qcsrc/server/cl_client.qc index 03ab777b9..b569e519d 100644 --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@ -3,6 +3,37 @@ void send_CSQC_teamnagger() { WriteByte(MSG_BROADCAST, TE_CSQC_TEAMNAGGER); } +float CountSpectators(entity player, entity to) +{ + if(!player) { return FALSE; } // not sure how, but best to be safe + + entity head; + float spec_count = 0; + FOR_EACH_REALCLIENT(head) + { + if(IS_SPEC(head)) + if(head != to) + if(head.enemy == player) + spec_count += 1; + } + + return spec_count; +} + +void WriteSpectators(entity player, entity to) +{ + if(!player) { return; } // not sure how, but best to be safe + + entity head; + FOR_EACH_REALCLIENT(head) + { + if(IS_SPEC(head)) + if(head != to) + if(head.enemy == player) + WriteByte(MSG_ENTITY, num_for_edict(head)); + } +} + float ClientData_Send(entity to, float sf) { if(to != self.owner) @@ -27,6 +58,8 @@ float ClientData_Send(entity to, float sf) sf |= 4; // zoomed if(e.porto_v_angle_held) sf |= 8; // angles held + // always check spectators + sf |= 16; // spectator handling? WriteByte(MSG_ENTITY, ENT_CLIENT_CLIENTDATA); WriteByte(MSG_ENTITY, sf); @@ -40,6 +73,14 @@ float ClientData_Send(entity to, float sf) WriteAngle(MSG_ENTITY, e.v_angle_y); } + if(sf & 16) + { + float specs = CountSpectators(e, to); + WriteByte(MSG_ENTITY, e.clientfov); + WriteByte(MSG_ENTITY, specs); + WriteSpectators(e, to); + } + return TRUE; } @@ -137,10 +178,16 @@ putting a client as observer in the server void FixPlayermodel(); void PutObserverInServer (void) { - entity spot; + entity spot; + + SetSpectator(self, world); + self.hud = HUD_NORMAL; - if(IS_PLAYER(self)) { pointparticles(particleeffectnum("spawn_event_neutral"), self.origin, '0 0 0', 1); } + if(IS_PLAYER(self)) { Send_Effect(EFFECT_SPAWN_NEUTRAL, self.origin, '0 0 0', 1); } + + if(autocvar_g_observer_glowtrails) + CSQCModel_UnlinkEntity(); spot = SelectSpawnPoint (TRUE); if(!spot) @@ -170,7 +217,7 @@ void PutObserverInServer (void) } if(self.vehicle) - vehicles_exit(VHEF_RELESE); + vehicles_exit(VHEF_RELEASE); WaypointSprite_PlayerDead(); @@ -220,8 +267,6 @@ void PutObserverInServer (void) self.fade_time = 0; self.pain_frame = 0; self.pain_finished = 0; - self.strength_finished = 0; - self.invincible_finished = 0; self.superweapons_finished = 0; self.pushltime = 0; self.istypefrag = 0; @@ -242,7 +287,7 @@ void PutObserverInServer (void) self.model = ""; FixPlayermodel(); setmodel(self, "null"); - self.drawonlytoclient = self; + if(!autocvar_g_observer_glowtrails) { self.drawonlytoclient = self; } setsize (self, PL_CROUCH_MIN, PL_CROUCH_MAX); // give the spectator some space between walls for MOVETYPE_FLY_WORLDONLY self.view_ofs = '0 0 0'; // so that your view doesn't go into the ceiling with MOVETYPE_FLY_WORLDONLY, previously "PL_VIEW_OFS" @@ -261,6 +306,14 @@ void PutObserverInServer (void) self.oldvelocity = self.velocity; self.fire_endtime = -1; self.event_damage = func_null; + + if(autocvar_g_observer_glowtrails) + { + //self.glow_color = 254; + self.glow_color = ((self.team == NUM_TEAM_1) ? 251 : ((self.team == NUM_TEAM_2) ? 210 : ((self.team == NUM_TEAM_3) ? 110 : ((self.team == NUM_TEAM_4) ? 145 : 254)))); + self.glow_size = 25; + self.glow_trail = 1; + } } .float model_randomizer; @@ -311,6 +364,30 @@ void FixPlayermodel() } } + if(autocvar_sv_allow_customplayermodels) + if(!cvar("g_overkill")) + { + // public hax + if(self.cvar_cl_pony) + { + defaultmodel = "models/player/pony.iqm"; + defaultskin = self.cvar_cl_pony_skin; + } + if(self.cvar_cl_robot == 1) + defaultmodel = "models/player/terminusmale.iqm"; + + // special hax + if(checkinlist(self.crypto_idfp, autocvar_sv_allow_customplayermodels_idlist) || IS_BOT_CLIENT(self)) + { + if(self.cvar_cl_thestars == 1) + defaultmodel = "models/player/rosalina.dpm"; + if(self.cvar_cl_damnfurries == 1) + defaultmodel = "models/player/renamon.iqm"; + if(self.cvar_cl_robot == 2) + defaultmodel = "models/player/ubot.iqm"; + } + } + if(defaultmodel != "") { if (defaultmodel != self.model) @@ -351,6 +428,61 @@ void FixPlayermodel() setcolor(self, stof(autocvar_sv_defaultplayercolors)); } +void PlayerTouch (void) +{ + if(other == world) + return; + + if(!IS_PLAYER(self) || !IS_PLAYER(other)) + return; + + if(self.deadflag != DEAD_NO || other.deadflag != DEAD_NO) + return; + + if(!self.iscreature || !other.iscreature) + return; + + if(forbidWeaponUse(self)) + return; + + if(autocvar_g_player_crush_simple) + { + vector vdir = normalize(other.origin - self.origin); + + if(vdir_z > autocvar_g_player_crush_headheight) // adjust this to set how sharp from above players need to hit the player to crush them. + Damage (self, other, other, autocvar_g_player_crush_damage, DEATH_CRUSH, self.origin, '0 0 0'); + } + else + { + + tracebox(self.origin, self.mins, self.maxs, self.origin - ('0 0 1' * (self.maxs_z + 5)), MOVE_NORMAL, self); + + if(trace_ent == other) + { + float mjumpheight = autocvar_g_player_crush_bounce; + + setorigin(self, self.origin + '0 0 2'); + + if(self.BUTTON_JUMP) + { + mjumpheight = autocvar_g_player_crush_bounce_jump; + self.flags &= ~FL_JUMPRELEASED; + } + + self.flags &= ~FL_ONGROUND; + + self.velocity_z = mjumpheight; + self.oldvelocity_z = self.velocity_z; + + animdecide_setaction(self, ANIMACTION_JUMP, TRUE); + + self.restart_jump = -1; // restart jump anim next time + + Damage (other, self, self, autocvar_g_player_crush_damage, DEATH_CRUSH, other.origin, '0 0 0'); + } + } +} + /* ============= PutClientInServer @@ -361,7 +493,10 @@ Called when a client spawns in the server void PutClientInServer (void) { if(IS_BOT_CLIENT(self)) + { self.classname = "player"; + self.cvar_cl_robot = 2; // always make bots ubot? + } else if(IS_REAL_CLIENT(self)) { msg_entity = self; @@ -399,7 +534,10 @@ void PutClientInServer (void) RemoveGrapplingHook(self); // Wazat's Grappling Hook if(self.vehicle) - vehicles_exit(VHEF_RELESE); + vehicles_exit(VHEF_RELEASE); + + if(autocvar_g_observer_glowtrails) + CSQCMODEL_AUTOINIT(); self.classname = "player"; self.wasplayer = TRUE; @@ -424,6 +562,10 @@ void PutClientInServer (void) self.effects |= EF_TELEPORT_BIT | EF_RESTARTANIM_BIT; self.air_finished = time + 12; self.dmg = 2; + self.glow_trail = 0; + self.taunt_soundtime = 0; + self.glow_color = 0; + self.glow_size = 0; if(WEP_CVAR(vortex, charge)) { if(WEP_CVAR_SEC(vortex, chargepool)) @@ -439,6 +581,7 @@ void PutClientInServer (void) self.ammo_cells = warmup_start_ammo_cells; self.ammo_plasma = warmup_start_ammo_plasma; self.ammo_fuel = warmup_start_ammo_fuel; + self.ammo_supercells = 0; // never start with super cells self.health = warmup_start_health; self.armorvalue = warmup_start_armorvalue; self.weapons = WARMUP_START_WEAPONS; @@ -451,6 +594,7 @@ void PutClientInServer (void) self.ammo_cells = start_ammo_cells; self.ammo_plasma = start_ammo_plasma; self.ammo_fuel = start_ammo_fuel; + self.ammo_supercells = 0; // never start with super cells self.health = start_health; self.armorvalue = start_armorvalue; self.weapons = start_weapons; @@ -493,8 +637,6 @@ void PutClientInServer (void) self.fade_time = 0; self.pain_frame = 0; self.pain_finished = 0; - self.strength_finished = 0; - self.invincible_finished = 0; self.pushltime = 0; // players have no think function self.think = func_null; @@ -548,9 +690,15 @@ void PutClientInServer (void) self.bot_attack = TRUE; self.monster_attack = TRUE; - + self.spider_slowness = 0; + if(self.cvar_cl_sparkle >= 1 && checkinlist(self.crypto_idfp, autocvar_sv_allow_customplayermodels_idlist)) + self.effects |= EF_STARDUST; + + if(self.cvar_cl_sparkle == 2 && checkinlist(self.crypto_idfp, autocvar_sv_allow_customplayermodels_idlist)) + self.colormap = 4351; + self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = 0; if(self.killcount == -666) { @@ -610,6 +758,9 @@ void PutClientInServer (void) self.weaponname = ""; self.switchingweapon = 0; + if(autocvar_g_player_crush) + self.touch = PlayerTouch; + if(!warmup_stage) if(!self.alivetime) self.alivetime = time; @@ -654,6 +805,9 @@ float ClientInit_SendEntity(entity to, float sf) WriteByte(MSG_ENTITY, WEP_CVAR_SEC(hagar, load_max)); // hagar max loadable rockets // WEAPONTODO WriteCoord(MSG_ENTITY, autocvar_g_trueaim_minrange); WriteByte(MSG_ENTITY, WEP_CVAR(porto, secondary)); // WEAPONTODO + WriteByte(MSG_ENTITY, WEP_CVAR_PRI(vaporizer, refire)); + WriteByte(MSG_ENTITY, sv_showfps); + WriteString(MSG_ENTITY, autocvar_sv_announcer); return TRUE; } @@ -770,7 +924,7 @@ void ClientKill_Now() { if(self.vehicle) { - vehicles_exit(VHEF_RELESE); + vehicles_exit(VHEF_RELEASE); if(!self.killindicator_teamchange) { self.vehicle_health = -1; @@ -842,6 +996,12 @@ void ClientKill_TeamChange (float targetteam) // 0 = don't change, -1 = auto, -2 if (gameover) return; + if(g_infection && IS_PLAYER(self) && targetteam > 0 && round_handler_IsRoundStarted()) + { + sprint(self, "You cannot change teams after the round has started.\n"); + return; + } + killtime = autocvar_g_balance_kill_delay; if(g_race_qualifying || g_cts) @@ -998,8 +1158,6 @@ Called when a client connects to the server ============= */ void DecodeLevelParms (void); -//void dom_player_join_team(entity pl); -void set_dom_state(entity e); void ClientConnect (void) { float t; @@ -1216,7 +1374,7 @@ void ReadyCount(); void ClientDisconnect (void) { if(self.vehicle) - vehicles_exit(VHEF_RELESE); + vehicles_exit(VHEF_RELEASE); if (!IS_CLIENT(self)) { @@ -1226,7 +1384,12 @@ void ClientDisconnect (void) PlayerStats_GameReport_FinalizePlayer(self); - if(IS_PLAYER(self)) { pointparticles(particleeffectnum("spawn_event_neutral"), self.origin, '0 0 0', 1); } + if ( self.active_minigame ) + part_minigame(self); + + SetSpectator(self, world); + + if(IS_PLAYER(self)) { Send_Effect(EFFECT_SPAWN_NEUTRAL, self.origin, '0 0 0', 1); } CheatShutdownClient(); @@ -1293,6 +1456,22 @@ void ClientDisconnect (void) } .float BUTTON_CHAT; +.float crouch; +.float bubble_oldmax; +float ChatBubbleCustomize() +{ + entity e = WaypointSprite_getviewentity(other), own = self.owner; + + if(!own.deadflag && IS_PLAYER(own)) + { + if(own.BUTTON_CHAT) { self.skin = 0; return TRUE; } + if(own.active_minigame) { self.skin = 1; return TRUE; } + if(SAME_TEAM(own, e) && e != own) { self.skin = 2; return TRUE; } + } + + return FALSE; +} + void ChatBubbleThink() { self.nextthink = time; @@ -1303,14 +1482,12 @@ void ChatBubbleThink() remove(self); return; } - if ((self.owner.BUTTON_CHAT && !self.owner.deadflag) -#ifdef TETRIS - || self.owner.tetris_on -#endif - ) - self.model = self.mdl; - else - self.model = ""; + + if(self.bubble_oldmax != self.owner.maxs_z) + { + setorigin(self, '0 0 20' + self.owner.maxs_z * '0 0 1'); + self.bubble_oldmax = self.owner.maxs_z; + } } void UpdateChatBubble() @@ -1323,14 +1500,16 @@ void UpdateChatBubble() self.chatbubbleentity = spawn(); self.chatbubbleentity.owner = self; self.chatbubbleentity.exteriormodeltoclient = self; + self.chatbubbleentity.alpha = 1; + self.chatbubbleentity.customizeentityforclient = ChatBubbleCustomize; self.chatbubbleentity.think = ChatBubbleThink; self.chatbubbleentity.nextthink = time; - setmodel(self.chatbubbleentity, "models/misc/chatbubble.spr"); // precision set below + setmodel(self.chatbubbleentity, "models/misc/chatbubble.md3"); // precision set below //setorigin(self.chatbubbleentity, self.origin + '0 0 15' + self.maxs_z * '0 0 1'); setorigin(self.chatbubbleentity, '0 0 15' + self.maxs_z * '0 0 1'); setattachment(self.chatbubbleentity, self, ""); // sticks to moving player better, also conserves bandwidth self.chatbubbleentity.mdl = self.chatbubbleentity.model; - self.chatbubbleentity.model = ""; + //self.chatbubbleentity.model = ""; self.chatbubbleentity.effects = EF_LOWPRECISION; } } @@ -1362,7 +1541,7 @@ void respawn(void) self.velocity = '0 0 1' * autocvar_g_respawn_ghosts_speed; self.avelocity = randomvec() * autocvar_g_respawn_ghosts_speed * 3 - randomvec() * autocvar_g_respawn_ghosts_speed * 3; self.effects |= CSQCMODEL_EF_RESPAWNGHOST; - pointparticles(particleeffectnum("respawn_ghost"), self.origin, '0 0 0', 1); + Send_Effect(EFFECT_RESPAWN_GHOST, self.origin, '0 0 0', 1); if(autocvar_g_respawn_ghosts_maxtime) SUB_SetFade (self, time + autocvar_g_respawn_ghosts_maxtime / 2 + random () * (autocvar_g_respawn_ghosts_maxtime - autocvar_g_respawn_ghosts_maxtime / 2), 1.5); } @@ -1401,46 +1580,6 @@ void player_powerups (void) if (!g_instagib) { - if (self.items & IT_STRENGTH) - { - play_countdown(self.strength_finished, "misc/poweroff.wav"); - self.effects = self.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT); - if (time > self.strength_finished) - { - self.items = self.items - (self.items & IT_STRENGTH); - //Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERDOWN_STRENGTH, self.netname); - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERDOWN_STRENGTH); - } - } - else - { - if (time < self.strength_finished) - { - self.items = self.items | IT_STRENGTH; - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERUP_STRENGTH, self.netname); - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERUP_STRENGTH); - } - } - if (self.items & IT_INVINCIBLE) - { - play_countdown(self.invincible_finished, "misc/poweroff.wav"); - self.effects = self.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT); - if (time > self.invincible_finished) - { - self.items = self.items - (self.items & IT_INVINCIBLE); - //Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERDOWN_SHIELD, self.netname); - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERDOWN_SHIELD); - } - } - else - { - if (time < self.invincible_finished) - { - self.items = self.items | IT_INVINCIBLE; - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERUP_SHIELD, self.netname); - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERUP_SHIELD); - } - } if (self.items & IT_SUPERWEAPON) { if (!(self.weapons & WEPSET_SUPERWEAPONS)) @@ -1580,7 +1719,11 @@ void player_regen (void) // if player rotted to death... die! // check this outside above checks, as player may still be able to rot to death if(self.health < 1) + { + if(self.vehicle) + vehicles_exit(VHEF_RELEASE); self.event_damage(self, self, 1, DEATH_ROT, self.origin, '0 0 0'); + } if (!(self.items & IT_UNLIMITED_WEAPON_AMMO)) { @@ -1671,6 +1814,7 @@ void SpectateCopy(entity spectatee) { self.armorvalue = spectatee.armorvalue; self.ammo_cells = spectatee.ammo_cells; self.ammo_plasma = spectatee.ammo_plasma; + self.ammo_supercells = spectatee.ammo_supercells; self.ammo_shells = spectatee.ammo_shells; self.ammo_nails = spectatee.ammo_nails; self.ammo_rockets = spectatee.ammo_rockets; @@ -1684,8 +1828,6 @@ void SpectateCopy(entity spectatee) { self.last_pickup = spectatee.last_pickup; self.hit_time = spectatee.hit_time; self.metertime = spectatee.metertime; - self.strength_finished = spectatee.strength_finished; - self.invincible_finished = spectatee.invincible_finished; self.pressedkeys = spectatee.pressedkeys; self.weapons = spectatee.weapons; self.switchweapon = spectatee.switchweapon; @@ -1758,31 +1900,21 @@ float SpectateUpdate() float SpectateSet() { - if(self.enemy.classname != "player") + if(!IS_PLAYER(self.enemy)) return FALSE; - /*if(self.enemy.vehicle) - { - msg_entity = self; - WriteByte(MSG_ONE, SVC_SETVIEW); - WriteEntity(MSG_ONE, self.enemy); - //stuffcmd(self, "set viewsize $tmpviewsize \n"); + ClientData_Touch(self.enemy); - self.movetype = MOVETYPE_NONE; - accuracy_resend(self); - } - else - {*/ - msg_entity = self; - WriteByte(MSG_ONE, SVC_SETVIEW); - WriteEntity(MSG_ONE, self.enemy); - //stuffcmd(self, "set viewsize $tmpviewsize \n"); - self.movetype = MOVETYPE_NONE; - accuracy_resend(self); + msg_entity = self; + WriteByte(MSG_ONE, SVC_SETVIEW); + WriteEntity(MSG_ONE, self.enemy); + //stuffcmd(self, "set viewsize $tmpviewsize \n"); + self.movetype = MOVETYPE_NONE; + accuracy_resend(self); + + if(!SpectateUpdate()) + PutObserverInServer(); - if(!SpectateUpdate()) - PutObserverInServer(); - //} return TRUE; } @@ -1796,12 +1928,15 @@ void SetSpectator(entity player, entity spectatee) // these are required to fix the spectator bug with arc if(old_spectatee && old_spectatee.arc_beam) { old_spectatee.arc_beam.SendFlags |= ARC_SF_SETTINGS; } if(player.enemy && player.enemy.arc_beam) { player.enemy.arc_beam.SendFlags |= ARC_SF_SETTINGS; } + + // needed to update spectator list + if(old_spectatee) { ClientData_Touch(old_spectatee); } } float Spectate(entity pl) { if(g_ca && !autocvar_g_ca_spectate_enemies && self.caplayer) - if(pl.team != self.team) + if(DIFF_TEAM(pl, self)) return 0; SetSpectator(self, pl); @@ -1809,23 +1944,21 @@ float Spectate(entity pl) } // Returns next available player to spectate if g_ca_spectate_enemies == 0 -entity CA_SpectateNext(entity start) { - if (start.team == self.team) { - return start; - } +entity CA_SpectateNext(entity start) +{ + if(SAME_TEAM(start, self)) { return start; } other = start; // continue from current player - while(other && other.team != self.team) { + while(other && DIFF_TEAM(other, self)) other = find(other, classname, "player"); - } - if (!other) { + if (!other) + { // restart from begining other = find(other, classname, "player"); - while(other && other.team != self.team) { + while(other && DIFF_TEAM(other, self)) other = find(other, classname, "player"); - } } return other; @@ -1835,16 +1968,27 @@ float SpectateNext() { other = find(self.enemy, classname, "player"); - if (g_ca && !autocvar_g_ca_spectate_enemies && self.caplayer) { + if (g_ca && !autocvar_g_ca_spectate_enemies && self.caplayer) + { // CA and ca players when spectating enemies is forbidden other = CA_SpectateNext(other); - } else { + } + else + { // other modes and ca spectators or spectating enemies is allowed if (!other) other = find(other, classname, "player"); } - if(other) { SetSpectator(self, other); } + if(other) + { + if(autocvar_g_observer_glowtrails) + CSQCMODEL_AUTOINIT(); // reinitialize csqc models, so we see animations properly + self.glow_trail = 0; + self.glow_color = 0; + self.glow_size = 0; + SetSpectator(self, other); + } return SpectateSet(); } @@ -1924,6 +2068,8 @@ void LeaveSpectatorMode() self.classname = "player"; nades_RemoveBonus(self); + SetSpectator(self, world); + if(autocvar_g_campaign || autocvar_g_balance_teams) { JoinBestTeam(self, FALSE, TRUE); } @@ -2052,6 +2198,11 @@ void PrintWelcomeMessage() void ObserverThink() { + if ( self.impulse ) + { + MinigameImpulse(self.impulse); + self.impulse = 0; + } float prefered_movetype; if (self.flags & FL_JUMPRELEASED) { if (self.BUTTON_JUMP && !self.version_mismatch) { @@ -2082,6 +2233,11 @@ void ObserverThink() void SpectatorThink() { + if ( self.impulse ) + { + MinigameImpulse(self.impulse); + self.impulse = 0; + } if (self.flags & FL_JUMPRELEASED) { if (self.BUTTON_JUMP && !self.version_mismatch) { self.flags &= ~FL_JUMPRELEASED; @@ -2129,6 +2285,7 @@ void SpectatorThink() self.flags |= FL_CLIENT | FL_NOTARGET; } +void vehicles_enter (entity pl, entity veh); void PlayerUseKey() { if (!IS_PLAYER(self)) @@ -2136,8 +2293,41 @@ void PlayerUseKey() if(self.vehicle) { - vehicles_exit(VHEF_NORMAL); - return; + if(!gameover) + { + vehicles_exit(VHEF_NORMAL); + return; + } + } + else if(autocvar_g_vehicles_enter) + { + if(!self.frozen) + if(self.deadflag == DEAD_NO) + if(!gameover) + { + entity head, closest_target = world; + head = WarpZone_FindRadius(self.origin, autocvar_g_vehicles_enter_radius, TRUE); + + while(head) // find the closest acceptable target to enter + { + if(IS_VEHICLE(head)) + if(head.deadflag == DEAD_NO) + if(!head.owner || ((head.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(head.owner, self))) + if(head.takedamage != DAMAGE_NO) + { + if(closest_target) + { + if(vlen(self.origin - head.origin) < vlen(self.origin - closest_target.origin)) + { closest_target = head; } + } + else { closest_target = head; } + } + + head = head.chain; + } + + if(closest_target) { vehicles_enter(self, closest_target); return; } + } } // a use key was pressed; call handlers @@ -2181,6 +2371,7 @@ Called every frame for each client before the physics are run .float usekeypressed; void() nexball_setstatus; .float items_added; +.float last_vehiclecheck; void PlayerPreThink (void) { WarpZone_PlayerPhysics_FixVAngle(); @@ -2190,6 +2381,8 @@ void PlayerPreThink (void) self.stat_allow_oldvortexbeam = autocvar_g_allow_oldvortexbeam; self.stat_leadlimit = autocvar_leadlimit; + self.weaponsinmap = weaponsInMap; + if(frametime) { // physics frames: update anticheat stuff @@ -2262,11 +2455,6 @@ void PlayerPreThink (void) self.max_armorvalue = 0; } -#ifdef TETRIS - if (TetrisPreFrame()) - return; -#endif - if(self.frozen == 2) { self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1); @@ -2284,7 +2472,7 @@ void PlayerPreThink (void) if(self.health < 1) { if(self.vehicle) - vehicles_exit(VHEF_RELESE); + vehicles_exit(VHEF_RELEASE); self.event_damage(self, self.frozen_by, 1, DEATH_NADE_ICE_FREEZE, self.origin, '0 0 0'); } else if ( self.revive_progress <= 0 ) @@ -2293,6 +2481,30 @@ void PlayerPreThink (void) MUTATOR_CALLHOOK(PlayerPreThink); + if(autocvar_g_vehicles_enter) + if(time > self.last_vehiclecheck) + if(IS_PLAYER(self)) + if(!gameover) + if(!self.frozen) + if(!self.vehicle) + if(self.deadflag == DEAD_NO) + { + entity veh; + for(veh = world; (veh = findflags(veh, vehicle_flags, VHF_ISVEHICLE)); ) + if(vlen(veh.origin - self.origin) < autocvar_g_vehicles_enter_radius) + if(veh.deadflag == DEAD_NO) + if(veh.takedamage != DAMAGE_NO) + if((veh.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(veh.owner, self)) + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER); + else if(!veh.owner) + if(!veh.team || SAME_TEAM(self, veh)) + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_VEHICLE_ENTER); + else if(autocvar_g_vehicles_steal) + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_VEHICLE_ENTER_STEAL); + + self.last_vehiclecheck = time + 1; + } + if(!self.cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button { if(self.BUTTON_USE && !self.usekeypressed) @@ -2438,7 +2650,7 @@ void PlayerPreThink (void) if (self.crouch) { tracebox(self.origin, PL_MIN, PL_MAX, self.origin, FALSE, self); - if (!trace_startsolid) + if (!trace_startsolid || self.pbhost) { self.crouch = FALSE; self.view_ofs = PL_VIEW_OFS; @@ -2588,13 +2800,6 @@ void PlayerPostThink (void) } } -#ifdef TETRIS - if(self.impulse == 100) - ImpulseCommands(); - if (!TetrisPostFrame()) - { -#endif - CheatFrame(); //CheckPlayerJump(); @@ -2609,10 +2814,6 @@ void PlayerPostThink (void) GetPressedKeys(); } -#ifdef TETRIS - } -#endif - /* float i; for(i = 0; i < 1000; ++i) diff --git a/qcsrc/server/cl_impulse.qc b/qcsrc/server/cl_impulse.qc index 7446b7022..f8323142d 100644 --- a/qcsrc/server/cl_impulse.qc +++ b/qcsrc/server/cl_impulse.qc @@ -46,6 +46,10 @@ void ImpulseCommands (void) return; self.impulse = 0; + if ( self.active_minigame ) + if ( MinigameImpulse(imp) ) + return; + // allow only weapon change impulses when not in round time if(round_handler_IsActive() && !round_handler_IsRoundStarted()) if(imp == 17 || (imp >= 20 && imp < 200) || imp > 253) @@ -54,11 +58,11 @@ void ImpulseCommands (void) if (timeout_status == TIMEOUT_ACTIVE) //don't allow any impulses while the game is paused return; - if(self.vehicle) - if(self.vehicle.deadflag == DEAD_NO) - if(self.vehicle.vehicles_impulse) - if(self.vehicle.vehicles_impulse(imp)) - return; + if(self.vehicle) + if(self.vehicle.deadflag == DEAD_NO) + if(self.vehicle.vehicles_impulse) + if(self.vehicle.vehicles_impulse(imp)) + return; if(CheatImpulse(imp)) { @@ -73,7 +77,7 @@ void ImpulseCommands (void) } else if(imp >= 10 && imp <= 20) { - if(self.deadflag == DEAD_NO) + if(self.deadflag == DEAD_NO && !self.vehicle) { switch(imp) { @@ -108,12 +112,12 @@ void ImpulseCommands (void) W_PreviousWeapon(1); break; case 20: - if(!forbidWeaponUse()) { WEP_ACTION(self.weapon, WR_RELOAD); } + if(!forbidWeaponUse(self)) { WEP_ACTION(self.weapon, WR_RELOAD); } break; } } - else - self.impulse = imp; // retry in next frame + //else + //self.impulse = imp; // retry in next frame } else if(imp == 21) { @@ -121,6 +125,7 @@ void ImpulseCommands (void) } else if(imp >= 200 && imp <= 229) { + if(!self.vehicle) if(self.deadflag == DEAD_NO) { // custom order weapon cycling @@ -133,6 +138,7 @@ void ImpulseCommands (void) } else if(imp >= 230 && imp <= 253) { + if(!self.vehicle) if(self.deadflag == DEAD_NO) W_SwitchWeapon (imp - 230 + WEP_FIRST); else @@ -381,8 +387,4 @@ void ImpulseCommands (void) } } } -#ifdef TETRIS - else if(imp == 100) - TetrisImpulse(); -#endif } diff --git a/qcsrc/server/cl_physics.qc b/qcsrc/server/cl_physics.qc index fb283bb2a..12d696000 100644 --- a/qcsrc/server/cl_physics.qc +++ b/qcsrc/server/cl_physics.qc @@ -10,6 +10,23 @@ .float wasFlying; .float spectatorspeed; +// client side physics +float Physics_Valid(string thecvar) +{ + return autocvar_g_physics_clientselect && checkinlist(thecvar, autocvar_g_physics_clientselect_options); +} + +float Physics_ClientOption(entity pl, string option) +{ + if (Physics_Valid(pl.cvar_cl_physics)) + { + string var = sprintf("g_physics_%s_%s", pl.cvar_cl_physics, option); + if (cvar_type(var) & 1) + return cvar(var); + } + return cvar(strcat("sv_", option)); +} + /* ============= PlayerJump @@ -27,7 +44,7 @@ float PlayerJump (void) return TRUE; // no jumping while blocked float doublejump = FALSE; - float mjumpheight = autocvar_sv_jumpvelocity; + float mjumpheight = self.stat_sv_jumpvelocity; player_multijump = doublejump; player_jumpheight = mjumpheight; @@ -467,7 +484,7 @@ void CPM_PM_Aircontrol(vector wishdir, float wishspeed) return; #endif - k *= bound(0, wishspeed / autocvar_sv_maxairspeed, 1); + k *= bound(0, wishspeed / self.stat_sv_maxairspeed, 1); zspeed = self.velocity_z; self.velocity_z = 0; @@ -477,9 +494,9 @@ void CPM_PM_Aircontrol(vector wishdir, float wishspeed) if(dot > 0) // we can't change direction while slowing down { - k *= pow(dot, autocvar_sv_aircontrol_power)*frametime; - xyspeed = max(0, xyspeed - autocvar_sv_aircontrol_penalty * sqrt(max(0, 1 - dot*dot)) * k/32); - k *= autocvar_sv_aircontrol; + k *= pow(dot, self.stat_sv_aircontrol_power)*frametime; + xyspeed = max(0, xyspeed - self.stat_sv_aircontrol_penalty * sqrt(max(0, 1 - dot*dot)) * k/32); + k *= self.stat_sv_aircontrol; self.velocity = normalize(self.velocity * xyspeed + wishdir * k); } @@ -589,26 +606,26 @@ void PM_AirAccelerate(vector wishdir, float wishspeed) if(wishspeed > curspeed * 1.01) { - wishspeed = min(wishspeed, curspeed + autocvar_sv_warsowbunny_airforwardaccel * self.stat_sv_maxspeed * frametime); + wishspeed = min(wishspeed, curspeed + self.stat_sv_warsowbunny_airforwardaccel * self.stat_sv_maxspeed * frametime); } else { - f = max(0, (autocvar_sv_warsowbunny_topspeed - curspeed) / (autocvar_sv_warsowbunny_topspeed - self.stat_sv_maxspeed)); - wishspeed = max(curspeed, self.stat_sv_maxspeed) + autocvar_sv_warsowbunny_accel * f * self.stat_sv_maxspeed * frametime; + f = max(0, (self.stat_sv_warsowbunny_topspeed - curspeed) / (self.stat_sv_warsowbunny_topspeed - self.stat_sv_maxspeed)); + wishspeed = max(curspeed, self.stat_sv_maxspeed) + self.stat_sv_warsowbunny_accel * f * self.stat_sv_maxspeed * frametime; } wishvel = wishdir * wishspeed; acceldir = wishvel - curvel; addspeed = vlen(acceldir); acceldir = normalize(acceldir); - accelspeed = min(addspeed, autocvar_sv_warsowbunny_turnaccel * self.stat_sv_maxspeed * frametime); + accelspeed = min(addspeed, self.stat_sv_warsowbunny_turnaccel * self.stat_sv_maxspeed * frametime); - if(autocvar_sv_warsowbunny_backtosideratio < 1) + if(self.stat_sv_warsowbunny_backtosideratio < 1) { curdir = normalize(curvel); dot = acceldir * curdir; if(dot < 0) - acceldir = acceldir - (1 - autocvar_sv_warsowbunny_backtosideratio) * dot * curdir; + acceldir = acceldir - (1 - self.stat_sv_warsowbunny_backtosideratio) * dot * curdir; } self.velocity += accelspeed * acceldir; @@ -625,43 +642,11 @@ string specialcommand = "xwxwxsxsxaxdxaxdx1x "; .float specialcommand_pos; void SpecialCommand() { -#ifdef TETRIS - TetrisImpulse(); -#else if(!CheatImpulse(99)) print("A hollow voice says \"Plugh\".\n"); -#endif } -float speedaward_speed; -string speedaward_holder; -string speedaward_uid; -void race_send_speedaward(float msg) -{ - // send the best speed of the round - WriteByte(msg, SVC_TEMPENTITY); - WriteByte(msg, TE_CSQC_RACE); - WriteByte(msg, RACE_NET_SPEED_AWARD); - WriteInt24_t(msg, floor(speedaward_speed+0.5)); - WriteString(msg, speedaward_holder); -} - -float speedaward_alltimebest; -string speedaward_alltimebest_holder; -string speedaward_alltimebest_uid; -void race_send_speedaward_alltimebest(float msg) -{ - // send the best speed - WriteByte(msg, SVC_TEMPENTITY); - WriteByte(msg, TE_CSQC_RACE); - WriteByte(msg, RACE_NET_SPEED_AWARD_BEST); - WriteInt24_t(msg, floor(speedaward_alltimebest+0.5)); - WriteString(msg, speedaward_alltimebest_holder); -} - -string GetMapname(void); -float speedaward_lastupdate; -float speedaward_lastsent; +.float discomode; void SV_PlayerPhysics() { vector wishvel, wishdir, v; @@ -674,21 +659,42 @@ void SV_PlayerPhysics() WarpZone_PlayerPhysics_FixVAngle(); maxspd_mod = 1; - if(self.ballcarried) - if(g_keepaway) - maxspd_mod *= autocvar_g_keepaway_ballcarrier_highspeed; maxspd_mod *= autocvar_g_movement_highspeed; + if(time < self.spider_slowness) + maxspd_mod *= 0.5; // half speed while slow - TODO: add a cvar! + // fix physics stats for g_movement_highspeed // TODO maybe rather use maxairspeed? needs testing - self.stat_sv_airaccel_qw = AdjustAirAccelQW(autocvar_sv_airaccel_qw, maxspd_mod); - if(autocvar_sv_airstrafeaccel_qw) - self.stat_sv_airstrafeaccel_qw = AdjustAirAccelQW(autocvar_sv_airstrafeaccel_qw, maxspd_mod); + self.stat_sv_airaccel_qw = AdjustAirAccelQW(Physics_ClientOption(self, "airaccel_qw"), maxspd_mod); + if(Physics_ClientOption(self, "airstrafeaccel_qw")) + self.stat_sv_airstrafeaccel_qw = AdjustAirAccelQW(Physics_ClientOption(self, "airstrafeaccel_qw"), maxspd_mod); else self.stat_sv_airstrafeaccel_qw = 0; - self.stat_sv_airspeedlimit_nonqw = autocvar_sv_airspeedlimit_nonqw * maxspd_mod; - self.stat_sv_maxspeed = autocvar_sv_maxspeed * maxspd_mod; // also slow walking + self.stat_sv_airspeedlimit_nonqw = Physics_ClientOption(self, "airspeedlimit_nonqw") * maxspd_mod; + self.stat_sv_maxspeed = Physics_ClientOption(self, "maxspeed") * maxspd_mod; // also slow walking + + // fix some new settings + self.stat_sv_airaccel_qw_stretchfactor = Physics_ClientOption(self, "airaccel_qw_stretchfactor"); + self.stat_sv_maxairstrafespeed = Physics_ClientOption(self, "maxairstrafespeed"); + self.stat_sv_maxairspeed = Physics_ClientOption(self, "maxairspeed"); + self.stat_sv_airstrafeaccelerate = Physics_ClientOption(self, "airstrafeaccelerate"); + self.stat_sv_warsowbunny_turnaccel = Physics_ClientOption(self, "warsowbunny_turnaccel"); + self.stat_sv_airaccel_sideways_friction = Physics_ClientOption(self, "airaccel_sideways_friction"); + self.stat_sv_aircontrol = Physics_ClientOption(self, "aircontrol"); + self.stat_sv_aircontrol_power = Physics_ClientOption(self, "aircontrol_power"); + self.stat_sv_aircontrol_penalty = Physics_ClientOption(self, "aircontrol_penalty"); + self.stat_sv_warsowbunny_airforwardaccel = Physics_ClientOption(self, "warsowbunny_airforwardaccel"); + self.stat_sv_warsowbunny_topspeed = Physics_ClientOption(self, "warsowbunny_topspeed"); + self.stat_sv_warsowbunny_accel = Physics_ClientOption(self, "warsowbunny_accel"); + self.stat_sv_warsowbunny_backtosideratio = Physics_ClientOption(self, "warsowbunny_backtosideratio"); + self.stat_sv_friction = Physics_ClientOption(self, "friction"); + self.stat_sv_accelerate = Physics_ClientOption(self, "accelerate"); + self.stat_sv_stopspeed = Physics_ClientOption(self, "stopspeed"); + self.stat_sv_airaccelerate = Physics_ClientOption(self, "airaccelerate"); + self.stat_sv_airstopaccelerate = Physics_ClientOption(self, "airstopaccelerate"); + self.stat_sv_jumpvelocity = Physics_ClientOption(self, "jumpvelocity"); if(self.PlayerPhysplug) if(self.PlayerPhysplug()) @@ -811,6 +817,16 @@ void SV_PlayerPhysics() } } + if ( self.discomode ) + { + if(IS_PLAYER(self)) + self.BUTTON_JUMP = 1; + + self.angles_y = time*180; + self.velocity = randomvec() * 80; + self.fixangle = TRUE; + } + if (self.movetype == MOVETYPE_NONE) return; @@ -819,12 +835,6 @@ void SV_PlayerPhysics() if(time < self.ladder_time) self.disableclientprediction = 1; - if(time < self.spider_slowness) - { - self.stat_sv_maxspeed *= 0.5; // half speed while slow from spider - self.stat_sv_airspeedlimit_nonqw *= 0.5; - } - if(self.frozen) { if(autocvar_sv_dodging_frozen && IS_REAL_CLIENT(self)) @@ -889,7 +899,7 @@ void SV_PlayerPhysics() maxspd_mod = self.spectatorspeed; } - spd = max(self.stat_sv_maxspeed, autocvar_sv_maxairspeed) * maxspd_mod * swampspd_mod; + spd = max(self.stat_sv_maxspeed, self.stat_sv_maxairspeed) * maxspd_mod * swampspd_mod; if(self.speed != spd) { self.speed = spd; @@ -962,7 +972,7 @@ void SV_PlayerPhysics() // noclipping or flying self.flags &= ~FL_ONGROUND; - self.velocity = self.velocity * (1 - frametime * autocvar_sv_friction); + self.velocity = self.velocity * (1 - frametime * self.stat_sv_friction); makevectors(self.v_angle); //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z; wishvel = v_forward * self.movement_x + v_right * self.movement_y + '0 0 1' * self.movement_z; @@ -972,7 +982,7 @@ void SV_PlayerPhysics() if (wishspeed > self.stat_sv_maxspeed*maxspd_mod) wishspeed = self.stat_sv_maxspeed*maxspd_mod; if (time >= self.teleport_time) - PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0); + PM_Accelerate(wishdir, wishspeed, wishspeed, self.stat_sv_accelerate*maxspd_mod, 1, 0, 0, 0); } else if (self.waterlevel >= WATERLEVEL_SWIMMING) { @@ -992,10 +1002,10 @@ void SV_PlayerPhysics() wishspeed = wishspeed * 0.7; // water friction - self.velocity = self.velocity * (1 - frametime * autocvar_sv_friction); + self.velocity = self.velocity * (1 - frametime * self.stat_sv_friction); // water acceleration - PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0); + PM_Accelerate(wishdir, wishspeed, wishspeed, self.stat_sv_accelerate*maxspd_mod, 1, 0, 0, 0); } else if (time < self.ladder_time) { @@ -1012,7 +1022,7 @@ void SV_PlayerPhysics() self.velocity_z += g; } - self.velocity = self.velocity * (1 - frametime * autocvar_sv_friction); + self.velocity = self.velocity * (1 - frametime * self.stat_sv_friction); makevectors(self.v_angle); //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z; wishvel = v_forward * self.movement_x + v_right * self.movement_y + '0 0 1' * self.movement_z; @@ -1045,7 +1055,7 @@ void SV_PlayerPhysics() if (time >= self.teleport_time) { // water acceleration - PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0); + PM_Accelerate(wishdir, wishspeed, wishspeed, self.stat_sv_accelerate*maxspd_mod, 1, 0, 0, 0); } } else if (self.items & IT_USING_JETPACK) @@ -1054,7 +1064,7 @@ void SV_PlayerPhysics() makevectors(self.v_angle); wishvel = v_forward * self.movement_x + v_right * self.movement_y; // add remaining speed as Z component - maxairspd = autocvar_sv_maxairspeed*max(1, maxspd_mod); + maxairspd = self.stat_sv_maxairspeed*max(1, maxspd_mod); // fix speedhacks :P wishvel = normalize(wishvel) * min(vlen(wishvel) / maxairspd, 1); // add the unused velocity as up component @@ -1068,12 +1078,15 @@ void SV_PlayerPhysics() a_side = autocvar_g_jetpack_acceleration_side; a_up = autocvar_g_jetpack_acceleration_up; a_add = autocvar_g_jetpack_antigravity * autocvar_sv_gravity; + if(autocvar_g_jetpack_reverse_thrust && self.crouch) { a_up = autocvar_g_jetpack_reverse_thrust; } wishvel_x *= a_side; wishvel_y *= a_side; wishvel_z *= a_up; wishvel_z += a_add; + if(autocvar_g_jetpack_reverse_thrust && self.crouch) { wishvel_z *= -1; } + float best; best = 0; ////////////////////////////////////////////////////////////////////////////////////// @@ -1127,7 +1140,7 @@ void SV_PlayerPhysics() float fvel; fvel = min(1, vlen(wishvel) / best); - if(autocvar_g_jetpack_fuel && !(self.items & IT_UNLIMITED_WEAPON_AMMO)) + if(autocvar_g_jetpack_fuel && (!(self.items & IT_UNLIMITED_WEAPON_AMMO) || cvar("g_overkill"))) f = min(1, self.ammo_fuel / (autocvar_g_jetpack_fuel * frametime * fvel)); else f = 1; @@ -1137,7 +1150,7 @@ void SV_PlayerPhysics() if (f > 0 && wishvel != '0 0 0') { self.velocity = self.velocity + wishvel * f * frametime; - if (!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + if ((!(self.items & IT_UNLIMITED_WEAPON_AMMO) || cvar("g_overkill"))) self.ammo_fuel -= autocvar_g_jetpack_fuel * frametime * fvel * f; self.flags &= ~FL_ONGROUND; self.items |= IT_USING_JETPACK; @@ -1172,10 +1185,10 @@ void SV_PlayerPhysics() f = vlen(v); if(f > 0) { - if (f < autocvar_sv_stopspeed) - f = 1 - frametime * (autocvar_sv_stopspeed / f) * autocvar_sv_friction; + if (f < self.stat_sv_stopspeed) + f = 1 - frametime * (self.stat_sv_stopspeed / f) * self.stat_sv_friction; else - f = 1 - frametime * autocvar_sv_friction; + f = 1 - frametime * self.stat_sv_friction; if (f > 0) self.velocity = self.velocity * f; else @@ -1212,7 +1225,7 @@ void SV_PlayerPhysics() if (self.crouch) wishspeed = wishspeed * 0.5; if (time >= self.teleport_time) - PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0); + PM_Accelerate(wishdir, wishspeed, wishspeed, self.stat_sv_accelerate*maxspd_mod, 1, 0, 0, 0); } else { @@ -1223,13 +1236,13 @@ void SV_PlayerPhysics() if(maxspd_mod < 1) { - maxairspd = autocvar_sv_maxairspeed*maxspd_mod; - airaccel = autocvar_sv_airaccelerate*maxspd_mod; + maxairspd = self.stat_sv_maxairspeed*maxspd_mod; + airaccel = self.stat_sv_airaccelerate*maxspd_mod; } else { - maxairspd = autocvar_sv_maxairspeed; - airaccel = autocvar_sv_airaccelerate; + maxairspd = self.stat_sv_maxairspeed; + airaccel = self.stat_sv_airaccelerate; } // airborn makevectors(self.v_angle_y * '0 1 0'); @@ -1255,13 +1268,13 @@ void SV_PlayerPhysics() wishspeed2 = wishspeed; // CPM - if(autocvar_sv_airstopaccelerate) + if(self.stat_sv_airstopaccelerate) { vector curdir; curdir = self.velocity; curdir_z = 0; curdir = normalize(curdir); - airaccel = airaccel + (autocvar_sv_airstopaccelerate*maxspd_mod - airaccel) * max(0, -(curdir * wishdir)); + airaccel = airaccel + (self.stat_sv_airstopaccelerate*maxspd_mod - airaccel) * max(0, -(curdir * wishdir)); } // note that for straight forward jumping: // step = accel * frametime * wishspeed0; @@ -1272,20 +1285,20 @@ void SV_PlayerPhysics() // log dv/dt = logaccel + logmaxspeed (when slow) // log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast) strafity = IsMoveInDirection(self.movement, -90) + IsMoveInDirection(self.movement, +90); // if one is nonzero, other is always zero - if(autocvar_sv_maxairstrafespeed) - wishspeed = min(wishspeed, GeomLerp(autocvar_sv_maxairspeed*maxspd_mod, strafity, autocvar_sv_maxairstrafespeed*maxspd_mod)); - if(autocvar_sv_airstrafeaccelerate) - airaccel = GeomLerp(airaccel, strafity, autocvar_sv_airstrafeaccelerate*maxspd_mod); + if(self.stat_sv_maxairstrafespeed) + wishspeed = min(wishspeed, GeomLerp(self.stat_sv_maxairspeed*maxspd_mod, strafity, self.stat_sv_maxairstrafespeed*maxspd_mod)); + if(self.stat_sv_airstrafeaccelerate) + airaccel = GeomLerp(airaccel, strafity, self.stat_sv_airstrafeaccelerate*maxspd_mod); if(self.stat_sv_airstrafeaccel_qw) airaccelqw = copysign(1-GeomLerp(1-fabs(self.stat_sv_airaccel_qw), strafity, 1-fabs(self.stat_sv_airstrafeaccel_qw)), ((strafity > 0.5) ? self.stat_sv_airstrafeaccel_qw : self.stat_sv_airaccel_qw)); // !CPM - if(autocvar_sv_warsowbunny_turnaccel && accelerating && self.movement_y == 0 && self.movement_x != 0) + if(self.stat_sv_warsowbunny_turnaccel && accelerating && self.movement_y == 0 && self.movement_x != 0) PM_AirAccelerate(wishdir, wishspeed); else - PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, autocvar_sv_airaccel_qw_stretchfactor, autocvar_sv_airaccel_sideways_friction / maxairspd, self.stat_sv_airspeedlimit_nonqw); + PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, self.stat_sv_airaccel_qw_stretchfactor, self.stat_sv_airaccel_sideways_friction / maxairspd, self.stat_sv_airspeedlimit_nonqw); - if(autocvar_sv_aircontrol) + if(self.stat_sv_aircontrol) CPM_PM_Aircontrol(wishdir, wishspeed2); } } diff --git a/qcsrc/server/cl_player.qc b/qcsrc/server/cl_player.qc index d899b3db3..64eadf4a3 100644 --- a/qcsrc/server/cl_player.qc +++ b/qcsrc/server/cl_player.qc @@ -133,6 +133,8 @@ void player_anim (void) float animbits = deadbits; if(self.frozen) animbits |= ANIMSTATE_FROZEN; + if(self.movetype == MOVETYPE_FOLLOW) + animbits |= ANIMSTATE_FOLLOW; if(self.crouch) animbits |= ANIMSTATE_DUCK; animdecide_setstate(self, animbits, FALSE); @@ -155,7 +157,7 @@ void PlayerCorpseDamage (entity inflictor, entity attacker, float damage, float // damage resistance (ignore most of the damage from a bullet or similar) damage = max(damage - 5, 1); - v = healtharmor_applydamage(self.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage); + v = healtharmor_applydamage(self.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage, FALSE); take = v_x; save = v_y; @@ -206,7 +208,7 @@ void PlayerCorpseDamage (entity inflictor, entity attacker, float damage, float #define GAMETYPE_DEFAULTED_SETTING(str) \ ((gametype_setting_tmp = cvar(strcat("g_", GetGametype(), "_" #str))), \ (gametype_setting_tmp < 0) ? 0 : \ - (gametype_setting_tmp == 0) ? max(0, autocvar_g_##str) : \ + (gametype_setting_tmp == 0 || autocvar_g_respawn_delay_forced) ? max(0, autocvar_g_##str) : \ gametype_setting_tmp) @@ -351,7 +353,7 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker); - v = healtharmor_applydamage(self.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage); + v = healtharmor_applydamage(self.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage, autocvar_g_balance_armor_block_bycount); take = v_x; save = v_y; @@ -406,6 +408,7 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht if (take > 100) Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker); + if (!autocvar_g_spawnshield_nodamage) if (time >= self.spawnshieldtime) { if (!(self.flags & FL_GODMODE)) @@ -455,7 +458,7 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht float shake; if(IS_BOT_CLIENT(self) && self.health >= 1) { - shake = damage * 5 / (bound(0,skill,100) + 1); + shake = damage * 5 / (bound(0,bot_skill,100) + 1); self.v_angle_x = self.v_angle_x + (random() * 2 - 1) * shake; self.v_angle_y = self.v_angle_y + (random() * 2 - 1) * shake; self.v_angle_x = bound(-90, self.v_angle_x, 90); @@ -529,10 +532,18 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht if(deathtype == DEATH_KILL) { // for the lemmings fans, a small harmless explosion - pointparticles(particleeffectnum("rocket_explode"), self.origin, '0 0 0', 1); + Send_Effect(EFFECT_ROCKET_EXPLODE, self.origin, '0 0 0', 1); } } + jeff_Announcer_PlayerDies(attacker, deathtype, self, inflictor); + + if(IS_PLAYER(attacker)) + { + self.lastkiller = attacker; + attacker.lastkilled = self; + } + // print an obituary message if(self.classname != "body") Obituary (attacker, inflictor, self, deathtype); @@ -547,8 +558,10 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht frag_attacker = attacker; frag_inflictor = inflictor; frag_target = self; + frag_damage = excess; frag_deathtype = deathtype; MUTATOR_CALLHOOK(PlayerDies); + excess = frag_damage; WEP_ACTION(self.weapon, WR_PLAYERDEATH); @@ -609,6 +622,8 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht setsize(self, self.mins, self.maxs); } // set damage function to corpse damage + if(autocvar_g_player_gib_always) + excess = 10000; self.event_damage = PlayerCorpseDamage; // call the corpse damage function just in case it wants to gib self.event_damage(inflictor, attacker, excess, deathtype, hitloc, force); @@ -696,6 +711,9 @@ float Say(entity source, float teamsay, entity privatesay, string msgin, float f else colorprefix = "^7"; + if(source.waterlevel >= WATERLEVEL_SWIMMING) + msgin = "[glub glub glub]"; + if(msgin != "") { if(privatesay) @@ -716,7 +734,14 @@ float Say(entity source, float teamsay, entity privatesay, string msgin, float f } else { - msgstr = strcat("\{1}", colorprefix, namestr, "^7: ", msgin); + if(substring(msgin, 0, 3) == "/me") + { + //msgin = strreplace("/me", "", msgin); + msgin = substring(msgin, 3, strlen(msgin)); + msgstr = strcat("\{1}^4* ", colorprefix, namestr, "^7", msgin); + } + else + msgstr = strcat("\{1}", colorprefix, namestr, "^7: ", msgin); cmsgstr = ""; } msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint @@ -874,6 +899,15 @@ float Say(entity source, float teamsay, entity privatesay, string msgin, float f if(cmsgstr != "") centerprint(privatesay, cmsgstr); } + else if ( teamsay && source.active_minigame ) + { + sprint(source, sourcemsgstr); + dedicated_print(msgstr); // send to server console too + FOR_EACH_REALCLIENT(head) + if(head != source) + if(head.active_minigame == source.active_minigame) + sprint(head, msgstr); + } else if(teamsay > 0) // team message, only sent to team mates { sprint(source, sourcemsgstr); @@ -911,6 +945,36 @@ float Say(entity source, float teamsay, entity privatesay, string msgin, float f return ret; } +void IRCSay(string sourcename, string msgin) +{ + entity head; + + if(substring(msgin, 0, 1) == " ") + msgin = substring(msgin, 1, strlen(msgin) - 1); // work around DP say bug (say_team does not have this!) + + //msgin = formatmessage(msgin); + + if(msgin == "") + return; + + string msgstr = strcat("\{1}[IRC] ", sourcename, "^7", msgin); + + msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint + + FOR_EACH_CLIENTSLOT(head) + { + if(!intermission_running) + if((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !(warmup_stage || gameover))) + if(IS_PLAYER(head)) + continue; + + if(head.netaddress) + if(head.netaddress != "") + if(head.netaddress != "null/botclient") + sprint(head, msgstr); + } +} + float GetVoiceMessageVoiceType(string type) { if(type == "taunt") diff --git a/qcsrc/server/cl_weapons.qc b/qcsrc/server/cl_weapons.qc new file mode 100644 index 000000000..f27ec9f79 --- /dev/null +++ b/qcsrc/server/cl_weapons.qc @@ -0,0 +1,549 @@ +void W_TriggerReload() +{ + weapon_action(self.weapon, WR_RELOAD); +} + +// switch between weapons +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 + { + W_TriggerReload(); + } +} + +.float weaponcomplainindex; +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_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() +{ + if(client_hasweapon(self, self.cnt, TRUE, FALSE)) + W_SwitchWeapon(self.cnt); + else + W_SwitchToOtherWeapon(self); +} + +float w_getbestweapon(entity e) +{ + return W_GetCycleWeapon(e, e.cvar_cl_weaponpriority, 0, -1, FALSE, TRUE); +} + +// generic weapons table +// TODO should they be macros instead? +float weapon_action(float wpn, float wrequest) +{ + return (get_weaponinfo(wpn)).weapon_func(wrequest); +} + +.float savenextthink; +void thrown_wep_think() +{ + self.nextthink = time; + if(self.oldorigin != self.origin) + { + self.SendFlags |= ISF_LOCATION; + self.oldorigin = self.origin; + } + 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_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_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(w == 0) + return 0; + + wa = W_AmmoItemCode(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(self.frozen) + return; + if(self.vehicle) + return; + if(MUTATOR_CALLHOOK(ForbidThrowCurrentWeapon)) + return; + if(!autocvar_g_weapon_throwable) + return; + if(self.weaponentity.state != WS_READY) + return; + if(!W_IsWeaponThrowable(w)) + return; + + if(!(self.weapons & WepSet_FromWeapon(w))) + return; + self.weapons &= ~WepSet_FromWeapon(w); + + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + a = W_ThrowNewWeapon(self, w, doreduce, self.origin + delta, velo); + + if (!a) return; + Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_WEAPON_DROP, a, w); +} + +float forbidWeaponUse(entity player) +{ + if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown) + return 1; + if(player.player_blocked) + return 3; + if(player.weapon_blocked) + return 4; + if(player.frozen) + return 5; + if(round_handler_IsActive() && !round_handler_IsRoundStarted()) + return 2; + if ( player.discomode ) + return 6; + 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 + + float forbid_weaponuse = forbidWeaponUse(self); + + if(forbid_weaponuse && (!g_instagib || forbid_weaponuse != 2)) + if(self.weaponentity.state != WS_CLEAR) + { + w_ready(); + return; + } + + if(!self.switchweapon) + { + self.weapon = 0; + self.switchingweapon = 0; + self.weaponentity.state = WS_CLEAR; + self.weaponname = ""; + self.items &= ~IT_AMMO; + return; + } + + makevectors(self.v_angle); + fo = v_forward; // save them in case the weapon think functions change it + ri = v_right; + up = v_up; + + // Change weapon + if (self.weapon != self.switchweapon) + { + if (self.weaponentity.state == WS_CLEAR) + { + // end switching! + self.switchingweapon = self.switchweapon; + + entity newwep = get_weaponinfo(self.switchweapon); + + //setanim(self, self.anim_draw, FALSE, TRUE, TRUE); + self.weaponentity.state = WS_RAISE; + weapon_action(self.switchweapon, WR_SETUP); + + // set our clip load to the load of the weapon we switched to, if it's reloadable + if((newwep.spawnflags & WEP_FLAG_RELOADABLE) && cvar(strcat("g_balance_", newwep.netname, "_reload_ammo"))) // prevent accessing undefined cvars + { + self.clip_load = self.(weapon_load[self.switchweapon]); + self.clip_size = cvar(strcat("g_balance_", newwep.netname, "_reload_ammo")); + } + else + self.clip_load = self.clip_size = 0; + + // VorteX: add player model weapon select frame here + // setcustomframe(PlayerWeaponRaise); + weapon_thinkf(WFRAME_IDLE, cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname)), w_ready); + //printf("W_WeaponFrame(): cvar: %s, value: %f\n", sprintf("g_balance_%s_switchdelay_raise", newwep.netname), cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname))); + weapon_boblayer1(PLAYER_WEAPONSELECTION_SPEED, '0 0 0'); + } + 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, W_Sound("weapon_switch"), VOL_BASE, ATTEN_NORM); + self.weaponentity.state = WS_DROP; + // set up weapon switch think in the future, and start drop anim + weapon_thinkf(WFRAME_DONTCHANGE, cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname)), w_clear); + //printf("W_WeaponFrame(): cvar: %s, value: %f\n", sprintf("g_balance_%s_switchdelay_drop", oldwep.netname), cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname))); + weapon_boblayer1(PLAYER_WEAPONSELECTION_SPEED, PLAYER_WEAPONSELECTION_RANGE); +#ifndef INDEPENDENT_ATTACK_FINISHED + } +#endif + } + } + + // LordHavoc: network timing test code + //if (self.button0) + // print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n"); + + float w; + w = self.weapon; + + // call the think code which may fire the weapon + // and do so multiple times to resolve framerate dependency issues if the + // server framerate is very low and the weapon fire rate very high + float c; + c = 0; + while (c < W_TICSPERFRAME) + { + c = c + 1; + if(w && !(self.weapons & WepSet_FromWeapon(w))) + { + if(self.weapon == self.switchweapon) + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w = 0; + } + + v_forward = fo; + v_right = ri; + v_up = up; + + if(w && self.weapon == self.switchweapon) + weapon_action(self.weapon, WR_THINK); + else + weapon_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"); + } + } + + // don't let attack_finished fall behind when not firing (must be after weapon_setup calls!) + //if (ATTACK_FINISHED(self) < time) + // ATTACK_FINISHED(self) = time; + + //if (self.weapon_nextthink < time) + // self.weapon_nextthink = time; + + // update currentammo incase it has changed +#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 +} + +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); +} diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index 898e7db18..1a4768d27 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -159,6 +159,7 @@ void ClientCommand_join(float request) if(autocvar_g_campaign) { campaign_bots_may_start = 1; } self.classname = "player"; + nades_RemoveBonus(self); PlayerScore_Clear(self); Kill_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER_CPID, CPID_PREVENT_JOIN); Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_JOIN_PLAY, self.netname); @@ -184,158 +185,49 @@ void ClientCommand_join(float request) } } -void ClientCommand_mobedit(float request, float argc) +void ClientCommand_physics(float request, float argc) { switch(request) { case CMD_REQUEST_COMMAND: { - if(argv(1) && argv(2)) - { - makevectors(self.v_angle); - WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self); - - if(!autocvar_g_monsters_edit) { sprint(self, "Monster property editing is not enabled.\n"); return; } - if(trace_ent.flags & FL_MONSTER) - { - if(trace_ent.realowner != self) { sprint(self, "That monster does not belong to you.\n"); return; } - switch(argv(1)) - { - case "skin": - { - if(trace_ent.monsterid != MON_MAGE) - trace_ent.skin = stof(argv(2)); - return; - } - case "movetarget": - { - trace_ent.monster_moveflags = stof(argv(2)); - return; - } - } - } - } - } - default: - sprint(self, "Incorrect parameters for ^2mobedit^7\n"); - case CMD_REQUEST_USAGE: - { - sprint(self, "\nUsage:^3 cmd mobedit [argument]\n"); - sprint(self, " Where 'argument' can be skin or movetarget.\n"); - sprint(self, " Aim at your monster to edit its properties.\n"); - return; - } - } -} + string command = strtolower(argv(1)); -void ClientCommand_mobkill(float request) -{ - switch(request) - { - case CMD_REQUEST_COMMAND: - { - makevectors(self.v_angle); - WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self); - - if(trace_ent.flags & FL_MONSTER) + if(command == "list" || command == "help") { - if(trace_ent.realowner != self) - { - sprint(self, "That monster does not belong to you.\n"); - return; - } - sprint(self, strcat("Your pet '", trace_ent.monster_name, "' has been brutally mutilated.\n")); - Damage (trace_ent, world, world, trace_ent.health + trace_ent.max_health + 200, DEATH_KILL, trace_ent.origin, '0 0 0'); + sprint(self, strcat("Available physics sets: \n\n", autocvar_g_physics_clientselect_options, " default\n")); + if(!autocvar_g_physics_clientselect) + sprint(self, "These are unusable currently, as client physics selection is disabled\n"); return; } - } - - default: - sprint(self, "Incorrect parameters for ^2mobkill^7\n"); - case CMD_REQUEST_USAGE: - { - sprint(self, "\nUsage:^3 cmd mobkill\n"); - sprint(self, " Aim at your monster to kill it.\n"); - return; - } - } -} -void ClientCommand_mobspawn(float request, float argc) -{ - switch(request) - { - case CMD_REQUEST_COMMAND: - { - entity e; - string tospawn; - float moveflag, monstercount = 0; - - moveflag = (argv(2) ? stof(argv(2)) : 1); // follow owner if not defined - tospawn = strtolower(argv(1)); - - if(tospawn == "list") + if(Physics_Valid(command) || command == "default") { - sprint(self, monsterlist_reply); + stuffcmd(self, strcat("\nseta cl_physics ", command, "\nsendcvar cl_physics\n")); + + if(!autocvar_g_physics_clientselect) + sprint(self, strcat("Physics set to ^3", command, "^7, but will not be used since client physics selection is disabled\n")); + else + sprint(self, strcat("^2Physics set successfully changed to ^3", command, "\n")); return; } - - FOR_EACH_MONSTER(e) + + if(!autocvar_g_physics_clientselect) { - if(e.realowner == self) - ++monstercount; + sprint(self, "Client physics selection is currently disabled.\n"); + return; } - - if(autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { sprint(self, "Monster spawning is disabled.\n"); return; } - else if(!IS_PLAYER(self)) { sprint(self, "You can't spawn monsters while spectating.\n"); return; } - else if(MUTATOR_CALLHOOK(AllowMobSpawning)) { sprint(self, "Monster spawning is currently disabled by a mutator.\n"); return; } - else if(!autocvar_g_monsters) { Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_MONSTERS_DISABLED); return; } - else if(self.vehicle) { sprint(self, "You can't spawn monsters while driving a vehicle.\n"); return; } - else if(self.frozen) { sprint(self, "You can't spawn monsters while frozen.\n"); return; } - else if(autocvar_g_campaign) { sprint(self, "You can't spawn monsters in campaign mode.\n"); return; } - else if(self.deadflag != DEAD_NO) { sprint(self, "You can't spawn monsters while dead.\n"); return; } - else if(monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); return; } - else if(totalspawned >= autocvar_g_monsters_max) { sprint(self, "The global maximum monster count has been reached, kill some before trying to spawn any more.\n"); return; } - else if(tospawn != "") - { - float found = 0, i; - entity mon; - - for(i = MON_FIRST; i <= MON_LAST; ++i) - { - mon = get_monsterinfo(i); - if(mon.netname == tospawn) - { - found = TRUE; - break; - } - } - if(found || tospawn == "random") - { - totalspawned += 1; - - makevectors(self.v_angle); - WarpZone_TraceBox (CENTER_OR_VIEWOFS(self), PL_MIN, PL_MAX, CENTER_OR_VIEWOFS(self) + v_forward * 150, TRUE, self); - //WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 150, MOVE_NORMAL, self); - - e = spawnmonster(tospawn, 0, self, self, trace_endpos, FALSE, FALSE, moveflag); - - sprint(self, strcat("Spawned ", e.monster_name, "\n")); - - return; - } - } + } default: - sprint(self, "Incorrect parameters for ^2mobspawn^7\n"); + sprint(self, strcat("Current physics set: ^3", self.cvar_cl_physics, "\n")); case CMD_REQUEST_USAGE: { - sprint(self, "\nUsage:^3 cmd mobspawn [movetype]\n"); - sprint(self, " See 'cmd mobspawn list' for available monsters.\n"); - sprint(self, " Argument 'random' spawns a random monster.\n"); - sprint(self, " Monster will follow the owner if second argument is not defined.\n"); + sprint(self, "\nUsage:^3 cmd physics \n"); + sprint(self, " See 'cmd physics list' for available physics sets.\n"); + sprint(self, " Argument 'default' resets to standard physics.\n"); return; } } @@ -387,6 +279,38 @@ void ClientCommand_ready(float request) // todo: anti-spam for toggling readynes } } +void ClientCommand_report(float request, float argc) +{ + switch(request) + { + case CMD_REQUEST_COMMAND: + { + if(argv(1) == "fps") + if(sv_showfps) + if(IS_PLAYER(self)) + if(self.scorekeeper) + PlayerScore_Set(self, SP_FPS, stof(argv(2))); + + if(argv(1) == "fov") + { + self.clientfov = stof(argv(2)); + ClientData_Touch(self); + } + + return; // never fall through to usage + } + + default: + case CMD_REQUEST_USAGE: + { + sprint(self, "\nUsage:^3 cmd report [fps fov]\n"); + sprint(self, " Where 'fps' is your scoreboard frames per second.\n"); + sprint(self, " Where 'fov' is your current field of view.\n"); + return; + } + } +} + void ClientCommand_say(float request, float argc, string command) { switch(request) @@ -730,10 +654,9 @@ void ClientCommand_(float request) CLIENT_COMMAND("clientversion", ClientCommand_clientversion(request, arguments), "Release version of the game") \ CLIENT_COMMAND("mv_getpicture", ClientCommand_mv_getpicture(request, arguments), "Retrieve mapshot picture from the server") \ CLIENT_COMMAND("join", ClientCommand_join(request), "Become a player in the game") \ - CLIENT_COMMAND("mobedit", ClientCommand_mobedit(request, arguments), "Edit your monster's properties") \ - CLIENT_COMMAND("mobkill", ClientCommand_mobkill(request), "Kills your monster") \ - CLIENT_COMMAND("mobspawn", ClientCommand_mobspawn(request, arguments), "Spawn monsters infront of yourself") \ + CLIENT_COMMAND("physics", ClientCommand_physics(request, arguments), "Change physics set") \ CLIENT_COMMAND("ready", ClientCommand_ready(request), "Qualify as ready to end warmup stage (or restart server if allowed)") \ + CLIENT_COMMAND("report", ClientCommand_report(request, arguments), "Retrieve values from the client") \ CLIENT_COMMAND("say", ClientCommand_say(request, arguments, command), "Print a message to chat to all players") \ CLIENT_COMMAND("say_team", ClientCommand_say_team(request, arguments, command), "Print a message to chat to all team mates") \ CLIENT_COMMAND("selectteam", ClientCommand_selectteam(request, arguments), "Attempt to choose a team to join into") \ @@ -743,6 +666,7 @@ void ClientCommand_(float request) CLIENT_COMMAND("suggestmap", ClientCommand_suggestmap(request, arguments), "Suggest a map to the mapvote at match end") \ CLIENT_COMMAND("tell", ClientCommand_tell(request, arguments, command), "Send a message directly to a player") \ CLIENT_COMMAND("voice", ClientCommand_voice(request, arguments, command), "Send voice message via sound") \ + CLIENT_COMMAND("minigame", ClientCommand_minigame(request, arguments, command), "Start a minigame") \ /* nothing */ void ClientCommand_macro_help() diff --git a/qcsrc/server/command/common.qc b/qcsrc/server/command/common.qc index 04ed4b284..3156bc114 100644 --- a/qcsrc/server/command/common.qc +++ b/qcsrc/server/command/common.qc @@ -302,6 +302,147 @@ void CommonCommand_cvar_purechanges(float request, entity caller) } } +void CommonCommand_editmob(float request, entity caller, float argc) +{ + switch(request) + { + case CMD_REQUEST_COMMAND: + { + if(autocvar_g_campaign) { print_to(caller, "Monster editing is disabled in singleplayer"); return; } + // no checks for g_monsters here, as it may be toggled mid match which existing monsters + + if(caller) + { + makevectors(self.v_angle); + WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self); + } + + entity mon = trace_ent; + float is_visible = IS_MONSTER(mon); + string argument = argv(2); + + switch(argv(1)) + { + case "name": + { + if(!caller) { print_to(caller, "Only players can edit monsters"); return; } + if(!argument) { break; } // escape to usage + if(!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; } + if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; } + if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; } + + string mon_oldname = mon.monster_name; + + mon.monster_name = argument; + if(mon.sprite) { WaypointSprite_UpdateSprites(mon.sprite, strzone(mon.monster_name), string_null, string_null); } + print_to(caller, sprintf("Your pet '%s' is now known as '%s'", mon_oldname, mon.monster_name)); + return; + } + case "spawn": + { + if(!caller) { print_to(caller, "Only players can spawn monsters"); return; } + if(!argv(2)) { break; } // escape to usage + + float moveflag, tmp_moncount = 0; + string arg_lower = strtolower(argument); + moveflag = (argv(3)) ? stof(argv(3)) : 1; // follow owner if not defined + ret_string = "Monster spawning is currently disabled by a mutator"; + + if(arg_lower == "list") { print_to(caller, monsterlist_reply); return; } + + FOR_EACH_MONSTER(mon) { if(mon.realowner == caller) ++tmp_moncount; } + + if(!autocvar_g_monsters) { print_to(caller, "Monsters are disabled"); return; } + if(autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { print_to(caller, "Monster spawning is disabled"); return; } + if(!IS_PLAYER(caller)) { print_to(caller, "You must be playing to spawn a monster"); return; } + if(MUTATOR_CALLHOOK(AllowMobSpawning)) { print_to(caller, ret_string); return; } + if(caller.vehicle) { print_to(caller, "You can't spawn monsters while driving a vehicle"); return; } + if(caller.frozen) { print_to(caller, "You can't spawn monsters while frozen"); return; } + if(caller.deadflag != DEAD_NO) { print_to(caller, "You can't spawn monsters while dead"); return; } + if(tmp_moncount >= autocvar_g_monsters_max) { print_to(caller, "The maximum monster count has been reached"); return; } + if(tmp_moncount >= autocvar_g_monsters_max_perplayer) { print_to(caller, "You can't spawn any more monsters"); return; } + + float i = 0, found = FALSE; + for(i = MON_FIRST; i <= MON_LAST; ++i) + { + mon = get_monsterinfo(i); + if(mon.netname == arg_lower) { found = TRUE; break; } + } + + if(!found && arg_lower != "random") { print_to(caller, "Invalid monster"); return; } + + totalspawned += 1; + WarpZone_TraceBox (CENTER_OR_VIEWOFS(caller), caller.mins, caller.maxs, CENTER_OR_VIEWOFS(caller) + v_forward * 150, TRUE, caller); + mon = spawnmonster(arg_lower, 0, caller, caller, trace_endpos, FALSE, FALSE, moveflag); + print_to(caller, strcat("Spawned ", mon.monster_name)); + return; + } + case "kill": + { + if(!caller) { print_to(caller, "Only players can kill monsters"); return; } + if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; } + if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; } + + Damage (mon, world, world, mon.health + mon.max_health + 200, DEATH_KILL, mon.origin, '0 0 0'); + print_to(caller, strcat("Your pet '", mon.monster_name, "' has been brutally mutilated")); + return; + } + case "skin": + { + if(!caller) { print_to(caller, "Only players can edit monsters"); return; } + if(!argument) { break; } // escape to usage + if(!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; } + if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; } + if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; } + if(mon.monsterid == MON_MAGE) { print_to(caller, "Mage skins can't be changed"); return; } // TODO + + mon.skin = stof(argument); + print_to(caller, strcat("Monster skin successfully changed to ", ftos(mon.skin))); + return; + } + case "movetarget": + { + if(!caller) { print_to(caller, "Only players can edit monsters"); return; } + if(!argument) { break; } // escape to usage + if(!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; } + if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; } + if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; } + + mon.monster_moveflags = stof(argument); + print_to(caller, strcat("Monster move target successfully changed to ", ftos(mon.monster_moveflags))); + return; + } + case "butcher": + { + if(caller) { print_to(caller, "This command is not available to players"); return; } + if(g_invasion) { print_to(caller, "This command does not work during an invasion!"); return; } + + float tmp_remcount = 0; + entity tmp_entity; + + FOR_EACH_MONSTER(tmp_entity) { Monster_Remove(tmp_entity); ++tmp_remcount; } + + monsters_total = monsters_killed = totalspawned = 0; + + print_to(caller, (tmp_remcount) ? sprintf("Killed %d monster%s", tmp_remcount, (tmp_remcount == 1) ? "" : "s") : "No monsters to kill"); + return; + } + } + } + + default: + case CMD_REQUEST_USAGE: + { + print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " editmob command [arguments]")); + print_to(caller, " Where 'command' can be butcher spawn skin movetarget kill name"); + print_to(caller, " spawn, skin, movetarget and name require 'arguments'"); + print_to(caller, " spawn also takes arguments list and random"); + print_to(caller, " Monster will follow owner if third argument of spawn command is not defined"); + return; + } + } +} + void CommonCommand_info(float request, entity caller, float argc) { switch(request) @@ -552,7 +693,8 @@ void CommonCommand_timeout(float request, entity caller) // DEAR GOD THIS COMMAN { if(caller) { caller.allowed_timeouts -= 1; } - bprint(GetCallerName(caller), " ^7called a timeout", (caller ? strcat(" (", ftos(caller.allowed_timeouts), " timeout(s) left)") : ""), "!\n"); // write a bprint who started the timeout (and how many they have left) + // write a bprint who started the timeout (and how many they have left) + bprint(GetCallerName(caller), " ^7called a timeout", (caller ? strcat(" (", ftos(caller.allowed_timeouts), " timeout(s) left)") : ""), "!\n"); timeout_status = TIMEOUT_LEADTIME; timeout_caller = caller; @@ -678,6 +820,7 @@ void CommonCommand_(float request, entity caller) #define COMMON_COMMANDS(request,caller,arguments,command) \ COMMON_COMMAND("cvar_changes", CommonCommand_cvar_changes(request, caller), "Prints a list of all changed server cvars") \ COMMON_COMMAND("cvar_purechanges", CommonCommand_cvar_purechanges(request, caller), "Prints a list of all changed gameplay cvars") \ + COMMON_COMMAND("editmob", CommonCommand_editmob(request, caller, arguments), "Modifies a monster or all monsters") \ COMMON_COMMAND("info", CommonCommand_info(request, caller, arguments), "Request for unique server information set up by admin") \ COMMON_COMMAND("ladder", CommonCommand_ladder(request, caller), "Get information about top players if supported") \ COMMON_COMMAND("lsmaps", CommonCommand_lsmaps(request, caller), "List maps which can be used with the current game mode") \ diff --git a/qcsrc/server/command/sv_cmd.qc b/qcsrc/server/command/sv_cmd.qc index 27ed6b869..f3d619d2f 100644 --- a/qcsrc/server/command/sv_cmd.qc +++ b/qcsrc/server/command/sv_cmd.qc @@ -19,6 +19,16 @@ void make_mapinfo_Think() } } +float GameCommand_checkinlist(string game_command, string list) +{ + string l = strcat(" ", list, " "); + + if(strstrofs(l, strcat(" ", game_command, " "), 0) >= 0) + return TRUE; + + return FALSE; +} + // used by GameCommand_extendmatchtime() and GameCommand_reducematchtime() void changematchtime(float delta, float mi, float ma) { @@ -61,6 +71,21 @@ void changematchtime(float delta, float mi, float ma) cvar_set("timelimit", ftos(new / 60)); } +void DiscoMode(entity e, float enable) +{ + if(e == world) + return; + + float accepted; + + accepted = VerifyClientEntity(e, TRUE, FALSE); + + if(accepted > 0) + { + e.discomode = enable; + } +} + // ======================= // Command Sub-Functions @@ -139,47 +164,6 @@ void GameCommand_adminmsg(float request, float argc) } } -void GameCommand_mobbutcher(float request) -{ - switch(request) - { - case CMD_REQUEST_COMMAND: - { - if(autocvar_g_campaign) { print("This command doesn't work in campaign mode.\n"); return; } - if(g_invasion) { print("This command doesn't work during an invasion.\n"); return; } - - float removed_count = 0; - entity head; - - FOR_EACH_MONSTER(head) - { - monster_remove(head); - ++removed_count; - } - - monsters_total = 0; // reset stats? - monsters_killed = 0; - - totalspawned = 0; - - if(removed_count <= 0) - print("No monsters to kill\n"); - else - printf("Killed %d monster%s\n", removed_count, ((removed_count == 1) ? "" : "s")); - - return; // never fall through to usage - } - - default: - case CMD_REQUEST_USAGE: - { - print("\nUsage:^3 sv_cmd mobbutcher\n"); - print(" No arguments required.\n"); - return; - } - } -} - void GameCommand_allready(float request) { switch(request) @@ -481,7 +465,7 @@ void GameCommand_cointoss(float request, float argc) string result1 = (argv(2) ? strcat("^7", argv(1)) : "^1HEADS"); string result2 = (argv(2) ? strcat("^7", argv(2)) : "^4TAILS"); string choice = ((random() > 0.5) ? result1 : result2); - + Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_COINTOSS, choice); return; } @@ -949,6 +933,71 @@ void GameCommand_gotomap(float request, float argc) } } +void GameCommand_gravity(float request, float argc) +{ + switch(request) + { + case CMD_REQUEST_COMMAND: + { + string rm = (autocvar_g_rm ? "rm_" : ""); + string grav = argv(1); + string gravity_list = cvar_string(strcat(rm, "gravity_list")); + if(grav == "random") + { + cvar_set("g_random_gravity", "1"); + localcmd("defer 2 restart"); + return; + } + else if(grav == "help") + { + localcmd("say ^3Usage: ^2vcall gravity [value]\n"); + return; + } + else if(grav == "list") + { + localcmd(strcat("say ^3Available gravity votes: ^2", gravity_list)); + return; + } + if(!cvar(strcat(rm, "gravity_", grav)) || !GameCommand_checkinlist(grav, gravity_list)) + { + localcmd("say ^1That gravity does not exist, vote for ^2gravity list ^1to see available gravity votes\n"); + return; + } + cvar_set("sv_gravity", ftos(cvar(strcat(rm, "gravity_", grav)))); + localcmd(sprintf("say ^3Gravity is now: %d", autocvar_sv_gravity, "\n")); + return; + } + + default: + print("Incorrect parameters for ^2gravity^7\n"); + case CMD_REQUEST_USAGE: + { + print("\nUsage:^3 sv_cmd gravity [command]\n"); + return; + } + } +} + +void GameCommand_ircmsg(float request, float argc) +{ + switch(request) + { + case CMD_REQUEST_COMMAND: + { + IRCSay(argv(1), argv(2)); + return; + } + + default: + print("Incorrect parameters for ^2ircmsg^7\n"); + case CMD_REQUEST_USAGE: + { + print("\nUsage:^3 sv_cmd ircmsg [message]\n"); + return; + } + } +} + void GameCommand_lockteams(float request) { switch(request) @@ -1467,8 +1516,31 @@ void GameCommand_stuffto(float request, float argc) { if(argv(2)) { + float accepted; + if(argv(1) == "all") + { + entity head; + + FOR_EACH_REALCLIENT(head) + { + accepted = VerifyClientEntity(head, TRUE, FALSE); + if(accepted > 0) + { + stuffcmd(head, strcat("\n", argv(2), "\n")); + print(strcat("Command: \"", argv(2), "\" sent to ", GetCallerName(head), ".\n")); + continue; + } + else + { + print("stuffto failed on ", GetCallerName(head), ".\n"); + continue; + } + } + return; + } + entity client = GetIndexedEntity(argc, 1); - float accepted = VerifyClientEntity(client, TRUE, FALSE); + accepted = VerifyClientEntity(client, TRUE, FALSE); if(accepted > 0) { @@ -1501,6 +1573,73 @@ void GameCommand_stuffto(float request, float argc) #endif } +void GameCommand_discomode(float request, float argc) +{ + switch(request) + { + case CMD_REQUEST_COMMAND: + { + float accepted; + float enable; + string msg = ""; + + if ( argv(2) ) + { + enable = stof( argv(2) ); + if ( !enable ) + msg = " disabled"; + } + else + enable = 1; + + if(argv(1) == "all") + { + entity head; + + FOR_EACH_REALCLIENT(head) + { + accepted = VerifyClientEntity(head, TRUE, FALSE); + if(accepted > 0) + { + DiscoMode(head,enable); + print(strcat("Disco Mode",msg," for ", GetCallerName(head), ".\n")); + continue; + } + else + { + print("Disco Mode failed on ", GetCallerName(head), ".\n"); + continue; + } + } + return; + } + + entity client = GetIndexedEntity(argc, 1); + accepted = VerifyClientEntity(client, TRUE, FALSE); + + if(accepted > 0) + { + DiscoMode(client,enable); + print(strcat("Disco Mode",msg," for ", GetCallerName(client), ".\n")); + } + else + print("Disco Mode: ", GetClientErrorString(accepted, argv(1)), ".\n"); + + return; + } + + default: + print("Incorrect parameters for ^2discomode^7\n"); + case CMD_REQUEST_USAGE: + { + print("\nUsage:^3 sv_cmd discomode client [enable]\n"); + print(" 'client' is the entity number or name of the player,\n"); + print(" 'enable' 1 by default. 0 disable, 1 enable \n"); + return; + } + } +} + void GameCommand_trace(float request, float argc) { switch(request) @@ -1776,7 +1915,6 @@ void GameCommand_(float request) // Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;) #define SERVER_COMMANDS(request,arguments,command) \ SERVER_COMMAND("adminmsg", GameCommand_adminmsg(request, arguments), "Send an admin message to a client directly") \ - SERVER_COMMAND("mobbutcher", GameCommand_mobbutcher(request), "Instantly removes all monsters on the map") \ SERVER_COMMAND("allready", GameCommand_allready(request), "Restart the server and reset the players") \ SERVER_COMMAND("allspec", GameCommand_allspec(request, arguments), "Force all players to spectate") \ SERVER_COMMAND("anticheat", GameCommand_anticheat(request, arguments), "Create an anticheat report for a client") \ @@ -1788,12 +1926,15 @@ void GameCommand_(float request) SERVER_COMMAND("defer_clear", GameCommand_defer_clear(request, arguments), "Clear all queued defer commands for a specific client") \ SERVER_COMMAND("defer_clear_all", GameCommand_defer_clear_all(request), "Clear all queued defer commands for all clients") \ SERVER_COMMAND("delrec", GameCommand_delrec(request, arguments), "Delete race time record for a map") \ + SERVER_COMMAND("discomode", GameCommand_discomode(request, arguments), "Disco Mode!!") \ SERVER_COMMAND("effectindexdump", GameCommand_effectindexdump(request), "Dump list of effects from code and effectinfo.txt") \ SERVER_COMMAND("extendmatchtime", GameCommand_extendmatchtime(request), "Increase the timelimit value incrementally") \ SERVER_COMMAND("find", GameCommand_find(request, arguments), "Search through entities for matching classname") \ SERVER_COMMAND("gametype", GameCommand_gametype(request, arguments), "Simple command to change the active gametype") \ SERVER_COMMAND("gettaginfo", GameCommand_gettaginfo(request, arguments), "Get specific information about a weapon model") \ SERVER_COMMAND("gotomap", GameCommand_gotomap(request, arguments), "Simple command to switch to another map") \ + SERVER_COMMAND("gravity", GameCommand_gravity(request, arguments), "Changes gravity based on cvars") \ + SERVER_COMMAND("ircmsg", GameCommand_ircmsg(request, arguments), "Say a message as an IRC user") \ SERVER_COMMAND("lockteams", GameCommand_lockteams(request), "Disable the ability for players to switch or enter teams") \ SERVER_COMMAND("make_mapinfo", GameCommand_make_mapinfo(request), "Automatically rebuild mapinfo files") \ SERVER_COMMAND("moveplayer", GameCommand_moveplayer(request, arguments), "Change the team/status of a player") \ @@ -1864,6 +2005,11 @@ void GameCommand(string command) { float argc = tokenize_console(command); + // for the mutator hook system + cmd_name = strtolower(argv(0)); + cmd_argc = argc; + cmd_string = command; + // Guide for working with argc arguments by example: // argc: 1 - 2 - 3 - 4 // argv: 0 - 1 - 2 - 3 @@ -1907,6 +2053,10 @@ void GameCommand(string command) return; } } + else if(MUTATOR_CALLHOOK(SV_ParseServerCommand)) + { + return; // handled by a mutator + } else if(BanCommand(command)) { return; // handled by server/command/ipban.qc diff --git a/qcsrc/server/command/sv_cmd.qh b/qcsrc/server/command/sv_cmd.qh index 03bd80cef..be19cbcf3 100644 --- a/qcsrc/server/command/sv_cmd.qh +++ b/qcsrc/server/command/sv_cmd.qh @@ -13,4 +13,6 @@ float shuffleteams_players[SHUFFLETEAMS_MAX_PLAYERS]; // maximum of 255 player s float shuffleteams_teams[SHUFFLETEAMS_MAX_TEAMS]; // maximum of 4 teams // used by common/command/generic.qc:GenericCommand_dumpcommands to list all commands into a .txt file -void GameCommand_macro_write_aliases(float fh); \ No newline at end of file +void GameCommand_macro_write_aliases(float fh); + +void DiscoMode(entity e, float enable); diff --git a/qcsrc/server/command/vote.qc b/qcsrc/server/command/vote.qc index 522ef4b7a..8659fecf3 100644 --- a/qcsrc/server/command/vote.qc +++ b/qcsrc/server/command/vote.qc @@ -386,9 +386,6 @@ void reset_map(float dorespawn) } } - if(g_keyhunt) - kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round + (game_starttime - time), kh_StartRound); - self = oldself; } @@ -819,6 +816,17 @@ void VoteCommand_call(float request, entity caller, float argc, string vote_comm bprint("\{1}^2* ^3", GetCallerName(vote_caller), "^2 calls a vote for ", vote_called_display, "\n"); if(autocvar_sv_eventlog) { GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display)); } Nagger_VoteChanged(); + + if(autocvar_sv_vote_auto) + FOR_EACH_REALCLIENT(tmp_player) if(tmp_player != vote_caller) + switch(tmp_player.cvar_cl_autovote) + { + case "yes": tmp_player.vote_selection = VOTE_SELECT_ACCEPT; print_to(tmp_player, "You automatically accepted the vote\n"); break; + case "no": tmp_player.vote_selection = VOTE_SELECT_REJECT; print_to(tmp_player, "You automatically rejected the vote\n"); break; + case "abstain": tmp_player.vote_selection = VOTE_SELECT_ABSTAIN; print_to(tmp_player, "You automatically abstained from the vote\n"); break; + default: break; + } + VoteCount(TRUE); // needed if you are the only one } @@ -1083,7 +1091,7 @@ void VoteCommand_macro_help(entity caller, float argc) { string command_origin = GetCommandPrefix(caller); - if(argc == 2 || argv(2) == "help") // help display listing all commands + if(argc == 2 || argv(2) == "1" || argv(2) == "help") // help display listing all commands { print_to(caller, "\nVoting commands:\n"); #define VOTE_COMMAND(name,function,description,assignment) \ @@ -1092,9 +1100,13 @@ void VoteCommand_macro_help(entity caller, float argc) VOTE_COMMANDS(0, caller, 0, "") #undef VOTE_COMMAND + string vote_list = autocvar_sv_vote_commands; + if(argv(2) == "1") vote_list = strreplace(" ", "\n^3", vote_list); + print_to(caller, strcat("\nUsage:^3 ", command_origin, " vote COMMAND...^7, where possible commands are listed above.\n")); print_to(caller, strcat("For help about a specific command, type ", command_origin, " vote help COMMAND")); - print_to(caller, strcat("\n^7You can call a vote for or execute these commands: ^3", autocvar_sv_vote_commands, "^7 and maybe further ^3arguments^7")); + print_to(caller, "\n^7You can call a vote for or execute these commands (and maybe further ^3arguments^7):"); + print_to(caller, strcat("\n^3", vote_list, "^7\n")); } else // usage for individual command { diff --git a/qcsrc/server/controlpoint.qc b/qcsrc/server/controlpoint.qc new file mode 100644 index 000000000..d088e2e82 --- /dev/null +++ b/qcsrc/server/controlpoint.qc @@ -0,0 +1,36 @@ +float cpicon_send(entity to, float sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_CONTROLPOINT_ICON); + WriteByte(MSG_ENTITY, sf); + if(sf & CPSF_SETUP) + { + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); + + WriteByte(MSG_ENTITY, self.health); + WriteByte(MSG_ENTITY, self.max_health); + WriteByte(MSG_ENTITY, self.count); + WriteByte(MSG_ENTITY, self.team); + WriteByte(MSG_ENTITY, self.owner.iscaptured); + } + + if(sf & CPSF_STATUS) + { + WriteByte(MSG_ENTITY, self.team); + + if(self.health <= 0) + WriteByte(MSG_ENTITY, 0); + else + WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255)); + } + + return TRUE; +} + +void onslaught_controlpoint_icon_link(entity e, void() spawnproc) +{ + Net_LinkEntity(e, TRUE, 0, cpicon_send); + e.think = spawnproc; + e.nextthink = time * sys_frametime; +} diff --git a/qcsrc/server/controlpoint.qh b/qcsrc/server/controlpoint.qh new file mode 100644 index 000000000..e489f90ac --- /dev/null +++ b/qcsrc/server/controlpoint.qh @@ -0,0 +1,5 @@ +const vector CPICON_MIN = '-32 -32 -9'; +const vector CPICON_MAX = '32 32 25'; + +float CPSF_STATUS = 4; +float CPSF_SETUP = 8; diff --git a/qcsrc/server/defs.qh b/qcsrc/server/defs.qh index 757ee65e2..533b4ccb9 100644 --- a/qcsrc/server/defs.qh +++ b/qcsrc/server/defs.qh @@ -17,7 +17,7 @@ noref float require_spawnfunc_prefix; // if this float exists, only functions wi // Globals -float g_cloaked, g_footsteps, g_grappling_hook, g_instagib; +float g_cloaked, g_footsteps, g_grappling_hook; float g_warmup_limit; float g_warmup_allguns; float g_warmup_allow_timeout; @@ -85,8 +85,7 @@ float server_is_dedicated; .float pain_frame; //" .float crouch; // Crouching or not? -.float strength_finished; -.float invincible_finished; +.float invincible_finished, strength_finished; // TODO: vehicles system abuses these .float superweapons_finished; .vector finaldest, finalangle; //plat.qc stuff @@ -295,6 +294,8 @@ void FixClientCvars(entity e); // WEAPONTODO: remove this WepSet weaponsInMap; +.WepSet weaponsinmap; + .float respawn_countdown; // next number to count float bot_waypoints_for_items; @@ -447,6 +448,27 @@ float round_starttime; //point in time when the countdown to round start is over .float stat_sv_airspeedlimit_nonqw; .float stat_sv_maxspeed; +// new properties +.float stat_sv_jumpvelocity; +.float stat_sv_airaccel_qw_stretchfactor; +.float stat_sv_maxairstrafespeed; +.float stat_sv_maxairspeed; +.float stat_sv_airstrafeaccelerate; +.float stat_sv_warsowbunny_turnaccel; +.float stat_sv_airaccel_sideways_friction; +.float stat_sv_aircontrol; +.float stat_sv_aircontrol_power; +.float stat_sv_aircontrol_penalty; +.float stat_sv_warsowbunny_airforwardaccel; +.float stat_sv_warsowbunny_topspeed; +.float stat_sv_warsowbunny_accel; +.float stat_sv_warsowbunny_backtosideratio; +.float stat_sv_friction; +.float stat_sv_accelerate; +.float stat_sv_stopspeed; +.float stat_sv_airaccelerate; +.float stat_sv_airstopaccelerate; + void W_Porto_Remove (entity p); .float projectiledeathtype; @@ -618,3 +640,28 @@ string modname; .string playernick; .float elos; .float ranks; + +.float cvar_cl_sparkle; +.float cvar_cl_pony; +.float cvar_cl_pony_skin; +.float cvar_cl_damnfurries; +.float cvar_cl_thestars; +.float cvar_cl_robot; +.float cvar_cl_charge; + +.string cvar_cl_autovote; + +.entity lastkiller; +.entity lastkilled; + +.float vaporizer_refire; + +float sv_showfps; + +.float clientfov; + +.float sub_target_used; + +.string cvar_cl_physics; + +.float skill; diff --git a/qcsrc/server/func_breakable.qc b/qcsrc/server/func_breakable.qc index 0bf71059e..46c5ed936 100644 --- a/qcsrc/server/func_breakable.qc +++ b/qcsrc/server/func_breakable.qc @@ -200,6 +200,7 @@ void func_breakable_damage(entity inflictor, entity attacker, float damage, floa if(self.team) if(attacker.team == self.team) return; + self.pain_finished = time; self.health = self.health - damage; if(self.sprite) { diff --git a/qcsrc/server/g_damage.qc b/qcsrc/server/g_damage.qc index 4840e15dd..55bf4cb48 100644 --- a/qcsrc/server/g_damage.qc +++ b/qcsrc/server/g_damage.qc @@ -183,16 +183,10 @@ string AppendItemcodes(string s, entity player) if(w == 0) w = player.cnt; // previous weapon! s = strcat(s, ftos(w)); - if(time < player.strength_finished) - s = strcat(s, "S"); - if(time < player.invincible_finished) - s = strcat(s, "I"); if(player.flagcarried != world) s = strcat(s, "F"); if(player.BUTTON_CHAT) s = strcat(s, "T"); - if(player.kh_next) - s = strcat(s, "K"); return s; } @@ -415,6 +409,7 @@ void Obituary(entity attacker, entity inflictor, entity targ, float deathtype) attacker.taunt_soundtime = time + 1; attacker.killcount = attacker.killcount + 1; + // sprees 2 and 8 are blocked by "" value, so no sending #define SPREE_ITEM(counta,countb,center,normal,gentle) \ case counta: \ { \ @@ -436,6 +431,10 @@ void Obituary(entity attacker, entity inflictor, entity targ, float deathtype) PS_GR_P_ADDVAL(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1); PS_GR_P_ADDVAL(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1); +#ifdef JEFF + Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_JEFF_FIRSTBLOOD); +#endif + // tell spree_inf and spree_cen that this is a first-blood and first-victim event kill_count_to_attacker = -1; kill_count_to_target = -2; @@ -493,8 +492,12 @@ void Obituary(entity attacker, entity inflictor, entity targ, float deathtype) ); } + float f3 = 0; + if(deathtype == DEATH_BUFF) + f3 = attacker.buffs; + if (!Obituary_WeaponDeath(targ, TRUE, deathtype, targ.netname, attacker.netname, deathlocation, targ.killcount, kill_count_to_attacker)) - Obituary_SpecialDeath(targ, TRUE, deathtype, targ.netname, attacker.netname, deathlocation, targ.killcount, kill_count_to_attacker, 0); + Obituary_SpecialDeath(targ, TRUE, deathtype, targ.netname, attacker.netname, deathlocation, targ.killcount, kill_count_to_attacker, f3); } } @@ -508,13 +511,17 @@ void Obituary(entity attacker, entity inflictor, entity targ, float deathtype) // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options... // Later on you will only be able to make custom messages using DEATH_CUSTOM, // and there will be a REAL DEATH_VOID implementation which mappers will use. - /*case DEATH_HURTTRIGGER: + case DEATH_HURTTRIGGER: { - s1 = targ.netname; - s2 = inflictor.message; - if(strstrofs(s2, "%", 0) < 0) { s2 = strcat("%s ", s2); } + Obituary_SpecialDeath(targ, FALSE, deathtype, + targ.netname, + inflictor.message, + deathlocation, + targ.killcount, + 0, + 0); break; - }*/ + } case DEATH_CUSTOM: { @@ -562,13 +569,13 @@ void Ice_Think() void Freeze (entity targ, float freeze_time, float frozen_type, float show_waypoint) { - if(!IS_PLAYER(targ) && !(targ.flags & FL_MONSTER)) // only specified entities can be freezed + if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // only specified entities can be freezed return; if(targ.frozen) return; - float targ_maxhealth = ((targ.flags & FL_MONSTER) ? targ.max_health : start_health); + float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health); targ.frozen = frozen_type; targ.revive_progress = ((frozen_type == 3) ? 1 : 0); @@ -675,7 +682,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float { // exit the vehicle before killing (fixes a crash) if(IS_PLAYER(targ) && targ.vehicle) - vehicles_exit(VHEF_RELESE); + vehicles_exit(VHEF_RELEASE); // These are ALWAYS lethal // No damage modification here @@ -723,7 +730,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float if(autocvar_g_mirrordamage_virtual) { - vector v = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage); + vector v = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage, autocvar_g_balance_armor_block_bycount); attacker.dmg_take += v_x; attacker.dmg_save += v_y; attacker.dmg_inflictor = inflictor; @@ -733,7 +740,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float if(autocvar_g_friendlyfire_virtual) { - vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage); + vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage, autocvar_g_balance_armor_block_bycount); targ.dmg_take += v_x; targ.dmg_save += v_y; targ.dmg_inflictor = inflictor; @@ -760,6 +767,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float // should this be changed at all? If so, in what way? frag_attacker = attacker; + frag_inflictor = inflictor; frag_target = targ; frag_damage = damage; frag_force = force; @@ -773,24 +781,24 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float if(targ.frozen) if(deathtype != DEATH_HURTTRIGGER && deathtype != DEATH_TEAMCHANGE && deathtype != DEATH_AUTOTEAMCHANGE) { - if(autocvar_g_freezetag_revive_falldamage > 0) + if(autocvar_g_freeze_revive_falldamage > 0) if(deathtype == DEATH_FALL) - if(damage >= autocvar_g_freezetag_revive_falldamage) + if(damage >= autocvar_g_freeze_revive_falldamage) { Unfreeze(targ); - targ.health = autocvar_g_freezetag_revive_falldamage_health; - pointparticles(particleeffectnum("iceorglass"), targ.origin, '0 0 0', 3); + targ.health = autocvar_g_freeze_revive_falldamage_health; + Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3); Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname); Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF); } damage = 0; - force *= autocvar_g_freezetag_frozen_force; + force *= autocvar_g_freeze_frozen_force; } - if(targ.frozen && deathtype == DEATH_HURTTRIGGER && !autocvar_g_freezetag_frozen_damage_trigger) + if(targ.frozen && deathtype == DEATH_HURTTRIGGER && !autocvar_g_freeze_frozen_damage_trigger) { - pointparticles(particleeffectnum("teleport"), targ.origin, '0 0 0', 1); + Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1); entity oldself = self; self = targ; @@ -819,52 +827,30 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float // don't reset back to last position, even if new position is stuck in solid self.oldorigin = self.origin; self.prevorigin = self.origin; - - pointparticles(particleeffectnum("teleport"), self.origin, '0 0 0', 1); + + Send_Effect(EFFECT_TELEPORT, self.origin, '0 0 0', 1); } self = oldself; } - if(!g_instagib) - { - // apply strength multiplier - if (attacker.items & IT_STRENGTH) - { - if(targ == attacker) - { - damage = damage * autocvar_g_balance_powerup_strength_selfdamage; - force = force * autocvar_g_balance_powerup_strength_selfforce; - } - else - { - damage = damage * autocvar_g_balance_powerup_strength_damage; - force = force * autocvar_g_balance_powerup_strength_force; - } - } - - // apply invincibility multiplier - if (targ.items & IT_INVINCIBLE) - damage = damage * autocvar_g_balance_powerup_invincible_takedamage; - } - if (targ == attacker) damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself // count the damage if(attacker) if(!targ.deadflag) - if(deathtype != DEATH_BUFF_VENGEANCE) + if(deathtype != DEATH_BUFF) if(targ.takedamage == DAMAGE_AIM) if(targ != attacker) { entity victim; - if((targ.vehicle_flags & VHF_ISVEHICLE) && targ.owner) + if(IS_VEHICLE(targ) && targ.owner) victim = targ.owner; else victim = targ; - if(IS_PLAYER(victim) || (victim.turrcaps_flags & TFL_TURRCAPS_ISTURRET) || (victim.flags & FL_MONSTER)) + if(IS_PLAYER(victim) || IS_TURRET(victim) || IS_MONSTER(victim) || victim.classname == "func_assault_destructible" || (victim.classname == "onslaught_generator" && !victim.isshielded) || (victim.classname == "onslaught_controlpoint_icon" && !victim.owner.isshielded)) { if(DIFF_TEAM(victim, attacker) && !victim.frozen) { @@ -1187,7 +1173,7 @@ float Fire_AddDamage(entity e, entity o, float d, float t, float dt) mintime = e.fire_endtime - time; maxtime = max(mintime, t); - mindps = e.fire_damagepersec; + mindps = max(0.1, e.fire_damagepersec); maxdps = max(mindps, dps); if(maxtime > mintime || maxdps > mindps) @@ -1311,7 +1297,7 @@ void Fire_ApplyDamage(entity e) } e.fire_hitsound = TRUE; - if (!IS_INDEPENDENT_PLAYER(e)) + if(!IS_INDEPENDENT_PLAYER(e)) if(!e.frozen) FOR_EACH_PLAYER(other) if(e != other) { diff --git a/qcsrc/server/g_hook.qc b/qcsrc/server/g_hook.qc index eab482618..99294703c 100644 --- a/qcsrc/server/g_hook.qc +++ b/qcsrc/server/g_hook.qc @@ -49,6 +49,7 @@ And you should be done! .float hook_length; .float hook_switchweapon; +.float last_dmg; void RemoveGrapplingHook(entity pl) { @@ -64,6 +65,7 @@ void RemoveGrapplingHook(entity pl) void GrapplingHookReset(void) { + if(g_keyhunt) { return; } if(self.realowner.hook == self) RemoveGrapplingHook(self.owner); else // in any case: @@ -73,8 +75,8 @@ void GrapplingHookReset(void) void GrapplingHookThink(); void GrapplingHook_Stop() { - pointparticles(particleeffectnum("grapple_impact"), self.origin, '0 0 0', 1); - sound (self, CH_SHOTS, "weapons/hook_impact.wav", VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_HOOK_IMPACT, self.origin, '0 0 0', 1); + sound (self, CH_SHOTS, W_Sound("hook_impact"), VOL_BASE, ATTEN_NORM); self.state = 1; self.think = GrapplingHookThink; @@ -114,14 +116,14 @@ float GrapplingHookSend(entity to, float sf) void GrapplingHookThink() { - float spd, dist, minlength, pullspeed, ropestretch, ropeairfriction, rubberforce, newlength, rubberforce_overstretch, s; + float spd, dist, minlength, pullspeed, ropestretch, ropeairfriction, rubberforce, newlength, rubberforce_overstretch, s, frozen_pulling, tarzan; vector dir, org, end, v0, dv, v, myorg, vs; if(self.realowner.hook != self) // how did that happen? { error("Owner lost the hook!\n"); return; } - if(LostMovetypeFollow(self) || intermission_running || (round_handler_IsActive() && !round_handler_IsRoundStarted())) + if(LostMovetypeFollow(self) || intermission_running || (self.aiment.flags & FL_PROJECTILE)) { RemoveGrapplingHook(self.realowner); return; @@ -167,13 +169,29 @@ void GrapplingHookThink() // while hanging on the rope, this friction component will help you a // bit to control the rope + frozen_pulling = (autocvar_g_grappling_hook_tarzan >= 2 && autocvar_g_balance_grapplehook_pull_frozen); + + tarzan = autocvar_g_grappling_hook_tarzan; + + if(autocvar_g_balance_hook_secondary == 2) + if(self.realowner.BUTTON_ATCK2 && self.realowner.weapon == WEP_HOOK) + tarzan = 0; + dir = self.origin - myorg; dist = vlen(dir); dir = normalize(dir); - if(autocvar_g_grappling_hook_tarzan) + entity realpull = self.realowner; + + if(realpull.pbhost) + { + realpull = pb_RootOf(realpull); + tarzan = 2; // enforce tarzan + } + + if(tarzan) { - v = v0 = WarpZone_RefSys_TransformVelocity(self.realowner, self, self.realowner.velocity); + v = v0 = WarpZone_RefSys_TransformVelocity(realpull, self, realpull.velocity); // first pull the rope... if(self.realowner.hook_state & HOOK_PULLING) @@ -191,6 +209,9 @@ void GrapplingHookThink() self.hook_length = newlength; } + if(realpull.movetype == MOVETYPE_FLY) + realpull.movetype = MOVETYPE_WALK; + if(self.realowner.hook_state & HOOK_RELEASING) { newlength = dist; @@ -204,23 +225,35 @@ void GrapplingHookThink() v = v + frametime * dir * spd * rubberforce; dv = ((v - v0) * dir) * dir; - if(autocvar_g_grappling_hook_tarzan >= 2) + if(tarzan >= 2) { if(self.aiment.movetype == MOVETYPE_WALK) { + entity aim_ent = ((IS_VEHICLE(self.aiment) && self.aiment.owner) ? self.aiment.owner : self.aiment); v = v - dv * 0.5; - self.aiment.velocity = self.aiment.velocity - dv * 0.5; - self.aiment.flags &= ~FL_ONGROUND; - self.aiment.pusher = self.realowner; - self.aiment.pushltime = time + autocvar_g_maxpushtime; - self.aiment.istypefrag = self.aiment.BUTTON_CHAT; + if((frozen_pulling && self.aiment.frozen) || !frozen_pulling) + if(self.aiment.vehicleid != VEH_TANKLL48) + { + self.aiment.velocity = self.aiment.velocity - dv * 0.5; + self.aiment.flags &= ~FL_ONGROUND; + } + aim_ent.pusher = self.realowner; + aim_ent.pushltime = time + autocvar_g_maxpushtime; + aim_ent.istypefrag = aim_ent.BUTTON_CHAT; } } - self.realowner.flags &= ~FL_ONGROUND; + realpull.flags &= ~FL_ONGROUND; } - self.realowner.velocity = WarpZone_RefSys_TransformVelocity(self, self.realowner, v); + if(!frozen_pulling) + realpull.velocity = WarpZone_RefSys_TransformVelocity(self, realpull, ((realpull == self.realowner) ? v : (v * autocvar_g_balance_grapplehook_piggybackfriction))); + + if(frozen_pulling && autocvar_g_balance_grapplehook_pull_frozen == 2 && !self.aiment.frozen) + { + RemoveGrapplingHook(self.realowner); + return; + } } else { @@ -252,10 +285,34 @@ void GrapplingHookThink() self.SendFlags |= 4; self.hook_end = org; } + entity dmgent = ((SAME_TEAM(self.owner, self.aiment) && autocvar_g_rm_hook_team) ? self.owner : self.aiment); + + if(IS_PLAYER(self.aiment)) + if(self.last_dmg < time) + if(!self.aiment.frozen) + if(time >= game_starttime) + if(DIFF_TEAM(self.owner, self.aiment) || autocvar_g_rm_hook_team) + if(self.aiment.health > 0) + if(autocvar_g_rm || autocvar_g_rm_hook_damage_always) + if(autocvar_g_rm_hook_damage) + { + self.last_dmg = time + autocvar_g_rm_hook_damagefactor; + self.owner.damage_dealt += autocvar_g_rm_hook_damage; + Damage(dmgent, self, self.owner, autocvar_g_rm_hook_damage, WEP_HOOK, self.origin, '0 0 0'); + if(SAME_TEAM(self.owner, self.aiment)) + self.aiment.health = min(self.aiment.health + autocvar_g_rm_hook_damage_health, g_pickup_healthsmall_max); + else + self.owner.health = min(self.owner.health + autocvar_g_rm_hook_damage_health, g_pickup_healthsmall_max); + + if(dmgent == self.owner) + dmgent.health -= autocvar_g_rm_hook_damage; // FIXME: friendly fire?! + } } void GrapplingHookTouch (void) { + if(other.movetype == MOVETYPE_FOLLOW) + return; PROJECTILE_TOUCH; GrapplingHook_Stop(); @@ -275,6 +332,19 @@ void GrapplingHook_Damage (entity inflictor, entity attacker, float damage, floa if(self.health <= 0) return; + if(!autocvar_g_rm_hook_breakable) + return; + + if(!autocvar_g_rm_hook_breakable_owner && attacker == self.realowner) + return; + + if(DIFF_TEAM(frag_attacker, self.realowner)) + { + Damage (self.realowner, attacker, attacker, 5, WEP_HOOK | HITTYPE_SPLASH, self.realowner.origin, '0 0 0'); + RemoveGrapplingHook(self.realowner); + return; // dead + } + if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions return; // g_balance_projectiledamage says to halt @@ -299,8 +369,8 @@ void FireGrapplingHook (void) float s; vector vs; - if(forbidWeaponUse()) return; - if(self.vehicle) return; + if((time < game_starttime && !autocvar_sv_ready_restart_after_countdown) || self.vehicle || Player_Trapped(self)) + return; makevectors(self.v_angle); @@ -311,13 +381,13 @@ void FireGrapplingHook (void) vs = hook_shotorigin[s]; // UGLY WORKAROUND: play this on CH_WEAPON_B so it can't cut off fire sounds - sound (self, CH_WEAPON_B, "weapons/hook_fire.wav", VOL_BASE, ATTEN_NORM); + sound (self, CH_WEAPON_B, W_Sound("hook_fire"), VOL_BASE, ATTEN_NORM); org = self.origin + self.view_ofs + v_forward * vs_x + v_right * -vs_y + v_up * vs_z; tracebox(self.origin + self.view_ofs, '-3 -3 -3', '3 3 3', org, MOVE_NORMAL, self); org = trace_endpos; - pointparticles(particleeffectnum("grapple_muzzleflash"), org, '0 0 0', 1); + Send_Effect(EFFECT_HOOK_MUZZLEFLASH, org, '0 0 0', 1); missile = WarpZone_RefSys_SpawnSameRefSys(self); missile.owner = missile.realowner = self; @@ -326,7 +396,7 @@ void FireGrapplingHook (void) missile.classname = "grapplinghook"; missile.flags = FL_PROJECTILE; - missile.movetype = MOVETYPE_FLY; + missile.movetype = ((autocvar_g_balance_grapplehook_gravity) ? MOVETYPE_TOSS : MOVETYPE_FLY); PROJECTILE_MAKETRIGGER(missile); //setmodel (missile, "models/hook.md3"); // precision set below @@ -378,7 +448,7 @@ void FireGrapplingHook (void) void GrapplingHookFrame() { - if(g_grappling_hook && timeout_status != TIMEOUT_ACTIVE && self.weapon != WEP_HOOK) + if(g_grappling_hook && timeout_status != TIMEOUT_ACTIVE && self.weapon != WEP_HOOK && !self.vehicle) { // offhand hook controls if(self.BUTTON_HOOK) @@ -396,7 +466,7 @@ void GrapplingHookFrame() } self.hook_state &= ~HOOK_RELEASING; - if(self.BUTTON_CROUCH) + if(self.BUTTON_CROUCH && autocvar_g_balance_grapplehook_crouchslide) { self.hook_state &= ~HOOK_PULLING; //self.hook_state |= HOOK_RELEASING; @@ -407,7 +477,7 @@ void GrapplingHookFrame() //self.hook_state &= ~HOOK_RELEASING; } } - else if(!g_grappling_hook && self.switchweapon != WEP_HOOK) + else if(!g_grappling_hook && self.switchweapon != WEP_HOOK && !self.vehicle) { if(self.BUTTON_HOOK && !self.hook_switchweapon) W_SwitchWeapon(WEP_HOOK); diff --git a/qcsrc/server/g_subs.qc b/qcsrc/server/g_subs.qc index fd0dc7861..b27c6528c 100644 --- a/qcsrc/server/g_subs.qc +++ b/qcsrc/server/g_subs.qc @@ -487,9 +487,9 @@ void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, // do the trace if(wz) - WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent); + WarpZone_TraceBox (v1, mi, ma, v2, nomonst, world); else - tracebox (v1, mi, ma, v2, nomonst, forent); + tracebox (v1, mi, ma, v2, nomonst, world); // restore players to current positions if (lag) diff --git a/qcsrc/server/g_tetris.qc b/qcsrc/server/g_tetris.qc deleted file mode 100644 index f0ea33b61..000000000 --- a/qcsrc/server/g_tetris.qc +++ /dev/null @@ -1,1257 +0,0 @@ -/* - -Installation: - -compile with -DTETRIS - -*/ - -#ifdef TETRIS - -.vector tet_org; - -float tet_vs_current_id; -float tet_vs_current_timeout; -.float tet_vs_id, tet_vs_addlines; -.float tet_highest_line; -.float tetris_on, tet_gameovertime, tet_drawtime, tet_autodown; -.vector piece_pos; -.float piece_type, next_piece, tet_score, tet_lines; -.float tet_piece_bucket; - -// tetris_on states: -// 1 = running -// 2 = game over -// 3 = waiting for VS players - -var float tet_high_score = 0; - -const vector TET_START_PIECE_POS = '5 1 0'; -const float TET_LINES = 22; -const float TET_DISPLAY_LINES = 20; -const float TET_WIDTH = 10; -const string TET_EMPTY_LINE = "0000000000"; // must match TET_WIDTH -//character values -const float TET_BORDER = 139; -const float TET_BLOCK = 133; -const float TET_SPACE = 160; // blankness - - - -const float TETKEY_UP = 1; -const float TETKEY_DOWN = 2; -const float TETKEY_LEFT = 4; -const float TETKEY_RIGHT = 8; -const float TETKEY_ROTLEFT = 16; -const float TETKEY_ROTRIGHT = 32; -const float TETKEY_DROP = 64; -const string TET_PADDING_RIGHT = "\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0"; // get away from crosshair - -const float PIECES = 7; - -float tet_line_buf; - -float SVC_CENTERPRINTa = 26; - -float Tetris_Level() -{ - return ((floor((self.tet_lines / 10)) + 1)); -} - -void tetsnd(string snd) -{ - play2(self, strcat("sounds/tetris/", snd)); -} - -/* -********************************* - -Library Functions - -********************************* -*/ -void SetLine(float ln, string vl) -{ - if(ln < 1 || ln > TET_LINES) - error("WTF"); - bufstr_set(tet_line_buf, ln + TET_LINES * num_for_edict(self), vl); -} - -string GetLine(float ln) -{ - if(ln < 1 || ln > TET_LINES) - error("WTF"); - if(ln < 1 || ln > TET_LINES) - return TET_EMPTY_LINE; - return bufstr_get(tet_line_buf, ln + TET_LINES * num_for_edict(self)); -} - -float GetXBlock(float x, string dat) -{ - if(x < 1 || x > TET_WIDTH) - error("WTF"); - return stof(substring(dat, x-1, 1)); -} - -string SetXBlock(float x, string dat, float new) -{ - return strcat( - substring(dat, 0, x-1), - ftos(new), - substring(dat, x, -1) - ); -} - - -float GetSquare(float x, float y) -{ - return GetXBlock(x, GetLine(y)); -} - -void SetSquare(float x, float y, float val) -{ - string dat; - dat = GetLine(y); - dat = SetXBlock(x, dat, val); - SetLine(y, dat); -} - -float PieceColor(float pc) -{ - if (pc == 1) - return 3; // O - else if (pc == 2) - return 4; // J - else if (pc == 3) - return 7; // L // we don't have orange, let's use white instead! - else if (pc == 4) - return 5; // I - else if (pc == 5) - return 1; // Z - else if (pc == 6) - return 2; // S - else if (pc == 7) - return 6; // T - else - return 0; -} -vector PieceShape(float pc) -{ - if (pc == 1) - return '20 20 0'; // O - else if (pc == 2) - return '1 21 0'; // J - else if (pc == 3) - return '16 21 0'; // L - else if (pc == 4) - return '0 85 0'; // I - else if (pc == 5) - return '5 20 0'; // Z - else if (pc == 6) - return '20 5 0'; // S - else if (pc == 7) - return '4 21 0'; // T - else - return '0 0 0'; -} -vector PieceSize(float pc) -{ - if (pc == 1) - return '2 2 0'; // O - else if (pc == 2) - return '3 2 0'; // J - else if (pc == 3) - return '3 2 0'; // L - else if (pc == 4) - return '4 1 0'; // I - else if (pc == 5) - return '3 2 0'; // Z - else if (pc == 6) - return '3 2 0'; // S - else if (pc == 7) - return '3 2 0'; // T - else - return '0 0 0'; -} -vector PieceCenter(float pc) -{ - if(pc == 1) - return '2.5 1.5 0'; // O - else if (pc == 2) - return '2 2 0'; // J - else if (pc == 3) - return '2 2 0'; // L - else if (pc == 4) - return '2.5 2.5 0'; // I - else if (pc == 5) - return '2 2 0'; // Z - else if (pc == 6) - return '2 2 0'; // S - else if (pc == 7) - return '2 2 0'; // T - else - return '0 0 0'; -} - -// do x 1..4 and y 1..4 in case of rotation -float PieceMetric(float x, float y, float rot, float pc) -{ - float t; - vector ce; - - // return bits of a piece - ce = PieceCenter(pc); - if (rot == 1) // 90 degrees - { - // x+cx, y+cy -> -y+cx, x+cy - // X, Y -> -Y+cy+cx, X-cx+cy - // x = X-cx - // y = Y-cy - t = y; - y = x - ce_x + ce_y; - x = -t + ce_x + ce_y; - } - else if (rot == 2)//180 - { - x = 2 * ce_x - x; - y = 2 * ce_y - y; - } - else if (rot == 3) // 270 - { - // x+cx, y+cy -> y+cx, -x+cy - // X, Y -> Y-cy+cx, -X+cx+cy - // x = X-cx - // y = Y-cy - t = y; - y = -x + ce_y + ce_x; - x = t - ce_y + ce_x; - } - if (x < 1 || y < 1 || x > 4 || y > 2) - return 0; - ce = PieceShape(pc); - if (y == 1) - return !!(ce_x & pow(4, x-1)); // first row - else if (y == 2) - return !!(ce_y & pow(4, x-1)); // second row - else - return 0; // illegal parms -} -vector tet_piecemins; -vector tet_piecemaxs; -void PieceMinsMaxs(float rot, float pc) -{ - vector sz, ce; - float t; - vector v; - - sz = PieceSize(pc); - ce = PieceCenter(pc); - // 1 = 2..2 - // 2 = 2..3 - // 3 = 1..3 - // 4 = 1..4 - tet_piecemins_x = floor(3.0 - sz_x * 0.5); - tet_piecemaxs_x = floor(2.0 + sz_x * 0.5); - if(sz_y == 1) - { - // special case for "I" - tet_piecemins_y = tet_piecemaxs_y = 2; - } - else - { - tet_piecemins_y = 1; - tet_piecemaxs_y = sz_y; - } - //printf("ce%v sz%v mi%v ma%v\n", ce, sz, tet_piecemins, tet_piecemaxs); - if (rot == 1) // 90 degrees - { - t = tet_piecemins_y; - tet_piecemins_y = -tet_piecemins_x + ce_y + ce_x; - tet_piecemins_x = t - ce_y + ce_x; - t = tet_piecemaxs_y; - tet_piecemaxs_y = -tet_piecemaxs_x + ce_y + ce_x; - tet_piecemaxs_x = t - ce_y + ce_x; - // swap mins_y, maxs_y - t = tet_piecemins_y; - tet_piecemins_y = tet_piecemaxs_y; - tet_piecemaxs_y = t; - // TODO OPTIMIZE - } - else if (rot == 2)//180 - { - v = tet_piecemins; - tet_piecemins = 2 * ce - tet_piecemaxs; - tet_piecemaxs = 2 * ce - v; - } - else if (rot == 3) // 270 - { - t = tet_piecemins_y; - tet_piecemins_y = tet_piecemins_x - ce_x + ce_y; - tet_piecemins_x = -t + ce_x + ce_y; - t = tet_piecemaxs_y; - tet_piecemaxs_y = tet_piecemaxs_x - ce_x + ce_y; - tet_piecemaxs_x = -t + ce_x + ce_y; - // swap mins_x, maxs_x - t = tet_piecemins_x; - tet_piecemins_x = tet_piecemaxs_x; - tet_piecemaxs_x = t; - // TODO OPTIMIZE - } -#ifdef VERIFY - print(vtos(tet_piecemins), "-"); - print(vtos(tet_piecemaxs), "\n"); - if(tet_piecemins_x > tet_piecemaxs_x) - error("inconsistent mins/maxs"); - if(tet_piecemins_y > tet_piecemaxs_y) - error("inconsistent mins/maxs"); - float i, j; - vector realmins, realmaxs; - realmins = '4 4 0'; - realmaxs = '1 1 0'; - for(i = 1; i <= 4; ++i) - for(j = 1; j <= 4; ++j) - if(PieceMetric(i, j, rot, pc)) - { - realmins_x = min(realmins_x, i); - realmins_y = min(realmins_y, j); - realmaxs_x = max(realmaxs_x, i); - realmaxs_y = max(realmaxs_y, j); - } - if(realmins != tet_piecemins || realmaxs != tet_piecemaxs) - error(sprintf("incorrect mins/maxs: %v %v in %d rot %d mins %v maxs %v\n", realmins, realmaxs, rot, pc, tet_piecemins, tet_piecemaxs)); -#endif -} -/* -********************************* - -Draw - -********************************* -*/ - - -/* some prydon gate functions to make life easier.... - -somewhat modified because we don't need all the fanciness Prydon Gate is capable of - -*/ - -void WriteTetrisString(string s) -{ - WriteUnterminatedString(MSG_ONE, strconv(0, 2, 2, s)); -} - -float pnum(float num, float dig) -{ - float f, i; - if (num < 0) - { - WriteChar(MSG_ONE, 173); - num = 0 - num; - } - f = floor(num / 10); - num = num - (f * 10); - if (f) - dig = pnum(f, dig+1); - else - { - // pad to 6 - for (i = 0; i < (5 - dig); i = i + 1) - WriteChar(MSG_ONE, TET_SPACE); - } - WriteChar(MSG_ONE, 176 + num); - return dig; -} - -void DrawLine(float ln) -{ - float x, d; - WriteChar(MSG_ONE, TET_BORDER); - - for (x = 1; x <= TET_WIDTH; x = x + 1) - { - d = GetSquare(x, ln + TET_LINES - TET_DISPLAY_LINES); - if (d) - { - WriteChar(MSG_ONE, '^'); - WriteChar(MSG_ONE, d + '0'); - WriteChar(MSG_ONE, TET_BLOCK); - } - else - WriteChar(MSG_ONE, TET_SPACE); - } - WriteChar(MSG_ONE, '^'); - WriteChar(MSG_ONE, '7'); - WriteChar(MSG_ONE, TET_BORDER); -} - -void DrawPiece(float pc, float ln) -{ - float x, d, piece_ln, pcolor; - vector piece_dat; - pcolor = PieceColor(pc); - WriteChar(MSG_ONE, TET_SPACE); // pad to 6 - - piece_dat = PieceShape(pc); - if (ln == 1) - piece_ln = piece_dat_x; - else - piece_ln = piece_dat_y; - for (x = 1; x <= 4; x = x + 1) - { - if (piece_ln & pow(4, x-1)) - { - WriteChar(MSG_ONE, '^'); - WriteChar(MSG_ONE, pcolor + '0'); - WriteChar(MSG_ONE, TET_BLOCK); - } - else - WriteChar(MSG_ONE, TET_SPACE); - } - WriteChar(MSG_ONE, TET_SPACE); // pad to 6 -} -void Draw_Tetris() -{ - float i; - entity head; - msg_entity = self; - WriteChar(MSG_ONE, SVC_CENTERPRINTa); - if(autocvar_g_bastet) - { - WriteTetrisString("NEVER GONNA GIVE YOU"); - WriteChar(MSG_ONE, 10); - } - // decoration - for (i = 1; i <= (TET_WIDTH + 2); i = i + 1) - WriteChar(MSG_ONE, TET_BORDER); - WriteTetrisString(" "); - WriteUnterminatedString(MSG_ONE, TET_PADDING_RIGHT); - WriteChar(MSG_ONE, 10); - for (i = 1; i <= TET_DISPLAY_LINES; i = i + 1) - { - if(self.tetris_on == 2) - WriteTetrisString(" GAME OVER "); - else if(self.tetris_on == 3) - WriteTetrisString("PLEASE WAIT"); - else - DrawLine(i); - if (i == 1) - WriteTetrisString(autocvar_g_bastet ? " THAT " : " NEXT "); - else if (i == 3) - DrawPiece(self.next_piece, 1); - else if (i == 4) - DrawPiece(self.next_piece, 2); - else if (i == 6) - WriteTetrisString(" LINES"); - else if (i == 7) - pnum(self.tet_lines, 0); - else if (i == 9) - WriteTetrisString(" SCORE"); - else if (i == 10) - pnum(self.tet_score, 0); - else if (i == 12) - WriteTetrisString(" HIGH "); - else if (i == 13) - WriteTetrisString(" SCORE"); - else if (i == 14) - pnum(tet_high_score, 0); - else if (i == 16) - WriteTetrisString(" LEVEL"); - else if (i == 17) - pnum(Tetris_Level(), 0); - else - WriteTetrisString(" "); - WriteUnterminatedString(MSG_ONE, TET_PADDING_RIGHT); - WriteChar(MSG_ONE, 10); - } - // decoration - - for (i = 1; i <= (TET_WIDTH + 2); i = i + 1) - WriteChar(MSG_ONE, TET_BORDER); - WriteTetrisString(" "); - WriteUnterminatedString(MSG_ONE, TET_PADDING_RIGHT); - WriteChar(MSG_ONE, 10); - - // VS game status - if(self.tet_vs_id) - { - WriteChar(MSG_ONE, 10); - WriteChar(MSG_ONE, 10); - if(self.tetris_on == 3) - { - WriteUnterminatedString(MSG_ONE, strcat("WAITING FOR OTHERS (", ftos(ceil(tet_vs_current_timeout - time)), " SEC)\n")); - } - - WriteChar(MSG_ONE, 10); - FOR_EACH_REALCLIENT(head) if(head.tetris_on) if(head.tet_vs_id == self.tet_vs_id) - { - if(head == self) - WriteUnterminatedString(MSG_ONE, ">"); - else - WriteUnterminatedString(MSG_ONE, " "); - if(head.tetris_on == 2) - WriteUnterminatedString(MSG_ONE, " X_X"); - else - pnum(head.tet_highest_line, 0); - WriteUnterminatedString(MSG_ONE, " "); - WriteUnterminatedString(MSG_ONE, head.netname); - WriteChar(MSG_ONE, 10); - } - } - - WriteChar(MSG_ONE, 0); -} -/* -********************************* - -Game Functions - -********************************* -*/ - -// reset the game -void ResetTetris() -{ - float i; - - if(!tet_line_buf) - tet_line_buf = buf_create(); - - for (i=1; i<=TET_LINES; i = i + 1) - SetLine(i, TET_EMPTY_LINE); - self.piece_pos = '0 0 0'; - self.piece_type = 0; - self.next_piece = self.tet_lines = self.tet_score = 0; - self.tet_piece_bucket = 0; -} - -void Tet_GameExit() -{ - centerprint(self, " "); - self.tetris_on = 0; - self.tet_vs_id = 0; - ResetTetris(); - self.movetype = MOVETYPE_WALK; -} - -void PrintField() -{ - string l; - float r, c; - for(r = 1; r <= TET_LINES; ++r) - { - l = GetLine(r); - print(">"); - for(c = 1; c <= TET_WIDTH; ++c) - { - print(ftos(GetXBlock(c, l))); - } - print("\n"); - } -} - -float BastetEvaluate() -{ - float height; - string l; - float lines; - float score, score_save; - string occupied, occupied_save; - float occupied_count, occupied_count_save; - float i, j, line; - - score = 0; - - // adds a bonus for each free dot above the occupied blocks profile - occupied = TET_EMPTY_LINE; - occupied_count = TET_WIDTH; - height = 0; - lines = 0; - for(i = 1; i <= TET_LINES; ++i) - { - l = GetLine(i); - if(l == TET_EMPTY_LINE) - { - height = i; - continue; - } - line = 1; - occupied_save = occupied; - occupied_count_save = occupied_count; - score_save = score; - for(j = 1; j <= TET_WIDTH; ++j) - { - if(GetXBlock(j, l)) - { - if(!GetXBlock(j, occupied)) - { - occupied = SetXBlock(j, occupied, 1); - --occupied_count; - } - } - else - line = 0; - score += 10000 * occupied_count; - } - if(line) - { - occupied = occupied_save; - occupied_count = occupied_count_save; - score = score_save + 100000000 + 10000 * TET_WIDTH + 1000; - ++lines; - } - } - - score += 1000 * height; - - return score; -} - -float CheckMetrics(float piece, float orgx, float orgy, float rot); -void ClearPiece(float piece, float orgx, float orgy, float rot); -void CementPiece(float piece, float orgx, float orgy, float rot); -float bastet_profile_evaluate_time; -float bastet_profile_checkmetrics_time; -float BastetSearch(float buf, float pc, float x, float y, float rot, float move_bias) -// returns best score, or -1 if position is impossible -{ - string r; - float b; - float s, sm; - float t1, t2; - - if(move_bias < 0) - return 0; // DO NOT WANT - - if(x < 1 || x > TET_WIDTH || y < 1 || y > TET_LINES) - return -1; // impossible - if(rot < 0) rot = 3; - if(rot > 3) rot = 0; - - // did we already try? - b = x + (TET_WIDTH+2) * (y + (TET_LINES+2) * rot); - r = bufstr_get(buf, b); - if(r != "") // already tried - return stof(r); - - bufstr_set(buf, b, "0"); // in case we READ that, not that bad - we already got that value in another branch then anyway - - - - t1 = gettime(GETTIME_HIRES); - if(CheckMetrics(pc, x, y, rot)) - { - t2 = gettime(GETTIME_HIRES); - bastet_profile_checkmetrics_time += (t2 - t1); - // try all moves - sm = 1; - s = BastetSearch(buf, pc, x-1, y, rot, move_bias - 1); if(s > sm) sm = s; - s = BastetSearch(buf, pc, x+1, y, rot, move_bias - 1); if(s > sm) sm = s; - s = BastetSearch(buf, pc, x, y, rot+1, move_bias - 1); if(s > sm) sm = s; - s = BastetSearch(buf, pc, x, y, rot-1, move_bias - 1); if(s > sm) sm = s; - - s = BastetSearch(buf, pc, x, y+1, rot, move_bias + 2); if(s > sm) sm = s; - if(s < 0) - { - //printf("MAY CEMENT AT: %d %d %d\n", x, y, rot); - // moving down did not work - that means we can fixate the block here - t1 = gettime(GETTIME_HIRES); - - CementPiece(pc, x, y, rot); - s = BastetEvaluate(); - ClearPiece(pc, x, y, rot); - - t2 = gettime(GETTIME_HIRES); - bastet_profile_evaluate_time += (t2 - t1); - - if(s > sm) sm = s; - } - } - else - { - t2 = gettime(GETTIME_HIRES); - bastet_profile_checkmetrics_time += (t2 - t1); - sm = -1; // impassible - } - - bufstr_set(buf, b, ftos(sm)); - - return sm; -} - -float bastet_piece[7]; -float bastet_score[7]; -float bastet_piecetime[7]; -float BastetPiece() -{ - float b; - - bastet_profile_evaluate_time = 0; - bastet_profile_checkmetrics_time = 0; - var float t1 = gettime(GETTIME_HIRES); - - b = buf_create(); bastet_piece[0] = 1; bastet_score[0] = BastetSearch(b, 1, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[0]; buf_del(b); - b = buf_create(); bastet_piece[1] = 2; bastet_score[1] = BastetSearch(b, 2, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[1]; buf_del(b); - b = buf_create(); bastet_piece[2] = 3; bastet_score[2] = BastetSearch(b, 3, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[2]; buf_del(b); - b = buf_create(); bastet_piece[3] = 4; bastet_score[3] = BastetSearch(b, 4, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[3]; buf_del(b); - b = buf_create(); bastet_piece[4] = 5; bastet_score[4] = BastetSearch(b, 5, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[4]; buf_del(b); - b = buf_create(); bastet_piece[5] = 6; bastet_score[5] = BastetSearch(b, 6, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[5]; buf_del(b); - b = buf_create(); bastet_piece[6] = 7; bastet_score[6] = BastetSearch(b, 7, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[6]; buf_del(b); - - var float t2 = gettime(GETTIME_HIRES); - dprintf("Time taken: %.6f seconds (of this, ev = %.2f%%, cm = %.2f%%)\n", t2 - t1, 100 * bastet_profile_evaluate_time / (t2 - t1), 100 * bastet_profile_checkmetrics_time / (t2 - t1)); - - // sort - float i, j, k, p, s; - -/* - for(i = 0; i < 7; ++i) - { - printf("piece %s value = %d\n", substring("OJLIZST", bastet_piece[i]-1, 1), bastet_score[i]); - } -*/ - - for(i = 0; i < 7; ++i) - { - k = i; - p = bastet_piece[k]; - s = bastet_score[k]; - for(j = i + 1; j < 7; ++j) - { - if(bastet_score[j] < s) - { - k = j; - s = bastet_score[k]; - p = bastet_piece[k]; - } - } - if(k != i) - { - bastet_score[k] = bastet_score[i]; - bastet_piece[k] = bastet_piece[i]; - bastet_score[i] = s; - bastet_piece[i] = p; - } - } - - b = random(); - if(b < 0.8) - j = 0; - else if(b < 0.92) - j = 1; - else if(b < 0.98) - j = 2; - else - j = 3; - j = bastet_piece[j]; - - for(i = 0; i < 7; ++i) - { - if(i == j-1) - bastet_piecetime[i] = 0.2 * bastet_piecetime[i]; - else - bastet_piecetime[i] = 1.8 * bastet_piecetime[i] + 1000; - } - - return j; -} - - -/* -********************************* - -Game Mechanics - -********************************* -*/ -.float tet_piece_bucket; -float RandomPiece() -{ - float i, j; - float p, q; - float b; - float seen; - - if(self.tet_piece_bucket > 1) - { - p = mod(self.tet_piece_bucket, 7); - self.tet_piece_bucket = floor(self.tet_piece_bucket / 7); - return p + 1; - } - else - { - p = floor(random() * 7); - seen = pow(2, p); - b = 1; - for(i = 6; i > 0; --i) - { - q = floor(random() * i); - for(j = 0; j <= q; ++j) - if(seen & pow(2, j)) - ++q; - if(seen & pow(2, q)) - error("foo 1"); - if(q >= 7) - error("foo 2"); - seen |= pow(2, q); - b *= 7; - b += q; - } - self.tet_piece_bucket = b; - return p + 1; - } -} - -void TetAddScore(float n) -{ - self.tet_score = self.tet_score + n * Tetris_Level(); - if (self.tet_score > tet_high_score) - tet_high_score = self.tet_score; -} -float CheckMetrics(float piece, float orgx, float orgy, float rot) /*FIXDECL*/ -{ - // check to see if the piece, if moved to the locations will overlap - - float x, y; - string l; - // why did I start counting from 1, damnit - orgx = orgx - 1; - orgy = orgy - 1; - - PieceMinsMaxs(rot, piece); - if (tet_piecemins_x+orgx<1 || tet_piecemaxs_x+orgx > TET_WIDTH || tet_piecemins_y+orgy<1 || tet_piecemaxs_y+orgy> TET_LINES) - return FALSE; // ouside the level - for (y = tet_piecemins_y; y <= tet_piecemaxs_y; y = y + 1) - { - l = GetLine(y + orgy); - if(l != TET_EMPTY_LINE) - for (x = tet_piecemins_x; x <= tet_piecemaxs_x; x = x + 1) - if (PieceMetric(x, y, rot, piece)) - if (GetXBlock(x + orgx, l)) - return FALSE; // uhoh, gonna hit something. - } - return TRUE; -} - -void ClearPiece(float piece, float orgx, float orgy, float rot) /*FIXDECL*/ -{ - float x, y; - // why did I start counting from 1, damnit - orgx = orgx - 1; - orgy = orgy - 1; - - PieceMinsMaxs(rot, piece); - for (y = tet_piecemins_y; y <= tet_piecemaxs_y; y = y + 1) - { - for (x = tet_piecemins_x; x <= tet_piecemaxs_x; x = x + 1) - { - if (PieceMetric(x, y, rot, piece)) - { - SetSquare(x + orgx, y + orgy, 0); - } - } - } -} -void CementPiece(float piece, float orgx, float orgy, float rot) /*FIXDECL*/ -{ - float pcolor; - float x, y; - // why did I start counting from 1, damnit - orgx = orgx - 1; - orgy = orgy - 1; - - pcolor = PieceColor(piece); - - PieceMinsMaxs(rot, piece); - for (y = tet_piecemins_y; y <= tet_piecemaxs_y; y = y + 1) - { - for (x = tet_piecemins_x; x <= tet_piecemaxs_x; x = x + 1) - { - if (PieceMetric(x, y, rot, piece)) - { - SetSquare(x + orgx, y + orgy, pcolor); - } - } - } -} - -const float LINE_LOW = 349525; -const float LINE_HIGH = 699050; // above number times 2 - -void AddLines(float n) -{ - entity head; - if(!self.tet_vs_id) - return; - FOR_EACH_REALCLIENT(head) if(head != self) if(head.tetris_on) if(head.tet_vs_id == self.tet_vs_id) - head.tet_vs_addlines += n; -} - -void CompletedLines() -{ - float y, cleared, added, pos, i; - string ln; - - cleared = 0; - y = TET_LINES; - for(;;) - { - ln = GetLine(y); - if(strstrofs(ln, "0", 0) < 0) - cleared = cleared + 1; - else - y = y - 1; - if(y < 1) - break; - if(y - cleared < 1) - ln = TET_EMPTY_LINE; - else - ln = GetLine(y - cleared); - SetLine(y, ln); - } - - if(cleared >= 4) - AddLines(cleared); - else if(cleared >= 1) - AddLines(cleared - 1); - - self.tet_lines = self.tet_lines + cleared; - TetAddScore(cleared * cleared * 10); - - added = self.tet_vs_addlines; - self.tet_vs_addlines = 0; - - if(added) - { - for(y = 1; y <= TET_LINES - added; ++y) - { - SetLine(y, GetLine(y + added)); - } - for(y = max(1, TET_LINES - added + 1); y <= TET_LINES; ++y) - { - pos = floor(random() * TET_WIDTH); - ln = TET_EMPTY_LINE; - for(i = 1; i <= TET_WIDTH; ++i) - if(i != pos) - ln = SetXBlock(i, ln, floor(random() * 7 + 1)); - SetLine(y, ln); - } - } - - self.tet_highest_line = 0; - for(y = 1; y <= TET_LINES; ++y) - if(GetLine(y) != TET_EMPTY_LINE) - { - self.tet_highest_line = TET_LINES + 1 - y; - break; - } - - if(added) - tetsnd("tetadd"); - else if(cleared >= 4) - tetsnd("tetris"); - else if(cleared) - tetsnd("tetline"); - else - tetsnd("tetland"); -} - -void HandleGame(float keyss) -{ - - // first off, we need to see if we need a new piece - vector piece_data; - vector check_pos; - vector old_pos; - float brand_new; - float i; - brand_new = 0; - - - if (self.piece_type == 0) - { - self.piece_pos = TET_START_PIECE_POS; // that's about middle top, we count from 1 ARGH - - if(autocvar_g_bastet) - { - self.piece_type = BastetPiece(); - self.next_piece = bastet_piece[6]; - } - else - { - if (self.next_piece) - self.piece_type = self.next_piece; - else - self.piece_type = RandomPiece(); - self.next_piece = RandomPiece(); - } - keyss = 0; // no movement first frame - self.tet_autodown = time + 0.2; - brand_new = 1; - } - else - ClearPiece(self.piece_type, self.piece_pos_x, self.piece_pos_y, self.piece_pos_z); - - // next we need to check the piece metrics against what's on the level - // based on the key order - - old_pos = check_pos = self.piece_pos; - - float nudge; - nudge = 0; - if (keyss & TETKEY_RIGHT) - { - check_pos_x = check_pos_x + 1; - tetsnd("tetmove"); - } - else if (keyss & TETKEY_LEFT) - { - check_pos_x = check_pos_x - 1; - tetsnd("tetmove"); - } - else if (keyss & TETKEY_ROTRIGHT) - { - check_pos_z = check_pos_z + 1; - piece_data = PieceShape(self.piece_type); - nudge = 1; - tetsnd("tetrot"); - } - else if (keyss & TETKEY_ROTLEFT) - { - check_pos_z = check_pos_z - 1; - piece_data = PieceShape(self.piece_type); - nudge = 1; - tetsnd("tetrot"); - } - // bounds check - if (check_pos_z > 3) - check_pos_z = 0; - else if (check_pos_z < 0) - check_pos_z = 3; - - // reality check - if (CheckMetrics(self.piece_type, check_pos_x, check_pos_y, check_pos_z)) - self.piece_pos = check_pos; - else if (brand_new) - { - self.tetris_on = 2; - self.tet_gameovertime = time + 5; - return; - } - else - { - for(i = 1; i <= nudge; ++i) - { - if(CheckMetrics(self.piece_type, check_pos_x + i, check_pos_y, check_pos_z)) - self.piece_pos = check_pos + '1 0 0' * i; - else if(CheckMetrics(self.piece_type, check_pos_x - i, check_pos_y, check_pos_z)) - self.piece_pos = check_pos - '1 0 0' * i; - else - continue; - break; - } - } - check_pos = self.piece_pos; - if(keyss & TETKEY_DROP) - { - // drop to bottom, but do NOT cement it yet - // this allows sliding it - ++check_pos_y; - while(CheckMetrics(self.piece_type, check_pos_x, check_pos_y + 1, check_pos_z)) - ++check_pos_y; - self.tet_autodown = time + 2 / (1 + Tetris_Level()); - } - else if (keyss & TETKEY_DOWN) - { - check_pos_y = check_pos_y + 1; - self.tet_autodown = time + 2 / (1 + Tetris_Level()); - } - else if (self.tet_autodown < time) - { - check_pos_y = check_pos_y + 1; - self.tet_autodown = time + 2 / (1 + Tetris_Level()); - } - if (CheckMetrics(self.piece_type, check_pos_x, check_pos_y, check_pos_z)) - { - if(old_pos != check_pos) - self.tet_drawtime = 0; - self.piece_pos = check_pos; - } - else - { - CementPiece(self.piece_type, self.piece_pos_x, self.piece_pos_y, self.piece_pos_z); - TetAddScore(1); - CompletedLines(); - self.piece_type = 0; - self.tet_drawtime = 0; - return; - } - CementPiece(self.piece_type, self.piece_pos_x, self.piece_pos_y, self.piece_pos_z); -} - -/* -********************************* - -Important Linking Into Quake stuff - -********************************* -*/ - - -void TetrisImpulse() -{ - if(self.tetris_on == 0 || self.tetris_on == 2) // from "off" or "game over" - { - self.tetris_on = 3; - - if(time < tet_vs_current_timeout) - { - // join VS game - self.tet_vs_id = tet_vs_current_id; - } - else - { - // start new VS game - ++tet_vs_current_id; - tet_vs_current_timeout = time + 15; - self.tet_vs_id = tet_vs_current_id; - bprint("^1TET^7R^1IS: ", self.netname, "^1 started a new game. Do ^7impulse 100^1 to join.\n"); - } - self.tet_highest_line = 0; - ResetTetris(); - self.tet_org = self.origin; - self.movetype = MOVETYPE_NOCLIP; - } - else if(self.tetris_on == 1) // from "on" - { - Tet_GameExit(); - self.impulse = 0; - } -} - - -float TetrisPreFrame() -{ - if (!self.tetris_on) - return 0; - - self.tet_org = self.origin; - if (self.tet_drawtime > time) - return 1; - Draw_Tetris(); - if(self.tetris_on == 3) - self.tet_drawtime = ceil(time - tet_vs_current_timeout + 0.1) + tet_vs_current_timeout; - else - self.tet_drawtime = time + 0.5; - return 1; -} -float frik_anglemoda(float v) -{ - return v - floor(v/360) * 360; -} -float angcompa(float y1, float y2) -{ - y1 = frik_anglemoda(y1); - y2 = frik_anglemoda(y2); - - float answer; - answer = y1 - y2; - if (answer > 180) - answer = answer - 360; - else if (answer < -180) - answer = answer + 360; - return answer; -} - -.float tetkey_down, tetkey_rotright, tetkey_left, tetkey_right, tetkey_rotleft, tetkey_drop; - -float TetrisKeyRepeat(.float fld, float f) -{ - if(f) - { - if(self.fld == 0) // initial key press - { - self.fld = time + 0.3; - return 1; - } - else if(time > self.fld) - { - self.fld = time + 0.1; - return 1; - } - else - { - // repeating too fast - return 0; - } - } - else - { - self.fld = 0; - return 0; - } -} - -float TetrisPostFrame() -{ - float keysa; - - keysa = 0; - - if (!self.tetris_on) - return 0; - - if(self.tetris_on == 2 && time > self.tet_gameovertime) - { - Tet_GameExit(); - return 0; - } - - if(self.tetris_on == 3 && time > tet_vs_current_timeout) - { - self.tetris_on = 1; // start VS game - self.tet_drawtime = 0; - } - - setorigin(self, self.tet_org); - self.movetype = MOVETYPE_NONE; - - if(self.tetris_on == 1) - { - if(TetrisKeyRepeat(tetkey_down, self.movement_x < 0)) - keysa |= TETKEY_DOWN; - - if(TetrisKeyRepeat(tetkey_rotright, self.movement_x > 0)) - keysa |= TETKEY_ROTRIGHT; - - if(TetrisKeyRepeat(tetkey_left, self.movement_y < 0)) - keysa |= TETKEY_LEFT; - - if(TetrisKeyRepeat(tetkey_right, self.movement_y > 0)) - keysa |= TETKEY_RIGHT; - - if(TetrisKeyRepeat(tetkey_rotleft, self.BUTTON_CROUCH)) - keysa |= TETKEY_ROTLEFT; - - if(TetrisKeyRepeat(tetkey_drop, self.BUTTON_JUMP)) - keysa |= TETKEY_DROP; - - HandleGame(keysa); - } - - return 1; -} - -#else - -float TetrisPostFrame() -{ - if (autocvar_g_bastet) - { - cvar_set("g_bastet", "0"); - print("The useless cvar has been toggled.\n"); - } - return 0; -} - -#endif diff --git a/qcsrc/server/g_triggers.qc b/qcsrc/server/g_triggers.qc index c5fb08c96..d3500da9a 100644 --- a/qcsrc/server/g_triggers.qc +++ b/qcsrc/server/g_triggers.qc @@ -12,6 +12,41 @@ void DelayThink() remove(self); } +float foreachtarget_callid; +.float targethandled; +void SUB_ForEachTarget(entity s, void(entity, float, vector, string, entity) cback, float recursive, float fdata, vector vdata, string sdata, entity edata) +{ + float i; + string targname = ""; + entity targ; + + for(i = 0; i < 4; ++i) + { + switch(i) + { + case 0: targname = s.target ; break; + case 1: targname = s.target2; break; + case 2: targname = s.target3; break; + case 3: targname = s.target4; break; + } + + if(targname != "") + { + for(targ = world; (targ = find(targ, targetname, targname));) + if(targ && targ.use && targ.targethandled != foreachtarget_callid) + { + cback(targ, fdata, vdata, sdata, edata); + targ.targethandled = foreachtarget_callid; + + if(recursive) + SUB_ForEachTarget(targ, cback, recursive, fdata, vdata, sdata, edata); + } + } + } +} + +void SUB_ForEachTarget_Init() { ++foreachtarget_callid; } + /* ============================== SUB_UseTargets @@ -31,7 +66,7 @@ match (string)self.target and call their .use function ============================== */ -void SUB_UseTargets() +void SUB_UseTargets_Ex(float preventReuse) { entity t, stemp, otemp, act; string s; @@ -103,7 +138,7 @@ void SUB_UseTargets() if (s != "") { for(t = world; (t = find(t, targetname, s)); ) - if(t.use) + if(t.use && (t.sub_target_used != time || !preventReuse)) { if(stemp.target_random) { @@ -115,6 +150,8 @@ void SUB_UseTargets() other = stemp; activator = act; self.use(); + if(preventReuse) + self.sub_target_used = time; } } } @@ -126,6 +163,8 @@ void SUB_UseTargets() other = stemp; activator = act; self.use(); + if(preventReuse) + self.sub_target_used = time; } activator = act; @@ -133,6 +172,9 @@ void SUB_UseTargets() other = otemp; } +void SUB_UseTargets() { SUB_UseTargets_Ex(FALSE); } +void SUB_UseTargets_PreventReuse() { SUB_UseTargets_Ex(TRUE); } + //============================================================================= diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index 0fd5d2de3..d05a458ea 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -220,6 +220,7 @@ void cvar_changes_init() BADPREFIX("sv_weaponstats_"); BADPREFIX("sv_waypointsprite_"); BADCVAR("rescan_pending"); + BADPREFIX("sv_allow_customplayermodels"); // these can contain player IDs, so better hide BADPREFIX("g_forced_team_"); @@ -236,6 +237,16 @@ void cvar_changes_init() BADCVAR("g_domination_default_teams"); BADCVAR("g_freezetag"); BADCVAR("g_freezetag_teams"); + BADCVAR("g_conquest"); + BADCVAR("g_conquest_teams"); + BADCVAR("g_vip"); + BADCVAR("g_vip_teams"); + BADCVAR("g_jailbreak"); + BADCVAR("g_jailbreak_teams"); + BADCVAR("g_infection"); + BADCVAR("g_infection_teams"); + BADCVAR("g_invasion"); + BADCVAR("g_invasion_teams"); BADCVAR("g_keepaway"); BADCVAR("g_keyhunt"); BADCVAR("g_keyhunt_teams"); @@ -334,6 +345,8 @@ void cvar_changes_init() BADCVAR("g_ctf_ignore_frags"); BADCVAR("g_domination_point_limit"); BADCVAR("g_freezetag_teams_override"); + BADCVAR("g_conquest_teams_override"); + BADCVAR("g_jailbreak_teams_override"); BADCVAR("g_friendlyfire"); BADCVAR("g_fullbrightitems"); BADCVAR("g_fullbrightplayers"); @@ -516,10 +529,13 @@ void spawnfunc___init_dedicated_server(void) // needs to be done so early because of the constants they create CALL_ACCUMULATED_FUNCTION(RegisterWeapons); CALL_ACCUMULATED_FUNCTION(RegisterMonsters); + CALL_ACCUMULATED_FUNCTION(RegisterTurrets); + CALL_ACCUMULATED_FUNCTION(RegisterVehicles); CALL_ACCUMULATED_FUNCTION(RegisterGametypes); CALL_ACCUMULATED_FUNCTION(RegisterNotifications); CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes); CALL_ACCUMULATED_FUNCTION(RegisterBuffs); + CALL_ACCUMULATED_FUNCTION(RegisterEffects); MapInfo_Enumerate(); MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0); @@ -564,10 +580,15 @@ void spawnfunc_worldspawn (void) // needs to be done so early because of the constants they create CALL_ACCUMULATED_FUNCTION(RegisterWeapons); CALL_ACCUMULATED_FUNCTION(RegisterMonsters); + CALL_ACCUMULATED_FUNCTION(RegisterTurrets); + CALL_ACCUMULATED_FUNCTION(RegisterVehicles); CALL_ACCUMULATED_FUNCTION(RegisterGametypes); CALL_ACCUMULATED_FUNCTION(RegisterNotifications); CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes); CALL_ACCUMULATED_FUNCTION(RegisterBuffs); + CALL_ACCUMULATED_FUNCTION(RegisterEffects); + + initialize_minigames(); ServerProgsDB = db_load(strcat("server.db", autocvar_sessionid)); @@ -675,12 +696,6 @@ void spawnfunc_worldspawn (void) if(autocvar_g_norecoil) s = strcat(s, ":norecoil"); - // TODO to mutator system - if(autocvar_g_powerups == 0) - s = strcat(s, ":no_powerups"); - if(autocvar_g_powerups > 0) - s = strcat(s, ":powerups"); - GameLogEcho(s); GameLogEcho(":gameinfo:end"); } @@ -737,6 +752,7 @@ void spawnfunc_worldspawn (void) WeaponStats_Init(); WepSet_AddStat(); + WepSet_AddStat_InMap(); addstat(STAT_SWITCHWEAPON, AS_INT, switchweapon); addstat(STAT_SWITCHINGWEAPON, AS_INT, switchingweapon); addstat(STAT_GAMESTARTTIME, AS_FLOAT, stat_game_starttime); @@ -744,12 +760,11 @@ void spawnfunc_worldspawn (void) addstat(STAT_ALLOW_OLDVORTEXBEAM, AS_INT, stat_allow_oldvortexbeam); Nagger_Init(); - addstat(STAT_STRENGTH_FINISHED, AS_FLOAT, strength_finished); - addstat(STAT_INVINCIBLE_FINISHED, AS_FLOAT, invincible_finished); addstat(STAT_SUPERWEAPONS_FINISHED, AS_FLOAT, superweapons_finished); addstat(STAT_PRESSED_KEYS, AS_FLOAT, pressedkeys); addstat(STAT_FUEL, AS_INT, ammo_fuel); addstat(STAT_PLASMA, AS_INT, ammo_plasma); + addstat(STAT_SUPERCELLS, AS_INT, ammo_supercells); addstat(STAT_SHOTORG, AS_INT, stat_shotorg); addstat(STAT_LEADLIMIT, AS_FLOAT, stat_leadlimit); addstat(STAT_WEAPON_CLIPLOAD, AS_INT, clip_load); @@ -777,6 +792,27 @@ void spawnfunc_worldspawn (void) addstat(STAT_MOVEVARS_AIRACCEL_QW, AS_FLOAT, stat_sv_airaccel_qw); addstat(STAT_MOVEVARS_AIRSTRAFEACCEL_QW, AS_FLOAT, stat_sv_airstrafeaccel_qw); + // new properties + addstat(STAT_MOVEVARS_JUMPVELOCITY, AS_FLOAT, stat_sv_jumpvelocity); + addstat(STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR, AS_FLOAT, stat_sv_airaccel_qw_stretchfactor); + addstat(STAT_MOVEVARS_MAXAIRSTRAFESPEED, AS_FLOAT, stat_sv_maxairstrafespeed); + addstat(STAT_MOVEVARS_MAXAIRSPEED, AS_FLOAT, stat_sv_maxairspeed); + addstat(STAT_MOVEVARS_AIRSTRAFEACCELERATE, AS_FLOAT, stat_sv_airstrafeaccelerate); + addstat(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL, AS_FLOAT, stat_sv_warsowbunny_turnaccel); + addstat(STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, AS_FLOAT, stat_sv_airaccel_sideways_friction); + addstat(STAT_MOVEVARS_AIRCONTROL, AS_FLOAT, stat_sv_aircontrol); + addstat(STAT_MOVEVARS_AIRCONTROL_POWER, AS_FLOAT, stat_sv_aircontrol_power); + addstat(STAT_MOVEVARS_AIRCONTROL_PENALTY, AS_FLOAT, stat_sv_aircontrol_penalty); + addstat(STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL, AS_FLOAT, stat_sv_warsowbunny_airforwardaccel); + addstat(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED, AS_FLOAT, stat_sv_warsowbunny_topspeed); + addstat(STAT_MOVEVARS_WARSOWBUNNY_ACCEL, AS_FLOAT, stat_sv_warsowbunny_accel); + addstat(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO, AS_FLOAT, stat_sv_warsowbunny_backtosideratio); + addstat(STAT_MOVEVARS_FRICTION, AS_FLOAT, stat_sv_friction); + addstat(STAT_MOVEVARS_ACCELERATE, AS_FLOAT, stat_sv_accelerate); + addstat(STAT_MOVEVARS_STOPSPEED, AS_FLOAT, stat_sv_stopspeed); + addstat(STAT_MOVEVARS_AIRACCELERATE, AS_FLOAT, stat_sv_airaccelerate); + addstat(STAT_MOVEVARS_AIRSTOPACCELERATE, AS_FLOAT, stat_sv_airstopaccelerate); + // secrets addstat(STAT_SECRETS_TOTAL, AS_FLOAT, stat_secrets_total); addstat(STAT_SECRETS_FOUND, AS_FLOAT, stat_secrets_found); @@ -787,6 +823,7 @@ void spawnfunc_worldspawn (void) // misc addstat(STAT_RESPAWN_TIME, AS_FLOAT, stat_respawn_time); + addstat(STAT_DISCO_MODE, AS_INT, discomode); next_pingtime = time + 5; @@ -810,6 +847,9 @@ void spawnfunc_worldspawn (void) RandomSeed_Spawn(); PingPLReport_Spawn(); + if(!g_jailbreak) + InitializeEntity(world, JB_NonJBInit, INITPRIO_LAST); + CheatInit(); localcmd("\n_sv_hook_gamestart ", GetGametype(), "\n"); @@ -868,15 +908,9 @@ void spawnfunc_light (void) remove(self); } -string GetGametype() -{ - return MapInfo_Type_ToString(MapInfo_LoadedGametype); -} +string GetGametype() { return MapInfo_Type_ToString(MapInfo_LoadedGametype); } -string GetMapname() -{ - return mapname; -} +string GetMapname() { return mapname; } float Map_Count, Map_Current; string Map_Current_Name; @@ -1261,6 +1295,8 @@ void IntermissionThink() float server_screenshot = (autocvar_sv_autoscreenshot && self.cvar_cl_autoscreenshot); float client_screenshot = (self.cvar_cl_autoscreenshot == 2); + CSQCMODEL_AUTOUPDATE(); // we don't need to keep checking this after mapvote has started, as by then nothing can happen + if( (server_screenshot || client_screenshot) && ((self.autoscreenshot > 0) && (time > self.autoscreenshot)) ) { @@ -1636,48 +1672,6 @@ void ClearWinners(void) head.winning = 0; } -// Onslaught winning condition: -// game terminates if only one team has a working generator (or none) -float WinningCondition_Onslaught() -{ - entity head; - float t1, t2, t3, t4; - - WinningConditionHelper(); // set worldstatus - - if(warmup_stage) - return WINNING_NO; - - // first check if the game has ended - t1 = t2 = t3 = t4 = 0; - head = find(world, classname, "onslaught_generator"); - while (head) - { - if (head.health > 0) - { - if (head.team == NUM_TEAM_1) t1 = 1; - if (head.team == NUM_TEAM_2) t2 = 1; - if (head.team == NUM_TEAM_3) t3 = 1; - if (head.team == NUM_TEAM_4) t4 = 1; - } - head = find(head, classname, "onslaught_generator"); - } - if (t1 + t2 + t3 + t4 < 2) - { - // game over, only one team remains (or none) - ClearWinners(); - if (t1) SetWinners(team, NUM_TEAM_1); - if (t2) SetWinners(team, NUM_TEAM_2); - if (t3) SetWinners(team, NUM_TEAM_3); - if (t4) SetWinners(team, NUM_TEAM_4); - dprint("Have a winner, ending game.\n"); - return WINNING_YES; - } - - // Two or more teams remain - return WINNING_NO; -} - // Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives) // they win. Otherwise the defending team wins once the timelimit passes. void assault_new_round(); @@ -1707,9 +1701,12 @@ float WinningCondition_Assault() bprint("ASSAULT: round completed...\n"); SetWinners(team, assault_attacker_team); - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 666 - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 0)); + TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_DESTROYED, 1); - if(ent.cnt == 1 || autocvar_g_campaign) // this was the second round + // why... + //TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 666 - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 0)); + + if(ent.cnt >= autocvar_g_assault_round_limit || autocvar_g_campaign) // this was the second round { status = WINNING_YES; } @@ -1843,7 +1840,7 @@ float WinningCondition_Scores(float limit, float leadlimit) if(WinningConditionHelper_zeroisworst) leadlimit = 0; // not supported in this mode - if(g_dm || g_tdm || g_ca || g_freezetag || (g_race && !g_race_qualifying) || g_nexball) + if(MUTATOR_CALLHOOK(Scores_CountFragsRemaining)) // these modes always score in increments of 1, thus this makes sense { if(leaderfrags != WinningConditionHelper_topscore) @@ -2047,9 +2044,6 @@ void CheckRules_World() return; } - if(g_onslaught) - timelimit = 0; // ONS has its own overtime rule - float wantovertime; wantovertime = 0; @@ -2135,10 +2129,6 @@ void CheckRules_World() { checkrules_status = WinningCondition_LMS(); } - else if (g_onslaught) - { - checkrules_status = WinningCondition_Onslaught(); // TODO remove this? - } else { checkrules_status = WinningCondition_Scores(fraglimit, leadlimit); @@ -2171,6 +2161,7 @@ void CheckRules_World() if(checkrules_status == WINNING_YES) { //print("WINNING\n"); + jeff_Announcer_MatchEnd(); NextLevel(); } } diff --git a/qcsrc/server/generator.qc b/qcsrc/server/generator.qc new file mode 100644 index 000000000..13aa35ab5 --- /dev/null +++ b/qcsrc/server/generator.qc @@ -0,0 +1,35 @@ +float generator_send(entity to, float sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_GENERATOR); + WriteByte(MSG_ENTITY, sf); + if(sf & GSF_SETUP) + { + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); + + WriteByte(MSG_ENTITY, self.health); + WriteByte(MSG_ENTITY, self.max_health); + WriteByte(MSG_ENTITY, self.count); + WriteByte(MSG_ENTITY, self.team); + } + + if(sf & GSF_STATUS) + { + WriteByte(MSG_ENTITY, self.team); + + if(self.health <= 0) + WriteByte(MSG_ENTITY, 0); + else + WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255)); + } + + return TRUE; +} + +void generator_link(void() spawnproc) +{ + Net_LinkEntity(self, TRUE, 0, generator_send); + self.think = spawnproc; + self.nextthink = time; +} diff --git a/qcsrc/server/generator.qh b/qcsrc/server/generator.qh new file mode 100644 index 000000000..a991874c4 --- /dev/null +++ b/qcsrc/server/generator.qh @@ -0,0 +1,5 @@ +const vector GENERATOR_MIN = '-52 -52 -14'; +const vector GENERATOR_MAX = '52 52 75'; + +float GSF_STATUS = 4; +float GSF_SETUP = 8; diff --git a/qcsrc/server/jeff.qc b/qcsrc/server/jeff.qc new file mode 100644 index 000000000..b3cca029c --- /dev/null +++ b/qcsrc/server/jeff.qc @@ -0,0 +1,331 @@ +// Modded announcer and random functions for the Jeff Resurrection servers +#ifdef JEFF + +var float autocvar_jeff_announce_eagleeye_distance = 32768; +var float autocvar_jeff_announce_rocketscientist_count = 5; +var float autocvar_jeff_announce_shaftmaster_count = 5; +var float autocvar_jeff_announce_flakmaster_count = 5; +var float autocvar_jeff_announce_jackhammer_count = 5; +var float autocvar_jeff_announce_nodebuster_count = 5; +var float autocvar_jeff_announce_mutdestruct_time = 1.5; +var float autocvar_jeff_announce_multikill_time = 0.6; +var float autocvar_jeff_announce_roadrage_count = 6; +var float autocvar_jeff_announce_roadrampage_count = 12; +var float autocvar_jeff_announce_manslaughter_count = 20; +var float autocvar_jeff_announce_hattrick_count = 50; +var float autocvar_jeff_announce_denied_radius = 60; +var float autocvar_jeff_announce_bottomfeeder_count = 20; +var float autocvar_jeff_announce_wickedsick_count = 3; +var float autocvar_jeff_announce_bluestreak_distance = 400; +var float autocvar_jeff_announce_holy_distance = 800; +var float autocvar_jeff_announce_hitandrun_speed = 1000; +var float autocvar_jeff_announce_juggernaut_armor = 180; +var float autocvar_jeff_announce_juggernaut_health = 170; +var float autocvar_jeff_announce_slacker_time = 30; +var float autocvar_jeff_announce_comboking_count = 30; + +.float rocketkill_count; // counter for rocket scientist +.float arckill_count; // counter for shaft master +.float hagarkill_count; // counter for flak master +.float meleekill_count; // counter for flak master +.float electrokill_count; // counter for flak master +.float vkill_count; // counter for vehicle kills +.float minipickup_count; // counter for bottom feeder + +.float lastkiller_weapon; +.float lastkiller_time; +.float lastkilled_time; +.float lastkilled_flying; // actually a counter + +.float jeff_weaponswitch_count; + +.float annce_count; +.float last_announcer; + +float Fire_IsBurning(entity e); // defined later + +void jeff_Announcer_Send(entity player, float toall, float announce) +{ + if(player == world || !IS_PLAYER(player)) { return; } + if(player.last_announcer > time) { return; } + + player.last_announcer = time + 0.3; // avoid spam + player.annce_count += 1; + + //dprint("Sending an announcement to player ", player.netname, "\n"); + + if(player.annce_count == autocvar_jeff_announce_hattrick_count) + { + Send_Notification(NOTIF_ONE, player, MSG_ANNCE, ANNCE_JEFF_HATTRICK); + return; + } + + Send_Notification(((toall) ? NOTIF_ALL : NOTIF_ONE), ((toall) ? world : player), MSG_ANNCE, announce); +} + +void jeff_Announcer_PlayerDies(entity attacker, float deathtype, entity targ, entity inflictor) +{ + if(attacker == targ) { return; } // don't play announcements for self kills (yet) + if(!IS_PLAYER(targ) || !targ) { return; } // don't play announcements for non-player kills + + // set required values + float death_weapon = DEATH_WEAPONOF(deathtype); + float targ_flying = !(targ.flags & FL_ONGROUND); + float attacker_flying = !(attacker.flags & FL_ONGROUND); + float denied = FALSE; entity head; + float bluestreak = FALSE; + float killed = 0; + float player_count = 0, best_killcount = 0; + float wepcount = 0, mywepcount = 0; + float i; + + for(i = WEP_FIRST; i <= WEP_LAST; ++i) + { + if(weaponsInMap & WepSet_FromWeapon(i)) + { + if(attacker.weapons & WepSet_FromWeapon(i)) + ++mywepcount; + ++wepcount; + } + } + + FOR_EACH_PLAYER(head) + { + if(head != attacker) + if(head.lastkiller == attacker) + if(head.lastkiller_time - time <= 0.1) // killed within 0.1 seconds by this attacker + ++killed; + + ++player_count; + } + + if(player_count > 5) + FOR_EACH_PLAYER(head) + { + if(head != attacker) + if(head.killcount >= 1) + if(!best_killcount || head.killcount >= best_killcount) + best_killcount = head.killcount; + } + + if(!IS_PLAYER(attacker)) + FOR_EACH_PLAYER(head) + if(vlen(head.origin - targ.origin) <= autocvar_jeff_announce_bluestreak_distance) + { + bluestreak = TRUE; + break; + } + + if(DEATH_ISWEAPON(deathtype, WEP_DEVASTATOR)) { attacker.rocketkill_count += 1; } + else { attacker.rocketkill_count = 0; } + + if(DEATH_ISWEAPON(deathtype, WEP_HAGAR)) { attacker.hagarkill_count += 1; } + else { attacker.hagarkill_count = 0; } + + if(DEATH_ISWEAPON(deathtype, WEP_ARC)) { attacker.arckill_count += 1; } + else { attacker.arckill_count = 0; } + + if((DEATH_ISWEAPON(deathtype, WEP_SHOTGUN) || DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE)) && (deathtype & HITTYPE_SECONDARY)) { attacker.meleekill_count += 1; } + else { attacker.meleekill_count = 0; } + + if(DEATH_ISWEAPON(deathtype, WEP_ELECTRO)) { attacker.electrokill_count += 1; } + else { attacker.electrokill_count = 0; } + + if(attacker.vehicle) { attacker.vkill_count += 1; } + else { attacker.vkill_count = 0; } + + if(attacker_flying) { attacker.lastkilled_flying += 1; } + else { attacker.lastkilled_flying = 0; } + + targ.lastkiller_weapon = death_weapon; + targ.lastkiller_time = time; + + for(head = findradius(targ.origin, autocvar_jeff_announce_denied_radius); head; head = head.chain) + { + float avail = (head.ItemStatus & ITS_AVAILABLE); + if( (head.classname == "item_health_mega" && avail) + || (head.classname == "item_armor_large" && avail) + || (head.classname == "item_flag_team" && (CTF_DIFFTEAM(head, targ) || targ.flagcarried)) + || (avail && (head.weapon == WEP_VORTEX || head.weapon == WEP_DEVASTATOR || (head.weapon == WEP_VAPORIZER && !g_instagib))) + ) + { + denied = TRUE; + break; + } + } + + // play the announcements + if(attacker.meleekill_count == autocvar_jeff_announce_jackhammer_count) + if((DEATH_ISWEAPON(deathtype, WEP_SHOTGUN) || DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE)) && (deathtype & HITTYPE_SECONDARY)) + jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_JACKHAMMER); + + if(DEATH_ISWEAPON(deathtype, WEP_BLASTER) || ((DEATH_ISWEAPON(deathtype, WEP_SHOTGUN) || DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE)) && (deathtype & HITTYPE_SECONDARY))) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_HUMILIATION); + + if(deathtype == DEATH_FALL && IS_PLAYER(attacker)) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_PANCAKE); + + if(SAME_TEAM(attacker, targ)) + if(attacker.lastkiller == targ) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_RETRIBUTION); + else + jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_TEAMKILLER); + + if(vlen(attacker.origin - targ.origin) > autocvar_jeff_announce_eagleeye_distance) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_EAGLEEYE); + + if(attacker.lastkiller == targ) + if(attacker.lastkiller_weapon == death_weapon) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_PAYBACK); + else if(player_count > 2) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_VENGEANCE); + + if(attacker.rocketkill_count == autocvar_jeff_announce_rocketscientist_count && DEATH_ISWEAPON(deathtype, WEP_DEVASTATOR)) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_ROCKETSCIENTIST); + + if(attacker.arckill_count == autocvar_jeff_announce_shaftmaster_count && DEATH_ISWEAPON(deathtype, WEP_ARC)) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_SHAFTMASTER); + + if(time - attacker.lastkiller_time <= autocvar_jeff_announce_mutdestruct_time) + if(time - targ.lastkiller_time <= autocvar_jeff_announce_mutdestruct_time) + if(attacker.lastkiller == targ && targ.lastkiller == attacker) + { + jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_MUTDESTRUCT); + //jeff_Announcer_Send(targ, FALSE, ANNCE_JEFF_MUTDESTRUCT); + } + + if(time - attacker.lastkilled_time <= autocvar_jeff_announce_multikill_time && time - attacker.lastkilled_time > 0.2) // don't do this for real multikills + if(attacker.lastkilled_flying == autocvar_jeff_announce_wickedsick_count) + jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_NUKEMHOLY); + else + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_MULTIKILL); + + if(attacker.vehicle && deathtype == DEATH_VH_CRUSH && attacker.vkill_count && !!(attacker.vkill_count % 2)) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_ROADKILL); + + if(attacker.vehicle && attacker.vkill_count == autocvar_jeff_announce_roadrage_count) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_ROADRAGE); + + if(attacker.vehicle && attacker.vkill_count == autocvar_jeff_announce_roadrampage_count) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_ROADRAMPAGE); + + if(attacker.vehicle && attacker.vkill_count == autocvar_jeff_announce_manslaughter_count) + jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_MANSLAUGHTER); + + if(attacker_flying && targ_flying) + if(DEATH_ISWEAPON(deathtype, WEP_DEVASTATOR) && !(deathtype & HITTYPE_SPLASH)) + if(vlen(targ.origin - attacker.origin) >= autocvar_jeff_announce_holy_distance) + jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_HOLY); + + if(denied) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_DENIED); + + if(inflictor.realowner == attacker && inflictor.jeff_projowner == targ) // killed by their own projectile + jeff_Announcer_Send(targ, FALSE, ANNCE_JEFF_REJECTED); + + if(attacker.minipickup_count >= autocvar_jeff_announce_bottomfeeder_count) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_BOTTOMFEEDER); + + if(attacker.lastkilled_flying == autocvar_jeff_announce_wickedsick_count) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_WICKEDSICK); + + if(deathtype == DEATH_SLIME && IS_PLAYER(attacker)) + jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_BIOHAZARD); + + if(Fire_IsBurning(attacker) && deathtype == DEATH_FIRE) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_BLAZEOFGLORY); + + if(bluestreak) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_BLUESTREAK); + + if(vlen(attacker.velocity) >= autocvar_jeff_announce_hitandrun_speed) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_HITANDRUN); + + if(attacker.armorvalue >= autocvar_jeff_announce_juggernaut_armor && attacker.health >= autocvar_jeff_announce_juggernaut_health) + jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_JUGGERNAUT); + + switch(killed) + { + case 2: jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_DOUBLEKILL); break; + case 3: jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_MULTIKILL); break; + case 4: jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_MEGAKILL); break; + case 5: jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_ULTRAKILL); break; + case 6: jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_MONSTERKILL); break; + case 7: jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_LUDICROUSKILL); break; + case 10: jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_GODLIKE); break; + } + + if(attacker.hagarkill_count == autocvar_jeff_announce_flakmaster_count && DEATH_ISWEAPON(deathtype, WEP_HAGAR)) + jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_FLAKMASTER); + + if(attacker.electrokill_count == autocvar_jeff_announce_nodebuster_count && DEATH_ISWEAPON(deathtype, WEP_ELECTRO)) + jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_NODEBUSTER); + + if(attacker.lastkilled_time - time >= autocvar_jeff_announce_slacker_time) + jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_SLACKER); + + if(attacker.killcount >= 1) + if(best_killcount == attacker.killcount - 1) // attacker just took the lead (TODO: make sure this isn't spammed) + jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_TOPGUN); + + if(attacker.jeff_weaponswitch_count >= autocvar_jeff_announce_comboking_count) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_COMBOKING); + + if(mywepcount >= wepcount) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_GUNSLINGER); + + if(targ_flying && attacker_flying && (DEATH_ISWEAPON(deathtype, WEP_HAGAR) || DEATH_ISWEAPON(deathtype, WEP_SEEKER) || DEATH_ISWEAPON(deathtype, WEP_CRYLINK))) + jeff_Announcer_Send(attacker, FALSE, ANNCE_JEFF_OUTSTANDING); + + if(targ.buffs & BUFF_SPEED) + jeff_Announcer_Send(attacker, TRUE, ANNCE_JEFF_SPEED); + + + attacker.jeff_weaponswitch_count = 0; + attacker.lastkilled_time = time; +} + +void jeff_Announcer_MatchEnd() +{ + Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_JEFF_GAMEOVER); +} + +void jeff_Announcer_VehicleEnter(entity player, entity veh) +{ + if(DIFF_TEAM(player, veh)) + jeff_Announcer_Send(player, TRUE, ANNCE_JEFF_HIJACKED); +} + +void jeff_Announcer_ItemTouch(entity player, entity item) +{ + if(item.classname == "item_health_small" || item.classname == "item_armor_small") + player.minipickup_count += 1; + else + player.minipickup_count -= 1; +} + +void jeff_Announcer_FireBullet(entity targ, entity ent, vector hitloc, vector start, vector end) +{ + if(ent.weapon != WEP_RIFLE && ent.weapon != WEP_VAPORIZER && ent.weapon != WEP_VORTEX) + return; + if(!IS_PLAYER(targ)) + return; + if(!Player_Trapped(targ) || !targ.takedamage) + return; + vector headmins, headmaxs, org; + org = antilag_takebackorigin(targ, time - ANTILAG_LATENCY(ent)); + headmins = org - '5 5 10'; + headmaxs = org + '5 5 10'; + if(trace_hits_box(start, end, headmins, headmaxs)) + jeff_Announcer_Send(ent, FALSE, ANNCE_JEFF_BULLSEYE); +} + +#else + +void jeff_Announcer_PlayerDies(entity attacker, float deathtype, entity targ, entity inflictor) { } +void jeff_Announcer_MatchEnd() { } +void jeff_Announcer_VehicleEnter(entity player, entity veh) { } +void jeff_Announcer_ItemTouch(entity player, entity item) { } +void jeff_Announcer_FireBullet(entity targ, entity ent, vector hitloc, vector start, vector end) { } + +#endif diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index 81306a8b1..a8323e928 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -3,6 +3,28 @@ void objerror(string s); void droptofloor(); .vector dropped_origin; +float Player_Trapped(entity player) +{ + // 0: free, 1: confined, but free movement, 2: confined and unable to move + if(player.frozen) + return 2; + if(player.deadflag != DEAD_NO) + return 2; // technically speaking, player is kinda trapped in limbo (TODO: make sure checking this doesn't break anything) + if(player.jb_isprisoned) + return 1; + return 0; +} + +float checkinlist(string command, string list) +{ + string l = strcat(" ", list, " "); + + if(strstrofs(l, strcat(" ", command, " "), 0) >= 0) + return TRUE; + + return FALSE; +} + void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag); void crosshair_trace(entity pl) { @@ -29,17 +51,9 @@ void WarpZone_crosshair_trace(entity pl) WarpZone_traceline_antilag(pl, pl.cursor_trace_start, pl.cursor_trace_start + normalize(pl.cursor_trace_endpos - pl.cursor_trace_start) * MAX_SHOT_DISTANCE, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl)); } -void() spawnfunc_info_player_deathmatch; // needed for the other spawnpoints -void() spawnpoint_use; -string GetMapname(); +string GetMapname(); // why... -string admin_name(void) -{ - if(autocvar_sv_adminnick != "") - return autocvar_sv_adminnick; - else - return "SERVER ADMIN"; -} +string admin_name() { return (autocvar_sv_adminnick != "") ? autocvar_sv_adminnick : "SERVER ADMIN"; } float DistributeEvenly_amount; float DistributeEvenly_totalweight; @@ -90,6 +104,9 @@ const string STR_OBSERVER = "observer"; #define IS_BOT_CLIENT(v) (clienttype(v) == CLIENTTYPE_BOT) #define IS_REAL_CLIENT(v) (clienttype(v) == CLIENTTYPE_REAL) #define IS_NOT_A_CLIENT(v) (clienttype(v) == CLIENTTYPE_NOTACLIENT) +#define IS_TURRET(v) (v.turret_flags & TUR_FLAG_ISTURRET) +#define IS_VEHICLE(v) (v.vehicle_flags & VHF_ISVEHICLE) +#define IS_MONSTER(v) (v.flags & FL_MONSTER) #define FOR_EACH_CLIENTSLOT(v) for(v = world; (v = nextent(v)) && (num_for_edict(v) <= maxclients); ) #define FOR_EACH_CLIENT(v) FOR_EACH_CLIENTSLOT(v) if(IS_CLIENT(v)) @@ -197,7 +214,7 @@ entity findnearest(vector point, .string field, string value, vector axismod) localhead = find(world, field, value); while (localhead) { - if ((localhead.items == IT_KEY1 || localhead.items == IT_KEY2) && localhead.target == "###item###") + if ((localhead.items == IT_KEY1 || localhead.items == IT_KEY2 || localhead.classname == "item_flag_team") && localhead.target == "###item###") dist = localhead.oldorigin; else dist = localhead.origin; @@ -303,9 +320,17 @@ string formatmessage(string msg) entity cursor_ent; string escape; string replacement; + string ammoitems; p = 0; n = 7; + ammoitems = "batteries"; + if(self.items & IT_SUPERCELLS) ammoitems = "supercells"; + if(self.items & IT_PLASMA) ammoitems = "plasma"; + if(self.items & IT_CELLS) ammoitems = "cells"; + if(self.items & IT_ROCKETS) ammoitems = "rockets"; + if(self.items & IT_SHELLS) ammoitems = "shells"; + WarpZone_crosshair_trace(self); cursor = trace_endpos; cursor_ent = trace_ent; @@ -332,45 +357,33 @@ string formatmessage(string msg) replacement = substring(msg, p, 2); escape = substring(msg, p + 1, 1); - if (escape == "%") - replacement = "%"; - else if (escape == "\\") - replacement = "\\"; - else if (escape == "n") - replacement = "\n"; - else if (escape == "a") - replacement = ftos(floor(self.armorvalue)); - else if (escape == "h") - replacement = ftos(floor(self.health)); - else if (escape == "l") - replacement = NearestLocation(self.origin); - else if (escape == "y") - replacement = NearestLocation(cursor); - else if (escape == "d") - replacement = NearestLocation(self.death_origin); - else if (escape == "w") { - float wep; - wep = self.weapon; - if (!wep) - wep = self.switchweapon; - if (!wep) - wep = self.cnt; - replacement = WEP_NAME(wep); - } else if (escape == "W") { - if (self.items & IT_SHELLS) replacement = "shells"; - else if (self.items & IT_NAILS) replacement = "bullets"; - else if (self.items & IT_ROCKETS) replacement = "rockets"; - else if (self.items & IT_CELLS) replacement = "cells"; - else if (self.items & IT_PLASMA) replacement = "plasma"; - else replacement = "batteries"; // ;) - } else if (escape == "x") { - replacement = cursor_ent.netname; - if (replacement == "" || !cursor_ent) - replacement = "nothing"; - } else if (escape == "s") - replacement = ftos(vlen(self.velocity - self.velocity_z * '0 0 1')); - else if (escape == "S") - replacement = ftos(vlen(self.velocity)); + switch(escape) + { + case "%": replacement = "%"; break; + case "\\":replacement = "\\"; break; + case "n": replacement = "\n"; break; + case "a": replacement = ftos(floor(self.armorvalue)); break; + case "h": replacement = ftos(floor(self.health)); break; + case "j": replacement = ((self.lastkilled) ? self.lastkilled.netname : "(nobody)"); break; + case "k": replacement = ((self.lastkiller) ? self.lastkiller.netname : "(nobody)"); break; + case "l": replacement = NearestLocation(self.origin); break; + case "y": replacement = NearestLocation(cursor); break; + case "d": replacement = NearestLocation(self.death_origin); break; + case "w": replacement = WEP_NAME((!self.weapon) ? (!self.switchweapon ? self.cnt : self.switchweapon) : self.weapon); break; + case "W": replacement = ammoitems; break; + case "x": replacement = ((cursor_ent.netname == "" || !cursor_ent) ? "nothing" : cursor_ent.netname); break; + case "s": replacement = ftos(vlen(self.velocity - self.velocity_z * '0 0 1')); break; + case "S": replacement = ftos(vlen(self.velocity)); break; + default: + { + format_escape = escape; + format_replacement = replacement; + MUTATOR_CALLHOOK(FormatMessage); + escape = format_escape; + replacement = format_replacement; + break; + } + } msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2))); p = p + strlen(replacement); @@ -491,6 +504,15 @@ void GetCvars(float f) GetCvars_handleFloat(s, f, cvar_cl_autoscreenshot, "cl_autoscreenshot"); GetCvars_handleFloat(s, f, cvar_cl_jetpack_jump, "cl_jetpack_jump"); GetCvars_handleString(s, f, cvar_g_xonoticversion, "g_xonoticversion"); + GetCvars_handleString(s, f, cvar_cl_autovote, "cl_autovote"); + GetCvars_handleString(s, f, cvar_cl_physics, "cl_physics"); + GetCvars_handleFloat(s, f, cvar_cl_sparkle, "cl_sparkle"); + GetCvars_handleFloat(s, f, cvar_cl_pony, "cl_pony"); + GetCvars_handleFloat(s, f, cvar_cl_pony_skin, "cl_pony_skin"); + GetCvars_handleFloat(s, f, cvar_cl_damnfurries, "cl_damnfurries"); + GetCvars_handleFloat(s, f, cvar_cl_thestars, "cl_thestars"); + GetCvars_handleFloat(s, f, cvar_cl_robot, "cl_robot"); + GetCvars_handleFloat(s, f, cvar_cl_charge, "cl_charge"); GetCvars_handleFloat(s, f, cvar_cl_handicap, "cl_handicap"); GetCvars_handleFloat(s, f, cvar_cl_clippedspectating, "cl_clippedspectating"); GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete_AndBuildImpulseList); @@ -544,16 +566,6 @@ string playername(entity p) return p.netname; } -vector randompos(vector m1, vector m2) -{ - vector v; - m2 = m2 - m1; - v_x = m2_x * random() + m1_x; - v_y = m2_y * random() + m1_y; - v_z = m2_z * random() + m1_z; - return v; -} - //#NO AUTOCVARS START float g_pickup_shells; @@ -640,23 +652,25 @@ float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still ne if (!i) return 0; - if (g_lms || g_ca || allguns) + if (allguns) { if(weaponinfo.spawnflags & WEP_FLAG_NORMAL) d = TRUE; else d = FALSE; } - else if (g_cts) - d = (i == WEP_SHOTGUN); - else if (g_nexball) - d = 0; // weapon is set a few lines later else d = !(!weaponinfo.weaponstart); + ret_float = d; + other = weaponinfo; + MUTATOR_CALLHOOK(WantWeapon); + d = ret_float; + weaponinfo = other; + if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook d |= (i == WEP_HOOK); - if(!g_cts && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns + if(weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED) // never default mutator blocked guns d = 0; var float t = weaponinfo.weaponstartoverride; @@ -938,7 +952,7 @@ void readlevelcvars(void) g_bugrigs_speed_pow = cvar("g_bugrigs_speed_pow"); g_bugrigs_steer = cvar("g_bugrigs_steer"); - g_instagib = cvar("g_instagib"); + sv_showfps = cvar("sv_showfps"); sv_clones = cvar("sv_clones"); sv_foginterval = cvar("sv_foginterval"); @@ -956,7 +970,7 @@ void readlevelcvars(void) g_warmup_allguns = cvar("g_warmup_allguns"); g_warmup_allow_timeout = cvar("g_warmup_allow_timeout"); - if ((g_race && g_race_qualifying == 2) || g_assault || cvar("g_campaign")) + if (cvar("g_campaign")) warmup_stage = 0; // these modes cannot work together, sorry g_pickup_respawntime_weapon = cvar("g_pickup_respawntime_weapon"); @@ -1282,14 +1296,9 @@ void precache_all_playermodels(string pattern) void precache() { // gamemode related things - precache_model ("models/misc/chatbubble.spr"); + precache_model ("models/misc/chatbubble.md3"); precache_model("models/ice/ice.md3"); -#ifdef TTURRETS_ENABLED - if (autocvar_g_turrets) - turrets_precash(); -#endif - // Precache all player models if desired if (autocvar_sv_precacheplayermodels) { @@ -1318,7 +1327,29 @@ void precache() precache_playermodel(s); s = autocvar_sv_defaultplayermodel; if (s != "") - precache_playermodel(s); + { + float n = tokenize_console(s); + if(n > 0) + { + float i; + for (i = 0; i < n; ++i) + { + //print("Precaching: ", argv(i), "\n"); + precache_model(argv(i)); + } + } + + precache_playermodel(s); + } + } + + if(autocvar_sv_allow_customplayermodels) + { + precache_playermodel("models/player/pony.iqm"); + precache_playermodel("models/player/renamon.iqm"); + precache_playermodel("models/player/terminusmale.iqm"); + precache_playermodel("models/player/ubot.iqm"); + precache_playermodel("models/player/rosalina.dpm"); } if (g_footsteps) @@ -1361,15 +1392,15 @@ void precache() precache_model ("models/sprites/10.spr32"); // common weapon precaches - precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound here - precache_sound ("weapons/weapon_switch.wav"); - precache_sound ("weapons/weaponpickup.wav"); - precache_sound ("weapons/unavailable.wav"); - precache_sound ("weapons/dryfire.wav"); + precache_sound (W_Sound("reload")); // until weapons have individual reload sounds, precache the reload sound here + precache_sound (W_Sound("weapon_switch")); + precache_sound (W_Sound("weaponpickup")); + precache_sound (W_Sound("unavailable")); + precache_sound (W_Sound("dryfire")); if (g_grappling_hook) { - precache_sound ("weapons/hook_fire.wav"); // hook - precache_sound ("weapons/hook_impact.wav"); // hook + precache_sound (W_Sound("hook_fire")); // hook + precache_sound (W_Sound("hook_impact")); // hook } precache_model("models/elaser.mdl"); @@ -2358,10 +2389,12 @@ float LostMovetypeFollow(entity ent) float isPushable(entity e) { - if(e.iscreature) - return TRUE; if(e.pushable) return TRUE; + if(IS_VEHICLE(e)) + return FALSE; + if(e.iscreature) + return TRUE; switch(e.classname) { case "body": diff --git a/qcsrc/server/mutators/base.qc b/qcsrc/server/mutators/base.qc index 1ba8f2663..248f923d0 100644 --- a/qcsrc/server/mutators/base.qc +++ b/qcsrc/server/mutators/base.qc @@ -91,7 +91,7 @@ float CallbackChain_Call(entity cb) return r; // callbacks return an error status, so 0 is default return value } -#define MAX_MUTATORS 15 +#define MAX_MUTATORS 20 string loaded_mutators[MAX_MUTATORS]; float Mutator_Add(mutatorfunc_t func, string name) { diff --git a/qcsrc/server/mutators/base.qh b/qcsrc/server/mutators/base.qh index 1c33e3973..9bab139fe 100644 --- a/qcsrc/server/mutators/base.qh +++ b/qcsrc/server/mutators/base.qh @@ -94,6 +94,7 @@ MUTATOR_HOOKABLE(GetTeamCount); // should adjust ret_float to contain the team count // INPUT, OUTPUT: float ret_float; + string ret_string; MUTATOR_HOOKABLE(SpectateCopy); // copies variables for spectating "other" to "self" @@ -108,6 +109,12 @@ MUTATOR_HOOKABLE(WeaponRateFactor); // INPUT, OUTPUT: float weapon_rate; +MUTATOR_HOOKABLE(WantWeapon); + // returns which weapon the player/bot should choose + // INPUT, OUTPUT: + float ret_float; + entity other; + MUTATOR_HOOKABLE(SetStartItems); // adjusts {warmup_}start_{items,weapons,ammo_{cells,plasma,rockets,nails,shells,fuel}} @@ -167,10 +174,9 @@ MUTATOR_HOOKABLE(MonsterDies); // INPUT: entity frag_attacker; -MUTATOR_HOOKABLE(MonsterRespawn); - // called when a monster wants to respawn - // INPUT: - entity other; +MUTATOR_HOOKABLE(MonsterRemove); + // called when a monster is being removed + // returning TRUE hides removal effect MUTATOR_HOOKABLE(MonsterDropItem); // called when a monster is dropping loot @@ -196,6 +202,12 @@ MUTATOR_HOOKABLE(AllowMobSpawning); // called when a player tries to spawn a monster // return 1 to prevent spawning +MUTATOR_HOOKABLE(FormatMessage); + // for replacing chat message commands + // INPUT, OUTPUT: + string format_escape; + string format_replacement; + MUTATOR_HOOKABLE(PlayerDamage_SplitHealthArmor); // called when a player gets damaged to e.g. remove stuff he was carrying. // INPUT: @@ -203,6 +215,7 @@ MUTATOR_HOOKABLE(PlayerDamage_SplitHealthArmor); entity frag_attacker; entity frag_target; // same as self vector damage_force; // NOTE: this force already HAS been applied + float frag_mirrordamage; // INPUT, OUTPUT: float damage_take; float damage_save; @@ -239,6 +252,16 @@ MUTATOR_HOOKABLE(PlayerUseKey); // if MUTATOR_RETURNVALUE is 1, don't do anything // return 1 if the use key actually did something +MUTATOR_HOOKABLE(SV_ParseServerCommand); + // called when a server command is parsed + // NOTE: hooks MUST start with if(MUTATOR_RETURNVALUE) return 0; + // NOTE: return 1 if you handled the command, return 0 to continue handling + // NOTE: THESE HOOKS MUST NEVER EVER CALL tokenize() + // INPUT + string cmd_name; // command name + float cmd_argc; // also, argv() can be used + string cmd_string; // whole command, use only if you really have to + MUTATOR_HOOKABLE(SV_ParseClientCommand); // called when a client command is parsed // NOTE: hooks MUST start with if(MUTATOR_RETURNVALUE) return 0; @@ -277,6 +300,9 @@ MUTATOR_HOOKABLE(Spawn_Score); // IN+OUT vector spawn_score; // _x is priority, _y is "distance" +MUTATOR_HOOKABLE(Scores_CountFragsRemaining); + // allows a mutator to count the last remaining frags + MUTATOR_HOOKABLE(SV_StartFrame); // runs globally each server frame @@ -306,7 +332,7 @@ MUTATOR_HOOKABLE(Item_RespawnCountdown); MUTATOR_HOOKABLE(BotShouldAttack); // called when a bot checks a target to attack // INPUT - entity checkentity; + entity other; MUTATOR_HOOKABLE(PortalTeleport); // called whenever a player goes through a portal gun teleport diff --git a/qcsrc/server/mutators/gamemode_assault.qc b/qcsrc/server/mutators/gamemode_assault.qc index 8a8c50b38..ec353346c 100644 --- a/qcsrc/server/mutators/gamemode_assault.qc +++ b/qcsrc/server/mutators/gamemode_assault.qc @@ -54,22 +54,29 @@ void assault_objective_decrease_use() { if(self.enemy.health - self.dmg > 0.5) { - PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.dmg); + PlayerScore_Add(activator, SP_SCORE, self.dmg); self.enemy.health = self.enemy.health - self.dmg; } else { - PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.enemy.health); + PlayerScore_Add(activator, SP_SCORE, self.enemy.health); PlayerTeamScore_Add(activator, SP_ASSAULT_OBJECTIVES, ST_ASSAULT_OBJECTIVES, 1); self.enemy.health = -1; - entity oldself, oldactivator, head; + entity oldself, oldactivator; oldself = self; self = oldself.enemy; if(self.message) - FOR_EACH_PLAYER(head) - centerprint(head, self.message); + { + entity player; + string s; + FOR_EACH_PLAYER(player) + { + s = strcat(self.message, "\n"); + centerprint(player, s); + } + } oldactivator = activator; activator = oldself; @@ -135,6 +142,7 @@ void target_objective_decrease_activate() WaypointSprite_UpdateMaxHealth(spr, ent.max_health); WaypointSprite_UpdateHealth(spr, ent.health); ent.sprite = spr; + ent.colormod = '2 2 2'; } else WaypointSprite_UpdateSprites(spr, "as-defend", "as-push", "as-push"); @@ -163,7 +171,6 @@ void assault_roundstart_use() activator = self; SUB_UseTargets(); -#ifdef TTURRETS_ENABLED entity ent, oldself; //(Re)spawn all turrets @@ -179,12 +186,11 @@ void assault_roundstart_use() self = ent; // Dubbles as teamchange - turret_stdproc_respawn(); + turret_respawn(); ent = find(ent, classname, "turret_main"); } self = oldself; -#endif } void assault_wall_think() @@ -205,8 +211,31 @@ void assault_wall_think() // trigger new round // reset objectives, toggle spawnpoints, reset triggers, ... +void vehicles_clearreturn(entity veh); +void vehicles_spawn(); void assault_new_round() { + entity oldself; + //bprint("ASSAULT: new round\n"); + + oldself = self; + // Eject players from vehicles + FOR_EACH_PLAYER(self) + { + if(self.vehicle) + vehicles_exit(VHEF_RELEASE); + } + + self = findchainflags(vehicle_flags, VHF_ISVEHICLE); + while(self) + { + vehicles_clearreturn(self); + vehicles_spawn(); + self = self.chain; + } + + self = oldself; + // up round counter self.winning = self.winning + 1; @@ -525,8 +554,8 @@ MUTATOR_HOOKFUNCTION(assault_PlayerSpawn) MUTATOR_HOOKFUNCTION(assault_TurretSpawn) { - if (!self.team) - self.team = 14; + if(!self.team || self.team == MAX_SHOT_DISTANCE) + self.team = 5; // this gets reversed when match starts? return FALSE; } @@ -544,27 +573,84 @@ MUTATOR_HOOKFUNCTION(assault_BotRoles) return TRUE; } +MUTATOR_HOOKFUNCTION(assault_GetTeamCount) +{ + c1 = c2 = 0; // assault always has 2 teams + return TRUE; +} + +.float assault_repair_delay; +MUTATOR_HOOKFUNCTION(assault_PlayerThink) +{ + if(self.team == assault_attacker_team) { return FALSE; } + if(autocvar_g_assault_repair_amount <= 0) { return FALSE; } + if(time < self.assault_repair_delay) { return FALSE; } + if(gameover || self.frozen || self.deadflag != DEAD_NO || self.vehicle) + return FALSE; + + float n_players = 0; + entity cp = findchain(classname, "func_assault_destructible"), head; + self.assault_repair_delay = time + 0.5; + + FOR_EACH_PLAYER(head) if(SAME_TEAM(head, self)) { ++n_players; } + + for(; cp; cp = cp.chain) + if(vlen((cp.origin + 0.5 * (cp.absmin + cp.absmax)) - self.origin) < 300) + if(cp.health > 0) + if(cp.health < cp.max_health) + if(cp.target != "") + if(time - cp.pain_finished > 3) + { + cp.health += autocvar_g_assault_repair_amount / n_players; + if(cp.sprite) { WaypointSprite_UpdateHealth(cp.sprite, cp.health); } + } + return FALSE; +} + +MUTATOR_HOOKFUNCTION(assault_OnEntityPreSpawn) +{ + switch(self.classname) + { + case "info_player_team1": return TRUE; + case "info_player_team2": return TRUE; + case "info_player_team3": return TRUE; + case "info_player_team4": return TRUE; + } + + return FALSE; +} + // scoreboard setup void assault_ScoreRules() { - ScoreRules_basics(2, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, TRUE); - ScoreInfo_SetLabel_TeamScore( ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY); - ScoreInfo_SetLabel_PlayerScore(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY); + ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE); + ScoreInfo_SetLabel_TeamScore( ST_ASSAULT_DESTROYED, "destroyed", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_TeamScore( ST_ASSAULT_OBJECTIVES, "objectives", 0); + ScoreInfo_SetLabel_PlayerScore(SP_ASSAULT_OBJECTIVES, "objectives", 0); ScoreRules_basics_end(); } +void assault_Initialize() +{ + assault_ScoreRules(); + warmup_stage = 0; +} + MUTATOR_DEFINITION(gamemode_assault) { MUTATOR_HOOK(PlayerSpawn, assault_PlayerSpawn, CBC_ORDER_ANY); MUTATOR_HOOK(TurretSpawn, assault_TurretSpawn, CBC_ORDER_ANY); MUTATOR_HOOK(VehicleSpawn, assault_VehicleSpawn, CBC_ORDER_ANY); MUTATOR_HOOK(HavocBot_ChooseRole, assault_BotRoles, CBC_ORDER_ANY); + MUTATOR_HOOK(GetTeamCount, assault_GetTeamCount, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerPreThink, assault_PlayerThink, CBC_ORDER_ANY); + MUTATOR_HOOK(OnEntityPreSpawn, assault_OnEntityPreSpawn, CBC_ORDER_ANY); MUTATOR_ONADD { if(time > 1) // game loads at time 1 error("This is a game type and it cannot be added at runtime."); - assault_ScoreRules(); + assault_Initialize(); } MUTATOR_ONROLLBACK_OR_REMOVE diff --git a/qcsrc/server/mutators/gamemode_assault.qh b/qcsrc/server/mutators/gamemode_assault.qh index 9aecf87f2..1d0441315 100644 --- a/qcsrc/server/mutators/gamemode_assault.qh +++ b/qcsrc/server/mutators/gamemode_assault.qh @@ -23,9 +23,10 @@ void(float ratingscale, vector org, float sradius) havocbot_goalrating_items; void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers; // scoreboard stuff -#define ST_ASSAULT_OBJECTIVES 1 +#define ST_ASSAULT_DESTROYED 1 +#define ST_ASSAULT_OBJECTIVES -1 #define SP_ASSAULT_OBJECTIVES 4 // predefined spawnfuncs void spawnfunc_func_breakable(); -void target_objective_decrease_activate(); \ No newline at end of file +void target_objective_decrease_activate(); diff --git a/qcsrc/server/mutators/gamemode_ca.qc b/qcsrc/server/mutators/gamemode_ca.qc index 4ba183004..b44e8ab31 100644 --- a/qcsrc/server/mutators/gamemode_ca.qc +++ b/qcsrc/server/mutators/gamemode_ca.qc @@ -16,29 +16,18 @@ void CA_count_alive_players() { entity e; total_players = redalive = bluealive = yellowalive = pinkalive = 0; - FOR_EACH_PLAYER(e) { - if(e.team == NUM_TEAM_1) - { - ++total_players; - if (e.health >= 1) ++redalive; - } - else if(e.team == NUM_TEAM_2) - { - ++total_players; - if (e.health >= 1) ++bluealive; - } - else if(e.team == NUM_TEAM_3) - { - ++total_players; - if (e.health >= 1) ++yellowalive; - } - else if(e.team == NUM_TEAM_4) + FOR_EACH_PLAYER(e) + { + switch(e.team) { - ++total_players; - if (e.health >= 1) ++pinkalive; + case NUM_TEAM_1: if(e.health >= 1) ++redalive; ++total_players; break; + case NUM_TEAM_2: if(e.health >= 1) ++bluealive; ++total_players; break; + case NUM_TEAM_3: if(e.health >= 1) ++yellowalive; ++total_players; break; + case NUM_TEAM_4: if(e.health >= 1) ++pinkalive; ++total_players; break; } } - FOR_EACH_REALCLIENT(e) { + FOR_EACH_REALCLIENT(e) + { e.redalive_stat = redalive; e.bluealive_stat = bluealive; e.yellowalive_stat = yellowalive; @@ -75,15 +64,13 @@ float CA_GetWinnerTeam() #define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == ca_teams) float CA_CheckWinner() { - entity e; if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) { Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); allowed_to_spawn = FALSE; round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); - FOR_EACH_PLAYER(e) - nades_Clear(e); + nades_Clear(world, TRUE); return 1; } @@ -107,8 +94,7 @@ float CA_CheckWinner() allowed_to_spawn = FALSE; round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); - FOR_EACH_PLAYER(e) - nades_Clear(e); + nades_Clear(world, TRUE); return 1; } @@ -325,10 +311,6 @@ MUTATOR_HOOKFUNCTION(ca_PlayerDamage) MUTATOR_HOOKFUNCTION(ca_FilterItem) { - if(autocvar_g_powerups <= 0) - if(self.flags & FL_POWERUP) - return TRUE; - if(autocvar_g_pickup_items <= 0) return TRUE; @@ -351,6 +333,22 @@ MUTATOR_HOOKFUNCTION(ca_PlayerRegen) return TRUE; } +MUTATOR_HOOKFUNCTION(ca_WantWeapon) +{ + if(other.spawnflags & WEP_FLAG_NORMAL) + ret_float = TRUE; + else + ret_float = FALSE; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(ca_CountFrags) +{ + // announce remaining frags + return TRUE; +} + void ca_Initialize() { allowed_to_spawn = TRUE; @@ -392,6 +390,8 @@ MUTATOR_DEFINITION(gamemode_ca) MUTATOR_HOOK(FilterItem, ca_FilterItem, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerDamage_SplitHealthArmor, ca_PlayerDamage_SplitHealthArmor, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerRegen, ca_PlayerRegen, CBC_ORDER_ANY); + MUTATOR_HOOK(WantWeapon, ca_WantWeapon, CBC_ORDER_ANY); + MUTATOR_HOOK(Scores_CountFragsRemaining, ca_CountFrags, CBC_ORDER_ANY); MUTATOR_ONADD { diff --git a/qcsrc/server/mutators/gamemode_conquest.qc b/qcsrc/server/mutators/gamemode_conquest.qc new file mode 100644 index 000000000..07990f25c --- /dev/null +++ b/qcsrc/server/mutators/gamemode_conquest.qc @@ -0,0 +1,928 @@ +float cq_ControlPoint_Send(entity to, float sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_CONQUEST_CONTROLPOINT); + WriteByte(MSG_ENTITY, sf); + + if(sf & CQSF_SETUP) + { + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); + + WriteCoord(MSG_ENTITY, self.angles_y); + + WriteLong(MSG_ENTITY, self.cq_capdistance); + } + + if(sf & CQSF_STATE) + { + WriteByte(MSG_ENTITY, self.cq_status); + } + + if(sf & CQSF_TEAM) + { + WriteByte(MSG_ENTITY, self.team); + } + + if(sf & CQSF_HEALTH) + { + WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255)); + WriteLong(MSG_ENTITY, autocvar_g_conquest_capture_sps); + } + + if(sf & CQSF_NAME) + { + WriteString(MSG_ENTITY, self.netname); + } + + + return TRUE; +} + +void cq_ControlPoint_SwitchTeam(float newteam, float newstatus, float showmessage) +{ + // TODO: add sounds + // TODO: clean this up! + + if(showmessage) + if(newstatus == CP_NEUTRAL && self.cq_status != CP_NEUTRAL) + { + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(self, INFO_CONQUEST_LIBERATE_), self.netname); + Send_Notification(NOTIF_TEAM, self, MSG_CENTER, APP_TEAM_NUM_4(newteam, CENTER_CONQUEST_LOST_), self.netname); + + entity player; + FOR_EACH_REALPLAYER(player) + { + if(player.team == newteam) + Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(self, CENTER_CONQUEST_LIBERATE_TEAM_), self.netname); + else if(DIFF_TEAM(player, self)) + Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM_4(newteam, CENTER_CONQUEST_LIBERATE_), self.netname); + } + } + + self.team = newteam; + self.cq_status = newstatus; + + switch(self.cq_status) + { + case CP_NEUTRAL: + { + activator = world; + self.skin = 0; + break; + } + + case CP_CAPTURED: + { + activator = self; + self.skin = Team_TeamToNumber(self.team) + 1; + + if(showmessage) + { + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(self, INFO_CONQUEST_CAPTURE_), self.netname); + Send_Notification(NOTIF_TEAM, self, MSG_CENTER, CENTER_CONQUEST_CAPTURE_TEAM, self.netname); + + entity player; + FOR_EACH_PLAYER(player) + { + if(DIFF_TEAM(player, self)) + Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(self, CENTER_CONQUEST_CAPTURE_), self.netname); + } + } + + break; + } + + default: { dprint("Control point without status?\n"); break; } + } + + SUB_UseTargets(); + + self.SendFlags |= CQSF_STATE | CQSF_TEAM; +} + +void cq_ControlPoint_Think() +{ + self.nextthink = time + CQ_CP_THINKRATE; + + if(!round_handler_IsRoundStarted()) { return; } + + float dist, old_health = self.health; + entity player; + + FOR_EACH_PLAYER(player) + { + if(!player.deadflag) + if(!player.vehicle) + if(!player.frozen) + { + dist = vlen(player.origin - self.origin); + if(dist <= self.cq_capdistance) + { + traceline(CENTER_OR_VIEWOFS(player), CENTER_OR_VIEWOFS(self), MOVE_WORLDONLY, player); + if(trace_fraction == 1.0) + { + if(SAME_TEAM(player, self)) + self.health = min(self.max_health, self.health + autocvar_g_conquest_capture_sps * CQ_CP_THINKRATE); + else + self.health = max(0, self.health - autocvar_g_conquest_capture_sps * CQ_CP_THINKRATE); + + switch(self.cq_status) + { + case CP_CAPTURED: + { + if(self.health == 0) + { + PlayerScore_Add(player, SP_CONQUEST_LIBERATED, 1); + Send_Effect(EFFECT_EXPLOSION_BIG, findbetterlocation(self.origin, 16), '0 0 0', 1); + cq_ControlPoint_SwitchTeam(player.team, CP_NEUTRAL, TRUE); + } + + break; + } + + case CP_NEUTRAL: + { + if(self.health == 0) + cq_ControlPoint_SwitchTeam(player.team, CP_NEUTRAL, TRUE); + + if(self.health == self.max_health) + { + PlayerScore_Add(player, SP_CONQUEST_CAPTURED, 1); + cq_ControlPoint_SwitchTeam(player.team, CP_CAPTURED, TRUE); + } + + break; + } + } + } + } + } + } + + if(self.health != old_health) + self.SendFlags |= CQSF_HEALTH; +} + +void cq_ControlPoint_Reset() +{ + if(self.cq_originalteam) + self.cq_status = CP_CAPTURED; + else + { + self.cq_status = CP_NEUTRAL; + self.health = 0; + } + self.SendFlags |= CQSF_HEALTH | CQSF_TEAM; + cq_ControlPoint_SwitchTeam(self.cq_originalteam, self.cq_status, FALSE); +} + +void spawnfunc_conquest_controlpoint() +{ + if(!g_conquest) { remove(self); return; } + + self.cq_worldcpnext = cq_worldcplist; // link control point into cq_worldcplist + cq_worldcplist = self; + + self.model = ""; // should we support custom models? + + setsize(self, CQ_CP_MIN, CQ_CP_MAX); + + if(self.netname == "") { self.netname = "a spawnpoint"; } + if(!self.health) { self.health = autocvar_g_conquest_controlpoint_health_default; } + if(!self.cq_capdistance) { self.cq_capdistance = autocvar_g_conquest_capture_distance_default; } + + self.classname = "conquest_controlpoint"; + self.max_health = self.health; + self.solid = SOLID_TRIGGER; + self.nextthink = time + CQ_CP_THINKRATE; + self.think = cq_ControlPoint_Think; + self.reset = cq_ControlPoint_Reset; + + if(self.team == NUM_TEAM_4 && cq_teams < 4) + self.team = NUM_TEAM_2; + if(self.team == NUM_TEAM_3 && cq_teams < 3) + self.team = NUM_TEAM_1; + + if(self.team) + self.cq_status = CP_CAPTURED; + else + { + self.cq_status = CP_NEUTRAL; + self.health = 0; + } + + self.cq_originalteam = self.team; + + waypoint_spawnforitem(self); + Net_LinkEntity(self, FALSE, 0, cq_ControlPoint_Send); + self.SendFlags = CQSF_SETUP | CQSF_STATE | CQSF_TEAM | CQSF_HEALTH | CQSF_NAME; + cq_ControlPoint_SwitchTeam(self.team, self.cq_status, FALSE); +} + +// round handler +void cq_CountAlivePlayers() +{ + entity e; + total_players = redalive = bluealive = yellowalive = pinkalive = 0; + + FOR_EACH_PLAYER(e) + { + ++total_players; + redalive += (e.team == NUM_TEAM_1 && e.deadflag == DEAD_NO); + bluealive += (e.team == NUM_TEAM_2 && e.deadflag == DEAD_NO); + yellowalive += (e.team == NUM_TEAM_3 && e.deadflag == DEAD_NO); + pinkalive += (e.team == NUM_TEAM_4 && e.deadflag == DEAD_NO); + } +} + +#define CQ_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0)) +#define CQ_ALIVE_TEAMS_OK() (CQ_ALIVE_TEAMS() == cq_teams) + +float cq_getWinnerTeam() +{ + entity tmp_entity; + float redcp = 0, bluecp = 0, yellowcp = 0, pinkcp = 0; + for(tmp_entity = cq_worldcplist; tmp_entity; tmp_entity = tmp_entity.cq_worldcpnext) + { + if(tmp_entity.cq_status == CP_CAPTURED) + switch(tmp_entity.team) + { + case NUM_TEAM_1: ++redcp; break; + case NUM_TEAM_2: ++bluecp; break; + case NUM_TEAM_3: ++yellowcp; break; + case NUM_TEAM_4: ++pinkcp; break; + } + } + + float winner_team = 0; + if(redcp >= 1) { winner_team = NUM_TEAM_1; } + if(bluecp >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_2; + } + if(yellowcp >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_3; + } + if(pinkcp >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_4; + } + if(winner_team) + return winner_team; + return -1; // nobody owns anything +} + +float cq_CheckWinner() +{ + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); + round_handler_Init(5, autocvar_g_conquest_warmup, autocvar_g_conquest_round_timelimit); + return 1; + } + + cq_CountAlivePlayers(); + + entity tmp_entity; + float redcp = 0, bluecp = 0, yellowcp = 0, pinkcp = 0, neutralcp = 0; + for(tmp_entity = cq_worldcplist; tmp_entity; tmp_entity = tmp_entity.cq_worldcpnext) + { + if(tmp_entity.cq_status == CP_CAPTURED) + switch(tmp_entity.team) + { + case NUM_TEAM_1: ++redcp; break; + case NUM_TEAM_2: ++bluecp; break; + case NUM_TEAM_3: ++yellowcp; break; + case NUM_TEAM_4: ++pinkcp; break; + } + else { ++neutralcp; } + } + if(((redcp > 0) + (bluecp > 0) + (yellowcp > 0) + (pinkcp > 0) + (neutralcp > 0)) > 1) // more than 1 team owns control points + return 0; + + float winner_team; + winner_team = cq_getWinnerTeam(); + if(winner_team > 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_)); + TeamScore_AddToTeam(winner_team, ST_CONQUEST_ROUNDS, +1); + } + else if(winner_team == -1) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED); + } + + round_handler_Init(5, autocvar_g_conquest_warmup, autocvar_g_conquest_round_timelimit); + return 1; +} + +float prev_missing_teams_mask; +float cq_CheckTeams() +{ + allowed_to_spawn = TRUE; + cq_CountAlivePlayers(); + if(CQ_ALIVE_TEAMS_OK()) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 1; + } + if(total_players == 0) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 0; + } + float missing_teams_mask = (!redalive) + (!bluealive) * 2; + if(cq_teams >= 3) missing_teams_mask += (!yellowalive) * 4; + if(cq_teams >= 4) missing_teams_mask += (!pinkalive) * 8; + if(prev_missing_teams_mask != missing_teams_mask) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); + prev_missing_teams_mask = missing_teams_mask; + } + return 0; +} + + +// ====================== +// Teleportation Handling +// ====================== + +/* + * Find control point or generator owned by the same team self which is nearest to pos + * if max_dist is positive, only control points within this range will be considered + */ +entity cq_Nearest_ControlPoint(vector pos, float max_dist) +{ + entity tmp_entity, closest_target = world; + for(tmp_entity = cq_worldcplist; tmp_entity; tmp_entity = tmp_entity.cq_worldcpnext) + { + if(SAME_TEAM(tmp_entity, self)) + if(tmp_entity.cq_status == CP_CAPTURED) + if(max_dist <= 0 || vlen(tmp_entity.origin - pos) <= max_dist) + if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world) + closest_target = tmp_entity; + } + + return closest_target; +} + +/* + * Find control point or generator owned by the same team self which is nearest to pos + * if max_dist is positive, only control points within this range will be considered + * This function only check distances on the XY plane, disregarding Z + */ +entity cq_Nearest_ControlPoint_2D(vector pos, float max_dist) +{ + entity tmp_entity, closest_target = world; + vector delta; + float smallest_distance = 0, distance; + + for(tmp_entity = cq_worldcplist; tmp_entity; tmp_entity = tmp_entity.cq_worldcpnext) + { + delta = tmp_entity.origin - pos; + delta_z = 0; + distance = vlen(delta); + + if(SAME_TEAM(tmp_entity, self)) + if(tmp_entity.cq_status == CP_CAPTURED) + if(max_dist <= 0 || distance <= max_dist) + if(closest_target == world || distance <= smallest_distance ) + { + closest_target = tmp_entity; + smallest_distance = distance; + } + } + + return closest_target; +} +/** + * find the number of control points and generators in the same team as self + */ +float cq_Count_SelfControlPoints() +{ + float n = 0; + entity tmp_entity; + for(tmp_entity = cq_worldcplist; tmp_entity; tmp_entity = tmp_entity.cq_worldcpnext) + { + if(SAME_TEAM(tmp_entity, self)) + if(tmp_entity.cq_status == CP_CAPTURED) + n++; + } + return n; +} + +/** + * Teleport player to a random position near tele_target + * if tele_effects is true, teleport sound+particles are created + * return FALSE on failure + */ +float cq_Teleport(entity player, entity tele_target, float range, float tele_effects) +{ + if ( !tele_target ) + return FALSE; + + float i; + vector loc; + float theta; + for(i = 0; i < 16; ++i) + { + theta = random() * 2 * M_PI; + loc_y = sin(theta); + loc_x = cos(theta); + loc_z = 0; + loc *= random() * range; + + loc += tele_target.origin + '0 0 128'; + + tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, player); + if(trace_fraction == 1.0 && !trace_startsolid) + { + traceline(tele_target.origin, loc, MOVE_NOMONSTERS, tele_target); // double check to make sure we're not spawning outside the world + if(trace_fraction == 1.0) + { + if ( tele_effects ) + { + Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1); + sound (player, CH_TRIGGER, "misc/teleport.wav", VOL_BASE, ATTEN_NORM); + } + setorigin(player, loc); + player.teleport_antispam = time + autocvar_g_conquest_teleport_wait; + + if ( tele_effects ) + Send_Effect(EFFECT_TELEPORT, player.origin + v_forward * 32, '0 0 0', 1); + return TRUE; + } + } + } + + return FALSE; +} + + +// ================== +// Legacy Bot Support +// ================== + +void havocbot_role_cq_liberating(); + +void havocbot_goalrating_defendpoints(float ratingscale, vector org, float sradius) +{ + entity head; + float distance; + + for(head = cq_worldcplist; head; head = head.cq_worldcpnext) + { + if (SAME_TEAM(head, self)) + { + if (head.health < head.max_health) + { + distance = vlen(head.origin - org); + if (distance > sradius) + continue; + navigation_routerating(head, ratingscale, 2000); + } + else + { + // If control point is not under attack, seek it out anyway + navigation_routerating(head, ratingscale/3, 2000); + } + } + } +} + +void havocbot_goalrating_enemypoints(float ratingscale, vector org, float sradius) +{ + entity head; + float distance; + + for(head = cq_worldcplist; head; head = head.cq_worldcpnext) + { + if (DIFF_TEAM(head, self)) + { + distance = vlen(head.origin - org); + if (distance > sradius) + continue; + navigation_routerating(head, ratingscale, 2000); + } + } +} + +void havocbot_role_cq_offense() +{ + entity head; + float owned; + + if(self.deadflag != DEAD_NO) + return; + + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + random() * 10 + 20; + + // Count how many control points on team are owned. + owned = 0; + for(head = cq_worldcplist; head; head = head.cq_worldcpnext) + { + if ((SAME_TEAM(head, self)) && (head.cq_status == CP_CAPTURED)) + owned++; + } + + // If only one left on team or if role has timed out then start trying to liberate control points. + if ((owned == 0) || (time > self.havocbot_role_timeout)) + { + dprint("changing role to liberating\n"); + self.havocbot_role = havocbot_role_cq_liberating; + self.havocbot_role_timeout = 0; + return; + } + + if (time > self.bot_strategytime) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + + navigation_goalrating_start(); + havocbot_goalrating_items(10000, self.origin, 10000); + havocbot_goalrating_enemypoints(20000, self.origin, 10000); + havocbot_goalrating_defendpoints(9000, self.origin, 10000); + //havocbot_goalrating_waypoints(1, self.origin, 1000); + navigation_goalrating_end(); + } +} + +void havocbot_role_cq_liberating() +{ + if(self.deadflag != DEAD_NO) + return; + + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + random() * 10 + 20; + + if (time > self.havocbot_role_timeout) + { + dprint("changing role to offense\n"); + self.havocbot_role = havocbot_role_cq_offense; + self.havocbot_role_timeout = 0; + return; + } + + if (time > self.bot_strategytime) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + + navigation_goalrating_start(); + havocbot_goalrating_items(8000, self.origin, 10000); + havocbot_goalrating_enemyplayers(10000, self.origin, 10000); + havocbot_goalrating_defendpoints(20000, self.origin, 10000); + //havocbot_goalrating_waypoints(1, self.origin, 1000); + navigation_goalrating_end(); + } +} + + +// ============= +// Compatibility +// ============= + +void cq_Setup_Compat_dom() +{ + // if map already has control points, don't spawn more + if(cq_worldcplist && (!cq_worldcplist.cq_compat || cq_worldcplist.cq_compat != COMPAT_DOM)) + { + self.think = SUB_Remove; + self.nextthink = time; + return; + } + + float redcp = 0, bluecp = 0, yellowcp = 0, pinkcp = 0; + + entity head; + for(head = cq_worldcplist; head; head = head.cq_worldcpnext) + { + switch(head.team) + { + case NUM_TEAM_1: ++redcp; break; + case NUM_TEAM_2: ++bluecp; break; + case NUM_TEAM_3: ++yellowcp; break; + case NUM_TEAM_4: ++pinkcp; break; + } + } + + if(!redcp) { self.team = NUM_TEAM_1; } + if(!bluecp) { self.team = NUM_TEAM_2; } + if(!yellowcp && cq_teams >= 3) { self.team = NUM_TEAM_3; } + if(!pinkcp && cq_teams >= 4) { self.team = NUM_TEAM_4; } + + self.cq_compat = COMPAT_DOM; // compatibility flag + + spawnfunc_conquest_controlpoint(); +} + +void cq_Setup_Compat_ons() +{ + // if map already has control points, don't spawn more + if(cq_worldcplist && (!cq_worldcplist.cq_compat || cq_worldcplist.cq_compat != COMPAT_ONS)) + { + self.think = SUB_Remove; + self.nextthink = time; + return; + } + + // teams are already setup for onslaught + + self.cq_compat = COMPAT_ONS; // compatibility flag + + spawnfunc_conquest_controlpoint(); +} + + +// ============== +// Hook Functions +// ============== + +MUTATOR_HOOKFUNCTION(conquest_RemovePlayer) +{ + self.cq_deathloc = '0 0 0'; + return FALSE; +} + +MUTATOR_HOOKFUNCTION(conquest_PlayerDies) +{ + frag_target.cq_deathloc = frag_target.origin; + + if ( autocvar_g_conquest_spawn_choose ) + if ( cq_Count_SelfControlPoints() > 1 ) + stuffcmd(self, "qc_cmd_cl hud clickradar\n"); + return FALSE; +} + +MUTATOR_HOOKFUNCTION(conquest_ResetMap) +{ + for(self = cq_worldcplist; self; self = self.cq_worldcpnext) + self.reset(); // do this now as teams aren't setup in time for PlayerSpawn + + FOR_EACH_PLAYER(self) + PutClientInServer(); + return FALSE; +} + +MUTATOR_HOOKFUNCTION(conquest_PlayerSpawn) +{ + if ( autocvar_g_conquest_spawn_choose ) + if ( self.cq_spawn_by ) + if ( cq_Teleport(self,self.cq_spawn_by,autocvar_g_conquest_teleport_radius,FALSE) ) + { + self.cq_spawn_by = world; + return FALSE; + } + + float random_target = !(autocvar_g_conquest_spawn_close_to_death), owned_count = cq_Count_SelfControlPoints(); + entity tmp_entity, closest_target = world; + vector spawn_loc = self.cq_deathloc; + + // new joining player or round reset, don't bother checking + //if(spawn_loc == '0 0 0') { return FALSE; } + + if(random_target) { RandomSelection_Init(); } + + for(tmp_entity = cq_worldcplist; tmp_entity; tmp_entity = tmp_entity.cq_worldcpnext) + { + if(SAME_TEAM(tmp_entity, self) || (!tmp_entity.team && !owned_count)) + if(tmp_entity.cq_status == CP_CAPTURED || !owned_count) + if(random_target) + RandomSelection_Add(tmp_entity, 0, string_null, 1, 1); + else if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world) + closest_target = tmp_entity; + } + + if(random_target) { closest_target = RandomSelection_chosen_ent; } + + if(!closest_target) + { + for(tmp_entity = cq_worldcplist; tmp_entity; tmp_entity = tmp_entity.cq_worldcpnext) + { + if(SAME_TEAM(tmp_entity, self) || (!tmp_entity.team && !owned_count)) + if(tmp_entity.cq_status == CP_CAPTURED || !owned_count) + { + closest_target = tmp_entity; + break; + } + } + } + + if(closest_target) + { + float i; + vector loc; + for(i = 0; i < 10; ++i) + { + loc = closest_target.origin + '0 0 96'; + if(random() >= 0.5) + loc += ('1 1 0' * random()) * 1024; + else + loc -= ('1 1 0' * random()) * 1024; + tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self); + if(trace_fraction == 1.0 && !trace_startsolid) + { + traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world + if(trace_fraction == 1.0) + { + setorigin(self, loc); + self.angles = normalize(loc - closest_target.origin); + return FALSE; + } + } + } + } + + print("Warning: No spawns found for team ", ftos(self.team), "\n"); + return FALSE; +} + +MUTATOR_HOOKFUNCTION(conquest_SV_ParseClientCommand) +{ + if(MUTATOR_RETURNVALUE) // command was already handled? + return FALSE; + + if ( cmd_name == "cq_spawn" ) + { + vector pos = self.origin; + if(cmd_argc > 1) + pos_x = stof(argv(1)); + if(cmd_argc > 2) + pos_y = stof(argv(2)); + if(cmd_argc > 3) + pos_z = stof(argv(3)); + + if ( IS_PLAYER(self) ) + { + if(!self.vehicle) + if(!self.frozen) + { + entity source_point = cq_Nearest_ControlPoint(self.origin, autocvar_g_conquest_teleport_radius); + + if ( !source_point && self.health > 0 ) + { + sprint(self, "\nYou need to be next to a control point\n"); + return 1; + } + + + entity closest_target = cq_Nearest_ControlPoint_2D(pos, autocvar_g_conquest_click_radius); + + if ( closest_target == world ) + { + sprint(self, "\nNo control point found\n"); + return 1; + } + + if ( self.health <= 0 ) + { + self.cq_spawn_by = closest_target; + self.respawn_flags = self.respawn_flags | RESPAWN_FORCE; + } + else + { + if ( source_point == closest_target ) + { + sprint(self, "\nTeleporting to the same point\n"); + return 1; + } + + if ( !cq_Teleport(self,closest_target,autocvar_g_conquest_teleport_radius,TRUE) ) + sprint(self, "\nUnable to teleport there\n"); + } + + return 1; + } + + sprint(self, "\nNo teleportation for you\n"); + } + + return 1; + } + return 0; +} + +MUTATOR_HOOKFUNCTION(conquest_PlayerUseKey) +{ + if(MUTATOR_RETURNVALUE || gameover) { return FALSE; } + + if((time > self.teleport_antispam) && (self.deadflag == DEAD_NO) && !self.vehicle && !self.frozen) + { + entity source_point = cq_Nearest_ControlPoint(self.origin, autocvar_g_conquest_teleport_radius); + if ( source_point ) + { + stuffcmd(self, "qc_cmd_cl hud clickradar\n"); + return TRUE; + } + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(conquest_OnEntityPreSpawn) +{ + // onslaught support comes first, as it's most likely to have the best layout + if(self.classname == "onslaught_generator" || self.classname == "onslaught_controlpoint") + { + entity cp = spawn(), oldself = self; + cp.team = self.team; + setorigin(cp, self.origin + '0 0 20'); + self = cp; + droptofloor(); + InitializeEntity(cp, cq_Setup_Compat_ons, INITPRIO_FINDTARGET); + self = oldself; + return FALSE; + } + if(self.classname == "dom_controlpoint") + { + entity cp = spawn(), oldself = self; + // domination doesn't use teams + setorigin(cp, self.origin + '0 0 20'); + self = cp; + droptofloor(); + InitializeEntity(cp, cq_Setup_Compat_dom, INITPRIO_FINDTARGET); + self = oldself; + return FALSE; + } + return FALSE; +} + +MUTATOR_HOOKFUNCTION(conquest_BotRoles) +{ + self.havocbot_role = havocbot_role_cq_offense; + return TRUE; +} + +MUTATOR_HOOKFUNCTION(conquest_GetTeamCount) +{ + ret_float = cq_teams; + return FALSE; +} + +void cq_ScoreRules(float teams) +{ + ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, TRUE); + + ScoreInfo_SetLabel_TeamScore(ST_CONQUEST_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_CONQUEST_LIBERATED, "liberated", 0); + ScoreInfo_SetLabel_PlayerScore(SP_CONQUEST_CAPTURED, "captured", 0); + + ScoreRules_basics_end(); +} + +void cq_DelayedInit() +{ + cq_teams = autocvar_g_conquest_teams; + if(autocvar_g_conquest_teams_override >= 2) { cq_teams = autocvar_g_conquest_teams_override; } + cq_teams = bound(2, cq_teams, 4); + cq_ScoreRules(cq_teams); + + round_handler_Spawn(cq_CheckTeams, cq_CheckWinner, func_null); + round_handler_Init(5, autocvar_g_conquest_warmup, autocvar_g_conquest_round_timelimit); +} + +MUTATOR_DEFINITION(gamemode_conquest) +{ + //precache_model("models/conquest/spawn.md3"); + //precache_model("models/conquest/flag.md3"); + //precache_model("models/conquest/stand.md3"); + + MUTATOR_HOOK(MakePlayerObserver, conquest_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(ClientDisconnect, conquest_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerSpawn, conquest_PlayerSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDies, conquest_PlayerDies, CBC_ORDER_ANY); + MUTATOR_HOOK(reset_map_global, conquest_ResetMap, CBC_ORDER_ANY); + MUTATOR_HOOK(SV_ParseClientCommand, conquest_SV_ParseClientCommand, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerUseKey, conquest_PlayerUseKey, CBC_ORDER_ANY); + MUTATOR_HOOK(OnEntityPreSpawn, conquest_OnEntityPreSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(HavocBot_ChooseRole, conquest_BotRoles, CBC_ORDER_ANY); + MUTATOR_HOOK(GetTeamCount, conquest_GetTeamCount, CBC_ORDER_ANY); + + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + + InitializeEntity(world, cq_DelayedInit, INITPRIO_GAMETYPE); + } + + MUTATOR_ONREMOVE + { + error("This is a game type and it cannot be removed at runtime."); + } + + return 0; +} diff --git a/qcsrc/server/mutators/gamemode_conquest.qh b/qcsrc/server/mutators/gamemode_conquest.qh new file mode 100644 index 000000000..ee6a1fd9a --- /dev/null +++ b/qcsrc/server/mutators/gamemode_conquest.qh @@ -0,0 +1,44 @@ +// these are needed since mutators are compiled last + +//csqc networking flags +#define CQSF_SETUP 1 //! Initial setup, responsible for communicating location, y-angle and model +#define CQSF_TEAM 2 //! What team point belong to +#define CQSF_HEALTH 4 //! Capture progress. Networked as 0--255 +#define CQSF_STATE 8 //! Captured or not +#define CQSF_NAME 16 //! Display name (can be defined by mapper) + +// score rule declarations +#define ST_CONQUEST_ROUNDS 1 +#define SP_CONQUEST_LIBERATED 4 +#define SP_CONQUEST_CAPTURED 5 + +// list of control points on the map +entity cq_worldcplist; +.entity cq_worldcpnext; + +// control point constants +float cq_teams; +#define CQ_CP_THINKRATE 0.15 + +#define CQ_CP_MIN ('-35 -35 -3') +#define CQ_CP_MAX ('35 35 195') + +// teleportation +.float teleport_antispam; +.vector cq_deathloc; +.entity cq_spawn_by; + +// statuses +#define CP_NEUTRAL 1 +#define CP_CAPTURED 2 + +// control point properties +.float cq_status; // status of the control point (CP_NEUTRAL, CP_CAPTURED declared globally) + +// compatibility with old maps +#define COMPAT_DOM 1 +#define COMPAT_ONS 2 +.float cq_compat; // for checking if a map already has conquest support + +.float cq_capdistance; +.float cq_originalteam; // stored spawn team for resetting diff --git a/qcsrc/server/mutators/gamemode_ctf.qc b/qcsrc/server/mutators/gamemode_ctf.qc index 4e051d197..eff1c082e 100644 --- a/qcsrc/server/mutators/gamemode_ctf.qc +++ b/qcsrc/server/mutators/gamemode_ctf.qc @@ -17,7 +17,8 @@ void ctf_FakeTimeLimit(entity e, float t) void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later { if(autocvar_sv_eventlog) - GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); + GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : ""))); + //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""), ")); } void ctf_CaptureRecord(entity flag, entity player) @@ -27,11 +28,13 @@ void ctf_CaptureRecord(entity flag, entity player) string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname")); // notify about shit - if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); } - else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); } - else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); } + if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); } + else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); } + else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); } + else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); } // write that shit in the database + if(!ctf_oneflag) // but not in 1-flag mode if((!ctf_captimerecord) || (cap_time < cap_record)) { ctf_captimerecord = cap_time; @@ -118,15 +121,21 @@ float ctf_CheckPassDirection(vector head_center, vector passer_center, vector pa float ctf_CaptureShield_CheckStatus(entity p) { - float s, se; + float s, s2, s3, s4, se, se2, se3, se4, sr, ser; entity e; float players_worseeq, players_total; if(ctf_captureshield_max_ratio <= 0) return FALSE; - s = PlayerScore_Add(p, SP_SCORE, 0); - if(s >= -ctf_captureshield_min_negscore) + s = PlayerScore_Add(p, SP_CTF_CAPS, 0); + s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0); + s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0); + s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0); + + sr = ((s - s2) + (s3 + s4)); + + if(sr >= -ctf_captureshield_min_negscore) return FALSE; players_total = players_worseeq = 0; @@ -134,8 +143,14 @@ float ctf_CaptureShield_CheckStatus(entity p) { if(DIFF_TEAM(e, p)) continue; - se = PlayerScore_Add(e, SP_SCORE, 0); - if(se <= s) + se = PlayerScore_Add(e, SP_CTF_CAPS, 0); + se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0); + se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0); + se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0); + + ser = ((se - se2) + (se3 + se4)); + + if(ser <= sr) ++players_worseeq; ++players_total; } @@ -161,16 +176,28 @@ void ctf_CaptureShield_Update(entity player, float wanted_status) float ctf_CaptureShield_Customize() { + if(self.enemy.active != ACTIVE_ACTIVE) { return TRUE; } if(!other.ctf_captureshielded) { return FALSE; } - if(SAME_TEAM(self, other)) { return FALSE; } + if(CTF_SAMETEAM(self, other)) { return FALSE; } return TRUE; } void ctf_CaptureShield_Touch() { + if(self.enemy.active != ACTIVE_ACTIVE) + { + vector mymid = (self.absmin + self.absmax) * 0.5; + vector othermid = (other.absmin + other.absmax) * 0.5; + + Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force); + if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_INACTIVE); } + + return; + } + if(!other.ctf_captureshielded) { return; } - if(SAME_TEAM(self, other)) { return; } + if(CTF_SAMETEAM(self, other)) { return; } vector mymid = (self.absmin + self.absmax) * 0.5; vector othermid = (other.absmin + other.absmax) * 0.5; @@ -219,7 +246,7 @@ void ctf_Handle_Drop(entity flag, entity player, float droptype) flag.ctf_status = FLAG_DROPPED; // messages and sounds - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname); + Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname); sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE); ctf_EventLog("dropped", player.team, player); @@ -281,11 +308,11 @@ void ctf_Handle_Retrieve(entity flag, entity player) FOR_EACH_REALPLAYER(tmp_player) { if(tmp_player == sender) - Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname); + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname); else if(tmp_player == player) - Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname); + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname); else if(SAME_TEAM(tmp_player, sender)) - Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname); + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname); } // create new waypoint @@ -343,7 +370,8 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype) // other sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM); - WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin); + if(flag.passeffect == "") { Send_Effect(flag.passeffectnum, player.origin, targ_origin, 0); } + else { WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin); } ctf_EventLog("pass", flag.team, player); break; } @@ -352,7 +380,7 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype) { makevectors((player.v_angle_y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle_x, autocvar_g_ctf_throw_angle_max) * '1 0 0')); - flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1))); + flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward))); flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE); ctf_Handle_Drop(flag, player, droptype); break; @@ -393,16 +421,29 @@ void ctf_Handle_Capture(entity flag, entity toucher, float capturetype) { entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher); entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper); + entity player_team_flag = world, tmp_entity; float old_time, new_time; - if (!player) { return; } // without someone to give the reward to, we can't possibly cap + if(!player) { return; } // without someone to give the reward to, we can't possibly cap + if(CTF_DIFFTEAM(player, flag)) { return; } + + if(ctf_oneflag) + for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) + if(SAME_TEAM(tmp_entity, player)) + { + player_team_flag = tmp_entity; + break; + } nades_GiveBonus(player, autocvar_g_nades_bonus_score_high ); + player.throw_prevtime = time; + player.throw_count = 0; + // messages and sounds - Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_)); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL)); ctf_CaptureRecord(enemy_flag, player); - sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTEN_NONE); + sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE); switch(capturetype) { @@ -421,7 +462,8 @@ void ctf_Handle_Capture(entity flag, entity toucher, float capturetype) PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time); // effects - pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1); + if(flag.capeffect == "") { Send_Effect(flag.capeffectnum, flag.origin, '0 0 0', 1); } + else { pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1); } //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1); // other @@ -442,14 +484,14 @@ void ctf_Handle_Capture(entity flag, entity toucher, float capturetype) void ctf_Handle_Return(entity flag, entity player) { // messages and sounds - if(player.flags & FL_MONSTER) + if(IS_MONSTER(player)) { - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name); } - else + else if(flag.team) { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_)); - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname); } sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE); ctf_EventLog("return", flag.team, player); @@ -472,6 +514,10 @@ void ctf_Handle_Return(entity flag, entity player) flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time } + // other + if(player.flagcarried == flag) + WaypointSprite_Kill(player.wps_flagcarrier); + // reset the flag ctf_RespawnFlag(flag); } @@ -480,6 +526,7 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype) { // declarations float pickup_dropped_score; // used to calculate dropped pickup score + entity tmp_entity; // temporary entity // attach the flag to the player flag.owner = player; @@ -511,12 +558,28 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype) } // messages and sounds - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname); - Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname); if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); } - - Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname); - Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname); + if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); } + else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); } + else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team)); } + + Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname); + + if(!flag.team) + FOR_EACH_PLAYER(tmp_entity) + if(tmp_entity != player) + if(DIFF_TEAM(player, tmp_entity)) + Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); + + if(flag.team) + FOR_EACH_PLAYER(tmp_entity) + if(tmp_entity != player) + if(CTF_SAMETEAM(flag, tmp_entity)) + if(SAME_TEAM(player, tmp_entity)) + Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname); + else + Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname); sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE); @@ -554,7 +617,8 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype) } // effects - pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1); + if(flag.toucheffect == "") { Send_Effect(flag.toucheffectnum, player.origin, '0 0 0', 1); } + else { pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1); } // waypoints if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); } @@ -577,14 +641,14 @@ void ctf_CheckFlagReturn(entity flag, float returntype) { switch(returntype) { - case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break; - case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break; - case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break; - case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break; + case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break; + case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break; + case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break; + case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break; default: case RETURN_TIMEOUT: - { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; } + { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; } } sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE); ctf_EventLog("returned", flag.team, world); @@ -593,10 +657,25 @@ void ctf_CheckFlagReturn(entity flag, float returntype) } } +float ctf_Stalemate_Customize() +{ + // make spectators see what the player would see + entity e, wp_owner; + e = WaypointSprite_getviewentity(other); + wp_owner = self.owner; + + // team waypoints + if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return FALSE; } + if(SAME_TEAM(wp_owner, e)) { return FALSE; } + if(!IS_PLAYER(e)) { return FALSE; } + + return TRUE; +} + void ctf_CheckStalemate(void) { // declarations - float stale_red_flags = 0, stale_blue_flags = 0; + float stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0; entity tmp_entity; entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs @@ -606,7 +685,7 @@ void ctf_CheckStalemate(void) { if(autocvar_g_ctf_stalemate) if(tmp_entity.ctf_status != FLAG_BASE) - if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time) + if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag { tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist ctf_staleflaglist = tmp_entity; @@ -615,15 +694,25 @@ void ctf_CheckStalemate(void) { case NUM_TEAM_1: ++stale_red_flags; break; case NUM_TEAM_2: ++stale_blue_flags; break; + case NUM_TEAM_3: ++stale_yellow_flags; break; + case NUM_TEAM_4: ++stale_pink_flags; break; + default: ++stale_neutral_flags; break; } } } - if(stale_red_flags && stale_blue_flags) + if(ctf_oneflag) + stale_flags = (stale_neutral_flags >= 1); + else + stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1); + + if(ctf_oneflag && stale_flags == 1) + ctf_stalemate = TRUE; + else if(stale_flags == ctf_teams) ctf_stalemate = TRUE; - else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2) + else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2) { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; } - else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1) + else if(stale_flags < ctf_teams && autocvar_g_ctf_stalemate_endcondition == 1) { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; } // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary @@ -632,7 +721,10 @@ void ctf_CheckStalemate(void) for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext) { if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier)) - WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, tmp_entity.team, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team)); + { + WaypointSprite_Spawn(((ctf_oneflag) ? "flagcarrier" : "enemyflagcarrier"), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team)); + tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize; + } } if (!wpforenemy_announced) @@ -649,9 +741,15 @@ void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float death { if(ITEM_DAMAGE_NEEDKILL(deathtype)) { - // automatically kill the flag and return it - self.health = 0; - ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + if(autocvar_g_ctf_flag_return_damage_delay) + { + self.ctf_flagdamaged = TRUE; + } + else + { + self.health = 0; + ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + } return; } if(autocvar_g_ctf_flag_return_damage) @@ -734,7 +832,13 @@ void ctf_FlagThink() return; } } - if(autocvar_g_ctf_flag_return_time) + if(self.ctf_flagdamaged) + { + self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE); + ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + return; + } + else if(autocvar_g_ctf_flag_return_time) { self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE); ctf_CheckFlagReturn(self, RETURN_TIMEOUT); @@ -764,6 +868,13 @@ void ctf_FlagThink() wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check) } } + if(CTF_SAMETEAM(self, self.owner) && self.team) + { + if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed + ctf_Handle_Throw(self.owner, world, DROP_THROW); + else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried) + ctf_Handle_Return(self, self.owner); + } return; } @@ -802,29 +913,35 @@ void ctf_FlagThink() void ctf_FlagTouch() { if(gameover) { return; } + if(self.active != ACTIVE_ACTIVE) { return; } if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; } - entity toucher = other; - float is_not_monster = (!(toucher.flags & FL_MONSTER)); + entity toucher = other, tmp_entity; + float is_not_monster = (!IS_MONSTER(toucher)), num_perteam = 0; // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces if(ITEM_TOUCH_NEEDKILL()) { - self.health = 0; - ctf_CheckFlagReturn(self, RETURN_NEEDKILL); - return; + if(!autocvar_g_ctf_flag_return_damage_delay) + { + self.health = 0; + ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + } + if(!self.ctf_flagdamaged) { return; } } + FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; } + // special touch behaviors if(toucher.frozen) { return; } - else if(toucher.vehicle_flags & VHF_ISVEHICLE) + else if(IS_VEHICLE(toucher)) { if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner) toucher = toucher.owner; // the player is actually the vehicle owner, not other else return; // do nothing } - else if(toucher.flags & FL_MONSTER) + else if(IS_MONSTER(toucher)) { if(!autocvar_g_ctf_allow_monster_touch) return; // do nothing @@ -833,7 +950,8 @@ void ctf_FlagTouch() { if(time > self.wait) // if we haven't in a while, play a sound/effect { - pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1); + if(self.toucheffect == "") { Send_Effect(self.toucheffectnum, self.origin, '0 0 0', 1); } + else { pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1); } sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM); self.wait = time + FLAG_TOUCHRATE; } @@ -845,16 +963,23 @@ void ctf_FlagTouch() { case FLAG_BASE: { - if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster) + if(ctf_oneflag) + { + if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster) + ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base + else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) + ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag + } + else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster) ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base - else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) + else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag break; } case FLAG_DROPPED: { - if(SAME_TEAM(toucher, self)) + if(CTF_SAMETEAM(toucher, self) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && self.team) // automatically return if there's only 1 player on the team ctf_Handle_Return(self, toucher); // toucher just returned his own flag else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay))) ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag @@ -893,9 +1018,7 @@ void ctf_RespawnFlag(entity flag) // reset the player (if there is one) if((flag.owner) && (flag.owner.flagcarried == flag)) { - if(flag.owner.wps_enemyflagcarrier) - WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier); - + WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier); WaypointSprite_Kill(flag.wps_flagcarrier); flag.owner.flagcarried = world; @@ -907,7 +1030,7 @@ void ctf_RespawnFlag(entity flag) if((flag.owner) && (flag.owner.vehicle)) flag.scale = FLAG_SCALE; - if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped)) + if(flag.ctf_status == FLAG_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); } // reset the flag @@ -930,6 +1053,7 @@ void ctf_RespawnFlag(entity flag) flag.ctf_dropper = world; flag.ctf_pickuptime = 0; flag.ctf_droptime = 0; + flag.ctf_flagdamaged = 0; ctf_CheckStalemate(); } @@ -943,6 +1067,22 @@ void ctf_Reset() ctf_RespawnFlag(self); } +void ctf_Use() +{ + if(self.ctf_status != FLAG_BASE) { return; } + + self.active = ((self.active) ? ACTIVE_NOT : ACTIVE_ACTIVE); + + if(self.active == ACTIVE_ACTIVE) + WaypointSprite_Ping(self.wps_flagbase); +} + +float ctf_FlagWaypoint_Customize() +{ + if(self.owner.active != ACTIVE_ACTIVE) { return FALSE; } + return TRUE; +} + void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup() { // bot waypoints @@ -951,8 +1091,20 @@ void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf self.bot_basewaypoint = self.nearestwaypoint; // waypointsprites - WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE)); - WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE)); + string basename = "base"; + + switch(self.team) + { + case NUM_TEAM_1: basename = "redbase"; break; + case NUM_TEAM_2: basename = "bluebase"; break; + case NUM_TEAM_3: basename = "yellowbase"; break; + case NUM_TEAM_4: basename = "pinkbase"; break; + default: basename = "neutralbase"; break; + } + + WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, ((self.team) ? Team_ColorRGB(self.team) : '1 1 1')); + WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, FALSE) : '1 1 1')); + self.wps_flagbase.customizeentityforclient = ctf_FlagWaypoint_Customize; // captureshield setup ctf_CaptureShield_Spawn(self); @@ -961,7 +1113,6 @@ void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc { // declarations - teamnumber = fabs(teamnumber - bound(0, autocvar_g_ctf_reverse, 1)); // if we were originally 1, this will become 0. If we were originally 0, this will become 1. self = flag; // for later usage with droptofloor() // main setup @@ -970,9 +1121,8 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag setattachment(flag, world, ""); - flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name - flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue) - flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough) + flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber))); + flag.team = teamnumber; flag.classname = "item_flag_team"; flag.target = "###item###"; // wut? flag.flags = FL_ITEM | FL_NOTARGET; @@ -984,30 +1134,33 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag flag.event_damage = ctf_FlagDamage; flag.pushable = TRUE; flag.teleportable = TELEPORT_NORMAL; + flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable; flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable; flag.velocity = '0 0 0'; flag.mangle = flag.angles; flag.reset = ctf_Reset; + flag.use = ctf_Use; flag.touch = ctf_FlagTouch; flag.think = ctf_FlagThink; flag.nextthink = time + FLAG_THINKRATE; flag.ctf_status = FLAG_BASE; // appearence - if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); } + if(flag.model == "") { flag.model = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_model : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_model : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_model : ((teamnumber == NUM_TEAM_4) ? autocvar_g_ctf_flag_pink_model : autocvar_g_ctf_flag_neutral_model)))); } if(!flag.scale) { flag.scale = FLAG_SCALE; } - if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); } - if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); } - if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); } - if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); } + if(!flag.skin) { flag.skin = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_skin : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_skin : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_skin : ((teamnumber == NUM_TEAM_4) ? autocvar_g_ctf_flag_pink_skin : autocvar_g_ctf_flag_neutral_skin)))); } + if(flag.toucheffect == "") { flag.toucheffectnum = ((teamnumber == NUM_TEAM_1) ? EFFECT_FLAG_RED_TOUCH : ((teamnumber == NUM_TEAM_2) ? EFFECT_FLAG_BLUE_TOUCH : ((teamnumber == NUM_TEAM_3) ? EFFECT_FLAG_YELLOW_TOUCH : ((teamnumber == NUM_TEAM_4) ? EFFECT_FLAG_PINK_TOUCH : EFFECT_FLAG_NEUTRAL_TOUCH)))); } + if(flag.passeffect == "") { flag.passeffectnum = ((teamnumber == NUM_TEAM_1) ? EFFECT_RED_PASS : ((teamnumber == NUM_TEAM_2) ? EFFECT_BLUE_PASS : ((teamnumber == NUM_TEAM_3) ? EFFECT_YELLOW_PASS : ((teamnumber == NUM_TEAM_4) ? EFFECT_PINK_PASS : EFFECT_NEUTRAL_PASS)))); } + if(flag.capeffect == "") { flag.capeffectnum = ((teamnumber == NUM_TEAM_1) ? EFFECT_RED_CAP : ((teamnumber == NUM_TEAM_2) ? EFFECT_BLUE_CAP : ((teamnumber == NUM_TEAM_3) ? EFFECT_YELLOW_CAP : ((teamnumber == NUM_TEAM_4) ? EFFECT_PINK_CAP : 0)))); } // neutral flag cant be capped itself + if(!flag.active) { flag.active = ACTIVE_ACTIVE; } // sound - if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); } - if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); } - if(flag.snd_flag_capture == "") { flag.snd_flag_capture = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag + if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber == NUM_TEAM_1) ? "ctf/red_taken.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_taken.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_taken.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_taken.wav" : "ctf/neutral_taken.wav")))); } + if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber == NUM_TEAM_1) ? "ctf/red_returned.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_returned.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_returned.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_returned.wav" : "")))); } // neutral flag can't be returned by players + if(flag.snd_flag_capture == "") { flag.snd_flag_capture = ((teamnumber == NUM_TEAM_1) ? "ctf/red_capture.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_capture.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_capture.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_capture.wav" : "")))); } // again can't be captured if(flag.snd_flag_respawn == "") { flag.snd_flag_respawn = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match. - if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); } + if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber == NUM_TEAM_1) ? "ctf/red_dropped.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_dropped.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_dropped.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_dropped.wav" : "ctf/neutral_dropped.wav")))); } if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here @@ -1030,15 +1183,25 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag if(autocvar_g_ctf_flag_glowtrails) { - flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue + flag.glow_color = ((teamnumber == NUM_TEAM_1) ? 251 : ((teamnumber == NUM_TEAM_2) ? 210 : ((teamnumber == NUM_TEAM_3) ? 110 : ((teamnumber == NUM_TEAM_4) ? 145 : 254)))); flag.glow_size = 25; flag.glow_trail = 1; } flag.effects |= EF_LOWPRECISION; if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; } - if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); } - + if(autocvar_g_ctf_dynamiclights) + { + switch(teamnumber) + { + case NUM_TEAM_1: flag.effects |= EF_RED; break; + case NUM_TEAM_2: flag.effects |= EF_BLUE; break; + case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break; + case NUM_TEAM_4: flag.effects |= EF_RED; break; + default: flag.effects |= EF_DIMLIGHT; break; + } + } + // flag placement if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location { @@ -1091,7 +1254,7 @@ entity havocbot_ctf_find_flag(entity bot) f = ctf_worldflaglist; while (f) { - if (bot.team == f.team) + if (CTF_SAMETEAM(bot, f)) return f; f = f.ctf_worldflagnext; } @@ -1104,7 +1267,20 @@ entity havocbot_ctf_find_enemy_flag(entity bot) f = ctf_worldflaglist; while (f) { - if (bot.team != f.team) + if(ctf_oneflag) + { + if(CTF_DIFFTEAM(bot, f)) + { + if(f.team) + { + if(bot.flagcarried) + return f; + } + else if(!bot.flagcarried) + return f; + } + } + else if (CTF_DIFFTEAM(bot, f)) return f; f = f.ctf_worldflagnext; } @@ -1121,7 +1297,7 @@ float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius) FOR_EACH_PLAYER(head) { - if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot) + if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot) continue; if(vlen(head.origin - org) < tc_radius) @@ -1137,7 +1313,7 @@ void havocbot_goalrating_ctf_ourflag(float ratingscale) head = ctf_worldflaglist; while (head) { - if (self.team == head.team) + if (CTF_SAMETEAM(self, head)) break; head = head.ctf_worldflagnext; } @@ -1151,7 +1327,7 @@ void havocbot_goalrating_ctf_ourbase(float ratingscale) head = ctf_worldflaglist; while (head) { - if (self.team == head.team) + if (CTF_SAMETEAM(self, head)) break; head = head.ctf_worldflagnext; } @@ -1167,7 +1343,20 @@ void havocbot_goalrating_ctf_enemyflag(float ratingscale) head = ctf_worldflaglist; while (head) { - if (self.team != head.team) + if(ctf_oneflag) + { + if(CTF_DIFFTEAM(self, head)) + { + if(head.team) + { + if(self.flagcarried) + break; + } + else if(!self.flagcarried) + break; + } + } + else if(CTF_DIFFTEAM(self, head)) break; head = head.ctf_worldflagnext; } @@ -1335,7 +1524,10 @@ void havocbot_role_ctf_carrier() self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; navigation_goalrating_start(); - havocbot_goalrating_ctf_ourbase(50000); + if(ctf_oneflag) + havocbot_goalrating_ctf_enemybase(50000); + else + havocbot_goalrating_ctf_ourbase(50000); if(self.health<100) havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000); @@ -1723,27 +1915,39 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink) { entity flag; + float t = 0, t2 = 0, t3 = 0; + // initially clear items so they can be set as necessary later. - self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST - | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED); + self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST + | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST + | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST + | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST + | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST + | CTF_FLAG_NEUTRAL | CTF_SHIELDED); // scan through all the flags and notify the client about them for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) { + if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; } + if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; } + if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; } + if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; } + if(flag.team == 0) { t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; self.ctf_flagstatus |= CTF_FLAG_NEUTRAL; } + switch(flag.ctf_status) { case FLAG_PASSING: case FLAG_CARRY: { if((flag.owner == self) || (flag.pass_sender == self)) - self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag - else - self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag + self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag + else + self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag break; } case FLAG_DROPPED: { - self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map + self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map break; } } @@ -1751,7 +1955,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink) // item for stopping players from capturing the flag too often if(self.ctf_captureshielded) - self.items |= IT_CTF_SHIELDED; + self.ctf_flagstatus |= CTF_SHIELDED; // update the health of the flag carrier waypointsprite if(self.wps_flagcarrier) @@ -1775,7 +1979,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values t frag_force *= autocvar_g_ctf_flagcarrier_forcefactor; } } - else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier + else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier { if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON))) if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time) @@ -1797,7 +2001,11 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDies) } if(frag_target.flagcarried) - { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); } + { + entity tmp_entity = frag_target.flagcarried; + ctf_Handle_Throw(frag_target, world, DROP_NORMAL); + tmp_entity.ctf_dropper = world; + } return FALSE; } @@ -1982,7 +2190,7 @@ MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun) { if(self.flagcarried) { - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, ((self.flagcarried.team) ? APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL)); ctf_RespawnFlag(self.flagcarried); return TRUE; } @@ -2030,57 +2238,58 @@ MUTATOR_HOOKFUNCTION(ctf_BotRoles) return TRUE; } - -// ========== -// Spawnfuncs -// ========== - -/*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24) -CTF Starting point for a player in team one (Red). -Keys: "angle" viewing angle when spawning. */ -void spawnfunc_info_player_team1() +MUTATOR_HOOKFUNCTION(ctf_GetTeamCount) { - if(g_assault) { remove(self); return; } - - self.team = NUM_TEAM_1; // red - spawnfunc_info_player_deathmatch(); + //ret_float = ctf_teams; + ret_string = "ctf_team"; + return TRUE; } - -/*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24) -CTF Starting point for a player in team two (Blue). -Keys: "angle" viewing angle when spawning. */ -void spawnfunc_info_player_team2() +MUTATOR_HOOKFUNCTION(ctf_SpectateCopy) { - if(g_assault) { remove(self); return; } - - self.team = NUM_TEAM_2; // blue - spawnfunc_info_player_deathmatch(); + self.ctf_flagstatus = other.ctf_flagstatus; + return FALSE; } -/*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24) -CTF Starting point for a player in team three (Yellow). -Keys: "angle" viewing angle when spawning. */ -void spawnfunc_info_player_team3() +MUTATOR_HOOKFUNCTION(ctf_FormatMessage) { - if(g_assault) { remove(self); return; } + entity bluefc = world, redfc = world, yellowfc = world, pinkfc = world, tmp_entity; // NOTE: blue = red player + entity tfc = world, sfc = world; - self.team = NUM_TEAM_3; // yellow - spawnfunc_info_player_deathmatch(); -} + for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) + { + if(tmp_entity.owner) + { + switch(tmp_entity.team) + { + case NUM_TEAM_1: redfc = tmp_entity.owner; break; + case NUM_TEAM_2: bluefc = tmp_entity.owner; break; + case NUM_TEAM_3: yellowfc = tmp_entity.owner; break; + case NUM_TEAM_4: pinkfc = tmp_entity.owner; break; + } + if(SAME_TEAM(tmp_entity.owner, self)) { tfc = tmp_entity.owner; } + if(SAME_TEAM(tmp_entity, self)) { sfc = tmp_entity.owner; } + } + } -/*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24) -CTF Starting point for a player in team four (Purple). -Keys: "angle" viewing angle when spawning. */ -void spawnfunc_info_player_team4() -{ - if(g_assault) { remove(self); return; } + switch(format_escape) + { + case "r": format_replacement = ((tfc) ? tfc.netname : "(nobody)"); break; + case "R": format_replacement = ((sfc) ? sfc.netname : "(nobody)"); break; + case "t": format_replacement = ((redfc) ? redfc.netname : "(nobody)"); break; + case "T": format_replacement = ((bluefc) ? bluefc.netname : "(nobody)"); break; + case "p": format_replacement = ((yellowfc) ? yellowfc.netname : "(nobody)"); break; + case "P": format_replacement = ((pinkfc) ? pinkfc.netname : "(nobody)"); break; + } - self.team = NUM_TEAM_4; // purple - spawnfunc_info_player_deathmatch(); + return FALSE; } +// ========== +// Spawnfuncs +// ========== + /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37) CTF flag for team one (Red). Keys: @@ -2096,7 +2305,7 @@ void spawnfunc_item_flag_team1() { if(!g_ctf) { remove(self); return; } - ctf_FlagSetup(1, self); // 1 = red + ctf_FlagSetup(NUM_TEAM_1, self); } /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37) @@ -2114,7 +2323,62 @@ void spawnfunc_item_flag_team2() { if(!g_ctf) { remove(self); return; } - ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue. + ctf_FlagSetup(NUM_TEAM_2, self); +} + +/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag for team three (Yellow). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +void spawnfunc_item_flag_team3() +{ + if(!g_ctf) { remove(self); return; } + + ctf_FlagSetup(NUM_TEAM_3, self); +} + +/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag for team four (Pink). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +void spawnfunc_item_flag_team4() +{ + if(!g_ctf) { remove(self); return; } + + ctf_FlagSetup(NUM_TEAM_4, self); +} + +/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag (Neutral). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +void spawnfunc_item_flag_neutral() +{ + if(!g_ctf) { remove(self); return; } + if(!cvar("g_ctf_oneflag")) { remove(self); return; } + + ctf_FlagSetup(0, self); } /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32) @@ -2145,9 +2409,10 @@ void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); } // ============== // scoreboard setup -void ctf_ScoreRules() +void ctf_ScoreRules(float teams) { - ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE); + CheckAllowedTeams(world); + ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, TRUE); ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY); ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME); @@ -2175,15 +2440,28 @@ void ctf_SpawnTeam (string teamname, float teamcolor) void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up. { + ctf_teams = 2; + + entity tmp_entity; + for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) + { + if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); } + if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); } + if(tmp_entity.team == 0) { ctf_oneflag = TRUE; } + } + + ctf_teams = bound(2, ctf_teams, 4); + // if no teams are found, spawn defaults if(find(world, classname, "ctf_team") == world) { print("No ""ctf_team"" entities found on this map, creating them anyway.\n"); - ctf_SpawnTeam("Red", NUM_TEAM_1 - 1); - ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1); + float i; + for(i = 1; i <= ctf_teams; ++i) + ctf_SpawnTeam(Team_ColorName(Team_NumberToTeam(i)), Team_NumberToTeam(i) - 1); } - ctf_ScoreRules(); + ctf_ScoreRules(ctf_teams); } void ctf_Initialize() @@ -2194,6 +2472,8 @@ void ctf_Initialize() ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio; ctf_captureshield_force = autocvar_g_ctf_shield_force; + addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus); + InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE); } @@ -2214,6 +2494,9 @@ MUTATOR_DEFINITION(gamemode_ctf) MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY); MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY); MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY); + MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY); + MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY); + MUTATOR_HOOK(FormatMessage, ctf_FormatMessage, CBC_ORDER_ANY); MUTATOR_ONADD { diff --git a/qcsrc/server/mutators/gamemode_ctf.qh b/qcsrc/server/mutators/gamemode_ctf.qh index ca4961edd..2e80aa45f 100644 --- a/qcsrc/server/mutators/gamemode_ctf.qh +++ b/qcsrc/server/mutators/gamemode_ctf.qh @@ -34,9 +34,9 @@ void ctf_RespawnFlag(entity flag); #define VEHICLE_FLAG_SCALE 1.0 // waypoint colors -#define WPCOLOR_ENEMYFC(t) (colormapPaletteColor(t - 1, FALSE) * 0.75) +#define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, FALSE) * 0.75 : '1 1 1') #define WPCOLOR_FLAGCARRIER(t) ('0.8 0.8 0') -#define WPCOLOR_DROPPEDFLAG(t) (('0.25 0.25 0.25' + colormapPaletteColor(t - 1, FALSE)) * 0.5) +#define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, FALSE)) * 0.5 : '1 1 1') // sounds #define snd_flag_taken noise @@ -51,6 +51,10 @@ void ctf_RespawnFlag(entity flag); .string toucheffect; .string passeffect; .string capeffect; +// backup default effects +.float toucheffectnum; +.float passeffectnum; +.float capeffectnum; // list of flags on the map entity ctf_worldflaglist; @@ -101,6 +105,8 @@ float ctf_captimerecord; // record time for capturing the flag .entity ctf_dropper; // don't allow spam of dropping the flag .float max_flag_health; .float next_take_time; +.float ctf_flagdamaged; +float ctf_teams; // passing/throwing properties .float pass_distance; @@ -116,6 +122,9 @@ float ctf_captureshield_min_negscore; // punish at -20 points float ctf_captureshield_max_ratio; // punish at most 30% of each team float ctf_captureshield_force; // push force of the shield +// 1 flag ctf +float ctf_oneflag; // indicates whether or not a neutral flag has been found + // bot player logic #define HAVOCBOT_CTF_ROLE_NONE 0 #define HAVOCBOT_CTF_ROLE_DEFENSE 2 @@ -131,4 +140,30 @@ vector havocbot_ctf_middlepoint; float havocbot_ctf_middlepoint_radius; void havocbot_role_ctf_setrole(entity bot, float role); + +// team checking +#define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b)) +#define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b)) + #endif + +// networked flag statuses +.float ctf_flagstatus; + +const float CTF_RED_FLAG_TAKEN = 1; +const float CTF_RED_FLAG_LOST = 2; +const float CTF_RED_FLAG_CARRYING = 3; +const float CTF_BLUE_FLAG_TAKEN = 4; +const float CTF_BLUE_FLAG_LOST = 8; +const float CTF_BLUE_FLAG_CARRYING = 12; +const float CTF_YELLOW_FLAG_TAKEN = 16; +const float CTF_YELLOW_FLAG_LOST = 32; +const float CTF_YELLOW_FLAG_CARRYING = 48; +const float CTF_PINK_FLAG_TAKEN = 64; +const float CTF_PINK_FLAG_LOST = 128; +const float CTF_PINK_FLAG_CARRYING = 192; +const float CTF_NEUTRAL_FLAG_TAKEN = 256; +const float CTF_NEUTRAL_FLAG_LOST = 512; +const float CTF_NEUTRAL_FLAG_CARRYING = 768; +const float CTF_FLAG_NEUTRAL = 2048; +const float CTF_SHIELDED = 4096; diff --git a/qcsrc/server/mutators/gamemode_cts.qc b/qcsrc/server/mutators/gamemode_cts.qc index 9c674d45d..2d054c204 100644 --- a/qcsrc/server/mutators/gamemode_cts.qc +++ b/qcsrc/server/mutators/gamemode_cts.qc @@ -279,6 +279,18 @@ MUTATOR_HOOKFUNCTION(cts_SetMods) return FALSE; } +MUTATOR_HOOKFUNCTION(cts_WantWeapon) +{ + ret_float = (other.weapon == WEP_SHOTGUN); + return FALSE; +} + +MUTATOR_HOOKFUNCTION(cts_AllowMobSpawning) +{ + ret_string = "You cannot spawn monsters in CTS"; + return TRUE; +} + void cts_Initialize() { cts_ScoreRules(); @@ -301,6 +313,8 @@ MUTATOR_DEFINITION(gamemode_cts) MUTATOR_HOOK(PlayerDamage_Calculate, cts_PlayerDamage, CBC_ORDER_ANY); MUTATOR_HOOK(ForbidPlayerScore_Clear, cts_ForbidClearPlayerScore, CBC_ORDER_ANY); MUTATOR_HOOK(SetModname, cts_SetMods, CBC_ORDER_ANY); + MUTATOR_HOOK(WantWeapon, cts_WantWeapon, CBC_ORDER_ANY); + MUTATOR_HOOK(AllowMobSpawning, cts_AllowMobSpawning, CBC_ORDER_ANY); MUTATOR_ONADD { diff --git a/qcsrc/server/mutators/gamemode_deathmatch.qc b/qcsrc/server/mutators/gamemode_deathmatch.qc new file mode 100644 index 000000000..81caee50d --- /dev/null +++ b/qcsrc/server/mutators/gamemode_deathmatch.qc @@ -0,0 +1,36 @@ +void dm_DelayedInit() +{ +} + +MUTATOR_HOOKFUNCTION(dm_CountFrags) +{ + // announce remaining frags + return TRUE; +} + +MUTATOR_DEFINITION(gamemode_deathmatch) +{ + MUTATOR_HOOK(Scores_CountFragsRemaining, dm_CountFrags, CBC_ORDER_ANY); + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + InitializeEntity(world, dm_DelayedInit, INITPRIO_GAMETYPE); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back dm_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + print("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} diff --git a/qcsrc/server/mutators/gamemode_domination.qc b/qcsrc/server/mutators/gamemode_domination.qc index 8e4d929be..182ec1d0f 100644 --- a/qcsrc/server/mutators/gamemode_domination.qc +++ b/qcsrc/server/mutators/gamemode_domination.qc @@ -1,39 +1,24 @@ -void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later +void dom_EventLog(string mode, float cpteam, entity actor) // use an alias for easy changing and quick editing later { if(autocvar_sv_eventlog) - GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); -} - -void set_dom_state(entity e) -{ - e.dom_total_pps = total_pps; - e.dom_pps_red = pps_red; - e.dom_pps_blue = pps_blue; - if(domination_teams >= 3) - e.dom_pps_yellow = pps_yellow; - if(domination_teams >= 4) - e.dom_pps_pink = pps_pink; + GameLogEcho(sprintf(":dom:%s:%d%s", mode, cpteam, ((actor != world) ? strcat(":", ftos(actor.playerid)) : ""))); } void dompoint_captured () { entity head; - float old_delay, old_team, real_team; + float old_delay; string msg = "dom-neut"; - // now that the delay has expired, switch to the latest team to lay claim to this point - head = self.owner; - - real_team = self.cnt; + self.team = self.cnt; self.cnt = -1; dom_EventLog("taken", self.team, self.dmg_inflictor); self.dmg_inflictor = world; - self.goalentity = head; - self.model = head.mdl; - self.modelindex = head.dmg; - self.skin = head.skin; + self.skin = dom_skin[self.team]; + self.model = dom_model[self.team]; + self.modelindex = dom_modelindex[self.team]; float points, wait_time; if (autocvar_g_domination_point_amt) @@ -46,34 +31,53 @@ void dompoint_captured () wait_time = self.wait; if(domination_roundbased) - bprint(sprintf("^3%s^3%s\n", head.netname, self.message)); + { + PlayerScore_Add(self.enemy, SP_SCORE, +points); + string msg = ""; + if(dom_message[self.team] != "") + msg = dom_message[self.team]; + if(self.message != "") + msg = self.message; + if(msg != "") + bprint(strcat(msg, "\n")); + else + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_DOMINATION_CAPTURE_)); + } else - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time); + { + string msg = ""; + if(dom_message[self.team] != "") + msg = dom_message[self.team]; + if(self.message != "") + msg = self.message; + if(msg != "") + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_DOMINATION_CAPTURE_TIME_), msg, points, wait_time); + else + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_DOMINATION_CAPTURE_TIME_NOMSG_), points, wait_time); + } if(self.enemy.playerid == self.enemy_playerid) PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1); else self.enemy = world; - if (head.noise != "") - if(self.enemy) - sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM); - else - sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM); - if (head.noise1 != "") - play2all(head.noise1); + if(dom_noise[self.team] != "") + if(self.enemy) + sound(self.enemy, CH_TRIGGER, dom_noise[self.team], VOL_BASE, ATTEN_NORM); + else + sound(self, CH_TRIGGER, dom_noise[self.team], VOL_BASE, ATTEN_NORM); + + if(dom_noise1[self.team] != "") + play2all(dom_noise1[self.team]); - self.delay = time + wait_time; + //self.delay = time + wait_time; // do trigger work old_delay = self.delay; - old_team = self.team; - self.team = real_team; self.delay = 0; activator = self; SUB_UseTargets (); self.delay = old_delay; - self.team = old_team; switch(self.team) { @@ -96,7 +100,7 @@ void dompoint_captured () wait_time = autocvar_g_domination_point_rate; else wait_time = head.wait; - switch(head.goalentity.team) + switch(head.team) { case NUM_TEAM_1: pps_red += points/wait_time; @@ -114,13 +118,10 @@ void dompoint_captured () total_pps += points/wait_time; } - WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0)); + WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.team - 1, 0)); WaypointSprite_Ping(self.sprite); self.captime = time; - - FOR_EACH_REALCLIENT(head) - set_dom_state(head); } void AnimateDomPoint() @@ -136,11 +137,69 @@ void AnimateDomPoint() self.frame = 0; } -void dompointthink() +float dom_Cooldown(entity e) +{ + float base, pw, f, c; + + c = 1; + + if(e.cnt == -1 && !e.team) + { + base = autocvar_g_domination_controlpoint_idletime_neutral; + pw = autocvar_g_domination_controlpoint_idletime_neutral_power; + f = autocvar_g_domination_controlpoint_idletime_neutral_factor; + } + else + { + base = autocvar_g_domination_controlpoint_idletime; + pw = autocvar_g_domination_controlpoint_idletime_power; + f = autocvar_g_domination_controlpoint_idletime_factor; + } + + return base + pow(c, pw) * f * base; +} + +float dom_InitialCooldown(entity e) { return ((e.cnt == -1 && !e.team) ? autocvar_g_domination_controlpoint_idletime_neutral_initial : autocvar_g_domination_controlpoint_idletime_initial); } + +void dom_Activate(entity e) +{ + e.model = dom_model[e.team]; + e.modelindex = dom_modelindex[e.team]; + e.skin = dom_skin[e.team]; + + e.dom_active = TRUE; + e.dom_cooldown = 0; + //e.dom_cooldown_max = 0; + setsize(e, DOM_CP_MIN, DOM_CP_MAX); + WaypointSprite_UpdateMaxHealth(e.sprite, 0); + WaypointSprite_UpdateHealth(e.sprite, 0); +} + +void dom_Deactivate(entity e, float cooldown) +{ + e.dom_cooldown_max = max(e.dom_cooldown_max, cooldown); + e.dom_cooldown = max(e.dom_cooldown, cooldown); + + if(e.dom_active && e.dom_cooldown > 0) + { + e.model = dom_model[e.team]; + e.modelindex = dom_modelindex[e.team]; + setsize(e, DOM_CP_MIN, DOM_CP_MAX); + e.dom_active = FALSE; + } +} + +void Dom_ControlPoint_UpdateCooldownProgress(entity e) +{ + WaypointSprite_UpdateMaxHealth(e.sprite, e.dom_cooldown_max); + WaypointSprite_UpdateHealth(e.sprite, e.dom_cooldown_max - e.dom_cooldown); +} + +void dom_ControlPointThink() { float fragamt; - self.nextthink = time + 0.1; + self.nextthink = time; //self.frame = self.frame + 1; //if(self.frame > 119) @@ -149,10 +208,12 @@ void dompointthink() // give points - if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points + if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points return; - if(autocvar_g_domination_point_rate) + if(self.dom_cooldown) + self.delay = time; + else if(autocvar_g_domination_point_rate) self.delay = time + autocvar_g_domination_point_rate; else self.delay = time + self.wait; @@ -160,14 +221,15 @@ void dompointthink() // give credit to the team // NOTE: this defaults to 0 if (!domination_roundbased) - if (self.goalentity.netname != "") + if (self.team) + if (self.dom_active) { if(autocvar_g_domination_point_amt) fragamt = autocvar_g_domination_point_amt; else fragamt = self.frags; - TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt); - TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt); + TeamScore_AddToTeam(self.team, ST_SCORE, fragamt); + TeamScore_AddToTeam(self.team, ST_DOM_TICKS, fragamt); // give credit to the individual player, if he is still there if (self.enemy.playerid == self.enemy_playerid) @@ -178,116 +240,191 @@ void dompointthink() else self.enemy = world; } + + if(self.dom_cooldown) { Dom_ControlPoint_UpdateCooldownProgress(self); } + else if(!self.dom_active) { dom_Activate(self); } + + if(time - self.pointupdatetime >= 0.1) + { + self.dom_unlock_progress = 0; + self.dom_capturingplayer = world; + } + + self.dom_cooldown = max(0, self.dom_cooldown - frametime); } -void dompointtouch() +void dom_ControlPointTouch() { - entity head; - if (!IS_PLAYER(other)) - return; - if (other.health < 1) - return; + if(gameover) { return; } + if(round_handler_IsActive() && !round_handler_IsRoundStarted()) { return; } + + entity toucher = other; - if(round_handler_IsActive() && !round_handler_IsRoundStarted()) - return; + if(gameover) { return; } + if(!IS_PLAYER(toucher)) { return; } + if(toucher.health < 1 || toucher.frozen) { return; } + if(round_handler_IsActive() && !round_handler_IsRoundStarted()) { return; } - if(time < self.captime + 0.3) - return; + toucher.pointupdatetime = time; - // only valid teams can claim it - head = find(world, classname, "dom_team"); - while (head && head.team != other.team) - head = find(head, classname, "dom_team"); - if (!head || head.netname == "" || head == self.goalentity) + if(SAME_TEAM(toucher, self)) + { + if(time >= self.dom_lastmessage) + { + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_JAILBREAK_WRONGTEAM); + self.dom_lastmessage = time + 1.5; + } return; + } + + if(!self.dom_active) + { + if(time >= self.dom_lastmessage) + { + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_JAILBREAK_NOTREADY); + self.dom_lastmessage = time + 1.5; + } + return; + } + + if(self.dom_capturingplayer && self.dom_capturingplayer != toucher) + { + if(time >= self.dom_lastmessage) + { + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_JAILBREAK_TOOLATE); + self.dom_lastmessage = time + 1.5; + } + return; + } + + //if(self.cnt == -1 && !self.team) + self.dom_unlock_progress = bound(0, self.dom_unlock_progress + frametime * autocvar_g_domination_controlpoint_unlock_speed, 1); - // delay capture + self.pointupdatetime = time; + self.dom_capturingplayer = toucher; + toucher.dom_unlock_progress = self.dom_unlock_progress; - self.team = self.goalentity.team; // this stores the PREVIOUS team! + if(self.dom_unlock_progress >= 1) + { + nades_GiveBonus(toucher, autocvar_g_nades_bonus_score_medium); - self.cnt = other.team; - self.owner = head; // team to switch to after the delay - self.dmg_inflictor = other; + self.team = 0; // neutral for now + self.cnt = toucher.team; - // self.state = 1; - // self.delay = time + cvar("g_domination_point_capturetime"); - //self.nextthink = time + cvar("g_domination_point_capturetime"); - //self.think = dompoint_captured; + self.dmg_inflictor = toucher; - // go to neutral team in the mean time - head = find(world, classname, "dom_team"); - while (head && head.netname != "") - head = find(head, classname, "dom_team"); - if(head == world) - return; + WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", ""); + WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1'); + WaypointSprite_Ping(self.sprite); - WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", ""); - WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1'); - WaypointSprite_Ping(self.sprite); + self.skin = dom_skin[self.team]; + self.model = dom_model[self.team]; + self.modelindex = dom_modelindex[self.team]; + + self.dom_lastmessage = time + 2; // no spam - self.goalentity = head; - self.model = head.mdl; - self.modelindex = head.dmg; - self.skin = head.skin; + self.enemy = toucher; // individual player scoring + self.enemy_playerid = toucher.playerid; + dompoint_captured(); - self.enemy = other; // individual player scoring - self.enemy_playerid = other.playerid; - dompoint_captured(); + dom_Deactivate(self, dom_Cooldown(self)); + entity e; + for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; ) + if(e != self) + if(e.team) + dom_Deactivate(e, autocvar_g_domination_controlpoint_idletime); + else + dom_Deactivate(e, autocvar_g_domination_controlpoint_idletime_neutral); + } } -void dom_controlpoint_setup() +void dom_DelayedControlPointSetup(void) // called after a control point is placed on the map by dom_ControlPointSetup() { - entity head; - // find the spawnfunc_dom_team representing unclaimed points - head = find(world, classname, "dom_team"); - while(head && head.netname != "") - head = find(head, classname, "dom_team"); - if (!head) - objerror("no spawnfunc_dom_team with netname \"\" found\n"); - - // copy important properties from spawnfunc_dom_team entity - self.goalentity = head; - setmodel(self, head.mdl); // precision already set - self.skin = head.skin; + // model setup + self.model = dom_model[self.team]; + self.modelindex = dom_modelindex[self.team]; + self.noise = dom_noise[self.team]; // capture sound + self.noise1 = dom_noise1[self.team]; // global capture sound + self.skin = dom_skin[self.team]; + + // appearence + //setmodel(self, self.model); + self.effects = self.effects | EF_LOWPRECISION; + if(autocvar_g_domination_point_fullbright) + self.effects |= EF_FULLBRIGHT; - self.cnt = -1; + dom_Deactivate(self, dom_InitialCooldown(self)); - if(self.message == "") - self.message = " has captured a control point"; + waypoint_spawnforitem(self); + WaypointSprite_SpawnFixed("dom-neut", self.origin + DOM_CP_WPOFFSET, self, sprite, RADARICON_DOMPOINT, '0 1 1'); +} - if(self.frags <= 0) - self.frags = 1; - if(self.wait <= 0) - self.wait = 5; +void dom_Reset() +{ + // reset team + self.team = 0; + self.cnt = -1; - float points, waittime; - if (autocvar_g_domination_point_amt) - points = autocvar_g_domination_point_amt; - else - points = self.frags; - if (autocvar_g_domination_point_rate) - waittime = autocvar_g_domination_point_rate; - else - waittime = self.wait; + // model setup + self.model = dom_model[self.team]; + self.modelindex = dom_modelindex[self.team]; + self.noise = dom_noise[self.team]; // capture sound + self.noise1 = dom_noise1[self.team]; // global capture sound + self.skin = dom_skin[self.team]; - total_pps += points/waittime; + // main setup + self.think = dom_ControlPointThink; + self.nextthink = time; - if(!self.t_width) - self.t_width = 0.02; // frame animation rate - if(!self.t_length) - self.t_length = 239; // maximum frame + // waypoint setup + WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", ""); + WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1'); - self.think = dompointthink; - self.nextthink = time; - self.touch = dompointtouch; - self.solid = SOLID_TRIGGER; - self.flags = FL_ITEM; - setsize(self, '-32 -32 -32', '32 32 32'); - setorigin(self, self.origin + '0 0 20'); - droptofloor(); + dom_Deactivate(self, dom_InitialCooldown(self)); +} - waypoint_spawnforitem(self); - WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT, '0 1 1'); +void dom_ControlPointSetup(entity cp) +{ + // declarations + self = cp; // for later usage with droptofloor() + + if(!cp.t_width) { cp.t_width = CP_TICRATE; } + if(!cp.t_length) { cp.t_length = CP_MAXFRAMES; } + if(!cp.scale) { cp.scale = CP_SCALE; } + if(!cp.frags) { cp.frags = 1; } + if(!cp.wait) { cp.wait = 5; } + + total_pps += ((autocvar_g_domination_point_amt) ? autocvar_g_domination_point_amt : cp.frags) / ((autocvar_g_domination_point_rate) ? autocvar_g_domination_point_rate : cp.wait); + + // main setup + cp.think = dom_ControlPointThink; + cp.nextthink = time + 0.5; + cp.touch = dom_ControlPointTouch; + cp.solid = SOLID_TRIGGER; + cp.dom_capturingplayer = world; + cp.reset = dom_Reset; + cp.flags = FL_ITEM; + cp.dom_active = TRUE; + cp.cnt = -1; + cp.team = 0; + setsize(cp, DOM_CP_MIN, DOM_CP_MAX); + setorigin(cp, cp.origin + '0 0 20'); // move up so droptofloor doesn't put it in solid + + // control point placement + if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location + { + cp.noalign = TRUE; + cp.movetype = MOVETYPE_NONE; + } + else // drop to floor, automatically find a platform and set that as spawn origin + { + cp.noalign = FALSE; + self = cp; + droptofloor(); + cp.movetype = MOVETYPE_NONE; + } + + InitializeEntity(cp, dom_DelayedControlPointSetup, INITPRIO_SETLOCATION); } float total_controlpoints, redowned, blueowned, yellowowned, pinkowned; @@ -298,10 +435,10 @@ void Domination_count_controlpoints() for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; ) { ++total_controlpoints; - redowned += (e.goalentity.team == NUM_TEAM_1); - blueowned += (e.goalentity.team == NUM_TEAM_2); - yellowowned += (e.goalentity.team == NUM_TEAM_3); - pinkowned += (e.goalentity.team == NUM_TEAM_4); + redowned += (e.team == NUM_TEAM_1); + blueowned += (e.team == NUM_TEAM_2); + yellowowned += (e.team == NUM_TEAM_3); + pinkowned += (e.team == NUM_TEAM_4); } } @@ -339,16 +476,17 @@ float Domination_CheckWinner() Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); + nades_Clear(world, TRUE); return 1; } Domination_count_controlpoints(); float winner_team = Domination_GetWinnerTeam(); - + if(winner_team == -1) return 0; - + if(winner_team > 0) { Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_)); @@ -360,9 +498,11 @@ float Domination_CheckWinner() Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED); Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED); } - + round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); + nades_Clear(world, TRUE); + return 1; } @@ -373,9 +513,16 @@ float Domination_CheckPlayers() void Domination_RoundStart() { - entity e; - FOR_EACH_PLAYER(e) - e.player_blocked = 0; + entity oldself; + oldself = self; + FOR_EACH_PLAYER(self) + { + if(self.vehicle) + vehicles_exit(VHEF_RELEASE); + PutClientInServer(); + } + + self = oldself; } //go to best items, or control points you don't own @@ -398,36 +545,58 @@ void havocbot_role_dom() MUTATOR_HOOKFUNCTION(dom_GetTeamCount) { - ret_float = domination_teams; - return 0; + //ret_float = domination_teams; + ret_string = "dom_team"; + return TRUE; } MUTATOR_HOOKFUNCTION(dom_ResetMap) { total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0; - FOR_EACH_PLAYER(self) + return TRUE; +} + +MUTATOR_HOOKFUNCTION(dom_PlayerThink) +{ + if(round_handler_IsActive() && !round_handler_IsRoundStarted()) { - PutClientInServer(); - self.player_blocked = 1; - if(IS_REAL_CLIENT(self)) - set_dom_state(self); + self.dom_unlock_progress = 0; + return FALSE; } - return 1; + + if(time - self.pointupdatetime >= 0.01) + self.dom_unlock_progress = 0; + + self.dom_total_pps = total_pps; + self.dom_pps_red = pps_red; + self.dom_pps_blue = pps_blue; + self.dom_pps_yellow = pps_yellow; + self.dom_pps_pink = pps_pink; + + return FALSE; } MUTATOR_HOOKFUNCTION(dom_PlayerSpawn) { - if(domination_roundbased) - if(!round_handler_IsRoundStarted()) - self.player_blocked = 1; - else - self.player_blocked = 0; return FALSE; } -MUTATOR_HOOKFUNCTION(dom_ClientConnect) +MUTATOR_HOOKFUNCTION(dom_SpectateCopy) { - set_dom_state(self); + self.dom_unlock_progress = other.dom_unlock_progress; + return FALSE; +} + +MUTATOR_HOOKFUNCTION(dom_PlayerDamage) +{ + entity e; + if(self.dom_unlock_progress) + { + for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; ) + if(e.dom_capturingplayer == self) + e.dom_unlock_progress = bound(0, e.dom_unlock_progress - autocvar_g_domination_controlpoint_unlock_damage_pushback, 1); + } + return FALSE; } @@ -442,21 +611,9 @@ Control point for Domination gameplay. */ void spawnfunc_dom_controlpoint() { - if(!g_domination) - { - remove(self); - return; - } - self.think = dom_controlpoint_setup; - self.nextthink = time + 0.1; - self.reset = dom_controlpoint_setup; - - if(!self.scale) - self.scale = 0.6; + if(!g_domination) { remove(self); return; } - self.effects = self.effects | EF_LOWPRECISION; - if (autocvar_g_domination_point_fullbright) - self.effects |= EF_FULLBRIGHT; + dom_ControlPointSetup(self); } /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32) @@ -487,25 +644,28 @@ Keys: void spawnfunc_dom_team() { - if(!g_domination || autocvar_g_domination_teams_override >= 2) - { - remove(self); - return; - } - precache_model(self.model); - if (self.noise != "") - precache_sound(self.noise); - if (self.noise1 != "") - precache_sound(self.noise1); + if(!g_domination || autocvar_g_domination_teams_override >= 2) { remove(self); return; } + self.classname = "dom_team"; - setmodel(self, self.model); // precision not needed - self.mdl = self.model; - self.dmg = self.modelindex; - self.model = ""; - self.modelindex = 0; // this would have to be changed if used in quakeworld if(self.cnt) self.team = self.cnt + 1; // WHY are these different anyway? + + precache_model(self.model); + setmodel(self, self.model); + dom_model[self.team] = self.model; + dom_modelindex[self.team] = self.modelindex; + + self.model = ""; + self.modelindex = 0; + + if(self.noise != "") { precache_sound(self.noise); } + if(self.noise1 != "") { precache_sound(self.noise1); } + + dom_noise[self.team] = self.noise; + dom_noise1[self.team] = self.noise1; + dom_message[self.team] = self.message; + dom_skin[self.team] = self.skin; } // scoreboard setup @@ -541,24 +701,29 @@ void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float p oldself = self; self = spawn(); self.classname = "dom_team"; - self.netname = teamname; - self.cnt = teamcolor; self.model = pointmodel; self.skin = pointskin; - self.noise = capsound; - self.noise1 = capnarration; - self.message = capmessage; - - // this code is identical to spawnfunc_dom_team - setmodel(self, self.model); // precision not needed - self.mdl = self.model; - self.dmg = self.modelindex; + self.netname = teamname; // needed for team counting + self.team = teamcolor; + + precache_model(self.model); + setmodel(self, self.model); + dom_model[self.team] = self.model; + dom_modelindex[self.team] = self.modelindex; + self.model = ""; self.modelindex = 0; - // this would have to be changed if used in quakeworld - self.team = self.cnt + 1; + + if(capsound != "") { precache_sound(capsound); } + if(capnarration != "") { precache_sound(capnarration); } + + dom_noise[self.team] = capsound; + dom_noise1[self.team] = capnarration; + dom_message[self.team] = capmessage; + dom_skin[self.team] = pointskin; + + //printf("%d %s %d\n", self.team, dom_model[self.team], dom_modelindex[self.team]); - //eprint(self); self = oldself; } @@ -578,12 +743,14 @@ void dom_spawnpoint(vector org) // spawn some default teams if the map is not set up for domination void dom_spawnteams(float teams) { - dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point"); - dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point"); - if(teams >= 3) - dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point"); - if(teams >= 4) - dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point"); + float i, team_id; + for(i = 1; i <= teams; ++i) + { + team_id = Team_NumberToTeam(i); + + dom_spawnteam(Team_ColorName(team_id), team_id, sprintf("models/domination/dom_%s.md3", Static_Team_ColorName_Lower(team_id)), 0, "domination/claim.wav", "", ""); + } + dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", ""); } @@ -600,6 +767,7 @@ void dom_DelayedInit() // Do this check with a delay so we can wait for teams to CheckAllowedTeams(world); domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2); + addstat(STAT_CAPTURE_PROGRESS, AS_FLOAT, dom_unlock_progress); addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps); addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red); addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue); @@ -634,8 +802,10 @@ MUTATOR_DEFINITION(gamemode_domination) { MUTATOR_HOOK(GetTeamCount, dom_GetTeamCount, CBC_ORDER_ANY); MUTATOR_HOOK(reset_map_players, dom_ResetMap, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerPreThink, dom_PlayerThink, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerSpawn, dom_PlayerSpawn, CBC_ORDER_ANY); - MUTATOR_HOOK(ClientConnect, dom_ClientConnect, CBC_ORDER_ANY); + MUTATOR_HOOK(SpectateCopy, dom_SpectateCopy, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDamage_Calculate, dom_PlayerDamage, CBC_ORDER_ANY); MUTATOR_HOOK(HavocBot_ChooseRole, dom_BotRoles, CBC_ORDER_ANY); MUTATOR_ONADD @@ -651,5 +821,5 @@ MUTATOR_DEFINITION(gamemode_domination) return -1; } - return 0; + return FALSE; } diff --git a/qcsrc/server/mutators/gamemode_domination.qh b/qcsrc/server/mutators/gamemode_domination.qh index 6b5b334e4..fe962f8f6 100644 --- a/qcsrc/server/mutators/gamemode_domination.qh +++ b/qcsrc/server/mutators/gamemode_domination.qh @@ -24,6 +24,31 @@ float pps_pink; .entity sprite; .float captime; +.float dom_unlock_progress; +.float dom_cooldown; +.float dom_cooldown_max; +.float dom_active; +.float dom_lastmessage; + +.entity dom_capturingplayer; + +.float pointupdatetime; + +// control point properties +#define DOM_CP_MIN '-32 -32 -32' +#define DOM_CP_MAX '32 32 32' +#define DOM_CP_WPOFFSET ('0 0 32') +#define CP_SCALE 0.6 +#define CP_TICRATE 0.02 +#define CP_MAXFRAMES 239 + +string dom_model[17]; +float dom_modelindex[17]; +string dom_noise[17]; +string dom_noise1[17]; +string dom_message[17]; +float dom_skin[17]; + // misc globals float domination_roundbased; float domination_teams; diff --git a/qcsrc/server/mutators/gamemode_freezetag.qc b/qcsrc/server/mutators/gamemode_freezetag.qc index 5ab96277b..b1306a8d0 100644 --- a/qcsrc/server/mutators/gamemode_freezetag.qc +++ b/qcsrc/server/mutators/gamemode_freezetag.qc @@ -42,6 +42,8 @@ void freezetag_count_alive_players() float prev_missing_teams_mask; float freezetag_CheckTeams() { + freezetag_count_alive_players(); + if(FREEZETAG_ALIVE_TEAMS_OK()) { if(prev_missing_teams_mask > 0) @@ -92,22 +94,44 @@ float freezetag_getWinnerTeam() return -1; // no player left } +float ft_EnemyWaypoint_Customize() +{ + if(self.owner.frozen || self.owner.deadflag) { return FALSE; } + + entity e = WaypointSprite_getviewentity(other); + + if(SAME_TEAM(self.owner, e)) { return FALSE; } + + return TRUE; +} + +void ft_EnemyWaypoints(entity player) +{ + WaypointSprite_Spawn("enemy", 0, 0, player, ENEMY_WAYPOINT_OFFSET, world, 0, player, wps_enemy, TRUE, RADARICON_FLAG, WPCOLOR_ENEMY(player.team)); + WaypointSprite_UpdateMaxHealth(player.wps_enemy, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2); + WaypointSprite_UpdateHealth(player.wps_enemy, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)); + WaypointSprite_UpdateTeamRadar(player.wps_enemy, RADARICON_FLAGCARRIER, WPCOLOR_ENEMY(player.team)); + + player.wps_enemy.customizeentityforclient = ft_EnemyWaypoint_Customize; +} + float freezetag_CheckWinner() { entity e; + + if(!ft_stalemate) if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) { - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); + ft_stalemate = TRUE; + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_FREEZETAG_STALEMATE); FOR_EACH_PLAYER(e) { - e.freezetag_frozen_timeout = 0; - nades_Clear(e); + ft_EnemyWaypoints(e); } - round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); - return 1; } + freezetag_count_alive_players(); + if(FREEZETAG_ALIVE_TEAMS() > 1) return 0; @@ -125,11 +149,16 @@ float freezetag_CheckWinner() Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED); } + ft_stalemate = FALSE; FOR_EACH_PLAYER(e) { e.freezetag_frozen_timeout = 0; - nades_Clear(e); + e.revive_progress = 0; + WaypointSprite_Kill(e.wps_enemy); } + + nades_Clear(world, TRUE); + round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); return 1; } @@ -201,6 +230,8 @@ void freezetag_Unfreeze(entity attacker) self.freezetag_frozen_timeout = 0; Unfreeze(self); + + self.health = autocvar_g_freezetag_revive_health; } float freezetag_isEliminated(entity e) @@ -325,7 +356,6 @@ MUTATOR_HOOKFUNCTION(freezetag_RemovePlayer) if(!self.frozen) freezetag_LastPlayerForTeam_Notify(); freezetag_Unfreeze(world); - freezetag_count_alive_players(); return 1; } @@ -336,7 +366,6 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerDies) { if(self.frozen) freezetag_Unfreeze(world); - freezetag_count_alive_players(); return 1; // let the player die so that he can respawn whenever he wants } @@ -349,7 +378,6 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerDies) if(self.frozen != 1) { freezetag_Add_Score(frag_attacker); - freezetag_count_alive_players(); freezetag_LastPlayerForTeam_Notify(); } else @@ -362,6 +390,9 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerDies) if(self.frozen) return 1; + if(g_instagib) + frag_target.ammo_cells = start_ammo_cells; // we need more ammo in instagib, otherwise the player will defrost & die again + freezetag_Freeze(frag_attacker); freezetag_LastPlayerForTeam_Notify(); @@ -388,14 +419,16 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerSpawn) if(self.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players return 1; // do nothing, round is starting right now + if(ft_stalemate) + if(!self.wps_enemy) + ft_EnemyWaypoints(self); + if(self.freezetag_frozen_timeout == -2) // player was dead { freezetag_Freeze(world); return 1; } - freezetag_count_alive_players(); - if(round_handler_IsActive()) if(round_handler_IsRoundStarted()) { @@ -414,11 +447,17 @@ MUTATOR_HOOKFUNCTION(freezetag_reset_map_players) self.freezetag_frozen_timeout = -1; PutClientInServer(); self.freezetag_frozen_timeout = 0; + WaypointSprite_Kill(self.wps_enemy); } - freezetag_count_alive_players(); return 1; } +MUTATOR_HOOKFUNCTION(freezetag_ResetMap) +{ + ft_stalemate = FALSE; + return FALSE; +} + MUTATOR_HOOKFUNCTION(freezetag_GiveFragsForKill) { frag_score = 0; // no frags counted in Freeze Tag @@ -473,12 +512,11 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink) if(n && self.frozen == 1) // OK, there is at least one teammate reviving us { self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); - self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health)); + self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : autocvar_g_freezetag_revive_health)); if(self.revive_progress >= 1) { freezetag_Unfreeze(self); - freezetag_count_alive_players(); if(n == -1) { @@ -516,13 +554,16 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink) else if(!n && self.frozen == 1) // only if no teammate is nearby will we reset { self.revive_progress = bound(0, self.revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1); - self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health)); + self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : autocvar_g_freezetag_revive_health)); } else if(!n && !self.frozen) { self.revive_progress = 0; // thawing nobody } + if(self.wps_enemy) + WaypointSprite_UpdateHealth(self.wps_enemy, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)); + return 1; } @@ -542,7 +583,7 @@ MUTATOR_HOOKFUNCTION(freezetag_BotRoles) MUTATOR_HOOKFUNCTION(freezetag_GetTeamCount) { ret_float = freezetag_teams; - return 0; + return FALSE; } void freezetag_Initialize() diff --git a/qcsrc/server/mutators/gamemode_freezetag.qh b/qcsrc/server/mutators/gamemode_freezetag.qh new file mode 100644 index 000000000..48ddda235 --- /dev/null +++ b/qcsrc/server/mutators/gamemode_freezetag.qh @@ -0,0 +1,8 @@ +// stalemate +float ft_stalemate; + +// waypoint setup +.entity wps_enemy; +#define WPCOLOR_ENEMY(t) ((t) ? colormapPaletteColor(t - 1, FALSE) * 0.75 : '1 1 1') + +#define ENEMY_WAYPOINT_OFFSET ('0 0 64') diff --git a/qcsrc/server/mutators/gamemode_infection.qc b/qcsrc/server/mutators/gamemode_infection.qc new file mode 100644 index 000000000..97cfc205c --- /dev/null +++ b/qcsrc/server/mutators/gamemode_infection.qc @@ -0,0 +1,294 @@ +void INF_count_alive_players() +{ + entity e; + float i; + for(i = 1; i <= infection_teams; ++i) { aliveplayers[i] = 0; } + FOR_EACH_PLAYER(e) + { + ++total_players; + + if(e.health > 0) + aliveplayers[e.inf_team] += 1; + } +} + +void INF_count_survivors() +{ + entity e; + float i; + for(i = 1; i <= infection_teams; ++i) { aliveplayers[i] = 0; } + FOR_EACH_PLAYER(e) + { + ++total_players; + if(e.inf_realteam == e.inf_team) + //if(e.health > 0) // TODO: check if this is needed? + aliveplayers[e.inf_realteam] += 1; + } +} + +float inf_alive_teams() +{ + float total_alive = 0; + float i; + for(i = 1; i <= infection_teams; ++i) + if(aliveplayers[i] >= 1) + total_alive += 1; + + return total_alive; +} +#define INF_ALIVE_TEAMS_OK() (inf_alive_teams() == infection_teams) + +float Infection_CheckWinner() +{ + entity e; + + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); + allowed_to_spawn = FALSE; + round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit); + nades_Clear(world, TRUE); + return 1; + } + + INF_count_survivors(); + if(inf_alive_teams() > 1) + return 0; + + float winner_team = 0; + + FOR_EACH_CLIENT(e) + { + if(e.inf_realteam == e.inf_team) + if(IS_PLAYER(e)) + if(e.inf_nextteam == 0) + winner_team = e.inf_realteam; + } + + if(winner_team) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_INFECTION_WIN); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_INFECTION_WIN); + FOR_EACH_CLIENT(e) + { + if(e.inf_team == e.inf_realteam) + if(e.inf_realteam == winner_team) + { + PlayerScore_Add(e, SP_INF_ROUNDS, +1); + UpdateFrags(e, +1); + } + } + } + else + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED); + } + + round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit); + + nades_Clear(world, TRUE); + + return 1; +} + +void INF_RoundStart() +{ + allowed_to_spawn = warmup_stage; + + entity head; + FOR_EACH_PLAYER(head) + { + head.inf_realteam = head.inf_team; + head.inf_nextteam = 0; + } +} + +float INF_CheckTeams() +{ + allowed_to_spawn = TRUE; + INF_count_alive_players(); + if(INF_ALIVE_TEAMS_OK()) + { + if(prev_total_players > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_PLAYERS); + prev_total_players = -1; + return 1; + } + if(prev_total_players != total_players) + { + float plcnt = 0, i; + for(i = 1; i <= infection_teams; ++i) + { + if(aliveplayers[i] < 1) + ++plcnt; + } + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_PLAYERS, plcnt); + prev_total_players = total_players; + } + return 0; +} + +float inf_PickSmallestTeam() +{ + INF_count_alive_players(); + float i, smallest_team = 0; + + for(i = 1; i <= infection_teams; ++i) + { + if(aliveplayers[i] <= aliveplayers[smallest_team]) + smallest_team = i; + } + + return smallest_team; +} + +MUTATOR_HOOKFUNCTION(inf_RemovePlayer) +{ + self.inf_team = 0; + self.inf_realteam = 0; + self.inf_nextteam = 0; + return FALSE; +} + +MUTATOR_HOOKFUNCTION(inf_BotAttack) +{ + if(self.inf_team == other.inf_team) + return TRUE; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(inf_PlayerThink) +{ + // temporary hack to fix colors + setcolor(self, 16 * self.inf_team + self.inf_team); + return FALSE; +} + +MUTATOR_HOOKFUNCTION(inf_PlayerSpawn) +{ + if(IS_REAL_CLIENT(self)) + if(!self.inf_team) + if(bots_would_leave) + { + entity head = findchainfloat(isbot, TRUE); + if(head) + { + entity best = head; + float besttime = head.createdtime; + while (head) + { + if (besttime < head.createdtime) + { + besttime = head.createdtime; + best = head; + } + head = head.chain; + } + + self.inf_team = self.inf_realteam = best.inf_realteam; + } + } + + if(self.inf_nextteam) // respawning player + { + self.inf_team = self.inf_nextteam; + self.inf_nextteam = 0; + } + else if(!self.inf_team) // new joining player + self.inf_realteam = self.inf_team = inf_PickSmallestTeam(); + + setcolor(self, 16 * self.inf_team + self.inf_team); + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(inf_PlayerDies) +{ + if(IS_PLAYER(frag_attacker) && frag_attacker.inf_team != frag_target.inf_team) + { + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_INFECTION_INFECTED, frag_target.netname, frag_attacker.netname); + Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_INFECTION_INFECTED, frag_attacker.netname); + frag_target.inf_nextteam = frag_attacker.inf_team; + frag_target.respawn_flags |= RESPAWN_FORCE; + + PlayerScore_Add(frag_target, SP_SCORE, -1); + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(inf_ResetMap) +{ + FOR_EACH_PLAYER(self) + { + if(self.inf_realteam && self.inf_realteam != self.inf_team) + self.inf_team = self.inf_realteam; + self.inf_nextteam = 0; + PutClientInServer(); // player re-spawns on new team? + } + + FOR_EACH_SPEC(self) if(IS_BOT_CLIENT(self)) + { + self.inf_realteam = self.inf_team = inf_PickSmallestTeam(); + self.inf_nextteam = 0; + + PutClientInServer(); + } + + return TRUE; +} + +MUTATOR_HOOKFUNCTION(inf_ForbidPlayerScore_Clear) +{ + return TRUE; +} + +// scoreboard stuff +void inf_ScoreRules() +{ + ScoreRules_basics(0, SFL_SORT_PRIO_PRIMARY, 0, TRUE); + ScoreInfo_SetLabel_PlayerScore(SP_INF_ROUNDS, "survivals", SFL_SORT_PRIO_PRIMARY); + ScoreRules_basics_end(); +} + +void inf_Initialize() +{ + allowed_to_spawn = TRUE; + + infection_teams = autocvar_g_infection_teams; + + inf_ScoreRules(); + + round_handler_Spawn(INF_CheckTeams, Infection_CheckWinner, INF_RoundStart); + round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit); +} + +MUTATOR_DEFINITION(gamemode_infection) +{ + MUTATOR_HOOK(MakePlayerObserver, inf_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerPreThink, inf_PlayerThink, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerSpawn, inf_PlayerSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(ClientDisconnect, inf_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(BotShouldAttack, inf_BotAttack, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDies, inf_PlayerDies, CBC_ORDER_ANY); + MUTATOR_HOOK(reset_map_players, inf_ResetMap, CBC_ORDER_ANY); + MUTATOR_HOOK(ForbidPlayerScore_Clear, inf_ForbidPlayerScore_Clear, CBC_ORDER_ANY); + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + inf_Initialize(); + } + + MUTATOR_ONREMOVE + { + print("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return FALSE; +} diff --git a/qcsrc/server/mutators/gamemode_infection.qh b/qcsrc/server/mutators/gamemode_infection.qh new file mode 100644 index 000000000..9f75f89b5 --- /dev/null +++ b/qcsrc/server/mutators/gamemode_infection.qh @@ -0,0 +1,13 @@ +float total_players; +float infection_teams; +float aliveplayers[17]; +float allowed_to_spawn; + +.float inf_team; +.float inf_nextteam; +.float inf_realteam; + +float prev_total_players; + +// scores +#define SP_INF_ROUNDS 4 diff --git a/qcsrc/server/mutators/gamemode_invasion.qc b/qcsrc/server/mutators/gamemode_invasion.qc index dcdd3365a..ec4da5fb7 100644 --- a/qcsrc/server/mutators/gamemode_invasion.qc +++ b/qcsrc/server/mutators/gamemode_invasion.qc @@ -3,10 +3,6 @@ void spawnfunc_invasion_spawnpoint() if(!g_invasion) { remove(self); return; } self.classname = "invasion_spawnpoint"; - - if(autocvar_g_invasion_zombies_only) // precache only if it hasn't been already - if(self.monsterid) - MON_ACTION(self.monsterid, MR_PRECACHE); } float invasion_PickMonster(float supermonster_count) @@ -118,7 +114,7 @@ float Invasion_CheckWinner() if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) { FOR_EACH_MONSTER(head) - monster_remove(head); + Monster_Remove(head); Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); @@ -126,7 +122,7 @@ float Invasion_CheckWinner() return 1; } - float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0; + float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;; FOR_EACH_MONSTER(head) if(head.health > 0) { @@ -195,7 +191,7 @@ float Invasion_CheckWinner() } FOR_EACH_MONSTER(head) - monster_remove(head); + Monster_Remove(head); if(teamplay) { @@ -213,6 +209,8 @@ float Invasion_CheckWinner() round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit); + nades_Clear(world, TRUE); + return 1; } @@ -288,7 +286,7 @@ MUTATOR_HOOKFUNCTION(invasion_MonsterSpawn) self.monster_skill = inv_monsterskill; if((get_monsterinfo(self.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER) - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, M_NAME(self.monsterid)); + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, self.monster_name); self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP; @@ -358,7 +356,7 @@ MUTATOR_HOOKFUNCTION(invasion_PlayerCommand) MUTATOR_HOOKFUNCTION(invasion_BotShouldAttack) { - if(!(checkentity.flags & FL_MONSTER)) + if(!IS_MONSTER(other)) return TRUE; return FALSE; @@ -374,14 +372,14 @@ MUTATOR_HOOKFUNCTION(invasion_SetStartItems) MUTATOR_HOOKFUNCTION(invasion_AccuracyTargetValid) { - if(frag_target.flags & FL_MONSTER) + if(IS_MONSTER(frag_target)) return MUT_ACCADD_INVALID; return MUT_ACCADD_INDIFFERENT; } MUTATOR_HOOKFUNCTION(invasion_AllowMobSpawning) { - // monster spawning disabled during an invasion + ret_string = "You can't spawn monsters during an invasion!"; return TRUE; } @@ -416,28 +414,14 @@ void invasion_DelayedInit() // Do this check with a delay so we can wait for tea round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart); round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit); + allowed_to_spawn = TRUE; + inv_roundcnt = 0; inv_maxrounds = 15; // 15? } void invasion_Initialize() { - if(autocvar_g_invasion_zombies_only) - MON_ACTION(MON_ZOMBIE, MR_PRECACHE); - else - { - float i; - entity mon; - for(i = MON_FIRST; i <= MON_LAST; ++i) - { - mon = get_monsterinfo(i); - if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM)) - continue; // flying/swimming monsters not yet supported - - MON_ACTION(i, MR_PRECACHE); - } - } - InitializeEntity(world, invasion_DelayedInit, INITPRIO_GAMETYPE); } diff --git a/qcsrc/server/mutators/gamemode_jailbreak.qc b/qcsrc/server/mutators/gamemode_jailbreak.qc new file mode 100644 index 000000000..b9f9a9f15 --- /dev/null +++ b/qcsrc/server/mutators/gamemode_jailbreak.qc @@ -0,0 +1,1344 @@ +// round handling +float total_players; +float redalive, bluealive, yellowalive, pinkalive; +.float redalive_stat, bluealive_stat, yellowalive_stat, pinkalive_stat; +float allowed_to_spawn; + +void JB_count_alive_players() +{ + entity e; + total_players = redalive = bluealive = yellowalive = pinkalive = 0; + FOR_EACH_PLAYER(e) + { + switch(e.team) + { + case NUM_TEAM_1: ++total_players; if(!e.jb_isprisoned) ++redalive; break; + case NUM_TEAM_2: ++total_players; if(!e.jb_isprisoned) ++bluealive; break; + case NUM_TEAM_3: ++total_players; if(!e.jb_isprisoned) ++yellowalive; break; + case NUM_TEAM_4: ++total_players; if(!e.jb_isprisoned) ++pinkalive; break; + } + } + FOR_EACH_REALCLIENT(e) + { + e.redalive_stat = redalive; + e.bluealive_stat = bluealive; + e.yellowalive_stat = yellowalive; + e.pinkalive_stat = pinkalive; + } +} + +float JB_GetWinnerTeam() +{ + float winner_team = 0; + if(redalive >= 1) + winner_team = NUM_TEAM_1; + if(bluealive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_2; + } + if(yellowalive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_3; + } + if(pinkalive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_4; + } + if(winner_team) + return winner_team; + return -1; // no player left +} + +#define JB_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0)) +#define JB_ALIVE_TEAMS_OK() (JB_ALIVE_TEAMS() == jb_teams) +float JB_CheckWinner() +{ + entity e, oldself; + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); + allowed_to_spawn = FALSE; + round_handler_Init(5, autocvar_g_jailbreak_warmup, autocvar_g_jailbreak_round_timelimit); + FOR_EACH_PLAYER(e) + { + if(!e.jb_isprisoned) + { + oldself = self; + self = e; + if(!e.jb_isprisoned) + e.player_blocked = 1; + PutClientInServer(); + self = oldself; + } + } + nades_Clear(world, TRUE); + jb_roundover = TRUE; + return 1; + } + + JB_count_alive_players(); + if(JB_ALIVE_TEAMS() > 1) + return 0; + + float winner_team = JB_GetWinnerTeam(); + + if(JB_JailIsOpen(winner_team)) + return 0; // ??? + + if(winner_team > 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_)); + TeamScore_AddToTeam(winner_team, ST_JB_ROUNDS, +1); + + JB_ActivateCamera(winner_team); + JB_TorturePrisonersLater(winner_team, 3); + } + else if(winner_team == -1) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED); + } + + jb_roundover = TRUE; + + allowed_to_spawn = FALSE; + round_handler_Init(JB_TORTURE_DURATION, autocvar_g_jailbreak_warmup, autocvar_g_jailbreak_round_timelimit); + + FOR_EACH_PLAYER(e) + { + if(!e.jb_isprisoned) + { + oldself = self; + self = e; + e.player_blocked = 1; + PutClientInServer(); + self = oldself; + } + } + + nades_Clear(world, TRUE); + + return 1; +} + +void JB_RoundStart() +{ + if(warmup_stage) + allowed_to_spawn = TRUE; + else + allowed_to_spawn = FALSE; +} + +float prev_missing_teams_mask; +float JB_CheckTeams() +{ + allowed_to_spawn = TRUE; + JB_count_alive_players(); + if(JB_ALIVE_TEAMS_OK()) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 1; + } + if(total_players == 0) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 0; + } + float missing_teams_mask = (!redalive) + (!bluealive) * 2; + if(jb_teams >= 3) missing_teams_mask += (!yellowalive) * 4; + if(jb_teams >= 4) missing_teams_mask += (!pinkalive) * 8; + if(prev_missing_teams_mask != missing_teams_mask) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); + prev_missing_teams_mask = missing_teams_mask; + } + return 0; +} + +// logging +void jb_debug(string input) +{ + switch(autocvar_g_jailbreak_debug) + { + case 1: dprint(input); break; + case 2: print(input); break; + case 3: bprint(input); break; + } +} + +void JB_AddDoor(entity door, float theteam, vector vdata, string sdata, entity cpoint) +{ + if(door.classname != "door" && door.classname != "door_rotating") + { + jb_debug(sprintf("Warning: %s^7 at %v is linked with an entity of unhandled class (%s^7)\n", JB_ControlPoint_Name(cpoint), cpoint.origin, door.classname)); + return; + } + + door.jb_worlddoornext = jb_worlddoorlist; // link door into jb_worlddoorlist + jb_worlddoorlist = door; +} + +float jb_doors_opened[2]; +void JB_MaybeOpenDoor(entity door, float openjails, vector vdata, string sdata, entity cpoint) +{ + if(openjails == OPENJAILS_LOCKED && door.jaildoormode != JAILDOORMODE_OPEN) + return; + + if(openjails == OPENJAILS_OPEN && door.jaildoormode == JAILDOORMODE_CLOSED) + return; + + // OPENJAILS_LOCKED_FORCE is handled in JB_NonJBInit + // For OPENJAILS_OPEN_FORCE, the below is always executed + + entity oldself = self; + self = door; + float opened = TRUE; + + switch(door.classname) + { + case "door": + door_init_startopen(); + break; + + case "door_rotating": + door_rotating_init_startopen(); + InitMovingBrushTrigger(); + break; + + default: + jb_debug(sprintf("Warning: %s^7 at %v is linked with an entity of unhandled class (%s^7)\n", JB_ControlPoint_Name(cpoint), cpoint.origin, door.classname)); + opened = FALSE; + break; + } + + self = oldself; + + if(opened) + { + float idx = Team_TeamToNumber(cpoint.team); + jb_doors_opened[idx] = jb_doors_opened[idx] + 1; + } +} + +// This is called for non-jailbreak modes only, to alter jb-specific entities on the map +void JB_NonJBInit() +{ + entity tmp_entity; + float openjails = autocvar_g_jailbreak_nonjb_openjails; + + SUB_ForEachTarget_Init(); + for(tmp_entity = jb_worldcplist; tmp_entity; tmp_entity = tmp_entity.jb_worldcpnext) + { + if(tmp_entity.team) + { + if(openjails != OPENJAILS_LOCKED_FORCE) + SUB_ForEachTarget(tmp_entity, JB_MaybeOpenDoor, TRUE, openjails, '0 0 0', string_null, tmp_entity); + tmp_entity.think = SUB_Remove; + tmp_entity.nextthink = time; + } + } + + // If all jail doors are locked, it means that the jail is not intended to be accessible. + // We have to keep the jail sectors then to ensure it's not possible to get in with translocator (or something more evil to be added in the future). + // Otherwise, they have to be removed. TODO: do something about maps with multiple jails (if we ever get any). + entity e; // TODO + for(e = findchain(classname, "jailbreak_jail"); e; e = e.chain) + { + float idx = Team_TeamToNumber(e.team); + if(!autocvar_g_nades || jb_doors_opened[idx]) + { + e.think = SUB_Remove; + e.nextthink = time; + } + } +} + +// +// Gametype logic +// + +float JB_JailIsOpen(float theteam) +{ + entity tmp_entity; + for(tmp_entity = jb_worlddoorlist; tmp_entity; tmp_entity = tmp_entity.jb_worlddoornext) + { + if(tmp_entity.team == theteam) + if(tmp_entity.state != STATE_BOTTOM) + return TRUE; + } + return FALSE; +} + +void JB_TeleportToJail(entity p, entity attacker) +{ + vector a; + entity spot = jb_ChooseJailSpawn(p, attacker); + + float tries; + tries = 3; + + while(!spot && tries > 0) + { + spot = jb_ChooseJailSpawn(p, attacker); + tries--; + } + + if(!spot) + { + jb_debug(strcat("Failed to pick a jail spawnpoint for ", self.netname, "^7, cannot imprison!\n")); + return; + } + + a = spot.angles; + a_z = 0; + TeleportPlayer(spot, self, spot.origin, self.mangle, a, '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER); +} + +void JB_Imprison(entity attacker) +{ + if(self.jb_isprisoned) + { + jb_debug(strcat("Tried to imprison a prisoned player (", self.netname, "^7)\n")); + return; + } + + self.health = autocvar_g_jailbreak_prisoner_health; + self.armorvalue = autocvar_g_jailbreak_prisoner_armor; + + self.jb_had_unlimited_ammo = (self.items & IT_UNLIMITED_WEAPON_AMMO); + + if(!self.jb_had_unlimited_ammo) + self.items |= IT_UNLIMITED_WEAPON_AMMO; + + self.weapon_blocked = TRUE; + + nades_Clear(self, FALSE); + + jb_debug(sprintf("Imprisoning %s^7, attacker: %e with netname: %s\n", self.netname, attacker, attacker.netname)); + JB_TeleportToJail(self, attacker); + + self.jb_isprisoned = TRUE; + self.jb_prisontime = time; + + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_JAILBREAK_IMPRISON); +} + +void JB_TorturePrisonersLater_Think() +{ + JB_TorturePrisoners(self.team); + remove(self); +} + +void JB_TorturePrisonersLater(float theteam, float thedelay) +{ + entity e = spawn(); + e.team = theteam; + e.think = JB_TorturePrisonersLater_Think; + e.nextthink = time + thedelay; +} + +void JB_Release(entity saviour) +{ + if(!self.jb_isprisoned) + { + jb_debug(strcat("Tried to release a free player (", self.netname, "^7)\n")); + return; + } + + self.jb_isprisoned = FALSE; + self.jb_imprisoner = world; + self.weapon_blocked = FALSE; + self.player_blocked = FALSE; // just incase + + if(!self.jb_had_unlimited_ammo) + self.items &= ~IT_UNLIMITED_WEAPON_AMMO; + + if(g_jailbreak_jail_deathmatch) + { + self.health = max(self.health, autocvar_g_jailbreak_prisoner_health); + self.armorvalue = max(self.armorvalue, autocvar_g_jailbreak_prisoner_armor); + } +} + +// +// Torture logic +// + +#define JITTER(v,j) (v) + (j) * 2 * (random() - 0.5) + +void JB_TorturePrisoners(float theteam) +{ + entity spot = world; + + for(;(spot = find(spot, classname, "info_jailbreak_torturespawn"));) + if(spot.team == theteam) + JB_Torture_Start(spot); +} + +void JB_Torture_Think() +{ + if(gameover) + { + remove(self); + return; + } + + makevectors(self.angles); + //self.nextthink = time + JITTER(self.jb_torture_delay, self.jb_torture_delay_jitter); + + float j = self.jb_torture_delay - JITTER(self.jb_torture_delay, self.jb_torture_delay_jitter); + + if(j > 0) + j = 0.5 * j; + + self.nextthink = time + max(0.1, self.jb_torture_delay + j); + self.jb_torture_suggestedforce = JITTER(self.jb_torture_force, self.jb_torture_force_jitter); + + Send_Effect(EFFECT_FIREFIELD, self.origin, '0 0 0', 2); + + entity head; + FOR_EACH_PLAYER(head) + if(DIFF_TEAM(head, self)) + if(head.jb_isprisoned) + if(!Fire_IsBurning(head)) + Fire_AddDamage(head, world, 100, 6, DEATH_FIRE); +} + +void JB_Torture_Start(entity spot) +{ + entity e = spawn(); + e.classname = "jailbreak_torture"; + e.reset = SUB_Remove; + e.reset2 = e.reset; + e.think = JB_Torture_Think; + e.angles = spot.angles; + e.jb_torture_delay = spot.jb_torture_delay; + e.jb_torture_delay_jitter = spot.jb_torture_delay_jitter; + e.jb_torture_force = spot.jb_torture_force; + e.jb_torture_force_jitter = spot.jb_torture_force_jitter; + e.owner = spot; + e.team = e.owner.team; + setorigin(e, spot.origin); + e.nextthink = time + JITTER(0, e.jb_torture_delay_jitter); +} + +#undef JITTER + +.float pointupdatetime; + +// +// Utility functions +// + +entity jb_ChooseJailSpawn(entity player, entity attacker) +{ + entity spot; + + RandomSelection_Init(); + + for(spot = world; (spot = find(spot, classname, "info_jailbreak_jailspawn")); ) + { + if(attacker && DIFF_TEAM(player, attacker)) // don't throw teammates in own jail? + { + if(SAME_TEAM(spot, attacker)) + RandomSelection_Add(spot, 0, string_null, 1, 1); + } + else + { + if(DIFF_TEAM(spot, player)) + RandomSelection_Add(spot, 0, string_null, 1, 1); + } + } + + if(!RandomSelection_chosen_ent) + jb_debug(strcat("Unable to find an enemy jail spawnpoint, player team: ", ftos(player.team), "\n")); + + return RandomSelection_chosen_ent; +} + +float JB_TotalPlayersOnTeam(float theteam) +{ + entity e; + float plcount = 0; + FOR_EACH_PLAYER(e) if(e.team == theteam) ++plcount; + + return plcount; +} + +float JB_AlivePlayersOnTeam(float theteam) +{ + entity e; + float plcount = 0; + FOR_EACH_PLAYER(e) if(e.team == theteam) if(!e.jb_isprisoned) ++plcount; + + return plcount; +} + +entity JB_FindCamera(float theteam) +{ + RandomSelection_Init(); + + entity e = world; + for(;(e = find(e, classname, "info_jailbreak_jailcamera"));) if(e.team == theteam) + RandomSelection_Add(e, 0, string_null, 1, 1); + + return RandomSelection_chosen_ent; +} + +float jb_ce_pvs, jb_ce_trace; + +void JB_ActivateCamera(float theteam) +{ + entity cam = JB_FindCamera(theteam); + + if(!cam) + { + jb_debug(strcat("Team ", ftos(theteam), " has no camera entities, fail!\n")); + return; + } + + jb_ce_pvs = cvar("sv_cullentities_pvs"); + jb_ce_trace = cvar("sv_cullentities_trace"); + + // without this we won't be able to watch them burn! + cvar_settemp("sv_cullentities_pvs", "0"); + cvar_settemp("sv_cullentities_trace", "0"); + + entity p; + FOR_EACH_REALCLIENT(p) + p.jb_roundlost = TRUE; + + cam.active = TRUE; + cam.SendFlags |= 2; + + entity e; + for(e = world; (e = find(e, classname, "info_jailbreak_jailcamera")); ) + if(e != cam) + { + e.active = FALSE; + e.SendFlags |= 2; + } +} + +// +// Setup functions +// + +void JB_SetupJailSpawnpoint() +{ + if(!g_jailbreak) { remove(self); return; } + + self.classname = "info_jailbreak_jailspawn"; +} + +void JB_Jail_Touch() +{ + if(autocvar_g_nades) + if(other.classname == "nade") + { + entity own = other.realowner; + remove(other); + nades_Clear(own, FALSE); + return; + } + + if(other.classname == "grapplinghook") + { + RemoveGrapplingHook(other.realowner); + return; + } + + if(!g_jailbreak) + return; + + if(!IS_PLAYER(other)) + return; + + if(!other.jb_isprisoned) + { + vector mymid = (self.absmin + self.absmax) * 0.5; + vector othermid = (other.absmin + other.absmax) * 0.5; + + Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * min(500, vlen(other.velocity))); + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_JAILBREAK_NOENTRY); + return; + } + + other.jb_isprisoned = 2; + + if(SAME_TEAM(other, self)) + return; + + other.jb_jail_resettime = time + frametime * 5; +} + +void JB_SetupJail() +{ + self.classname = "jailbreak_jail"; + self.touch = JB_Jail_Touch; + EXACTTRIGGER_INIT; +} + +float jailcamera_send(entity to, float sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_JAILCAMERA); + WriteByte(MSG_ENTITY, sf); + + if(sf & 1) + { + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); + + WriteAngle(MSG_ENTITY, self.angles_x); + WriteAngle(MSG_ENTITY, self.angles_y); + } + + if(sf & 2) + { + WriteByte(MSG_ENTITY, self.active); + } + + return TRUE; +} + + +void JB_SetupJailCamera() +{ + if(!g_jailbreak) { remove(self); return; } + + self.classname = "info_jailbreak_jailcamera"; + + Net_LinkEntity(self, FALSE, 0, jailcamera_send); +} + +void JB_SetupTortureSpawnpoint() +{ + if(!g_jailbreak) { remove(self); return; } + + self.classname = "info_jailbreak_torturespawn"; + + if(!self.jb_torture_force) + self.jb_torture_force = 400; + + if(!self.jb_torture_force_jitter) + self.jb_torture_force_jitter = self.jb_torture_force * 0.1; + + if(!self.jb_torture_delay) + self.jb_torture_delay = 2; + + if(!self.jb_torture_delay_jitter) + self.jb_torture_delay_jitter = self.jb_torture_delay * 0.5; +} + +.entity sprite; +void AnimateDomPoint(); + +string JB_ControlPoint_ModelForTeam(float t) +{ + switch(t) + { + case NUM_TEAM_1: return "models/domination/dom_red.md3"; + case NUM_TEAM_2: return "models/domination/dom_blue.md3"; + case NUM_TEAM_3: return "models/domination/dom_yellow.md3"; + case NUM_TEAM_4: return "models/domination/dom_pink.md3"; + default: return "models/domination/dom_unclaimed.md3"; + } +} + +string JB_ControlPoint_WaypointForTeam(float t) +{ + switch(t) + { + case NUM_TEAM_1: return "dom-red"; + case NUM_TEAM_2: return "dom-blue"; + case NUM_TEAM_3: return "dom-yellow"; + case NUM_TEAM_4: return "dom-pink"; + default: return "dom-neut"; + } +} + +float JB_ControlPoint_Cooldown(entity e) +{ + float base, pw, f, c; + + c = e.jb_capturecount; + + if(e.team == 0) + { + base = autocvar_g_jailbreak_controlpoint_idletime_neutral; + pw = autocvar_g_jailbreak_controlpoint_idletime_neutral_power; + f = autocvar_g_jailbreak_controlpoint_idletime_neutral_factor; + } + else + { + base = autocvar_g_jailbreak_controlpoint_idletime; + pw = autocvar_g_jailbreak_controlpoint_idletime_power; + f = autocvar_g_jailbreak_controlpoint_idletime_factor; + } + + return base + pow(c, pw) * f * base; +} + +float JB_ControlPoint_InitialCooldown(entity e) { return ((e.team == 0) ? autocvar_g_jailbreak_controlpoint_idletime_neutral_initial : autocvar_g_jailbreak_controlpoint_idletime_initial); } + +void JB_ControlPoint_Activate(entity e) +{ + e.jb_active = TRUE; + e.jb_cooldown = 0; + //e.jb_cooldown_max = 0; + setmodel(e, JB_ControlPoint_ModelForTeam(e.team)); + setsize(e, JB_CP_MIN, JB_CP_MAX); + WaypointSprite_UpdateMaxHealth(e.jb_waypoint, 0); + WaypointSprite_UpdateHealth(e.jb_waypoint, 0); +} + +void JB_ControlPoint_Deactivate(entity e, float cooldown) +{ + e.jb_cooldown_max = max(e.jb_cooldown_max, cooldown); + e.jb_cooldown = max(e.jb_cooldown, cooldown); + + jb_debug(sprintf("%e: %ds cooldown, team: %d, caps: %d\n", e, e.jb_cooldown, e.team, e.jb_capturecount)); + + if(e.jb_active && e.jb_cooldown > 0) + { + setmodel(e, "models/domination/dom_unclaimed.md3"); + setsize(e, JB_CP_MIN, JB_CP_MAX); + e.jb_active = FALSE; + } +} + +void JB_ControlPoint_UpdateCooldownProgress(entity e) +{ + WaypointSprite_UpdateMaxHealth(e.jb_waypoint, e.jb_cooldown_max); + WaypointSprite_UpdateHealth(e.jb_waypoint, e.jb_cooldown_max - e.jb_cooldown); +} + +void JB_ControlPoint_Think() +{ + self.nextthink = time; + AnimateDomPoint(); + + if(time < game_starttime || jb_roundover) + return; + + if(self.jb_cooldown) { JB_ControlPoint_UpdateCooldownProgress(self); } + else if(!self.jb_active) { JB_ControlPoint_Activate(self); } + + if(time - self.pointupdatetime >= 0.1) + { + self.jb_unlock_progress = 0; + self.jb_capturingplayer = world; + } + + self.jb_cooldown = max(0, self.jb_cooldown - frametime); +} + +void JB_ControlPoint_SwitchTeam(entity e, float t) +{ + e.team = t; + //WaypointSprite_UpdateSprites(e.jb_waypoint, e.jb_waypoint.model1, "", e.jb_waypoint.model3); + WaypointSprite_UpdateTeamRadar(e.jb_waypoint, RADARICON_FLAG, (e.team) ? colormapPaletteColor(e.team - 1, FALSE) : '0 1 1'); + //WaypointSprite_UpdateTextColors(e.jb_waypoint, TeamColor(e.team), e.jb_waypoint.clr2, e.jb_waypoint.clr3); +} + +void JB_TriggerTeamControlPoints(entity cp, entity player) +{ + entity oldself = self; + self = world; + + for(self = jb_worldcplist; self; self = self.jb_worldcpnext) + { + if(cp.team) + { + if(SAME_TEAM(self, cp)) + SUB_UseTargets_PreventReuse(); + } + else + { + if(self.jb_team_initial != player.team) + SUB_UseTargets_PreventReuse(); + } + } + + self = oldself; +} + +string JB_ControlPoint_Name(entity p) +{ + string clr, tm, end; + + clr = Team_ColorCode(p.team); + tm = Team_ColorName(p.team); + + end = strcat(" (Point ", chr2str(str2chr("A", 0) + p.cnt), ")"); + + if(!p.netname) + return strcat(clr, tm, " Control Point", end); + return strcat(clr, strdecolorize(p.netname), end); +} + +void JB_ControlPoint_Capture(entity player) +{ + entity e; + float pc = FALSE; + + activator = self; + + if(!self.team || g_jailbreak_claim) + JB_TriggerTeamControlPoints(self, player); + else SUB_UseTargets(); + + FOR_EACH_PLAYER(e) + { + if(DIFF_TEAM(e, player)) + Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_JAILBREAK_ESCAPE, player.team); + else if(e == player) + Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_JAILBREAK_FREED); + + if(e.jb_isprisoned && SAME_TEAM(e, player)) + { + Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_JAILBREAK_FREE); + pc++; + } + + } + + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_JAILBREAK_CAPTURE, player.netname, JB_ControlPoint_Name(self)); + + for(e = jb_worldcplist; e; e = e.jb_worldcpnext) + { + e.jb_lastmessage = time + 3; + } + + PlayerScore_Add(player, SP_SCORE, ((self.team == 0)? autocvar_g_jailbreak_score_jbreak_neutralmultiplier : 1) + * (autocvar_g_jailbreak_score_jbreak + autocvar_g_jailbreak_score_jbreak_perplayer * pc)); + PlayerScore_Add(player, SP_JB_JBREAKS, 1); + PlayerScore_Add(player, SP_JB_FREED, pc); + nades_GiveBonus(player, autocvar_g_nades_bonus_score_medium); + play2all("kh/alarm.wav"); + + if(autocvar_g_jailbreak_controlpoint_claim_noneutral) + if(self.team == 0) + return; + + JB_ControlPoint_SwitchTeam(self, player.team); +} + +void JB_ControlPoint_Touch() +{ + if(jb_roundover) + return; + + if(other.health < 1 || other.frozen) + return; + + if(gameover) + return; + + if(!IS_PLAYER(other)) + return; + + other.pointupdatetime = time; + + if(SAME_TEAM(other, self)) + { + if(time >= self.jb_lastmessage) + { + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_JAILBREAK_WRONGTEAM); + self.jb_lastmessage = time + 1.5; + } + return; + } + + if(!self.jb_active) + { + if(time >= self.jb_lastmessage) + { + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_JAILBREAK_NOTREADY); + self.jb_lastmessage = time + 1.5; + } + return; + } + + if(self.jb_capturingplayer && self.jb_capturingplayer != other) + { + if(time >= self.jb_lastmessage) + { + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_JAILBREAK_TOOLATE); + self.jb_lastmessage = time + 1.5; + } + return; + } + + if(JB_TotalPlayersOnTeam(other.team) == JB_AlivePlayersOnTeam(other.team)) + { + if(time >= self.jb_lastmessage) + { + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_JAILBREAK_TEAMALIVE); + self.jb_lastmessage = time + 1.5; + } + return; + } + + entity tmp_entity; + float capping_neutral = FALSE; + if(self.team) + for(tmp_entity = jb_worldcplist; tmp_entity; tmp_entity = tmp_entity.jb_worldcpnext) + { + if(!tmp_entity.team) + if(tmp_entity.jb_unlock_progress) + if(SAME_TEAM(tmp_entity.jb_capturingplayer, other)) + { + capping_neutral = TRUE; + break; + } + } + + if(!capping_neutral || !self.team) + self.jb_unlock_progress = bound(0, self.jb_unlock_progress + frametime * autocvar_g_jailbreak_controlpoint_unlock_speed, 1); + + self.pointupdatetime = time; + self.jb_capturingplayer = other; + other.jb_unlock_progress = self.jb_unlock_progress; + + if(self.jb_unlock_progress >= 1) + { + JB_ControlPoint_Capture(other); + + JB_ControlPoint_Deactivate(self, JB_ControlPoint_Cooldown(self)); + + for(tmp_entity = jb_worldcplist; tmp_entity; tmp_entity = tmp_entity.jb_worldcpnext) + { + if(tmp_entity != self) + if(SAME_TEAM(tmp_entity, other)) + JB_ControlPoint_Deactivate(tmp_entity, autocvar_g_jailbreak_controlpoint_idletime_global_own); + else if(!tmp_entity.team || DIFF_TEAM(tmp_entity, other)) + JB_ControlPoint_Deactivate(tmp_entity, autocvar_g_jailbreak_controlpoint_idletime_global); + } + + self.jb_capturecount += 1; + } +} + +void JB_ControlPoint_Reset() +{ + self.jb_capturecount = 0; + self.jb_active = TRUE; + self.jb_cooldown = 0; + self.jb_cooldown_max = 0; + JB_ControlPoint_Deactivate(self, JB_ControlPoint_InitialCooldown(self)); + WaypointSprite_UpdateMaxHealth(self.jb_waypoint, 0); + WaypointSprite_UpdateHealth(self.jb_waypoint, 0); + JB_ControlPoint_SwitchTeam(self, autocvar_g_jailbreak_controlpoint_claim_allneutral ? 0 : self.jb_team_initial); +} + +float jb_ControlPoint_Waypoint_Customize() +{ + if(!self.owner.team) { return TRUE; } + + entity e = WaypointSprite_getviewentity(other); + + // hide from owner's team + if(SAME_TEAM(self.owner, e)) { return FALSE; } + + return TRUE; +} + +void JB_SetupControlPoint() +{ + self.jb_worldcpnext = jb_worldcplist; // link control point into jb_worldcplist + jb_worldcplist = self; + + if(!g_jailbreak) { return; } // removal is done in JB_NonJBInit + + self.classname = "jailbreak_controlpoint"; + self.jb_team_initial = self.team; + + if(autocvar_g_jailbreak_controlpoint_claim_allneutral) + self.team = 0; + + setmodel(self, JB_ControlPoint_ModelForTeam(self.team)); + self.skin = 0; + + if(!self.t_width) + self.t_width = 0.02; // frame animation rate + if(!self.t_length) + self.t_length = 239; // maximum frame + + self.think = JB_ControlPoint_Think; + self.nextthink = time; + self.touch = JB_ControlPoint_Touch; + self.solid = SOLID_TRIGGER; + self.flags = FL_ITEM; + self.reset = JB_ControlPoint_Reset; + self.jb_capturecount = 0; + self.jb_active = TRUE; + self.cnt = jb_cp_num; + self.scale = JB_CP_SCALE; + JB_ControlPoint_Deactivate(self, JB_ControlPoint_InitialCooldown(self)); + setsize(self, JB_CP_MIN, JB_CP_MAX); + setorigin(self, self.origin + '0 0 20'); + droptofloor(); + + waypoint_spawnforitem_force(self, self.origin); + self.nearestwaypointtimeout = 0; // activate waypointing again + WaypointSprite_SpawnFixed(strzone(strcat("Point ", chr2str(str2chr("A", 0) + jb_cp_num))), self.origin + JB_CP_WPOFFSET, self, jb_waypoint, RADARICON_DOMPOINT, '0 1 1'); + self.jb_waypoint.customizeentityforclient = jb_ControlPoint_Waypoint_Customize; + WaypointSprite_UpdateTeamRadar(self.jb_waypoint, RADARICON_FLAG, (self.team) ? colormapPaletteColor(self.team - 1, FALSE) : '0 1 1'); + //WaypointSprite_UpdateTextColors(self.jb_waypoint, TeamColor(self.team), '1 0.5 0', '0 0 0'); + //WaypointSprite_UpdateSprites(self.jb_waypoint, self.jb_waypoint.model1, self.jb_waypoint.model2, ""); + + ++jb_cp_num; +} + +// mutator hooks +MUTATOR_HOOKFUNCTION(jb_OnEntityPreSpawn) +{ + switch(self.classname) + { + case "item_flag_team1": + case "item_flag_team2": + case "item_flag_team3": + case "item_flag_team4": + return TRUE; + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(jb_ResetMap) +{ + jb_roundover = FALSE; + + cvar_set("sv_cullentities_pvs", ftos(jb_ce_pvs)); + cvar_set("sv_cullentities_trace", ftos(jb_ce_trace)); + + for(self = world; (self = find(self, classname, "info_jailbreak_jailcamera")); ) + self.active = FALSE; + + FOR_EACH_CLIENT(self) + { + if(IS_PLAYER(self)) + { + JB_Release(world); + PutClientInServer(); + } + self.player_blocked = 0; + self.weapon_blocked = FALSE; + self.jb_roundlost = FALSE; + } + + return TRUE; +} + +MUTATOR_HOOKFUNCTION(jb_PlayerDies) +{ + if(jb_roundover && frag_deathtype == DEATH_FIRE) + PlayerScore_Add(frag_target, SP_KILLS, +1); // dying negates 1, so we bring it back up + + if(!round_handler_IsRoundStarted() || jb_roundover) + return FALSE; + + if(!frag_target.jb_isprisoned) + { + if(frag_attacker == frag_target || !frag_attacker) + PlayerScore_Add(frag_target, SP_SCORE, -autocvar_g_jailbreak_penalty_death); + else if(IS_PLAYER(frag_attacker)) + { + if(DIFF_TEAM(frag_attacker, frag_target)) + { + PlayerScore_Add(frag_target, SP_SCORE, -autocvar_g_jailbreak_penalty_death); + PlayerScore_Add(frag_attacker, SP_SCORE, autocvar_g_jailbreak_score_imprison); + + float rng = autocvar_g_jailbreak_defense_range; + entity cp; + if(rng) + for(cp = jb_worldcplist; cp; cp = cp.jb_worldcpnext) + { + if(SAME_TEAM(cp, frag_attacker) || (cp.team == 0 && cp.jb_active)) + { + // Rewards control point defense if fragging nearby your team's or neutral cp. + // In case of neutral cp, it has to be active (no defense farming in the beginning of the round) + if(vlen(cp.origin - frag_target.origin) < rng) + { + Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_JAILBREAK_DEFENSE); + PlayerScore_Add(frag_attacker, SP_SCORE, autocvar_g_jailbreak_score_defense); + PlayerScore_Add(frag_attacker, SP_JB_DEFENSE, 1); + nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); + break; + } + } + } + } + else PlayerScore_Add(frag_attacker, SP_SCORE, -autocvar_g_jailbreak_penalty_teamkill); + } + + frag_target.jb_imprisoner = frag_attacker; + frag_target.respawn_flags |= RESPAWN_FORCE; + } + else + { + jb_debug(strcat("Prisoned player ", frag_target.netname, "^7 just died, should this really happen?\n")); + PutClientInServer(); + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(jb_PlayerSpawn) +{ + //self.jb_isprisoned = FALSE; + + if(!round_handler_IsRoundStarted()) { return FALSE; } + + if(self.jb_imprisoner != world) + { + JB_Imprison(self.jb_imprisoner); + self.jb_imprisoner = world; + } + + if(JB_TotalPlayersOnTeam(self.team) - 1 > 0) // allow to spawn non-prisoned if there are no players on that team + JB_Imprison(world); + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(jb_SpectateCopy) +{ + self.stat_jb_isprisoned = other.jb_isprisoned; + self.jb_unlock_progress = other.jb_unlock_progress; + return FALSE; +} + +MUTATOR_HOOKFUNCTION(jb_RemovePlayer) +{ + if(self.jb_isprisoned) + JB_Release(world); + + self.jb_roundlost = FALSE; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(jb_PlayerPreThink) +{ + if(gameover) + { + self.stat_jb_isprisoned = FALSE; + return FALSE; + } + + self.stat_jb_isprisoned = self.jb_isprisoned; // these are different to allow spectating + + if(!round_handler_IsRoundStarted()) + { + self.jb_isprisoned_prev = 0; + self.jb_unlock_progress = 0; + return FALSE; + } + + float ps = min(1, self.jb_isprisoned); + if(ps != self.jb_isprisoned_prev) + { + if(!ps) + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_JAILBREAK_FREE, self.netname); + self.jb_isprisoned_prev = ps; + } + + if(time - self.pointupdatetime >= 0.01) + self.jb_unlock_progress = 0; + + if(time - self.jb_prisontime < 0.5) + return FALSE; + + if(self.jb_isprisoned == 1) + { + jb_debug(strcat("Warning: ", self.netname, "^7managed to leave the jail without touching the jail sector! Attempting to put them back in\n")); + JB_TeleportToJail(self, world); + } + else if(self.jb_isprisoned) + if(time > self.jb_jail_resettime) + JB_Release(world); + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(jb_PlayerDamage) +{ + if(self.jb_isprisoned && frag_deathtype != DEATH_FIRE) + frag_damage = 0; + + entity tmp_entity; + if(self.jb_unlock_progress) + for(tmp_entity = jb_worldcplist; tmp_entity; tmp_entity = tmp_entity.jb_worldcpnext) + { + if(tmp_entity.jb_capturingplayer == self) + tmp_entity.jb_unlock_progress = bound(0, tmp_entity.jb_unlock_progress - autocvar_g_jailbreak_controlpoint_unlock_damage_pushback, 1); + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(jb_ForbidThrowing) +{ + if(self.jb_isprisoned) + return TRUE; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(jb_GiveFrags) +{ + if(jb_roundover) + { + frag_score = 0; + return TRUE; + } + return FALSE; +} + +MUTATOR_HOOKFUNCTION(jb_GetTeamCount) +{ + ret_float = jb_teams; + return FALSE; +} + +MUTATOR_HOOKFUNCTION(jb_AllowMobSpawning) +{ + if(self.jb_isprisoned) + { + ret_string = "You can't spawn monsters in prison!"; + return TRUE; + } + + return FALSE; +} + +// spawn functions +#define JB_SPAWNFUNC(e,s,t) void spawnfunc_##e() { self.team = t; s(); } + +JB_SPAWNFUNC(info_jailbreak_jailspawn_red, JB_SetupJailSpawnpoint, NUM_TEAM_1) +JB_SPAWNFUNC(info_jailbreak_jailspawn_blue, JB_SetupJailSpawnpoint, NUM_TEAM_2) +JB_SPAWNFUNC(info_jailbreak_jailspawn_yellow, JB_SetupJailSpawnpoint, NUM_TEAM_3) +JB_SPAWNFUNC(info_jailbreak_jailspawn_pink, JB_SetupJailSpawnpoint, NUM_TEAM_4) + +JB_SPAWNFUNC(func_jailbreak_jail_red, JB_SetupJail, NUM_TEAM_1) +JB_SPAWNFUNC(func_jailbreak_jail_blue, JB_SetupJail, NUM_TEAM_2) +JB_SPAWNFUNC(func_jailbreak_jail_yellow, JB_SetupJail, NUM_TEAM_3) +JB_SPAWNFUNC(func_jailbreak_jail_pink, JB_SetupJail, NUM_TEAM_4) + +JB_SPAWNFUNC(info_jailbreak_jailcamera_red, JB_SetupJailCamera, NUM_TEAM_1) +JB_SPAWNFUNC(info_jailbreak_jailcamera_blue, JB_SetupJailCamera, NUM_TEAM_2) +JB_SPAWNFUNC(info_jailbreak_jailcamera_yellow, JB_SetupJailCamera, NUM_TEAM_3) +JB_SPAWNFUNC(info_jailbreak_jailcamera_pink, JB_SetupJailCamera, NUM_TEAM_4) + +JB_SPAWNFUNC(info_jailbreak_torturespawn_red, JB_SetupTortureSpawnpoint, NUM_TEAM_1) +JB_SPAWNFUNC(info_jailbreak_torturespawn_blue, JB_SetupTortureSpawnpoint, NUM_TEAM_2) +JB_SPAWNFUNC(info_jailbreak_torturespawn_yellow, JB_SetupTortureSpawnpoint, NUM_TEAM_3) +JB_SPAWNFUNC(info_jailbreak_torturespawn_pink, JB_SetupTortureSpawnpoint, NUM_TEAM_4) + +JB_SPAWNFUNC(jailbreak_controlpoint_red, JB_SetupControlPoint, NUM_TEAM_1) +JB_SPAWNFUNC(jailbreak_controlpoint_blue, JB_SetupControlPoint, NUM_TEAM_2) +JB_SPAWNFUNC(jailbreak_controlpoint_yellow, JB_SetupControlPoint, NUM_TEAM_3) +JB_SPAWNFUNC(jailbreak_controlpoint_pink, JB_SetupControlPoint, NUM_TEAM_4) +JB_SPAWNFUNC(jailbreak_controlpoint_neutral, JB_SetupControlPoint, 0) + +// scores +void jb_ScoreRules(float teams) +{ + ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, TRUE); // SFL_SORT_PRIO_PRIMARY + ScoreInfo_SetLabel_TeamScore(ST_JB_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_JB_JBREAKS, "jbs", 0); + ScoreInfo_SetLabel_PlayerScore(SP_JB_FREED, "freed", SFL_SORT_PRIO_SECONDARY); + ScoreInfo_SetLabel_PlayerScore(SP_JB_DEFENSE, "def", 0); + ScoreRules_basics_end(); +} + +// initialization +void jb_DelayedInit() +{ + entity tmp_entity; + + SUB_ForEachTarget_Init(); + for(tmp_entity = jb_worldcplist; tmp_entity; tmp_entity = tmp_entity.jb_worldcpnext) + SUB_ForEachTarget(tmp_entity, JB_AddDoor, TRUE, tmp_entity.jb_team_initial, '0 0 0', string_null, tmp_entity); +} + +void jb_Initialize() +{ + precache_sound("kh/alarm.wav"); + + if(autocvar_g_jailbreak_teams_override >= 2) + jb_teams = autocvar_g_jailbreak_teams_override; + else + jb_teams = autocvar_g_jailbreak_teams; + + jb_teams = bound(2, jb_teams, 4); + + jb_ScoreRules(jb_teams); + + round_handler_Spawn(JB_CheckTeams, JB_CheckWinner, JB_RoundStart); + round_handler_Init(5, autocvar_g_jailbreak_warmup, autocvar_g_jailbreak_round_timelimit); + + g_jailbreak_claim = autocvar_g_jailbreak_controlpoint_claim; + + addstat(STAT_REDALIVE, AS_INT, redalive_stat); + addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat); + addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat); + addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat); + addstat(STAT_CAPTURE_PROGRESS, AS_FLOAT, jb_unlock_progress); + addstat(STAT_PRISONED, AS_INT, stat_jb_isprisoned); + addstat(STAT_ROUNDLOST, AS_INT, jb_roundlost); + + g_jailbreak_jail_deathmatch = autocvar_g_jailbreak_jail_deathmatch; + InitializeEntity(world, jb_DelayedInit, INITPRIO_GAMETYPE); +} + +MUTATOR_DEFINITION(gamemode_jailbreak) +{ + MUTATOR_HOOK(OnEntityPreSpawn, jb_OnEntityPreSpawn, CBC_ORDER_FIRST); + MUTATOR_HOOK(reset_map_players, jb_ResetMap, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDies, jb_PlayerDies, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerSpawn, jb_PlayerSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(SpectateCopy, jb_SpectateCopy, CBC_ORDER_ANY); + MUTATOR_HOOK(ClientDisconnect, jb_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(MakePlayerObserver, jb_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerPreThink, jb_PlayerPreThink, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDamage_Calculate, jb_PlayerDamage, CBC_ORDER_ANY); + MUTATOR_HOOK(GiveFragsForKill, jb_GiveFrags, CBC_ORDER_ANY); + MUTATOR_HOOK(ForbidThrowCurrentWeapon, jb_ForbidThrowing, CBC_ORDER_ANY); + MUTATOR_HOOK(GetTeamCount, jb_GetTeamCount, CBC_ORDER_ANY); + MUTATOR_HOOK(AllowMobSpawning, jb_AllowMobSpawning, CBC_ORDER_LAST); + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + jb_Initialize(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back jb_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + print("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} diff --git a/qcsrc/server/mutators/gamemode_jailbreak.qh b/qcsrc/server/mutators/gamemode_jailbreak.qh new file mode 100644 index 000000000..4d3301c4d --- /dev/null +++ b/qcsrc/server/mutators/gamemode_jailbreak.qh @@ -0,0 +1,86 @@ +float g_jailbreak_claim; +float g_jailbreak_jail_deathmatch; + +void JB_NonJBInit(); +entity jb_ChooseJailSpawn(entity player, entity attacker); +float JB_TotalPlayersOnTeam(float); +float JB_AlivePlayersOnTeam(float); +void JB_ActivateCamera(float); +void JB_TorturePrisoners(float); +void JB_TorturePrisonersLater(float, float); +void JB_Torture_Start(entity); +// void JB_SetCamera(entity, entity, float); +// void JB_ClearCamera(entity); +// void JB_ClearCameraForAll(); +void JB_ControlPoint_Activate(entity); +void JB_ControlPoint_Deactivate(entity, float); +void JB_ControlPoint_UpdateCooldownProgress(entity); +string JB_ControlPoint_Name(entity); +float JB_JailIsOpen(float); + +.float stat_jb_isprisoned; + +.float jb_unlock_progress; +.float jb_isprisoned; +.float jb_isprisoned_prev; +.float jb_jail_resettime; +.float jb_prisontime; +.float jb_cooldown; +.float jb_cooldown_max; +.float jb_active; +.float jb_capturecount; +.float jb_roundlost; +.float jb_team_initial; +.float jb_defendthink_next; +.float jb_had_unlimited_ammo; +.float jb_lastmessage; + +.entity jb_waypoint; +.entity jb_capturingplayer; +.entity jb_imprisoner; + +float jb_roundover; +float jb_teams; + +float jb_cp_num; + +#define JB_CP_MIN ('-32 -32 -32') +#define JB_CP_MAX ('32 32 32') +#define JB_CP_SCALE 0.6 +#define JB_CP_WPOFFSET ('0 0 32') + +#define ST_JB_ROUNDS 1 +#define SP_JB_JBREAKS 4 +#define SP_JB_FREED 5 +#define SP_JB_DEFENSE 6 + +.float jaildoormode; +#define JAILDOORMODE_DEFAULT 0 +#define JAILDOORMODE_OPEN 1 +#define JAILDOORMODE_CLOSED 2 + +#define OPENJAILS_LOCKED 0 +#define OPENJAILS_OPEN 1 +#define OPENJAILS_LOCKED_FORCE 2 +#define OPENJAILS_OPEN_FORCE 3 + +// list of doors on the map +entity jb_worlddoorlist; +.entity jb_worlddoornext; + +// list of control points on the map +entity jb_worldcplist; +.entity jb_worldcpnext; + +.float jb_torture_force; +.float jb_torture_force_jitter; +.float jb_torture_delay; +.float jb_torture_delay_jitter; +.float jb_torture_suggestedforce; + +#define JB_PROJ_OWNERSTATE_UNDEFINED 0 +#define JB_PROJ_OWNERSTATE_IMPRISONED 1 +#define JB_PROJ_OWNERSTATE_FREE 2 + +// TODO: cvar this +#define JB_TORTURE_DURATION 15 diff --git a/qcsrc/server/mutators/gamemode_keepaway.qc b/qcsrc/server/mutators/gamemode_keepaway.qc index 7e1006ec1..f46d6883b 100644 --- a/qcsrc/server/mutators/gamemode_keepaway.qc +++ b/qcsrc/server/mutators/gamemode_keepaway.qc @@ -40,8 +40,8 @@ void ka_RespawnBall() // runs whenever the ball needs to be relocated self.think = ka_RespawnBall; self.nextthink = time + autocvar_g_keepawayball_respawntime; - pointparticles(particleeffectnum("electro_combo"), oldballorigin, '0 0 0', 1); - pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_COMBO, self.origin, '0 0 0', 1); WaypointSprite_Spawn("ka-ball", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attachedforcarrier, FALSE, RADARICON_FLAGCARRIER, '0 1 1'); WaypointSprite_Ping(self.waypointsprite_attachedforcarrier); @@ -72,9 +72,9 @@ void ka_TouchEvent() // runs any time that the ball comes in contact with someth } if(other.deadflag != DEAD_NO) { return; } if(other.frozen) { return; } - if (!IS_PLAYER(other)) + if(!IS_PLAYER(other)) { // The ball just touched an object, most likely the world - pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1); + Send_Effect(EFFECT_BALL_SPARKS, self.origin, '0 0 0', 1); sound(self, CH_TRIGGER, "keepaway/touch.wav", VOL_BASE, ATTEN_NORM); return; } @@ -343,6 +343,16 @@ MUTATOR_HOOKFUNCTION(ka_PlayerPowerups) return 0; } +MUTATOR_HOOKFUNCTION(ka_PlayerPhysics) +{ + if(self.ballcarried) + { + self.stat_sv_airspeedlimit_nonqw *= autocvar_g_keepaway_ballcarrier_highspeed; + self.stat_sv_maxspeed *= autocvar_g_keepaway_ballcarrier_highspeed; + } + return FALSE; +} + MUTATOR_HOOKFUNCTION(ka_BotRoles) { if (self.ballcarried) @@ -352,6 +362,14 @@ MUTATOR_HOOKFUNCTION(ka_BotRoles) return TRUE; } +MUTATOR_HOOKFUNCTION(ka_BotShouldAttack) +{ + // if neither player has ball then don't attack unless the ball is on the ground + if(!other.ballcarried && !self.ballcarried && ka_ball.owner) + return TRUE; + return FALSE; +} + // ============== // Initialization @@ -417,7 +435,9 @@ MUTATOR_DEFINITION(gamemode_keepaway) MUTATOR_HOOK(PlayerDamage_Calculate, ka_PlayerDamage, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerPowerups, ka_PlayerPowerups, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerUseKey, ka_PlayerUseKey, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerPhysics, ka_PlayerPhysics, CBC_ORDER_ANY); MUTATOR_HOOK(HavocBot_ChooseRole, ka_BotRoles, CBC_ORDER_ANY); + MUTATOR_HOOK(BotShouldAttack, ka_BotShouldAttack, CBC_ORDER_ANY); MUTATOR_ONADD { diff --git a/qcsrc/server/mutators/gamemode_keyhunt.qc b/qcsrc/server/mutators/gamemode_keyhunt.qc index 7b3ea98af..e31bf110a 100644 --- a/qcsrc/server/mutators/gamemode_keyhunt.qc +++ b/qcsrc/server/mutators/gamemode_keyhunt.qc @@ -1,201 +1,84 @@ -#define FOR_EACH_KH_KEY(v) for(v = kh_worldkeylist; v; v = v.kh_worldkeynext ) - -// #define KH_PLAYER_USE_ATTACHMENT -// #define KH_PLAYER_USE_CARRIEDMODEL - -#ifdef KH_PLAYER_USE_ATTACHMENT -const vector KH_PLAYER_ATTACHMENT_DIST_ROTATED = '0 -4 0'; -const vector KH_PLAYER_ATTACHMENT_DIST = '4 0 0'; -const vector KH_PLAYER_ATTACHMENT = '0 0 0'; -const vector KH_PLAYER_ATTACHMENT_ANGLES = '0 0 0'; -const string KH_PLAYER_ATTACHMENT_BONE = ""; -#else -const float KH_KEY_ZSHIFT = 22; -const float KH_KEY_XYDIST = 24; -const float KH_KEY_XYSPEED = 45; -#endif -const float KH_KEY_WP_ZSHIFT = 20; - -const vector KH_KEY_MIN = '-10 -10 -46'; -const vector KH_KEY_MAX = '10 10 3'; -const float KH_KEY_BRIGHTNESS = 2; - -float kh_no_radar_circles; - -// kh_state -// bits 0- 4: team of key 1, or 0 for no such key, or 30 for dropped, or 31 for self -// bits 5- 9: team of key 2, or 0 for no such key, or 30 for dropped, or 31 for self -// bits 10-14: team of key 3, or 0 for no such key, or 30 for dropped, or 31 for self -// bits 15-19: team of key 4, or 0 for no such key, or 30 for dropped, or 31 for self -.float kh_state; -.float siren_time; // time delay the siren -//.float stuff_time; // time delay to stuffcmd a cvar - -float kh_keystatus[17]; -//kh_keystatus[0] = status of dropped keys, kh_keystatus[1 - 16] = player # -//replace 17 with cvar("maxplayers") or similar !!!!!!!!! -//for(i = 0; i < maxplayers; ++i) -// kh_keystatus[i] = "0"; - -float kh_Team_ByID(float t) -{ - if(t == 0) return NUM_TEAM_1; - if(t == 1) return NUM_TEAM_2; - if(t == 2) return NUM_TEAM_3; - if(t == 3) return NUM_TEAM_4; - return 0; -} - -entity kh_worldkeylist; -.entity kh_worldkeynext; -entity kh_controller; -float kh_tracking_enabled; -float kh_teams; -float kh_interferemsg_time, kh_interferemsg_team; -.entity kh_next, kh_prev; // linked list -.float kh_droptime; -.float kh_dropperteam; -.entity kh_previous_owner; -.float kh_previous_owner_playerid; -.float kh_cp_duration; - -string kh_sound_capture = "kh/capture.wav"; -string kh_sound_destroy = "kh/destroy.wav"; -string kh_sound_drop = "kh/drop.wav"; -string kh_sound_collect = "kh/collect.wav"; -string kh_sound_alarm = "kh/alarm.wav"; // the new siren/alarm - -float kh_key_dropped, kh_key_carried; - -#define ST_KH_CAPS 1 -#define SP_KH_CAPS 4 -#define SP_KH_PUSHES 5 -#define SP_KH_DESTROYS 6 -#define SP_KH_PICKUPS 7 -#define SP_KH_KCKILLS 8 -#define SP_KH_LOSSES 9 -void kh_ScoreRules(float teams) -{ - ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, TRUE); - ScoreInfo_SetLabel_TeamScore( ST_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); - ScoreInfo_SetLabel_PlayerScore(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); - ScoreInfo_SetLabel_PlayerScore(SP_KH_PUSHES, "pushes", 0); - ScoreInfo_SetLabel_PlayerScore(SP_KH_DESTROYS, "destroyed", SFL_LOWER_IS_BETTER); - ScoreInfo_SetLabel_PlayerScore(SP_KH_PICKUPS, "pickups", 0); - ScoreInfo_SetLabel_PlayerScore(SP_KH_KCKILLS, "kckills", 0); - ScoreInfo_SetLabel_PlayerScore(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER); - ScoreRules_basics_end(); -} +// ========================================================= +// Unofficial key hunt game mode coding, reworked by Mario +// ========================================================= -float kh_KeyCarrier_waypointsprite_visible_for_player(entity e) // runs all the time +void kh_EventLog(string mode, float keyteam, entity actor) // use an alias for easy changing and quick editing later { - if(!IS_PLAYER(e) || self.team != e.team) - if(!kh_tracking_enabled) - return FALSE; - - return TRUE; + if(autocvar_sv_eventlog) + GameLogEcho(sprintf(":keyhunt:%s:%d:%d:%s", mode, keyteam, actor.team, ((actor != world) ? ftos(actor.playerid) : ""))); } -float kh_Key_waypointsprite_visible_for_player(entity e) // ?? +void kh_KeycarrierWaypoints(entity player) { - if(!kh_tracking_enabled) - return FALSE; - if(!self.owner) - return TRUE; - if(!self.owner.owner) - return TRUE; - return FALSE; // draw only when key is not owned + WaypointSprite_Spawn("keycarrier", 0, 0, player, KEY_WAYPOINT_OFFSET, world, 0, player, wps_keycarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYKC(player.team)); + WaypointSprite_UpdateMaxHealth(player.wps_keycarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2); + WaypointSprite_UpdateHealth(player.wps_keycarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)); + WaypointSprite_UpdateTeamRadar(player.wps_keycarrier, RADARICON_FLAGCARRIER, WPCOLOR_ENEMYKC(player.team)); } -void kh_update_state() +void kh_CalculatePassVelocity(entity key, vector to, vector from, float turnrate) { - entity player; - entity key; - float s; - float f; + float current_distance = vlen((('1 0 0' * to_x) + ('0 1 0' * to_y)) - (('1 0 0' * from_x) + ('0 1 0' * from_y))); // for the sake of this check, exclude Z axis + float initial_height = min(autocvar_g_keyhunt_pass_arc_max, (key.pass_distance * tanh(autocvar_g_keyhunt_pass_arc))); + float current_height = (initial_height * min(1, (current_distance / key.pass_distance))); + //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n"); - s = 0; - FOR_EACH_KH_KEY(key) + vector targpos; + if(current_height) // make sure we can actually do this arcing path { - if(key.owner) - f = key.team; - else - f = 30; - s |= pow(32, key.count) * f; + targpos = (to + ('0 0 1' * current_height)); + WarpZone_TraceLine(key.origin, targpos, MOVE_NOMONSTERS, key); + if(trace_fraction < 1) + { + //print("normal arc line failed, trying to find new pos..."); + WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, key); + targpos = (trace_endpos + KEY_PASS_ARC_OFFSET); + WarpZone_TraceLine(key.origin, targpos, MOVE_NOMONSTERS, key); + if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ } + /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */ + } } + else { targpos = to; } - FOR_EACH_CLIENT(player) - { - player.kh_state = s; - } + //key.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y)); - FOR_EACH_KH_KEY(key) - { - if(key.owner) - key.owner.kh_state |= pow(32, key.count) * 31; - } - //print(ftos((nextent(world)).kh_state), "\n"); + vector desired_direction = normalize(targpos - from); + if(turnrate) { key.velocity = (normalize(normalize(key.velocity) + (desired_direction * autocvar_g_keyhunt_pass_turnrate)) * autocvar_g_keyhunt_pass_velocity); } + else { key.velocity = (desired_direction * autocvar_g_keyhunt_pass_velocity); } } - - - -var kh_Think_t kh_Controller_Thinkfunc; -void kh_Controller_SetThink(float t, kh_Think_t func) // runs occasionaly +float kh_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer) { - kh_Controller_Thinkfunc = func; - kh_controller.cnt = ceil(t); - if(t == 0) - kh_controller.nextthink = time; // force -} -void kh_WaitForPlayers(); -void kh_Controller_Think() // called a lot -{ - if(intermission_running) - return; - if(self.cnt > 0) - { if(self.think != kh_WaitForPlayers) { self.cnt -= 1; } } - else if(self.cnt == 0) + if(autocvar_g_keyhunt_pass_directional_max || autocvar_g_keyhunt_pass_directional_min) { - self.cnt -= 1; - kh_Controller_Thinkfunc(); - } - self.nextthink = time + 1; -} - -// frags f: take from cvar * f -// frags 0: no frags -void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner) // update the score when a key is captured -{ - string s; - if(intermission_running) - return; - - if(frags_player) - UpdateFrags(player, frags_player); - - if(key && key.owner && frags_owner) - UpdateFrags(key.owner, frags_owner); - - if(!autocvar_sv_eventlog) //output extra info to the console or text file - return; + // directional tracing only + float spreadlimit; + makevectors(passer_angle); - s = strcat(":keyhunt:", what, ":", ftos(player.playerid), ":", ftos(frags_player)); + // find the closest point on the enemy to the center of the attack + float ang; // angle between shotdir and h + float h; // hypotenuse, which is the distance between attacker to head + float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin - if(key && key.owner) - s = strcat(s, ":", ftos(key.owner.playerid)); - else - s = strcat(s, ":0"); + h = vlen(head_center - passer_center); + ang = acos(dotproduct(normalize(head_center - passer_center), v_forward)); + a = h * cos(ang); - s = strcat(s, ":", ftos(frags_owner), ":"); + vector nearest_on_line = (passer_center + a * v_forward); + float distance_from_line = vlen(nearest_to_passer - nearest_on_line); - if(key) - s = strcat(s, key.netname); + spreadlimit = (autocvar_g_keyhunt_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_keyhunt_pass_radius)) : 1); + spreadlimit = (autocvar_g_keyhunt_pass_directional_min * (1 - spreadlimit) + autocvar_g_keyhunt_pass_directional_max * spreadlimit); - GameLogEcho(s); + if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90)) + { return TRUE; } + else + { return FALSE; } + } + else { return TRUE; } } -vector kh_AttachedOrigin(entity e) // runs when a team captures the flag, it can run 2 or 3 times. +vector kh_AttachedOrigin(entity e) { if(e.tag_entity) { @@ -206,889 +89,1502 @@ vector kh_AttachedOrigin(entity e) // runs when a team captures the flag, it ca return e.origin; } -void kh_Key_Attach(entity key) // runs when a player picks up a key and several times when a key is assigned to a player at the start of a round -{ -#ifdef KH_PLAYER_USE_ATTACHMENT - entity first; - first = key.owner.kh_next; - if(key == first) - { - setattachment(key, key.owner, KH_PLAYER_ATTACHMENT_BONE); - if(key.kh_next) - { - setattachment(key.kh_next, key, ""); - setorigin(key, key.kh_next.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST); - setorigin(key.kh_next, KH_PLAYER_ATTACHMENT_DIST_ROTATED); - key.kh_next.angles = '0 0 0'; - } - else - setorigin(key, KH_PLAYER_ATTACHMENT); - key.angles = KH_PLAYER_ATTACHMENT_ANGLES; - } - else - { - setattachment(key, key.kh_prev, ""); - if(key.kh_next) - setattachment(key.kh_next, key, ""); - setorigin(key, KH_PLAYER_ATTACHMENT_DIST_ROTATED); - setorigin(first, first.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST); - key.angles = '0 0 0'; - } -#else - setattachment(key, key.owner, ""); - setorigin(key, '0 0 1' * KH_KEY_ZSHIFT); // fixing x, y in think - key.angles_y -= key.owner.angles_y; -#endif - key.flags = 0; - key.solid = SOLID_NOT; - key.movetype = MOVETYPE_NONE; - key.team = key.owner.team; - key.nextthink = time; - key.damageforcescale = 0; - key.takedamage = DAMAGE_NO; - key.modelindex = kh_key_carried; -} -void kh_Key_Detach(entity key) // runs every time a key is dropped or lost. Runs several times times when all the keys are captured +// ================ +// Round Handling +// ================ + +float KH_GetWinnerTeam() { -#ifdef KH_PLAYER_USE_ATTACHMENT - entity first; - first = key.owner.kh_next; - if(key == first) - { - if(key.kh_next) - { - setattachment(key.kh_next, key.owner, KH_PLAYER_ATTACHMENT_BONE); - setorigin(key.kh_next, key.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST); - key.kh_next.angles = KH_PLAYER_ATTACHMENT_ANGLES; - } - } - else + float winner_team = 0; + entity key; + + KH_FOR_EACH_KEY(key) { - if(key.kh_next) - setattachment(key.kh_next, key.kh_prev, ""); - setorigin(first, first.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST); + if(!key.owner) { return 0; } // no carriers, no winners + if(winner_team && winner_team != key.owner.team) { return 0; } + winner_team = key.owner.team; } - // in any case: - setattachment(key, world, ""); - setorigin(key, key.owner.origin + '0 0 1' * (PL_MIN_z - KH_KEY_MIN_z)); - key.angles = key.owner.angles; -#else - setorigin(key, key.owner.origin + key.origin_z * '0 0 1'); - setattachment(key, world, ""); - key.angles_y += key.owner.angles_y; -#endif - key.flags = FL_ITEM; - key.solid = SOLID_TRIGGER; - key.movetype = MOVETYPE_TOSS; - key.pain_finished = time + autocvar_g_balance_keyhunt_delay_return; - key.damageforcescale = autocvar_g_balance_keyhunt_damageforcescale; - key.takedamage = DAMAGE_YES; - // let key.team stay - key.modelindex = kh_key_dropped; - key.kh_previous_owner = key.owner; - key.kh_previous_owner_playerid = key.owner.playerid; + + if(winner_team) { return winner_team; } + + return -1; // no winner? } -void kh_Key_AssignTo(entity key, entity player) // runs every time a key is picked up or assigned. Runs prior to kh_key_attach +void kh_Handle_Capture(float capturetype); +void kh_RemoveKey(entity key); +float KH_CheckWinner() { - entity k; - float ownerteam0, ownerteam; - if(key.owner == player) - return; - - ownerteam0 = kh_Key_AllOwnedByWhichTeam(); - - if(key.owner) + entity e, key; + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) { - kh_Key_Detach(key); - - // remove from linked list - if(key.kh_next) - key.kh_next.kh_prev = key.kh_prev; - key.kh_prev.kh_next = key.kh_next; - key.kh_next = world; - key.kh_prev = world; - - if(key.owner.kh_next == world) + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); + round_handler_Init(5, autocvar_g_keyhunt_warmup, autocvar_g_keyhunt_round_timelimit); + FOR_EACH_CLIENT(e) { - // No longer a key carrier - if(!kh_no_radar_circles) - WaypointSprite_Ping(key.owner.waypointsprite_attachedforcarrier); - WaypointSprite_DetachCarrier(key.owner); + e.kh_lastkiller = world; } + nades_Clear(world, TRUE); + KH_FOR_EACH_KEY(key) { if(!wasfreed(key)) kh_RemoveKey(key); } + kh_worldkeylist = world; // reset key list + return 1; } - - key.owner = player; - - if(player) - { - // insert into linked list - key.kh_next = player.kh_next; - key.kh_prev = player; - player.kh_next = key; - if(key.kh_next) - key.kh_next.kh_prev = key; - - float i; - i = kh_keystatus[key.owner.playerid]; - if(key.netname == "^1red key") - i += 1; - if(key.netname == "^4blue key") - i += 2; - if(key.netname == "^3yellow key") - i += 4; - if(key.netname == "^6pink key") - i += 8; - kh_keystatus[key.owner.playerid] = i; - - kh_Key_Attach(key); - - if(key.kh_next == world) + + float winner_team = KH_GetWinnerTeam(); + + float key_count = 0; + KH_FOR_EACH_KEY(key) { if(!wasfreed(key)) ++key_count; } + if(key_count >= kh_teams && winner_team == 0) { return 0; } + + if(winner_team > 0) + { + float largest_dist = 0; + KH_FOR_EACH_KEY(key) { - // player is now a key carrier - WaypointSprite_AttachCarrier("", player, RADARICON_FLAGCARRIER, colormapPaletteColor(player.team - 1, 0)); - player.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_KeyCarrier_waypointsprite_visible_for_player; - WaypointSprite_UpdateRule(player.waypointsprite_attachedforcarrier, player.team, SPRITERULE_TEAMPLAY); - if(player.team == NUM_TEAM_1) - WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, "keycarrier-red", "keycarrier-friend", "keycarrier-red"); - else if(player.team == NUM_TEAM_2) - WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, "keycarrier-blue", "keycarrier-friend", "keycarrier-blue"); - else if(player.team == NUM_TEAM_3) - WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, "keycarrier-yellow", "keycarrier-friend", "keycarrier-yellow"); - else if(player.team == NUM_TEAM_4) - WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, "keycarrier-pink", "keycarrier-friend", "keycarrier-pink"); - if(!kh_no_radar_circles) - WaypointSprite_Ping(player.waypointsprite_attachedforcarrier); + if(!key.owner) { return 0; } + if(key.kh_worldkeynext) + if(vlen(kh_AttachedOrigin(key) - kh_AttachedOrigin(key.kh_worldkeynext)) >= largest_dist || largest_dist == 0) + largest_dist = vlen(kh_AttachedOrigin(key) - kh_AttachedOrigin(key.kh_worldkeynext)); } - } - - // moved that here, also update if there's no player - kh_update_state(); - - key.pusher = world; - - ownerteam = kh_Key_AllOwnedByWhichTeam(); - if(ownerteam != ownerteam0) - { - if(ownerteam != -1) + if(largest_dist >= autocvar_g_keyhunt_capture_radius) { - kh_interferemsg_time = time + 0.2; - kh_interferemsg_team = player.team; - - // audit all key carrier sprites, update them to RUN HERE - FOR_EACH_KH_KEY(k) + entity head; + if(time >= kh_alarm_time) + FOR_EACH_PLAYER(head) { - if(k.owner) - WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, k.owner.waypointsprite_attachedforcarrier.model1, "keycarrier-finish", k.owner.waypointsprite_attachedforcarrier.model3); + float head_iscarrier = FALSE; + KH_FOR_EACH_KEY(key) + if(key.owner == head) + { + head_iscarrier = TRUE; + break; + } + + if(head_iscarrier) { Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_KEYHUNT_MEET); } + else if(head.team == winner_team) { Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_KEYHUNT_HELP); } + else { Send_Notification(NOTIF_ONE, head, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_KEYHUNT_INTERFERE_)); } + + kh_alarm_time = time + 2; } - } - else - { - kh_interferemsg_time = 0; - - // audit all key carrier sprites, update them to RUN HERE - FOR_EACH_KH_KEY(k) + if(time >= kh_siren_time) { - if(k.owner) - WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, k.owner.waypointsprite_attachedforcarrier.model1, "keycarrier-friend", k.owner.waypointsprite_attachedforcarrier.model3); + KH_FOR_EACH_KEY(key) + sound(key.owner, CH_TRIGGER, "kh/alarm.wav", VOL_BASE, ATTEN_NORM); + kh_siren_time = time + 3; } + return 0; } - } -} -void kh_Key_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - if(self.owner) - return; - if(ITEM_DAMAGE_NEEDKILL(deathtype)) - { - // touching lava, or hurt trigger - // what shall we do? - // immediately return is bad - // maybe start a shorter countdown? + kh_Handle_Capture(CAPTURE_NORMAL); + TeamScore_AddToTeam(winner_team, ST_KH_CAPS, 1); + + kh_EventLog("capture", winner_team, world); } - if(vlen(force) <= 0) - return; - if(time > self.pushltime) - if(IS_PLAYER(attacker)) - self.team = attacker.team; -} -void kh_Key_Collect(entity key, entity player) //a player picks up a dropped key -{ - sound(player, CH_TRIGGER, kh_sound_collect, VOL_BASE, ATTEN_NORM); + round_handler_Init(5, autocvar_g_keyhunt_warmup, autocvar_g_keyhunt_round_timelimit); - if(key.kh_dropperteam != player.team) + FOR_EACH_CLIENT(e) { - kh_Scores_Event(player, key, "collect", autocvar_g_balance_keyhunt_score_collect, 0); - PlayerScore_Add(player, SP_KH_PICKUPS, 1); + e.kh_lastkiller = world; } - key.kh_dropperteam = 0; - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_PICKUP_), player.netname); + + nades_Clear(world, TRUE); - kh_Key_AssignTo(key, player); // this also updates .kh_state + KH_FOR_EACH_KEY(key) { if(!wasfreed(key)) kh_RemoveKey(key); } + kh_worldkeylist = world; // reset key list + + return 1; } -void kh_Key_Touch() // runs many, many times when a key has been dropped and can be picked up +entity KH_ChooseCarrier(float teamnumber) { - if(intermission_running) - return; - - if(self.owner) // already carried - return; - - if(ITEM_TOUCH_NEEDKILL()) - { - // touching sky, or nodrop - // what shall we do? - // immediately return is bad - // maybe start a shorter countdown? - } + entity head; + RandomSelection_Init(); + FOR_EACH_PLAYER(head) + if(head.health > 0) + if(!head.vehicle || autocvar_g_keyhunt_allow_vehicle_carry) + if(head.frozen == 0) + if(head.team == teamnumber) + RandomSelection_Add(head, 0, string_null, 1, ((IS_REAL_CLIENT(head)) ? 1 : 0.5)); + + return RandomSelection_chosen_ent; +} - if (!IS_PLAYER(other)) - return; - if(other.deadflag != DEAD_NO) - return; - if(other == self.enemy) - if(time < self.kh_droptime + autocvar_g_balance_keyhunt_delay_collect) - return; // you just dropped it! - kh_Key_Collect(self, other); +void kh_KeySetup(float teamnumber, entity key, entity player); +void KH_RoundStart() +{ + entity tmp_entity; + kh_KeySetup(NUM_TEAM_1, (tmp_entity = spawn()), KH_ChooseCarrier(NUM_TEAM_1)); + kh_KeySetup(NUM_TEAM_2, (tmp_entity = spawn()), KH_ChooseCarrier(NUM_TEAM_2)); + if(kh_teams >= 3) { kh_KeySetup(NUM_TEAM_3, (tmp_entity = spawn()), KH_ChooseCarrier(NUM_TEAM_3)); } + if(kh_teams >= 4) { kh_KeySetup(NUM_TEAM_4, (tmp_entity = spawn()), KH_ChooseCarrier(NUM_TEAM_4)); } + + FOR_EACH_PLAYER(tmp_entity) { tmp_entity.spawnshieldtime = time + autocvar_g_spawnshieldtime; } } -void kh_Key_Remove(entity key) // runs after when all the keys have been collected or when a key has been dropped for more than X seconds +float total_players; +float redalive, bluealive, yellowalive, pinkalive; +void KH_count_alive_players() { - entity o; - o = key.owner; - kh_Key_AssignTo(key, world); - if(o) // it was attached - WaypointSprite_Kill(key.waypointsprite_attachedforcarrier); - else // it was dropped - WaypointSprite_DetachCarrier(key); - - // remove key from key list - if (kh_worldkeylist == key) - kh_worldkeylist = kh_worldkeylist.kh_worldkeynext; - else + entity e; + total_players = redalive = bluealive = yellowalive = pinkalive = 0; + FOR_EACH_PLAYER(e) { - o = kh_worldkeylist; - while (o) + switch(e.team) { - if (o.kh_worldkeynext == key) - { - o.kh_worldkeynext = o.kh_worldkeynext.kh_worldkeynext; - break; - } - o = o.kh_worldkeynext; + case NUM_TEAM_1: ++total_players; if(e.health > 0 && e.frozen == 0 && (!e.vehicle || autocvar_g_keyhunt_allow_vehicle_carry)) ++redalive; break; + case NUM_TEAM_2: ++total_players; if(e.health > 0 && e.frozen == 0 && (!e.vehicle || autocvar_g_keyhunt_allow_vehicle_carry)) ++bluealive; break; + case NUM_TEAM_3: ++total_players; if(e.health > 0 && e.frozen == 0 && (!e.vehicle || autocvar_g_keyhunt_allow_vehicle_carry)) ++yellowalive; break; + case NUM_TEAM_4: ++total_players; if(e.health > 0 && e.frozen == 0 && (!e.vehicle || autocvar_g_keyhunt_allow_vehicle_carry)) ++pinkalive; break; } } +} - remove(key); +#define KH_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0)) +#define KH_ALIVE_TEAMS_OK() (KH_ALIVE_TEAMS() == kh_teams) - kh_update_state(); +float prev_missing_teams_mask; +float KH_CheckTeams() +{ + allowed_to_spawn = TRUE; + KH_count_alive_players(); + if(KH_ALIVE_TEAMS_OK()) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 1; + } + if(total_players == 0) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 0; + } + float missing_teams_mask = (!redalive) + (!bluealive) * 2; + if(kh_teams >= 3) missing_teams_mask += (!yellowalive) * 4; + if(kh_teams >= 4) missing_teams_mask += (!pinkalive) * 8; + if(prev_missing_teams_mask != missing_teams_mask) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); + prev_missing_teams_mask = missing_teams_mask; + } + return 0; } -void kh_FinishRound() // runs when a team captures the keys +entity kh_ChooseNewCarrier(entity key) { - // prepare next round - kh_interferemsg_time = 0; - entity key; + entity tmp_entity, player = key.kh_dropper; + + RandomSelection_Init(); + FOR_EACH_PLAYER(tmp_entity) + if(tmp_entity != player) + if(!tmp_entity.frozen) + if(!tmp_entity.vehicle || autocvar_g_keyhunt_allow_vehicle_carry) + if(tmp_entity.health > 0) + if(DIFF_TEAM(tmp_entity, player)) + RandomSelection_Add(tmp_entity, 0, string_null, 1, ((IS_REAL_CLIENT(tmp_entity)) ? 1 : 0.5)); + + return RandomSelection_chosen_ent; +} - kh_no_radar_circles = TRUE; - FOR_EACH_KH_KEY(key) - kh_Key_Remove(key); - kh_no_radar_circles = FALSE; - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round); - kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound); -} +// ==================== +// Drop/Pass/Throw Code +// ==================== -void kh_WinnerTeam(float teem) // runs when a team wins // Samual: Teem?.... TEEM?!?! what the fuck is wrong with you people +void kh_Handle_Drop(entity key, entity player, float droptype) { - // all key carriers get some points - vector firstorigin, lastorigin, midpoint; - float first; - entity key; - float score; - score = (kh_teams - 1) * autocvar_g_balance_keyhunt_score_capture; - DistributeEvenly_Init(score, kh_teams); - // twice the score for 3 team games, three times the score for 4 team games! - // note: for a win by destroying the key, this should NOT be applied - FOR_EACH_KH_KEY(key) - { - float f; - f = DistributeEvenly_Get(1); - kh_Scores_Event(key.owner, key, "capture", f, 0); - PlayerTeamScore_Add(key.owner, SP_KH_CAPS, ST_KH_CAPS, 1); - nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high); - } - - first = TRUE; - string keyowner = ""; - FOR_EACH_KH_KEY(key) - if(key.owner.kh_next == key) - { - if(!first) - keyowner = strcat(keyowner, ", "); - keyowner = key.owner.netname; - first = FALSE; - } + //print("Drop called for ", key.netname, " by player ", player, ", droptype: ", ftos(droptype), "\n"); + // declarations + player = (player ? player : key.pass_sender); + + // main + key.movetype = MOVETYPE_TOSS; + key.takedamage = DAMAGE_YES; + key.angles = '0 0 0'; + key.health = key.max_key_health; + key.kh_droptime = time; + key.kh_dropper = player; + key.kh_status = KEY_DROPPED; - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(teem, INFO_KEYHUNT_CAPTURE_), keyowner); + // messages and sounds + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_LOST_), player.netname); + sound(key, CH_TRIGGER, key.snd_key_dropped, VOL_BASE, ATTEN_NONE); + kh_EventLog("dropped", player.team, player); - first = TRUE; - midpoint = '0 0 0'; - firstorigin = '0 0 0'; - lastorigin = '0 0 0'; - FOR_EACH_KH_KEY(key) - { - vector thisorigin; + // scoring + PlayerTeamScore_AddScore(player, -autocvar_g_keyhunt_score_penalty_drop); + PlayerScore_Add(player, SP_KH_DROPS, 1); - thisorigin = kh_AttachedOrigin(key); - //dprint("Key origin: ", vtos(thisorigin), "\n"); - midpoint += thisorigin; + // waypoints + if(autocvar_g_keyhunt_key_dropped_waypoint) + WaypointSprite_Spawn("keydropped", 0, 0, key, KEY_DROP_OFFSET, world, ((autocvar_g_keyhunt_key_dropped_waypoint == 2) ? 0 : player.team), key, wps_keydropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDKEY(key.team)); - if(!first) - te_lightning2(world, lastorigin, thisorigin); - lastorigin = thisorigin; - if(first) - firstorigin = thisorigin; - first = FALSE; - } - if(kh_teams > 2) + if(autocvar_g_keyhunt_key_return_time || (autocvar_g_keyhunt_key_return_damage && autocvar_g_keyhunt_key_health)) { - te_lightning2(world, lastorigin, firstorigin); + WaypointSprite_UpdateMaxHealth(key.wps_keydropped, key.max_key_health); + WaypointSprite_UpdateHealth(key.wps_keydropped, key.health); } - midpoint = midpoint * (1 / kh_teams); - te_customflash(midpoint, 1000, 1, Team_ColorRGB(teem) * 0.5 + '0.5 0.5 0.5'); // make the color >=0.5 in each component - play2all(kh_sound_capture); - kh_FinishRound(); + player.throw_antispam = time + autocvar_g_keyhunt_pass_wait; + + if(droptype == DROP_PASS) + { + key.pass_distance = 0; + key.pass_sender = world; + key.pass_target = world; + } } -void kh_LoserTeam(float teem, entity lostkey) // runs when a player pushes a flag carrier off the map +void kh_Handle_Retrieve(entity key, entity player) { - entity player, key, attacker; - float players; - float keys; - float f; + entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players + entity sender = key.pass_sender; - attacker = world; - if(lostkey.pusher) - if(lostkey.pusher.team != teem) - if(IS_PLAYER(lostkey.pusher)) - attacker = lostkey.pusher; - - players = keys = 0; + // transfer key to player + key.owner = player; - if(attacker) + // reset key + if(player.vehicle) { - if(lostkey.kh_previous_owner) - kh_Scores_Event(lostkey.kh_previous_owner, world, "pushed", 0, -autocvar_g_balance_keyhunt_score_push); - // don't actually GIVE him the -nn points, just log - kh_Scores_Event(attacker, world, "push", autocvar_g_balance_keyhunt_score_push, 0); - PlayerScore_Add(attacker, SP_KH_PUSHES, 1); - //centerprint(attacker, "Your push is the best!"); // does this really need to exist? + setattachment(key, player.vehicle, ""); + setorigin(key, VEHICLE_KEY_OFFSET); + key.scale = VEHICLE_KEY_SCALE; } else { - float of, fragsleft, i, j, thisteam; - of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor; - - FOR_EACH_PLAYER(player) - if(player.team != teem) - ++players; - - FOR_EACH_KH_KEY(key) - if(key.owner && key.team != teem) - ++keys; - - if(lostkey.kh_previous_owner) - kh_Scores_Event(lostkey.kh_previous_owner, world, "destroyed", 0, -autocvar_g_balance_keyhunt_score_destroyed); - // don't actually GIVE him the -nn points, just log - - if(lostkey.kh_previous_owner.playerid == lostkey.kh_previous_owner_playerid) - PlayerScore_Add(lostkey.kh_previous_owner, SP_KH_DESTROYS, 1); - - DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players); - - FOR_EACH_KH_KEY(key) - if(key.owner && key.team != teem) - { - f = DistributeEvenly_Get(of); - kh_Scores_Event(key.owner, world, "destroyed_holdingkey", f, 0); - } - - fragsleft = DistributeEvenly_Get(players); - - // Now distribute these among all other teams... - j = kh_teams - 1; - for(i = 0; i < kh_teams; ++i) - { - thisteam = kh_Team_ByID(i); - if(thisteam == teem) // bad boy, no cookie - this WILL happen - continue; - - players = 0; - FOR_EACH_PLAYER(player) - if(player.team == thisteam) - ++players; - - DistributeEvenly_Init(fragsleft, j); - fragsleft = DistributeEvenly_Get(j - 1); - DistributeEvenly_Init(DistributeEvenly_Get(1), players); + setattachment(key, player, ""); + setorigin(key, KEY_CARRY_OFFSET); + } + key.movetype = MOVETYPE_NONE; + key.takedamage = DAMAGE_NO; + key.solid = SOLID_NOT; + key.angles = '0 0 0'; + key.kh_status = KEY_CARRY; - FOR_EACH_PLAYER(player) - if(player.team == thisteam) - { - f = DistributeEvenly_Get(1); - kh_Scores_Event(player, world, "destroyed", f, 0); - } + // messages and sounds + sound(player, CH_TRIGGER, key.snd_key_pass, VOL_BASE, ATTEN_NORM); + kh_EventLog("receive", key.team, player); - --j; - } + FOR_EACH_REALPLAYER(tmp_player) + { + if(tmp_player == sender) + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(key, CENTER_KEYHUNT_PASS_SENT_), player.netname); + else if(tmp_player == player) + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(key, CENTER_KEYHUNT_PASS_RECEIVED_), sender.netname); + else if(SAME_TEAM(tmp_player, sender)) + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(key, CENTER_KEYHUNT_PASS_OTHER_), sender.netname, player.netname); } - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(lostkey, INFO_KEYHUNT_LOST_), lostkey.kh_previous_owner.netname); + // create new waypoint + kh_KeycarrierWaypoints(player); - play2all(kh_sound_destroy); - te_tarexplosion(lostkey.origin); + sender.throw_antispam = time + autocvar_g_keyhunt_pass_wait; + player.throw_antispam = sender.throw_antispam; - kh_FinishRound(); + key.pass_distance = 0; + key.pass_sender = world; + key.pass_target = world; } -void kh_Key_Think() // runs all the time +void kh_Handle_Throw(entity player, entity receiver, entity key, float droptype) { - entity head; - //entity player; // needed by FOR_EACH_PLAYER + vector targ_origin, key_velocity; - if(intermission_running) - return; + if(!key) { return; } + if((droptype == DROP_PASS) && !receiver) { return; } - if(self.owner) - { -#ifndef KH_PLAYER_USE_ATTACHMENT - makevectors('0 1 0' * (self.cnt + (time % 360) * KH_KEY_XYSPEED)); - setorigin(self, v_forward * KH_KEY_XYDIST + '0 0 1' * self.origin_z); -#endif - } + // reset the key + setattachment(key, world, ""); + setorigin(key, player.origin + KEY_DROP_OFFSET); + key.angles_y += key.owner.angles_y; + key.owner = world; + key.solid = SOLID_TRIGGER; + key.kh_droptime = time; + key.kh_dropper = player; - // if in nodrop or time over, end the round - if(!self.owner) - if(time > self.pain_finished) - kh_LoserTeam(self.team, self); + key.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS - if(self.owner) - if(kh_Key_AllOwnedByWhichTeam() != -1) + switch(droptype) { - if(self.siren_time < time) + case DROP_PASS: { - sound(self.owner, CH_TRIGGER, kh_sound_alarm, VOL_BASE, ATTEN_NORM); // play a simple alarm - self.siren_time = time + 2.5; // repeat every 2.5 seconds + // warpzone support: + // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver + // findradius has already put wzn ... wz1 into receiver's warpzone parameters! + WarpZone_RefSys_Copy(key, receiver); + WarpZone_RefSys_AddInverse(key, receiver); // wz1^-1 ... wzn^-1 receiver + targ_origin = WarpZone_RefSys_TransformOrigin(receiver, key, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the key + + key.pass_distance = vlen((('1 0 0' * targ_origin_x) + ('0 1 0' * targ_origin_y)) - (('1 0 0' * player.origin_x) + ('0 1 0' * player.origin_y))); // for the sake of this check, exclude Z axis + kh_CalculatePassVelocity(key, targ_origin, player.origin, FALSE); + + // main + key.movetype = MOVETYPE_FLY; + key.takedamage = DAMAGE_NO; + key.pass_sender = player; + key.pass_target = receiver; + key.kh_status = KEY_PASSING; + + // other + sound(player, CH_TRIGGER, key.snd_key_touch, VOL_BASE, ATTEN_NORM); + Send_Effect(key.passeffectnum, player.origin, targ_origin, 0); + kh_EventLog("pass", key.team, player); + break; } - entity key; - vector p; - p = self.owner.origin; - FOR_EACH_KH_KEY(key) - if(vlen(key.owner.origin - p) > autocvar_g_balance_keyhunt_maxdist) - goto not_winning; - kh_WinnerTeam(self.team); -:not_winning - } - - if(kh_interferemsg_time && time > kh_interferemsg_time) - { - kh_interferemsg_time = 0; - FOR_EACH_PLAYER(head) + case DROP_THROW: { - if(head.team == kh_interferemsg_team) - if(head.kh_next) - Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_KEYHUNT_MEET); - else - Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_KEYHUNT_HELP); - else - Send_Notification(NOTIF_ONE, head, MSG_CENTER, APP_TEAM_NUM_4(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE_)); - } - } - - self.nextthink = time + 0.05; -} - -void key_reset() -{ - kh_Key_AssignTo(self, world); - kh_Key_Remove(self); -} + makevectors((player.v_angle_y * '0 1 0') + (bound(autocvar_g_keyhunt_throw_angle_min, player.v_angle_x, autocvar_g_keyhunt_throw_angle_max) * '1 0 0')); -const string STR_ITEM_KH_KEY = "item_kh_key"; -void kh_Key_Spawn(entity initial_owner, float angle, float i) // runs every time a new flag is created, ie after all the keys have been collected -{ - entity key; - key = spawn(); - key.count = i; - key.classname = STR_ITEM_KH_KEY; - key.touch = kh_Key_Touch; - key.think = kh_Key_Think; - key.nextthink = time; - key.items = IT_KEY1 | IT_KEY2; - key.cnt = angle; - key.angles = '0 360 0' * random(); - key.event_damage = kh_Key_Damage; - key.takedamage = DAMAGE_YES; - key.modelindex = kh_key_dropped; - key.model = "key"; - key.kh_dropperteam = 0; - key.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; - setsize(key, KH_KEY_MIN, KH_KEY_MAX); - key.colormod = Team_ColorRGB(initial_owner.team) * KH_KEY_BRIGHTNESS; - key.reset = key_reset; - - switch(initial_owner.team) - { - case NUM_TEAM_1: - key.netname = "^1red key"; - break; - case NUM_TEAM_2: - key.netname = "^4blue key"; - break; - case NUM_TEAM_3: - key.netname = "^3yellow key"; + key_velocity = (('0 0 1' * autocvar_g_keyhunt_throw_velocity_up) + ((v_forward * autocvar_g_keyhunt_throw_velocity_forward))); + key.velocity = W_CalculateProjectileVelocity(player.velocity, key_velocity, FALSE); + kh_Handle_Drop(key, player, droptype); break; - case NUM_TEAM_4: - key.netname = "^6pink key"; + } + + case DROP_RESET: + { + key.velocity = '0 0 0'; // do nothing break; + } + default: - key.netname = "NETGIER key"; + case DROP_NORMAL: + { + key.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_keyhunt_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_keyhunt_drop_velocity_side)), FALSE); + kh_Handle_Drop(key, player, droptype); break; + } } - // link into key list - key.kh_worldkeynext = kh_worldkeylist; - kh_worldkeylist = key; - - Send_Notification(NOTIF_ONE, initial_owner, MSG_CENTER, APP_TEAM_NUM_4(initial_owner.team, CENTER_KEYHUNT_START_)); - - WaypointSprite_Spawn("key-dropped", 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, world, key.team, key, waypointsprite_attachedforcarrier, FALSE, RADARICON_FLAG, '0 1 1'); - key.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_Key_waypointsprite_visible_for_player; - - kh_Key_AssignTo(key, initial_owner); -} + // kill old waypointsprite + if(player.wps_keycarrier) + { + WaypointSprite_Ping(player.wps_keycarrier); + WaypointSprite_Kill(player.wps_keycarrier); + } -// -1 when no team completely owns all keys yet -float kh_Key_AllOwnedByWhichTeam() // constantly called. check to see if all the keys are owned by the same team -{ - entity key; - float teem; - float keys; - - teem = -1; - keys = kh_teams; - FOR_EACH_KH_KEY(key) - { - if(!key.owner) - return -1; - if(teem == -1) - teem = key.team; - else if(teem != key.team) - return -1; - --keys; - } - if(keys != 0) - return -1; - return teem; + if(player.wps_enemykeycarrier) + WaypointSprite_Kill(player.wps_enemykeycarrier); } -void kh_Key_DropOne(entity key) -{ - // prevent collecting this one for some time - entity player; - player = key.owner; - - key.kh_droptime = time; - key.enemy = player; - - kh_Scores_Event(player, key, "dropkey", 0, 0); - PlayerScore_Add(player, SP_KH_LOSSES, 1); - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_DROP_), player.netname); - - kh_Key_AssignTo(key, world); - makevectors(player.v_angle); - key.velocity = W_CalculateProjectileVelocity(player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, FALSE); - key.pusher = world; - key.pushltime = time + autocvar_g_balance_keyhunt_protecttime; - key.kh_dropperteam = key.team; - sound(player, CH_TRIGGER, kh_sound_drop, VOL_BASE, ATTEN_NORM); -} +// ============== +// Event Handlers +// ============== -void kh_Key_DropAll(entity player, float suicide) // runs whenever a player dies +void kh_RemoveKey(entity key); +void kh_Handle_Capture(float capturetype) { entity key; - entity mypusher; - if(player.kh_next) - { - mypusher = world; - if(player.pusher) - if(time < player.pushltime) - mypusher = player.pusher; - while((key = player.kh_next)) + KH_FOR_EACH_KEY(key) + { + entity player = key.owner; + + if(!player) { continue; } // without someone to give the reward to, we can't possibly cap + + nades_GiveBonus(player, autocvar_g_nades_bonus_score_high); + + player.throw_prevtime = time; + player.throw_count = 0; + + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_KEYHUNT_CAPTURE); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_CAPTURE_), player.netname); + + PlayerTeamScore_AddScore(player, autocvar_g_keyhunt_score_capture); + PlayerScore_Add(player, SP_KH_CAPS, 1); + + Send_Effect(key.capeffectnum, kh_AttachedOrigin(key), '0 0 0', 1); + + if(key.kh_worldkeynext) + te_lightning2(world, kh_AttachedOrigin(key), kh_AttachedOrigin(key.kh_worldkeynext)); + + // kill old waypointsprite + WaypointSprite_Kill(player.wps_keycarrier); + WaypointSprite_Kill(player.wps_enemykeycarrier); + + if(capturetype == CAPTURE_NORMAL) { - kh_Scores_Event(player, key, "losekey", 0, 0); - PlayerScore_Add(player, SP_KH_LOSSES, 1); - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_LOST_), player.netname); - kh_Key_AssignTo(key, world); - makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random()); - key.velocity = W_CalculateProjectileVelocity(player.velocity, autocvar_g_balance_keyhunt_dropvelocity * v_forward, FALSE); - key.pusher = mypusher; - key.pushltime = time + autocvar_g_balance_keyhunt_protecttime; - if(suicide) - key.kh_dropperteam = player.team; + if((key.kh_dropper) && (player != key.kh_dropper)) + { PlayerTeamScore_AddScore(key.kh_dropper, autocvar_g_keyhunt_score_capture_assist); } } - sound(player, CH_TRIGGER, kh_sound_drop, VOL_BASE, ATTEN_NORM); + + player.next_take_time = time + autocvar_g_keyhunt_key_collect_delay; + kh_RemoveKey(key); // when all is done, it should be safe to kill the key here } + + play2all("kh/capture.wav"); } -float kh_CheckPlayers(float num) +void kh_Handle_Pickup(entity key, entity player, float pickuptype) { - if(num < kh_teams) + // declarations + float pickup_dropped_score; // used to calculate dropped pickup score + + // attach the key to the player + key.owner = player; + if(player.vehicle) + { + setattachment(key, player.vehicle, ""); + //setorigin(key, VEHICLE_KEY_OFFSET); + key.scale = VEHICLE_KEY_SCALE; + setorigin(key, '0 0 1' * KH_VEHICLE_KEY_ZSHIFT); // fixing x, y in think + key.angles_y -= player.angles_y; + } + else { - float t_team = kh_Team_ByID(num); - float players = 0; - entity tmp_player; - FOR_EACH_PLAYER(tmp_player) - if(tmp_player.deadflag == DEAD_NO) - if(!tmp_player.BUTTON_CHAT) - if(tmp_player.team == t_team) - ++players; + setattachment(key, player, ""); + //setorigin(key, KEY_CARRY_OFFSET); + setorigin(key, '0 0 1' * KH_KEY_ZSHIFT); // fixing x, y in think + key.angles_y -= player.angles_y; + } - if (!players) { return t_team; } + // key setup + key.movetype = MOVETYPE_NONE; + key.takedamage = DAMAGE_NO; + key.solid = SOLID_NOT; + key.angles = '0 0 0'; + key.kh_status = KEY_CARRY; + + //if(pickuptype != PICKUP_KILLED) + if(key.kh_dropper) + { + entity tmp_entity; + float key_count = 0; + KH_FOR_EACH_KEY(tmp_entity) if(tmp_entity.kh_dropper == key.kh_dropper && tmp_entity != key) { ++key_count; } + if(key_count < 1) // player dropped no other keys + key.kh_dropper.kh_lastkiller = world; // reset when picked up } - return 0; -} -void kh_WaitForPlayers() // delay start of the round until enough players are present -{ - if(time < game_starttime) + switch(pickuptype) { - kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers); - return; + case PICKUP_KILLED: + case PICKUP_DROPPED: key.health = key.max_key_health; break; // reset health/return timelimit + default: break; } - float p1 = kh_CheckPlayers(0), p2 = kh_CheckPlayers(1), p3 = kh_CheckPlayers(2), p4 = kh_CheckPlayers(3); - if (!(p1 || p2 || p3 || p4)) + // messages and sounds + if(pickuptype == PICKUP_START) { - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round); - kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_START_), player.netname); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(key, CENTER_KEYHUNT_START_)); + Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_TEAM_ENT_4(key, CHOICE_KEYHUNT_START_TEAM_), Team_ColorCode(player.team), player.netname); } else { - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_WAIT, p1, p2, p3, p4); - kh_Controller_SetThink(1, kh_WaitForPlayers); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_PICKUP_), player.netname); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(key, CENTER_KEYHUNT_PICKUP_)); + Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_TEAM_ENT_4(key, CHOICE_KEYHUNT_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname); + + sound(player, CH_TRIGGER, key.snd_key_taken, VOL_BASE, ATTEN_NONE); } -} -void kh_EnableTrackingDevice() // runs after each round -{ - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT); - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT_OTHER); + // scoring + PlayerScore_Add(player, SP_KH_PICKUPS, 1); + nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor); + switch(pickuptype) + { + case PICKUP_KILLED: + case PICKUP_DROPPED: + { + pickup_dropped_score = (autocvar_g_keyhunt_key_return_time ? bound(0, ((key.kh_droptime + autocvar_g_keyhunt_key_return_time) - time) / autocvar_g_keyhunt_key_return_time, 1) : 1); + pickup_dropped_score = floor((autocvar_g_keyhunt_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_keyhunt_score_pickup_dropped_early * pickup_dropped_score) + 0.5); + dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n"); + PlayerTeamScore_AddScore(player, pickup_dropped_score); + kh_EventLog("pickup", key.team, player); + break; + } - kh_tracking_enabled = TRUE; -} + default: break; + } -void kh_StartRound() // runs at the start of each round -{ - float i, players, teem; - entity player; + // effects + Send_Effect(key.toucheffectnum, player.origin, '0 0 0', 1); - if(time < game_starttime) - { - kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers); - return; - } + // waypoints + if(pickuptype == PICKUP_DROPPED || pickuptype == PICKUP_KILLED) { WaypointSprite_Kill(key.wps_keydropped); } + kh_KeycarrierWaypoints(player); + WaypointSprite_Ping(player.wps_keycarrier); +} - float p1 = kh_CheckPlayers(0), p2 = kh_CheckPlayers(1), p3 = kh_CheckPlayers(2), p4 = kh_CheckPlayers(3); - if(p1 || p2 || p3 || p4) - { - kh_Controller_SetThink(1, kh_WaitForPlayers); - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_WAIT, p1, p2, p3, p4); - return; - } - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT); - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT_OTHER); +// =================== +// Main Key Functions +// =================== - for(i = 0; i < kh_teams; ++i) +void kh_CheckKeyReturn(entity key, float returntype) +{ + if((key.kh_status == KEY_DROPPED) || (key.kh_status == KEY_PASSING)) { - teem = kh_Team_ByID(i); - players = 0; - entity my_player = world; - FOR_EACH_PLAYER(player) - if(player.deadflag == DEAD_NO) - if(!player.BUTTON_CHAT) - if(player.team == teem) + if(key.wps_keydropped) { WaypointSprite_UpdateHealth(key.wps_keydropped, key.health); } + + if((key.health <= 0) || (time >= key.kh_droptime + autocvar_g_keyhunt_key_return_time)) + { + switch(returntype) + { + case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_KEYRETURN_DAMAGED_)); break; + case RETURN_NEEDKILL: + { + entity killer = key.kh_dropper.kh_lastkiller; + if(autocvar_g_keyhunt_key_return_tokiller && IS_PLAYER(killer) && killer.health > 0 && !killer.frozen) { - ++players; - if(random() * players <= 1) - my_player = player; + kh_Handle_Pickup(key, killer, PICKUP_KILLED); + return; } - kh_Key_Spawn(my_player, 360 * i / kh_teams, i); + else + { + entity newcarrier = kh_ChooseNewCarrier(key); + if(autocvar_g_keyhunt_key_return_toenemy && newcarrier) + { + kh_Handle_Pickup(key, newcarrier, PICKUP_KILLED); + return; + } + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_KEYRETURN_NEEDKILL_)); + break; + } + } + + default: + case RETURN_TIMEOUT: + { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_KEYRETURN_TIMEOUT_)); break; } + } + sound(key, CH_TRIGGER, key.snd_key_respawn, VOL_BASE, ATTEN_NONE); + kh_EventLog("returned", key.team, world); + kh_RemoveKey(key); + } } +} - kh_tracking_enabled = FALSE; - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_SCAN, autocvar_g_balance_keyhunt_delay_tracking); - kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_tracking, kh_EnableTrackingDevice); +void kh_KeyDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if(ITEM_DAMAGE_NEEDKILL(deathtype)) + { + self.health = 0; + kh_CheckKeyReturn(self, RETURN_NEEDKILL); + return; + } + if(autocvar_g_keyhunt_key_return_damage) + { + // reduce health and check if it should be returned + self.health = self.health - damage; + kh_CheckKeyReturn(self, RETURN_DAMAGE); + return; + } } -float kh_HandleFrags(entity attacker, entity targ, float f) // adds to the player score +void kh_KeyThink() { - if(attacker == targ) - return f; + self.nextthink = time + KEY_THINKRATE; // only 5 fps, more is unnecessary. + + // sanity checks + if(self.mins != KEY_MIN || self.maxs != KEY_MAX) { // reset the key boundaries in case it got squished + dprint("wtf the key got squashed?\n"); + tracebox(self.origin, KEY_MIN, KEY_MAX, self.origin, MOVE_NOMONSTERS, self); + if(!trace_startsolid) // can we resize it without getting stuck? + setsize(self, KEY_MIN, KEY_MAX); } + + switch(self.kh_status) // reset key angles in case warpzones adjust it + { + case KEY_DROPPED: + { + self.angles = '0 0 0'; + break; + } + + default: break; + } - if(targ.kh_next) + // main think method + switch(self.kh_status) { - if(attacker.team == targ.team) + case KEY_DROPPED: { - entity k; - float nk; - nk = 0; - for(k = targ.kh_next; k != world; k = k.kh_next) - ++nk; - kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", -nk * autocvar_g_balance_keyhunt_score_collect, 0); + if(autocvar_g_keyhunt_key_dropped_floatinwater) + { + vector midpoint = ((self.absmin + self.absmax) * 0.5); + if(pointcontents(midpoint) == CONTENT_WATER) + { + self.velocity = self.velocity * 0.5; + + if(pointcontents(midpoint + KEY_FLOAT_OFFSET) == CONTENT_WATER) + { self.velocity_z = autocvar_g_keyhunt_key_dropped_floatinwater; } + else + { self.movetype = MOVETYPE_FLY; } + } + else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; } + } + if(autocvar_g_keyhunt_key_return_time) + { + self.health -= ((self.max_key_health / autocvar_g_keyhunt_key_return_time) * KEY_THINKRATE); + kh_CheckKeyReturn(self, RETURN_TIMEOUT); + return; + } + return; + } + + case KEY_CARRY: + { + makevectors('0 1 0' * (self.cnt + (time % 360) * KH_KEY_XYSPEED)); + setorigin(self, v_forward * ((self.owner.vehicle) ? KH_VEHICLE_KEY_XYDIST : KH_KEY_XYDIST) + '0 0 1' * self.origin_z); + return; } + + case KEY_PASSING: + { + vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5); + targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the key (us) + WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self); + + if((self.pass_target == world) + || (self.pass_target.deadflag != DEAD_NO) + || (vlen(self.origin - targ_origin) > autocvar_g_keyhunt_pass_radius) + || ((trace_fraction < 1) && (trace_ent != self.pass_target)) + || (time > self.kh_droptime + autocvar_g_keyhunt_pass_timelimit)) + { + // give up, pass failed + kh_Handle_Drop(self, world, DROP_PASS); + } + else + { + // still a viable target, go for it + kh_CalculatePassVelocity(self, targ_origin, self.origin, TRUE); + } + return; + } + + default: // this should never happen + { + dprint("kh_KeyThink(): Key exists with no status?\n"); + return; + } + } +} + +float kh_Customize() +{ + entity e = WaypointSprite_getviewentity(other); + + if(self.owner == e) + self.glow_trail = 0; + else if(autocvar_g_keyhunt_key_glowtrails) + self.glow_trail = 1; + + return TRUE; +} + +void kh_KeyTouch() +{ + if(gameover) { return; } + + entity toucher = other; + + // automatically kill the key and return it if it touched lava/slime/nodrop surfaces + if(ITEM_TOUCH_NEEDKILL()) + { + self.health = 0; + kh_CheckKeyReturn(self, RETURN_NEEDKILL); + return; + } + + // special touch behaviors + if(toucher.frozen) { return; } + else if(IS_VEHICLE(toucher)) + { + if(autocvar_g_keyhunt_allow_vehicle_touch && toucher.owner) + toucher = toucher.owner; // the player is actually the vehicle owner, not other else + return; // do nothing + } + else if (!IS_PLAYER(toucher)) // The key just touched an object, most likely the world + { + if(time > self.wait) // if we haven't in a while, play a sound/effect { - kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", autocvar_g_balance_keyhunt_score_carrierfrag-1, 0); - PlayerScore_Add(attacker, SP_KH_KCKILLS, 1); - // the frag gets added later + Send_Effect(self.toucheffectnum, self.origin, '0 0 0', 1); + sound(self, CH_TRIGGER, self.snd_key_touch, VOL_BASE, ATTEN_NORM); + self.wait = time + KEY_TOUCHRATE; } + return; } + else if(toucher.deadflag != DEAD_NO) { return; } + + switch(self.kh_status) + { + case KEY_DROPPED: + { + if(((toucher != self.kh_dropper) || (time > self.kh_droptime + autocvar_g_keyhunt_key_collect_delay))) + kh_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy key + break; + } + + case KEY_CARRY: + { + dprint("Someone touched a key even though it was being carried?\n"); + break; + } - return f; + case KEY_PASSING: + { + if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender)) + kh_Handle_Retrieve(self, toucher); + break; + } + } } -void kh_Initialize() // sets up th KH environment +void kh_RemoveKey(entity key) { - precache_sound(kh_sound_capture); - precache_sound(kh_sound_destroy); - precache_sound(kh_sound_drop); - precache_sound(kh_sound_collect); - precache_sound(kh_sound_alarm); // the new siren - -#ifdef KH_PLAYER_USE_CARRIEDMODEL - precache_model("models/keyhunt/key-carried.md3"); -#endif - precache_model("models/keyhunt/key.md3"); + // kill old waypointsprite + WaypointSprite_Kill(key.owner.wps_keycarrier); + WaypointSprite_Kill(key.owner.wps_enemykeycarrier); - // setup variables - kh_teams = autocvar_g_keyhunt_teams_override; - if(kh_teams < 2) - kh_teams = autocvar_g_keyhunt_teams; - kh_teams = bound(2, kh_teams, 4); + if(key.kh_status == KEY_DROPPED) + { WaypointSprite_Kill(key.wps_keydropped); } - // make a KH entity for controlling the game - kh_controller = spawn(); - kh_controller.think = kh_Controller_Think; - kh_Controller_SetThink(0, kh_WaitForPlayers); - - setmodel(kh_controller, "models/keyhunt/key.md3"); - kh_key_dropped = kh_controller.modelindex; - /* - dprint(vtos(kh_controller.mins)); - dprint(vtos(kh_controller.maxs)); - dprint("\n"); - */ -#ifdef KH_PLAYER_USE_CARRIEDMODEL - setmodel(kh_controller, "models/keyhunt/key-carried.md3"); - kh_key_carried = kh_controller.modelindex; -#else - kh_key_carried = kh_key_dropped; -#endif - - kh_controller.model = ""; - kh_controller.modelindex = 0; - - addstat(STAT_KH_KEYS, AS_INT, kh_state); + // reset the key + setattachment(key, world, ""); - kh_ScoreRules(kh_teams); + key.think = SUB_Remove; + key.nextthink = time; +} + +void kh_Reset() +{ + if(self.owner) + if(IS_PLAYER(self.owner)) + kh_Handle_Throw(self.owner, world, self, DROP_RESET); + + kh_RemoveKey(self); +} + +void kh_KeySetup(float teamnumber, entity key, entity player) // called when spawning a key entity +{ + // declarations + self = key; // for later usage with droptofloor() + + // main setup + key.kh_worldkeynext = kh_worldkeylist; // link key into kh_worldkeylist + kh_worldkeylist = key; + + setattachment(key, world, ""); + + key.netname = sprintf("%s%s^7 key", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)); + key.team = teamnumber; + key.classname = "item_key_team"; + key.target = "###item###"; // wut? + key.flags = FL_ITEM | FL_NOTARGET; + key.solid = SOLID_TRIGGER; + key.takedamage = DAMAGE_NO; + key.customizeentityforclient = kh_Customize; + key.damageforcescale = autocvar_g_keyhunt_key_damageforcescale; + key.max_key_health = ((autocvar_g_keyhunt_key_return_damage && autocvar_g_keyhunt_key_health) ? autocvar_g_keyhunt_key_health : 100); + key.health = key.max_key_health; + key.event_damage = kh_KeyDamage; + key.pushable = TRUE; + key.teleportable = TELEPORT_NORMAL; + key.damagedbytriggers = autocvar_g_keyhunt_key_return_when_unreachable; + key.damagedbycontents = autocvar_g_keyhunt_key_return_when_unreachable; + key.velocity = '0 0 0'; + key.mangle = key.angles; + key.cnt = 360 * Team_TeamToNumber(teamnumber) / kh_teams; + key.reset = kh_Reset; + key.touch = kh_KeyTouch; + key.think = kh_KeyThink; + key.nextthink = time + KEY_THINKRATE; + key.kh_status = KEY_CARRY; + key.colormod = Team_ColorRGB(teamnumber) * KEY_BRIGHTNESS; + + // appearence + key.model = "models/keyhunt/key.md3"; + key.scale = KEY_SCALE; + key.toucheffectnum = ((teamnumber == NUM_TEAM_1) ? EFFECT_FLAG_RED_TOUCH : ((teamnumber == NUM_TEAM_2) ? EFFECT_FLAG_BLUE_TOUCH : ((teamnumber == NUM_TEAM_3) ? EFFECT_FLAG_YELLOW_TOUCH : EFFECT_FLAG_PINK_TOUCH))); + key.passeffectnum = ((teamnumber == NUM_TEAM_1) ? EFFECT_RED_PASS : ((teamnumber == NUM_TEAM_2) ? EFFECT_BLUE_PASS : ((teamnumber == NUM_TEAM_3) ? EFFECT_YELLOW_PASS : EFFECT_PINK_PASS))); + key.capeffectnum = ((teamnumber == NUM_TEAM_1) ? EFFECT_RED_CAP : ((teamnumber == NUM_TEAM_2) ? EFFECT_BLUE_CAP : ((teamnumber == NUM_TEAM_3) ? EFFECT_YELLOW_CAP : EFFECT_PINK_CAP))); + + // sound + key.snd_key_taken = "kh/collect.wav"; + key.snd_key_capture = "kh/capture.wav"; + key.snd_key_dropped = "kh/drop.wav"; + key.snd_key_touch = "ctf/touch.wav"; + key.snd_key_pass = "ctf/pass.wav"; + + // appearence + setmodel(key, key.model); // precision set below + setsize(key, KEY_MIN, KEY_MAX); + setorigin(key, (key.origin + KEY_SPAWN_OFFSET)); + + if(autocvar_g_keyhunt_key_glowtrails) + { + key.glow_color = ((teamnumber == NUM_TEAM_1) ? 251 : ((teamnumber == NUM_TEAM_2) ? 210 : ((teamnumber == NUM_TEAM_3) ? 110 : 145))); + key.glow_size = 25; + key.glow_trail = 1; + } + + key.effects |= EF_LOWPRECISION; + if(autocvar_g_keyhunt_fullbrightkeys) { key.effects |= EF_FULLBRIGHT; } + if(autocvar_g_keyhunt_dynamiclights) + { + switch(teamnumber) + { + case NUM_TEAM_1: key.effects |= EF_RED; break; + case NUM_TEAM_2: key.effects |= EF_BLUE; break; + case NUM_TEAM_3: key.effects |= EF_DIMLIGHT; break; + case NUM_TEAM_4: key.effects |= EF_RED; break; + } + } + + kh_Handle_Pickup(key, player, PICKUP_START); } -void kh_finalize() + +// ================== +// Legacy Bot Logic +// ================== + +void() havocbot_role_kh_carrier; +void() havocbot_role_kh_defense; +void() havocbot_role_kh_offense; +void() havocbot_role_kh_freelancer; +void havocbot_goalrating_kh(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy) { - // to be called before intermission - kh_FinishRound(); - remove(kh_controller); - kh_controller = world; + entity head; + for (head = kh_worldkeylist; head && !wasfreed(head); head = head.kh_worldkeynext) + { + if(head.owner == self) + continue; + if(!head.owner) + navigation_routerating(head, ratingscale_dropped * BOT_PICKUP_RATING_HIGH, 100000); + else if(head.team == self.team) + navigation_routerating(head.owner, ratingscale_team * BOT_PICKUP_RATING_HIGH, 100000); + else + navigation_routerating(head.owner, ratingscale_enemy * BOT_PICKUP_RATING_HIGH, 100000); + } + + havocbot_goalrating_items(1, self.origin, 10000); } -// register this as a mutator +void havocbot_role_kh_carrier() +{ + if(self.deadflag != DEAD_NO) + return; + + entity key; + float is_carrier = FALSE; + KH_FOR_EACH_KEY(key) + if(key.owner == self) + { + is_carrier = TRUE; + break; + } + + if (!is_carrier) + { + dprint("changing role to freelancer\n"); + self.havocbot_role = havocbot_role_kh_freelancer; + self.havocbot_role_timeout = 0; + return; + } + + if (self.bot_strategytime < time) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + + float key_count = 0; + KH_FOR_EACH_KEY(key) { if(!wasfreed(key) && SAME_TEAM(key, self)) ++key_count; } + + if(key_count >= kh_teams) + havocbot_goalrating_kh(10, 0.1, 0.1); // bring home + else + havocbot_goalrating_kh(4, 4, 1); // play defensively -MUTATOR_HOOKFUNCTION(kh_Key_DropAll) + navigation_goalrating_end(); + } +} + +void havocbot_role_kh_defense() { - kh_Key_DropAll(self, TRUE); - return 0; + if(self.deadflag != DEAD_NO) + return; + + entity key; + float is_carrier = FALSE; + KH_FOR_EACH_KEY(key) + if(key.owner == self) + { + is_carrier = TRUE; + break; + } + + if (is_carrier) + { + dprint("changing role to carrier\n"); + self.havocbot_role = havocbot_role_kh_carrier; + self.havocbot_role_timeout = 0; + return; + } + + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + random() * 10 + 20; + if (time > self.havocbot_role_timeout) + { + dprint("changing role to freelancer\n"); + self.havocbot_role = havocbot_role_kh_freelancer; + self.havocbot_role_timeout = 0; + return; + } + + if (self.bot_strategytime < time) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + + float key_count = 0; + KH_FOR_EACH_KEY(key) { if(!wasfreed(key) && SAME_TEAM(key, self)) ++key_count; } + + if(key_count >= kh_teams) + havocbot_goalrating_kh(10, 0.1, 0.1); // defend key carriers + else if(key_count == 0) + havocbot_goalrating_kh(4, 1, 0.1); // play defensively + else + havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY + + navigation_goalrating_end(); + } +} + +void havocbot_role_kh_offense() +{ + if(self.deadflag != DEAD_NO) + return; + + entity key; + float is_carrier = FALSE; + KH_FOR_EACH_KEY(key) + if(key.owner == self) + { + is_carrier = TRUE; + break; + } + + if (is_carrier) + { + dprint("changing role to carrier\n"); + self.havocbot_role = havocbot_role_kh_carrier; + self.havocbot_role_timeout = 0; + return; + } + + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + random() * 10 + 20; + if (time > self.havocbot_role_timeout) + { + dprint("changing role to freelancer\n"); + self.havocbot_role = havocbot_role_kh_freelancer; + self.havocbot_role_timeout = 0; + return; + } + + if (self.bot_strategytime < time) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + + float key_count = 0; + KH_FOR_EACH_KEY(key) { if(!wasfreed(key) && SAME_TEAM(key, self)) ++key_count; } + + if(key_count >= kh_teams) + havocbot_goalrating_kh(10, 0.1, 0.1); // defend key carriers + else if(key_count == 0) + havocbot_goalrating_kh(4, 1, 0.1); // play defensively + else + havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY + + navigation_goalrating_end(); + } +} + +void havocbot_role_kh_freelancer() +{ + if(self.deadflag != DEAD_NO) + return; + + entity key; + float is_carrier = FALSE; + KH_FOR_EACH_KEY(key) + if(key.owner == self) + { + is_carrier = TRUE; + break; + } + + if (is_carrier) + { + dprint("changing role to carrier\n"); + self.havocbot_role = havocbot_role_kh_carrier; + self.havocbot_role_timeout = 0; + return; + } + + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + random() * 10 + 10; + if (time > self.havocbot_role_timeout) + { + if (random() < 0.5) + { + dprint("changing role to offense\n"); + self.havocbot_role = havocbot_role_kh_offense; + } + else + { + dprint("changing role to defense\n"); + self.havocbot_role = havocbot_role_kh_defense; + } + self.havocbot_role_timeout = 0; + return; + } + + if (self.bot_strategytime < time) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + + float key_count = 0; + KH_FOR_EACH_KEY(key) { if(!wasfreed(key) && SAME_TEAM(key, self)) ++key_count; } + + if(key_count >= kh_teams) + havocbot_goalrating_kh(10, 0.1, 0.1); // defend key carriers + else if(key_count == 0) + havocbot_goalrating_kh(4, 1, 0.1); // play defensively + else + havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY + + navigation_goalrating_end(); + } +} + + +// ============== +// Hook Functions +// ============== + +MUTATOR_HOOKFUNCTION(kh_PlayerPreThink) +{ + entity key; + float s; + float f; + + s = 0; + KH_FOR_EACH_KEY(key) + { + if(key.owner) + f = key.team; + else + f = 30; + s |= pow(32, Team_TeamToNumber(key.team) - 1) * f; + } + + self.kh_keystatus = s; + + KH_FOR_EACH_KEY(key) + { + if(key.owner == self) + key.owner.kh_keystatus |= pow(32, Team_TeamToNumber(key.team) - 1) * 31; + } + + // update the health of the key carrier waypointsprite + if(self.wps_keycarrier) + WaypointSprite_UpdateHealth(self.wps_keycarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)); + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(kh_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc +{ + entity key; + float targ_iscarrier = FALSE, attacker_iscarrier = FALSE; + KH_FOR_EACH_KEY(key) + { + if(key.owner == frag_attacker) { attacker_iscarrier = TRUE; } + if(key.owner == frag_target) { targ_iscarrier = TRUE; } + } + + if(attacker_iscarrier) // if the attacker is a keycarrier + { + if(frag_target == frag_attacker) // damage done to yourself + { + frag_damage *= autocvar_g_keyhunt_keycarrier_selfdamagefactor; + frag_force *= autocvar_g_keyhunt_keycarrier_selfforcefactor; + } + else // damage done to everyone else + { + frag_damage *= autocvar_g_keyhunt_keycarrier_damagefactor; + frag_force *= autocvar_g_keyhunt_keycarrier_forcefactor; + } + } + else if(targ_iscarrier && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a keycarrier + { + if(autocvar_g_keyhunt_keycarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON))) + if(time > frag_target.wps_helpme_time + autocvar_g_keyhunt_keycarrier_auto_helpme_time) + { + frag_target.wps_helpme_time = time; + WaypointSprite_HelpMePing(frag_target.wps_keycarrier); + } + // todo: add notification for when key carrier needs help? + } + return FALSE; } MUTATOR_HOOKFUNCTION(kh_PlayerDies) { - if(self == other) - kh_Key_DropAll(self, TRUE); - else if(IS_PLAYER(other)) - kh_Key_DropAll(self, FALSE); - else - kh_Key_DropAll(self, TRUE); - return 0; + entity tmp_entity; + float targ_iscarrier = FALSE; + + KH_FOR_EACH_KEY(tmp_entity) if(tmp_entity.owner == frag_target) { targ_iscarrier = TRUE; break; } + + if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && targ_iscarrier) + { + PlayerTeamScore_AddScore(frag_attacker, autocvar_g_keyhunt_score_kill); + PlayerScore_Add(frag_attacker, SP_KH_KCKILLS, 1); + } + + KH_FOR_EACH_KEY(tmp_entity) if(tmp_entity.owner == frag_target) { kh_Handle_Throw(frag_target, world, tmp_entity, DROP_NORMAL); } + + if(targ_iscarrier) + if(IS_PLAYER(frag_attacker)) + if(frag_attacker != frag_target) + frag_target.kh_lastkiller = frag_attacker; + + FOR_EACH_PLAYER(tmp_entity) if(tmp_entity.kh_lastkiller == frag_target) { tmp_entity.kh_lastkiller = frag_attacker; } + + return FALSE; } MUTATOR_HOOKFUNCTION(kh_GiveFragsForKill) { - frag_score = kh_HandleFrags(frag_attacker, frag_target, frag_score); - return 0; + frag_score = 0; + return (autocvar_g_keyhunt_ignore_frags); // no frags counted in keyhunt if this is true } -MUTATOR_HOOKFUNCTION(kh_finalize) +MUTATOR_HOOKFUNCTION(kh_RemovePlayer) { - kh_finalize(); - return 0; + entity key; // temporary entity for the search method + + KH_FOR_EACH_KEY(key) if(key.owner == self) { kh_Handle_Throw(self, world, key, DROP_NORMAL); } + + KH_FOR_EACH_KEY(key) // handle this separately, as the above may reset them + { + if(key.pass_sender == self) { key.pass_sender = world; } + if(key.pass_target == self) { key.pass_target = world; } + if(key.kh_dropper == self) { key.kh_dropper = world; } + } + + return FALSE; } -MUTATOR_HOOKFUNCTION(kh_GetTeamCount) +MUTATOR_HOOKFUNCTION(kh_PortalTeleport) { - ret_float = kh_teams; - return 0; + entity key; + + if(!autocvar_g_keyhunt_portalteleport) + KH_FOR_EACH_KEY(key) + if(key.owner == self) { kh_Handle_Throw(self, world, key, DROP_NORMAL); } + + return FALSE; } -MUTATOR_HOOKFUNCTION(kh_SpectateCopy) +MUTATOR_HOOKFUNCTION(kh_PlayerUseKey) { - self.kh_state = other.kh_state; - return 0; + if(MUTATOR_RETURNVALUE || gameover) { return FALSE; } + + entity player = self, key, player_key = world; + + KH_FOR_EACH_KEY(key) + if(key.owner == player) { player_key = key; } + + if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && (!player.vehicle || autocvar_g_keyhunt_allow_vehicle_touch)) + { + // pass the key to a team mate + if(autocvar_g_keyhunt_pass) + { + entity head, closest_target = world; + head = WarpZone_FindRadius(player.origin, autocvar_g_keyhunt_pass_radius, TRUE); + + while(head) // find the closest acceptable target to pass to + { + if(IS_PLAYER(head) && head.deadflag == DEAD_NO) + if(head != player && SAME_TEAM(head, player)) + if(!head.vehicle) + { + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head)); + vector passer_center = CENTER_OR_VIEWOFS(player); + entity head_key = world; + + KH_FOR_EACH_KEY(key) + if(key.owner == head) { head_key = key; break; } + + if(kh_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest)) + { + if(autocvar_g_keyhunt_pass_request && !player_key && head_key) + { + if(IS_BOT_CLIENT(head)) + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_KEYHUNT_PASS_REQUESTING, head.netname); + kh_Handle_Throw(head, player, head_key, DROP_PASS); + } + else + { + Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_KEYHUNT_PASS_REQUESTED, player.netname); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_KEYHUNT_PASS_REQUESTING, head.netname); + } + player.throw_antispam = time + autocvar_g_keyhunt_pass_wait; + return TRUE; + } + else if(player_key) + { + if(closest_target) + { + vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target)); + if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center)) + { closest_target = head; } + } + else { closest_target = head; } + } + } + } + head = head.chain; + } + + if(closest_target) { kh_Handle_Throw(player, closest_target, player_key, DROP_PASS); return TRUE; } + } + + // throw the key in front of you + if(autocvar_g_keyhunt_throw && player_key) + { + if(player.throw_count == -1) + { + if(time > player.throw_prevtime + autocvar_g_keyhunt_throw_punish_delay) + { + player.throw_prevtime = time; + player.throw_count = 1; + kh_Handle_Throw(player, world, player_key, DROP_THROW); + return TRUE; + } + else + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_KEYHUNT_KEY_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_keyhunt_throw_punish_delay) - time)); + return FALSE; + } + } + else + { + if(time > player.throw_prevtime + autocvar_g_keyhunt_throw_punish_time) { player.throw_count = 1; } + else { player.throw_count += 1; } + if(player.throw_count >= autocvar_g_keyhunt_throw_punish_count) { player.throw_count = -1; } + + player.throw_prevtime = time; + kh_Handle_Throw(player, world, player_key, DROP_THROW); + return TRUE; + } + } + } + + return FALSE; } -MUTATOR_HOOKFUNCTION(kh_PlayerUseKey) +MUTATOR_HOOKFUNCTION(kh_ResetMapGlobal) +{ + entity e, key; + FOR_EACH_CLIENT(e) + { + e.kh_lastkiller = world; + } + KH_FOR_EACH_KEY(key) { if(!wasfreed(key)) kh_RemoveKey(key); } + kh_worldkeylist = world; // reset key list + return FALSE; +} + +MUTATOR_HOOKFUNCTION(kh_ResetMap) +{ + // don't reset players + return TRUE; +} + +MUTATOR_HOOKFUNCTION(kh_HelpMePing) +{ + if(self.wps_keycarrier) // update the keycarrier waypointsprite with "NEEDING HELP" notification + { + self.wps_helpme_time = time; + WaypointSprite_HelpMePing(self.wps_keycarrier); + } + else // create a normal help me waypointsprite + { + WaypointSprite_Spawn("helpme", waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, KEY_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, FALSE, RADARICON_HELPME, '1 0.5 0'); + WaypointSprite_Ping(self.wps_helpme); + } + + return TRUE; +} + +MUTATOR_HOOKFUNCTION(kh_VehicleEnter) { - if(MUTATOR_RETURNVALUE == 0) + entity key; + if(!autocvar_g_keyhunt_allow_vehicle_carry && !autocvar_g_keyhunt_allow_vehicle_touch) { - entity k; - k = self.kh_next; - if(k) + KH_FOR_EACH_KEY(key) + if(key.owner == vh_player) { kh_Handle_Throw(vh_player, world, key, DROP_NORMAL); } + } + else + { + KH_FOR_EACH_KEY(key) + if(key.owner == vh_player) { - kh_Key_DropOne(k); - return 1; + setattachment(key, vh_vehicle, ""); + setorigin(key, VEHICLE_KEY_OFFSET); + key.scale = VEHICLE_KEY_SCALE; + //key.angles = '0 0 0'; } } - return 0; + + return FALSE; } +MUTATOR_HOOKFUNCTION(kh_VehicleExit) +{ + entity key; + KH_FOR_EACH_KEY(key) + if(key.owner == vh_player) + { + setattachment(key, vh_player, ""); + setorigin(key, KEY_CARRY_OFFSET); + key.scale = KEY_SCALE; + key.angles = '0 0 0'; + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(kh_MatchEnd) +{ + entity key; // temporary entity for the search method + + KH_FOR_EACH_KEY(key) + { + switch(key.kh_status) + { + case KEY_DROPPED: + case KEY_PASSING: + { + // lock the key, game is over + key.movetype = MOVETYPE_NONE; + key.takedamage = DAMAGE_NO; + key.solid = SOLID_NOT; + key.nextthink = FALSE; // stop thinking + + //dprint("stopping the ", key.netname, " from moving.\n"); + break; + } + + default: + case KEY_CARRY: + { + // do nothing for these keys + break; + } + } + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(kh_BotRoles) +{ + float r = random() * 3; + if (r < 1) + self.havocbot_role = havocbot_role_kh_offense; + else if (r < 2) + self.havocbot_role = havocbot_role_kh_defense; + else + self.havocbot_role = havocbot_role_kh_freelancer; + return TRUE; +} + +MUTATOR_HOOKFUNCTION(kh_GetTeamCount) +{ + ret_float = kh_teams; + return FALSE; +} + + +// ============== +// Initialization +// ============== + +// scoreboard setup +void kh_ScoreRules(float teams) +{ + CheckAllowedTeams(world); + ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, TRUE); + ScoreInfo_SetLabel_TeamScore (ST_KH_CAPS, "caps", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); + ScoreInfo_SetLabel_PlayerScore(SP_KH_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME); + ScoreInfo_SetLabel_PlayerScore(SP_KH_PICKUPS, "pickups", 0); + ScoreInfo_SetLabel_PlayerScore(SP_KH_KCKILLS, "kckills", 0); + ScoreInfo_SetLabel_PlayerScore(SP_KH_DROPS, "drops", SFL_LOWER_IS_BETTER); + ScoreRules_basics_end(); +} + +void kh_DelayedInit() // Do this check with a delay so we can wait for teams to be set up. +{ + kh_teams = autocvar_g_keyhunt_teams_override; + if(kh_teams < 2 || kh_teams > 4) + kh_teams = autocvar_g_keyhunt_teams; + kh_teams = bound(2, kh_teams, 4); + + kh_ScoreRules(kh_teams); + + round_handler_Spawn(KH_CheckTeams, KH_CheckWinner, KH_RoundStart); + round_handler_Init(5, autocvar_g_keyhunt_warmup, autocvar_g_keyhunt_round_timelimit); +} + +void kh_Initialize() +{ + precache_model("models/keyhunt/key.md3"); + + precache_sound("kh/collect.wav"); + precache_sound("kh/capture.wav"); + precache_sound("kh/drop.wav"); + precache_sound("ctf/touch.wav"); + precache_sound("ctf/pass.wav"); + + addstat(STAT_KH_KEYSTATUS, AS_INT, kh_keystatus); + + InitializeEntity(world, kh_DelayedInit, INITPRIO_GAMETYPE); +} + + MUTATOR_DEFINITION(gamemode_keyhunt) { - MUTATOR_HOOK(MakePlayerObserver, kh_Key_DropAll, CBC_ORDER_ANY); - MUTATOR_HOOK(ClientDisconnect, kh_Key_DropAll, CBC_ORDER_ANY); + MUTATOR_HOOK(MakePlayerObserver, kh_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(ClientDisconnect, kh_RemovePlayer, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerDies, kh_PlayerDies, CBC_ORDER_ANY); - MUTATOR_HOOK(GiveFragsForKill, kh_GiveFragsForKill, CBC_ORDER_FIRST); - MUTATOR_HOOK(MatchEnd, kh_finalize, CBC_ORDER_ANY); - MUTATOR_HOOK(GetTeamCount, kh_GetTeamCount, CBC_ORDER_EXCLUSIVE); - MUTATOR_HOOK(SpectateCopy, kh_SpectateCopy, CBC_ORDER_ANY); + MUTATOR_HOOK(MatchEnd, kh_MatchEnd, CBC_ORDER_ANY); + MUTATOR_HOOK(PortalTeleport, kh_PortalTeleport, CBC_ORDER_ANY); + MUTATOR_HOOK(GiveFragsForKill, kh_GiveFragsForKill, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerPreThink, kh_PlayerPreThink, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDamage_Calculate, kh_PlayerDamage, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerUseKey, kh_PlayerUseKey, CBC_ORDER_ANY); + MUTATOR_HOOK(reset_map_global, kh_ResetMapGlobal, CBC_ORDER_ANY); + MUTATOR_HOOK(reset_map_players, kh_ResetMap, CBC_ORDER_ANY); + MUTATOR_HOOK(HelpMePing, kh_HelpMePing, CBC_ORDER_ANY); + MUTATOR_HOOK(VehicleEnter, kh_VehicleEnter, CBC_ORDER_ANY); + MUTATOR_HOOK(VehicleExit, kh_VehicleExit, CBC_ORDER_ANY); + MUTATOR_HOOK(HavocBot_ChooseRole, kh_BotRoles, CBC_ORDER_ANY); + MUTATOR_HOOK(GetTeamCount, kh_GetTeamCount, CBC_ORDER_ANY); MUTATOR_ONADD { diff --git a/qcsrc/server/mutators/gamemode_keyhunt.qh b/qcsrc/server/mutators/gamemode_keyhunt.qh index 611f7f065..d63b05685 100644 --- a/qcsrc/server/mutators/gamemode_keyhunt.qh +++ b/qcsrc/server/mutators/gamemode_keyhunt.qh @@ -1,11 +1,135 @@ -// ALL OF THESE should be removed in the future, as other code should not have -// to care +// these are needed since mutators are compiled last -// used by bots: -float kh_tracking_enabled; -.entity kh_next; -float kh_Key_AllOwnedByWhichTeam(); +#ifdef SVQC -typedef void(void) kh_Think_t; -void kh_StartRound(); -void kh_Controller_SetThink(float t, kh_Think_t func); +// score rule declarations +#define ST_KH_CAPS 1 +#define SP_KH_CAPS 4 +#define SP_KH_CAPTIME 5 +#define SP_KH_PICKUPS 6 +#define SP_KH_DROPS 7 +#define SP_KH_KCKILLS 8 + +// key constants // for most of these, there is just one question to be asked: WHYYYYY? +#define KEY_MIN ('-10 -10 -46') +#define KEY_MAX ('10 10 3') + +#define KH_KEY_ZSHIFT 22 +#define KH_KEY_XYDIST 24 +#define KH_KEY_XYSPEED 45 + +#define KH_VEHICLE_KEY_XYDIST 100 +#define KH_VEHICLE_KEY_ZSHIFT 80 + +#define KEY_SCALE 1 + +#define KEY_BRIGHTNESS 2 + +#define KEY_THINKRATE 0.1 +#define KEY_TOUCHRATE 0.5 + +#define KEY_DROP_OFFSET ('0 0 16') +#define KEY_CARRY_OFFSET ('-16 0 8') +#define KEY_SPAWN_OFFSET ('0 0 1' * (PL_MAX_z - 13)) +#define KEY_WAYPOINT_OFFSET ('0 0 64') +#define KEY_FLOAT_OFFSET ('0 0 16') +#define KEY_PASS_ARC_OFFSET ('0 0 -10') + +#define VEHICLE_KEY_OFFSET ('0 0 96') +#define VEHICLE_KEY_SCALE 2 + +// waypoint colors +#define WPCOLOR_ENEMYKC(t) (colormapPaletteColor(t - 1, FALSE) * 0.75) +#define WPCOLOR_KEYCARRIER(t) ('0.8 0.8 0') +#define WPCOLOR_DROPPEDKEY(t) (('0.25 0.25 0.25' + colormapPaletteColor(t - 1, FALSE)) * 0.5) + +// sounds +#define snd_key_taken noise +#define snd_key_returned noise1 +#define snd_key_capture noise2 +#define snd_key_respawn noise3 +.string snd_key_dropped; +.string snd_key_touch; +.string snd_key_pass; + +// effects +.float toucheffectnum; +.float passeffectnum; +.float capeffectnum; + +// list of keys on the map +entity kh_worldkeylist; +.entity kh_worldkeynext; +.entity kh_stalekeynext; + +// waypoint sprites +.entity wps_helpme; +.entity wps_keycarrier; +.entity wps_keydropped; +.entity wps_enemykeycarrier; +.float wps_helpme_time; +float wpforenemy_announced; +float wpforenemy_nextthink; + +// statuses +#define KEY_DROPPED 2 +#define KEY_CARRY 3 +#define KEY_PASSING 4 + +// others defined in CTF code (TODO: separate?) +#define PICKUP_START 3 +#define PICKUP_KILLED 4 + +// carrier stats +.float stat_kh_redkey_team; +.float stat_kh_bluekey_team; +.float stat_kh_yellowkey_team; +.float stat_kh_pinkkey_team; + +// key properties +.float kh_pickuptime; +.float kh_droptime; +.float kh_status; // status of the key (KEY_DROPPED, KEY_CARRY declared globally) +.entity kh_dropper; // don't allow spam of dropping the key +.entity kh_lastkiller; +.float max_key_health; +.float next_take_time; +float kh_teams; + +// passing/throwing properties +.float pass_distance; +.entity pass_sender; +.entity pass_target; +.float throw_antispam; +.float throw_prevtime; +.float throw_count; + +// alarms +float kh_alarm_time; +float kh_siren_time; + +// macro for checking all keys +#define KH_FOR_EACH_KEY(v) for(v = kh_worldkeylist; v; v = v.kh_worldkeynext) + +#endif + + +// networked key statuses +// bits 0- 4: team of key 1, or 0 for no such key, or 30 for dropped, or 31 for self +// bits 5- 9: team of key 2, or 0 for no such key, or 30 for dropped, or 31 for self +// bits 10-14: team of key 3, or 0 for no such key, or 30 for dropped, or 31 for self +// bits 15-19: team of key 4, or 0 for no such key, or 30 for dropped, or 31 for self +.float kh_keystatus; + +const float KH_RED_KEY_TAKEN = 1; +const float KH_RED_KEY_LOST = 2; +const float KH_RED_KEY_CARRYING = 3; +const float KH_BLUE_KEY_TAKEN = 4; +const float KH_BLUE_KEY_LOST = 8; +const float KH_BLUE_KEY_CARRYING = 12; +const float KH_YELLOW_KEY_TAKEN = 16; +const float KH_YELLOW_KEY_LOST = 32; +const float KH_YELLOW_KEY_CARRYING = 48; +const float KH_PINK_KEY_TAKEN = 64; +const float KH_PINK_KEY_LOST = 128; +const float KH_PINK_KEY_CARRYING = 192; diff --git a/qcsrc/server/mutators/gamemode_lms.qc b/qcsrc/server/mutators/gamemode_lms.qc index 0684ac8ed..a548e1082 100644 --- a/qcsrc/server/mutators/gamemode_lms.qc +++ b/qcsrc/server/mutators/gamemode_lms.qc @@ -41,7 +41,10 @@ MUTATOR_HOOKFUNCTION(lms_PlayerPreSpawn) // player is dead and becomes observer // FIXME fix LMS scoring for new system if(PlayerScore_Add(self, SP_LMS_RANK, 0) > 0) + { self.classname = "observer"; + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_LMS_NOLIVES); + } return FALSE; } @@ -56,7 +59,7 @@ MUTATOR_HOOKFUNCTION(lms_PlayerDies) MUTATOR_HOOKFUNCTION(lms_RemovePlayer) { // Only if the player cannot play at all - if(PlayerScore_Add(self, SP_LMS_RANK, 0) == 666) + if(PlayerScore_Add(self, SP_LMS_RANK, 0) == 666 || PlayerScore_Add(self, SP_LMS_LIVES, 0) > 0) self.frags = FRAGS_SPECTATOR; else self.frags = FRAGS_LMS_LOSER; @@ -72,7 +75,11 @@ MUTATOR_HOOKFUNCTION(lms_RemovePlayer) MUTATOR_HOOKFUNCTION(lms_ClientConnect) { - self.classname = "player"; + if(autocvar_g_lms_join_anytime) + self.frags = FRAGS_SPECTATOR; + else + self.classname = "player"; + campaign_bots_may_start = 1; if(PlayerScore_Add(self, SP_LMS_LIVES, LMS_NewPlayerLives()) <= 0) @@ -171,6 +178,16 @@ MUTATOR_HOOKFUNCTION(lms_ItemTouch) return MUT_ITEMTOUCH_CONTINUE; } +MUTATOR_HOOKFUNCTION(lms_WantWeapon) +{ + if(other.spawnflags & WEP_FLAG_NORMAL) + ret_float = TRUE; + else + ret_float = FALSE; + + return FALSE; +} + // scoreboard stuff void lms_ScoreRules() { @@ -204,6 +221,7 @@ MUTATOR_DEFINITION(gamemode_lms) MUTATOR_HOOK(ForbidPlayerScore_Clear, lms_KeepScore, CBC_ORDER_ANY); MUTATOR_HOOK(FilterItem, lms_FilterItem, CBC_ORDER_ANY); MUTATOR_HOOK(ItemTouch, lms_ItemTouch, CBC_ORDER_ANY); + MUTATOR_HOOK(WantWeapon, lms_WantWeapon, CBC_ORDER_ANY); MUTATOR_ONADD { diff --git a/qcsrc/server/mutators/gamemode_nexball.qc b/qcsrc/server/mutators/gamemode_nexball.qc index 346ae1eee..8c894cc53 100644 --- a/qcsrc/server/mutators/gamemode_nexball.qc +++ b/qcsrc/server/mutators/gamemode_nexball.qc @@ -439,28 +439,28 @@ void nb_spawnteams(void) case NUM_TEAM_1: if(!t_r) { - nb_spawnteam("Red", e.team-1) ; + nb_spawnteam(NAME_TEAM_1, e.team-1); t_r = 1; } break; case NUM_TEAM_2: if(!t_b) { - nb_spawnteam("Blue", e.team-1) ; + nb_spawnteam(NAME_TEAM_2, e.team-1); t_b = 1; } break; case NUM_TEAM_3: if(!t_y) { - nb_spawnteam("Yellow", e.team-1); + nb_spawnteam(NAME_TEAM_3, e.team-1); t_y = 1; } break; case NUM_TEAM_4: if(!t_p) { - nb_spawnteam("Pink", e.team-1) ; + nb_spawnteam(NAME_TEAM_4, e.team-1); t_p = 1; } break; @@ -772,7 +772,6 @@ void W_Nexball_Attack2(void) if(!(balls & BALL_BASKET)) return; W_SetupShot(self, FALSE, 2, "nexball/shoot2.wav", CH_WEAPON_A, 0); -// pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); missile = spawn(); missile.owner = self; @@ -858,9 +857,9 @@ float w_nexball_weapon(float req) } else if(req == 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(W_Model("g_porto.md3")); + precache_model(W_Model("v_porto.md3")); + precache_model(W_Model("h_porto.iqm")); precache_model("models/elaser.mdl"); precache_sound("nexball/shoot1.wav"); precache_sound("nexball/shoot2.wav"); @@ -975,6 +974,12 @@ MUTATOR_HOOKFUNCTION(nexball_SetStartItems) return FALSE; } +MUTATOR_HOOKFUNCTION(nexball_GetTeamCount) +{ + ret_string = "nexball_team"; + return TRUE; +} + MUTATOR_HOOKFUNCTION(nexball_ForbidThrowing) { if(self.weapon == WEP_MORTAR) @@ -992,6 +997,18 @@ MUTATOR_HOOKFUNCTION(nexball_FilterItem) return FALSE; } +MUTATOR_HOOKFUNCTION(nexball_WantWeapon) +{ + ret_float = 0; // weapon is set a few lines later + return FALSE; +} + +MUTATOR_HOOKFUNCTION(nexball_AllowMobSpawning) +{ + ret_string = "You cannot spawn monsters in nexball!"; + return TRUE; +} + MUTATOR_DEFINITION(gamemode_nexball) { MUTATOR_HOOK(PlayerDies, nexball_BallDrop, CBC_ORDER_ANY); @@ -1001,8 +1018,11 @@ MUTATOR_DEFINITION(gamemode_nexball) MUTATOR_HOOK(PlayerPreThink, nexball_PlayerPreThink, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerPhysics, nexball_PlayerPhysics, CBC_ORDER_ANY); MUTATOR_HOOK(SetStartItems, nexball_SetStartItems, CBC_ORDER_ANY); + MUTATOR_HOOK(GetTeamCount, nexball_GetTeamCount, CBC_ORDER_ANY); MUTATOR_HOOK(ForbidThrowCurrentWeapon, nexball_ForbidThrowing, CBC_ORDER_ANY); MUTATOR_HOOK(FilterItem, nexball_FilterItem, CBC_ORDER_ANY); + MUTATOR_HOOK(WantWeapon, nexball_WantWeapon, CBC_ORDER_ANY); + MUTATOR_HOOK(AllowMobSpawning, nexball_AllowMobSpawning, CBC_ORDER_ANY); MUTATOR_ONADD { diff --git a/qcsrc/server/mutators/gamemode_onslaught.qc b/qcsrc/server/mutators/gamemode_onslaught.qc index 74cba2897..752a66387 100644 --- a/qcsrc/server/mutators/gamemode_onslaught.qc +++ b/qcsrc/server/mutators/gamemode_onslaught.qc @@ -1,231 +1,190 @@ -float autocvar_g_onslaught_spawn_at_controlpoints; -float autocvar_g_onslaught_spawn_at_generator; -float autocvar_g_onslaught_cp_proxydecap; -var float autocvar_g_onslaught_cp_proxydecap_distance = 512; -var float autocvar_g_onslaught_cp_proxydecap_dps = 100; +// ======================= +// CaptureShield Functions +// ======================= -void onslaught_generator_updatesprite(entity e); -void onslaught_controlpoint_updatesprite(entity e); -void onslaught_link_checkupdate(); - -.entity sprite; -.string target2; -.float iscaptured; -.float islinked; -.float isgenneighbor_red; -.float isgenneighbor_blue; -.float iscpneighbor_red; -.float iscpneighbor_blue; -.float isshielded; -.float lasthealth; -.float lastteam; -.float lastshielded; -.float lastcaptured; +float ons_CaptureShield_Customize() +{ + entity e = WaypointSprite_getviewentity(other); -entity ons_red_generator; -entity ons_blue_generator; + if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, e.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return FALSE; } + if(SAME_TEAM(self, e)) { return FALSE; } -void ons_gib_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce) -{ - self.velocity = self.velocity + vforce; + return TRUE; } -.float giblifetime; -void ons_throwgib_think() +void ons_CaptureShield_Touch() { - float d; + if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, other.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return; } + if(!IS_PLAYER(other)) { return; } + if(SAME_TEAM(other, self)) { return; } - self.nextthink = time + 0.05; + vector mymid = (self.absmin + self.absmax) * 0.5; + vector othermid = (other.absmin + other.absmax) * 0.5; - d = self.giblifetime - time; + Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ons_captureshield_force); - if(d<0) + if(IS_REAL_CLIENT(other)) { - self.think = SUB_Remove; - return; + play2(other, "onslaught/damageblockedbyshield.wav"); + + if(self.enemy.classname == "onslaught_generator") + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_GENERATOR_SHIELDED); + else + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_CONTROLPOINT_SHIELDED); } - if(d<1) - self.alpha = d; +} - if(d>2) - if(random()<0.6) - pointparticles(particleeffectnum("onslaught_generator_gib_flame"), self.origin, '0 0 0', 1); +void ons_CaptureShield_Reset() +{ + self.colormap = self.enemy.colormap; + self.team = self.enemy.team; } -void ons_throwgib(vector v_from, vector v_to, string smodel, float f_lifetime, float b_burn) +void ons_CaptureShield_Spawn(entity generator, float is_generator) { - entity gib; + entity shield = spawn(); + + shield.enemy = generator; + shield.team = generator.team; + shield.colormap = generator.colormap; + shield.reset = ons_CaptureShield_Reset; + shield.touch = ons_CaptureShield_Touch; + shield.customizeentityforclient = ons_CaptureShield_Customize; + shield.classname = "ons_captureshield"; + shield.effects = EF_ADDITIVE; + shield.movetype = MOVETYPE_NOCLIP; + shield.solid = SOLID_TRIGGER; + shield.avelocity = '7 0 11'; + shield.scale = 1; + shield.model = ((is_generator) ? "models/onslaught/generator_shield.md3" : "models/onslaught/controlpoint_shield.md3"); + + precache_model(shield.model); + setorigin(shield, generator.origin); + setmodel(shield, shield.model); + setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs); +} - gib = spawn(); - setmodel(gib, smodel); - setorigin(gib, v_from); - gib.solid = SOLID_BBOX; - gib.movetype = MOVETYPE_BOUNCE; - gib.takedamage = DAMAGE_YES; - gib.event_damage = ons_gib_damage; - gib.health = -1; - gib.effects = EF_LOWPRECISION; - gib.flags = FL_NOTARGET; - gib.velocity = v_to; - gib.giblifetime = time + f_lifetime; +// ========== +// Junk Pile +// ========== - if (b_burn) +void ons_debug(string input) +{ + switch(autocvar_g_onslaught_debug) { - gib.think = ons_throwgib_think; - gib.nextthink = time + 0.05; + case 1: dprint(input); break; + case 2: print(input); break; } - else - SUB_SetFade(gib, gib.giblifetime, 2); } void onslaught_updatelinks() { - entity l, links; - float stop, t1, t2, t3, t4; + entity l; // first check if the game has ended - dprint("--- updatelinks ---\n"); - links = findchain(classname, "onslaught_link"); + ons_debug("--- updatelinks ---\n"); // mark generators as being shielded and networked - l = findchain(classname, "onslaught_generator"); - while (l) + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) { if (l.iscaptured) - dprint(etos(l), " (generator) belongs to team ", ftos(l.team), "\n"); + ons_debug(strcat(etos(l), " (generator) belongs to team ", ftos(l.team), "\n")); else - dprint(etos(l), " (generator) is destroyed\n"); + ons_debug(strcat(etos(l), " (generator) is destroyed\n")); l.islinked = l.iscaptured; l.isshielded = l.iscaptured; - l = l.chain; + l.sprite.SendFlags |= 16; } // mark points as shielded and not networked - l = findchain(classname, "onslaught_controlpoint"); - while (l) + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) { l.islinked = FALSE; l.isshielded = TRUE; - l.isgenneighbor_red = FALSE; - l.isgenneighbor_blue = FALSE; - l.iscpneighbor_red = FALSE; - l.iscpneighbor_blue = FALSE; - dprint(etos(l), " (point) belongs to team ", ftos(l.team), "\n"); - l = l.chain; + float i; + for(i = 0; i < 17; ++i) { l.isgenneighbor[i] = FALSE; l.iscpneighbor[i] = FALSE; } + ons_debug(strcat(etos(l), " (point) belongs to team ", ftos(l.team), "\n")); + l.sprite.SendFlags |= 16; } // flow power outward from the generators through the network - l = links; - while (l) - { - dprint(etos(l), " (link) connects ", etos(l.goalentity), " with ", etos(l.enemy), "\n"); - l = l.chain; - } - stop = FALSE; + float stop = FALSE; while (!stop) { stop = TRUE; - l = links; - while (l) + for(l = ons_worldlinklist; l; l = l.ons_worldlinknext) { // if both points are captured by the same team, and only one of // them is powered, mark the other one as powered as well if (l.enemy.iscaptured && l.goalentity.iscaptured) if (l.enemy.islinked != l.goalentity.islinked) - if (l.enemy.team == l.goalentity.team) + if(SAME_TEAM(l.enemy, l.goalentity)) { if (!l.goalentity.islinked) { stop = FALSE; l.goalentity.islinked = TRUE; - dprint(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)\n"); + ons_debug(strcat(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)\n")); } else if (!l.enemy.islinked) { stop = FALSE; l.enemy.islinked = TRUE; - dprint(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)\n"); + ons_debug(strcat(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)\n")); } } - l = l.chain; } } // now that we know which points are powered we can mark their neighbors // as unshielded if team differs - l = links; - while (l) + for(l = ons_worldlinklist; l; l = l.ons_worldlinknext) { if (l.goalentity.islinked) { - if (l.goalentity.team != l.enemy.team) + if(DIFF_TEAM(l.goalentity, l.enemy)) { - dprint(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)\n"); + ons_debug(strcat(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)\n")); l.enemy.isshielded = FALSE; } if(l.goalentity.classname == "onslaught_generator") - { - if(l.goalentity.team == NUM_TEAM_1) - l.enemy.isgenneighbor_red = TRUE; - else if(l.goalentity.team == NUM_TEAM_2) - l.enemy.isgenneighbor_blue = TRUE; - } + l.enemy.isgenneighbor[l.goalentity.team] = TRUE; else - { - if(l.goalentity.team == NUM_TEAM_1) - l.enemy.iscpneighbor_red = TRUE; - else if(l.goalentity.team == NUM_TEAM_2) - l.enemy.iscpneighbor_blue = TRUE; - } + l.enemy.iscpneighbor[l.goalentity.team] = TRUE; } if (l.enemy.islinked) { - if (l.goalentity.team != l.enemy.team) + if(DIFF_TEAM(l.goalentity, l.enemy)) { - dprint(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)\n"); + ons_debug(strcat(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)\n")); l.goalentity.isshielded = FALSE; } if(l.enemy.classname == "onslaught_generator") - { - if(l.enemy.team == NUM_TEAM_1) - l.goalentity.isgenneighbor_red = TRUE; - else if(l.enemy.team == NUM_TEAM_2) - l.goalentity.isgenneighbor_blue = TRUE; - } + l.goalentity.isgenneighbor[l.enemy.team] = TRUE; else - { - if(l.enemy.team == NUM_TEAM_1) - l.goalentity.iscpneighbor_red = TRUE; - else if(l.enemy.team == NUM_TEAM_2) - l.goalentity.iscpneighbor_blue = TRUE; - } + l.goalentity.iscpneighbor[l.enemy.team] = TRUE; } - l = l.chain; } - // now update the takedamage and alpha variables on generator shields - l = findchain(classname, "onslaught_generator"); - while (l) + // now update the generators + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) { if (l.isshielded) { - dprint(etos(l), " (generator) is shielded\n"); - l.enemy.alpha = 1; + ons_debug(strcat(etos(l), " (generator) is shielded\n")); l.takedamage = DAMAGE_NO; l.bot_attack = FALSE; } else { - dprint(etos(l), " (generator) is not shielded\n"); - l.enemy.alpha = -1; + ons_debug(strcat(etos(l), " (generator) is not shielded\n")); l.takedamage = DAMAGE_AIM; l.bot_attack = TRUE; } - l = l.chain; + + ons_Generator_UpdateSprite(l); } // now update the takedamage and alpha variables on control point icons - l = findchain(classname, "onslaught_controlpoint"); - while (l) + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) { if (l.isshielded) { - dprint(etos(l), " (point) is shielded\n"); - l.enemy.alpha = 1; + ons_debug(strcat(etos(l), " (point) is shielded\n")); if (l.goalentity) { l.goalentity.takedamage = DAMAGE_NO; @@ -234,105 +193,98 @@ void onslaught_updatelinks() } else { - dprint(etos(l), " (point) is not shielded\n"); - l.enemy.alpha = -1; + ons_debug(strcat(etos(l), " (point) is not shielded\n")); if (l.goalentity) { l.goalentity.takedamage = DAMAGE_AIM; l.goalentity.bot_attack = TRUE; } } - onslaught_controlpoint_updatesprite(l); - l = l.chain; + ons_ControlPoint_UpdateSprite(l); } - // count generators owned by each team - t1 = t2 = t3 = t4 = 0; - l = findchain(classname, "onslaught_generator"); - while (l) + l = findchain(classname, "ons_captureshield"); + while(l) { - if (l.iscaptured) - { - if (l.team == NUM_TEAM_1) t1 = 1; - if (l.team == NUM_TEAM_2) t2 = 1; - if (l.team == NUM_TEAM_3) t3 = 1; - if (l.team == NUM_TEAM_4) t4 = 1; - } - onslaught_generator_updatesprite(l); + l.team = l.enemy.team; + l.colormap = l.enemy.colormap; l = l.chain; } - // see if multiple teams remain (if not, it's game over) - if (t1 + t2 + t3 + t4 < 2) - dprint("--- game over ---\n"); - else - dprint("--- done updating links ---\n"); } -float onslaught_controlpoint_can_be_linked(entity cp, float t) + +// =================== +// Main Link Functions +// =================== + +float ons_Link_Send(entity to, float sendflags) { - if(t == NUM_TEAM_1) + WriteByte(MSG_ENTITY, ENT_CLIENT_RADARLINK); + WriteByte(MSG_ENTITY, sendflags); + if(sendflags & 1) { - if(cp.isgenneighbor_red) - return 2; - if(cp.iscpneighbor_red) - return 1; + WriteCoord(MSG_ENTITY, self.goalentity.origin_x); + WriteCoord(MSG_ENTITY, self.goalentity.origin_y); + WriteCoord(MSG_ENTITY, self.goalentity.origin_z); } - else if(t == NUM_TEAM_2) + if(sendflags & 2) { - if(cp.isgenneighbor_blue) - return 2; - if(cp.iscpneighbor_blue) - return 1; + WriteCoord(MSG_ENTITY, self.enemy.origin_x); + WriteCoord(MSG_ENTITY, self.enemy.origin_y); + WriteCoord(MSG_ENTITY, self.enemy.origin_z); } - return 0; - /* - entity e; - // check to see if this player has a legitimate claim to capture this - // control point - more specifically that there is a captured path of - // points leading back to the team generator - e = findchain(classname, "onslaught_link"); - while (e) - { - if (e.goalentity == cp) - { - dprint(etos(e), " (link) connects to ", etos(e.enemy), " (point)"); - if (e.enemy.islinked) - { - dprint(" which is linked"); - if (e.enemy.team == t) + if(sendflags & 4) { - dprint(" and has the correct team!\n"); - return 1; - } - else - dprint(" but has the wrong team\n"); - } - else - dprint("\n"); + WriteByte(MSG_ENTITY, self.clientcolors); // which is goalentity's color + enemy's color * 16 } - else if (e.enemy == cp) - { - dprint(etos(e), " (link) connects to ", etos(e.goalentity), " (point)"); - if (e.goalentity.islinked) - { - dprint(" which is linked"); - if (e.goalentity.team == t) + return TRUE; +} + +void ons_Link_CheckUpdate() +{ + // TODO check if the two sides have moved (currently they won't move anyway) + float cc = 0, cc1 = 0, cc2 = 0; + + if(self.goalentity.islinked || self.goalentity.iscaptured) { cc1 = (self.goalentity.team - 1) * 0x01; } + if(self.enemy.islinked || self.enemy.iscaptured) { cc2 = (self.enemy.team - 1) * 0x10; } + + cc = cc1 + cc2; + + if(cc != self.clientcolors) { - dprint(" and has a team!\n"); - return 1; - } - else - dprint(" but has the wrong team\n"); - } - else - dprint("\n"); - } - e = e.chain; + self.clientcolors = cc; + self.SendFlags |= 4; } + + self.nextthink = time; +} + +void ons_DelayedLinkSetup() +{ + self.goalentity = find(world, targetname, self.target); + self.enemy = find(world, targetname, self.target2); + if(!self.goalentity) { objerror("can not find target\n"); } + if(!self.enemy) { objerror("can not find target2\n"); } + + ons_debug(strcat(etos(self.goalentity), " linked with ", etos(self.enemy), "\n")); + self.SendFlags |= 3; + self.think = ons_Link_CheckUpdate; + self.nextthink = time; +} + + +// ============================= +// Main Control Point Functions +// ============================= + +float ons_ControlPoint_CanBeLinked(entity cp, float teamnumber) +{ + if(cp.isgenneighbor[teamnumber]) { return 2; } + if(cp.iscpneighbor[teamnumber]) { return 1; } + return 0; - */ } -float onslaught_controlpoint_attackable(entity cp, float t) +float ons_ControlPoint_Attackable(entity cp, float teamnumber) // -2: SAME TEAM, attackable by enemy! // -1: SAME TEAM! // 0: off limits @@ -350,16 +302,16 @@ float onslaught_controlpoint_attackable(entity cp, float t) else if(cp.goalentity) { // if there's already an icon built, nothing happens - if(cp.team == t) + if(cp.team == teamnumber) { - a = onslaught_controlpoint_can_be_linked(cp, NUM_TEAM_1 + NUM_TEAM_2 - t); + a = ons_ControlPoint_CanBeLinked(cp, teamnumber); if(a) // attackable by enemy? return -2; // EMERGENCY! return -1; } // we know it can be linked, so no need to check // but... - a = onslaught_controlpoint_can_be_linked(cp, t); + a = ons_ControlPoint_CanBeLinked(cp, teamnumber); if(a == 2) // near our generator? return 3; // EMERGENCY! return 1; @@ -367,9 +319,9 @@ float onslaught_controlpoint_attackable(entity cp, float t) else { // free point - if(onslaught_controlpoint_can_be_linked(cp, t)) + if(ons_ControlPoint_CanBeLinked(cp, teamnumber)) { - a = onslaught_controlpoint_can_be_linked(cp, NUM_TEAM_1 + NUM_TEAM_2 - t); + a = ons_ControlPoint_CanBeLinked(cp, teamnumber); // why was this here NUM_TEAM_1 + NUM_TEAM_2 - t if(a == 2) return 4; // GET THIS ONE NOW! else @@ -379,428 +331,303 @@ float onslaught_controlpoint_attackable(entity cp, float t) return 0; } -float overtime_msg_time; -void onslaught_generator_think() +void ons_ControlPoint_Icon_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) { - float d; - entity e; - self.nextthink = ceil(time + 1); - if (!gameover) + entity oself; + + if(damage <= 0) { return; } + + if (self.owner.isshielded) { - if (autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) - { - if (!overtime_msg_time) - { - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); - overtime_msg_time = time; - } - // self.max_health / 300 gives 5 minutes of overtime. - // control points reduce the overtime duration. - sound(self, CH_TRIGGER, "onslaught/generator_decay.wav", VOL_BASE, ATTEN_NORM); - d = 1; - e = findchain(classname, "onslaught_controlpoint"); - while (e) + // this is protected by a shield, so ignore the damage + if (time > self.pain_finished) + if (IS_PLAYER(attacker)) { - if (e.team != self.team) - if (e.islinked) - d = d + 1; - e = e.chain; + play2(attacker, "onslaught/damageblockedbyshield.wav"); + self.pain_finished = time + 1; + attacker.typehitsound += 1; // play both sounds (shield is way too quiet) } - if(autocvar_g_campaign && autocvar__campaign_testrun) - d = d * self.max_health; - else - d = d * self.max_health / max(30, 60 * autocvar_timelimit_suddendeath); - - Damage(self, self, self, d, DEATH_HURTTRIGGER, self.origin, '0 0 0'); - } - else if (overtime_msg_time) - overtime_msg_time = 0; - - if(!self.isshielded && self.wait < time) - { - self.wait = time + 5; - FOR_EACH_REALPLAYER(e) - { - if(SAME_TEAM(e, self)) - { - Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_ONS_NOTSHIELDED); - soundto(MSG_ONE, e, CHAN_AUTO, "kh/alarm.wav", VOL_BASE, ATTEN_NONE); // FIXME: Uniqe sound? - } - } - } + return; } -} - -void onslaught_generator_ring_spawn(vector org) -{ - modeleffect_spawn("models/onslaught/shockwavetransring.md3", 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, -16, 0.1, 1.25, 0.25); -} -void onslaught_generator_ray_think() -{ - self.nextthink = time + 0.05; - if(self.count > 10) + if(IS_PLAYER(attacker)) + if(time - ons_notification_time[self.team] > 10) { - self.think = SUB_Remove; - return; + play2team(self.team, "onslaught/controlpoint_underattack.wav"); + ons_notification_time[self.team] = time; } - if(self.count > 5) - self.alpha -= 0.1; + self.health = self.health - damage; + if(self.owner.iscaptured) + WaypointSprite_UpdateHealth(self.owner.sprite, self.health); + else + WaypointSprite_UpdateBuildFinished(self.owner.sprite, time + (self.max_health - self.health) / (self.count / ONS_CP_THINKRATE)); + self.pain_finished = time + 1; + // particles on every hit + Send_Effect(EFFECT_SPARKS, hitloc, force * -1, 1); + //sound on every hit + if (random() < 0.5) + sound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE+0.3, ATTEN_NORM); else - self.alpha += 0.1; + sound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE+0.3, ATTEN_NORM); - self.scale += 0.2; - self.count +=1; -} + if (self.health < 0) + { + sound(self, CH_TRIGGER, W_Sound("grenade_impact"), VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_ROCKET_EXPLODE, self.origin, '0 0 0', 1); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_CPDESTROYED_), self.owner.message, attacker.netname); + + PlayerScore_Add(attacker, SP_ONS_TAKES, 1); + PlayerScore_Add(attacker, SP_SCORE, 10); + + self.owner.goalentity = world; + self.owner.islinked = FALSE; + self.owner.iscaptured = FALSE; + self.owner.team = 0; + self.owner.colormap = 1024; -void onslaught_generator_ray_spawn(vector org) -{ - entity e; - e = spawn(); - setmodel(e, "models/onslaught/ons_ray.md3"); - setorigin(e, org); - e.angles = randomvec() * 360; - e.alpha = 0; - e.scale = random() * 5 + 8; - e.think = onslaught_generator_ray_think; - e.nextthink = time + 0.05; -} + WaypointSprite_UpdateMaxHealth(self.owner.sprite, 0); -void onslaught_generator_shockwave_spawn(vector org) -{ - shockwave_spawn("models/onslaught/shockwave.md3", org, -64, 0.75, 0.5); -} + onslaught_updatelinks(); -void onslaught_generator_damage_think() -{ - if(self.owner.health < 0) - { - self.think = SUB_Remove; - return; - } - self.nextthink = time+0.1; + // Use targets now (somebody make sure this is in the right place..) + oself = self; + self = self.owner; + activator = self; + SUB_UseTargets (); + self = oself; - // damaged fx (less probable the more damaged is the generator) - if(random() < 0.9 - self.owner.health / self.owner.max_health) - if(random() < 0.01) - { - pointparticles(particleeffectnum("electro_ballexplode"), self.origin + randompos('-50 -50 -20', '50 50 50'), '0 0 0', 1); - sound(self, CH_TRIGGER, "onslaught/electricity_explode.wav", VOL_BASE, ATTEN_NORM); - } - else - pointparticles(particleeffectnum("torch_small"), self.origin + randompos('-60 -60 -20', '60 60 60'), '0 0 0', 1); -} + self.owner.waslinked = self.owner.islinked; + if(self.owner.model != "models/onslaught/controlpoint_pad.md3") + setmodel_fixsize(self.owner, "models/onslaught/controlpoint_pad.md3"); + //setsize(self, '-32 -32 0', '32 32 8'); -void onslaught_generator_damage_spawn(entity gd_owner) -{ - entity e; - e = spawn(); - e.owner = gd_owner; - e.health = self.owner.health; - setorigin(e, gd_owner.origin); - e.think = onslaught_generator_damage_think; - e.nextthink = time+1; + remove(self); + } + + self.SendFlags |= CPSF_STATUS; } -void onslaught_generator_deaththink() +void ons_ControlPoint_Icon_Think() { - vector org; - float i; - - if (!self.count) - self.count = 40; + entity oself; + self.nextthink = time + ONS_CP_THINKRATE; - // White shockwave - if(self.count==40||self.count==20) + if(autocvar_g_onslaught_cp_proxydecap) { - onslaught_generator_ring_spawn(self.origin); - sound(self, CH_TRIGGER, "onslaught/shockwave.wav", VOL_BASE, ATTEN_NORM); - } + float _enemy_count = 0; + float _friendly_count = 0; + float _dist; + entity _player; - // Throw some gibs - if(random() < 0.3) - { - i = random(); - if(i < 0.3) - ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 11 + '0 0 20', "models/onslaught/gen_gib1.md3", 6, TRUE); - else if(i > 0.7) - ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 12 + '0 0 20', "models/onslaught/gen_gib2.md3", 6, TRUE); - else - ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 13 + '0 0 20', "models/onslaught/gen_gib3.md3", 6, TRUE); - } + FOR_EACH_PLAYER(_player) + { + if(!_player.deadflag) + { + _dist = vlen(_player.origin - self.origin); + if(_dist < autocvar_g_onslaught_cp_proxydecap_distance) + { + if(SAME_TEAM(_player, self)) + ++_friendly_count; + else + ++_enemy_count; + } + } + } + + _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE); + _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE); + + self.health = bound(0, self.health + (_friendly_count - _enemy_count), self.max_health); + self.SendFlags |= CPSF_STATUS; + if(self.health <= 0) + { + ons_ControlPoint_Icon_Damage(self, self, 1, 0, self.origin, '0 0 0'); + return; + } + } - // Spawn fire balls - for(i=0;i < 10;++i) + if (time > self.pain_finished + 5) { - org = self.origin + randompos('-30 -30 -30' * i + '0 0 -20', '30 30 30' * i + '0 0 20'); - pointparticles(particleeffectnum("onslaught_generator_gib_explode"), org, '0 0 0', 1); + if(self.health < self.max_health) + { + self.health = self.health + self.count; + if (self.health >= self.max_health) + self.health = self.max_health; + WaypointSprite_UpdateHealth(self.owner.sprite, self.health); + } } - // Short explosion sound + small explosion - if(random() < 0.25) + if(self.owner.islinked != self.owner.waslinked) { - te_explosion(self.origin); - sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM); - } + // unteam the spawnpoint if needed + float t; + t = self.owner.team; + if(!self.owner.islinked) + self.owner.team = 0; - // Particles - org = self.origin + randompos(self.mins + '8 8 8', self.maxs + '-8 -8 -8'); - pointparticles(particleeffectnum("onslaught_generator_smallexplosion"), org, '0 0 0', 1); + oself = self; + self = self.owner; + activator = self; + SUB_UseTargets (); + self = oself; - // rays - if(random() > 0.25 ) - { - onslaught_generator_ray_spawn(self.origin); + self.owner.team = t; + + self.owner.waslinked = self.owner.islinked; } - // Final explosion - if(self.count==1) + // damaged fx + if(random() < 0.6 - self.health / self.max_health) { - org = self.origin; - te_explosion(org); - onslaught_generator_shockwave_spawn(org); - pointparticles(particleeffectnum("onslaught_generator_finalexplosion"), org, '0 0 0', 1); - sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); - } - else - self.nextthink = time + 0.05; + Send_Effect(EFFECT_ELECTRIC_SPARKS, self.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1); - self.count = self.count - 1; + if(random() > 0.8) + sound(self, CH_PAIN, "onslaught/ons_spark1.wav", VOL_BASE, ATTEN_NORM); + else if (random() > 0.5) + sound(self, CH_PAIN, "onslaught/ons_spark2.wav", VOL_BASE, ATTEN_NORM); + } } -void onslaught_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +void ons_ControlPoint_Icon_BuildThink() { - float i; - if (damage <= 0) - return; - if(warmup_stage) + entity oself; + float a; + + self.nextthink = time + ONS_CP_THINKRATE; + + // only do this if there is power + a = ons_ControlPoint_CanBeLinked(self.owner, self.owner.team); + if(!a) return; - if (attacker != self) - { - if (self.isshielded) - { - // this is protected by a shield, so ignore the damage - if (time > self.pain_finished) - if (IS_PLAYER(attacker)) - { - play2(attacker, "onslaught/damageblockedbyshield.wav"); - self.pain_finished = time + 1; - } - return; - } - if (time > self.pain_finished) - { - self.pain_finished = time + 10; - bprint(Team_ColoredFullName(self.team), " generator under attack!\n"); - play2team(self.team, "onslaught/generator_underattack.wav"); - } - } - self.health = self.health - damage; - WaypointSprite_UpdateHealth(self.sprite, self.health); - // choose an animation frame based on health - self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1); - // see if the generator is still functional, or dying - if (self.health > 0) - { -#ifdef ONSLAUGHT_SPAM - float h, lh; - lh = ceil(self.lasthealth / 100) * 100; - h = ceil(self.health / 100) * 100; - if(lh != h) - bprint(Team_ColoredFullName(self.team), " generator has less than ", ftos(h), " health remaining\n"); -#endif - self.lasthealth = self.health; - } - else if (!warmup_stage) + + self.health = self.health + self.count; + + self.SendFlags |= CPSF_STATUS; + + if (self.health >= self.max_health) { - if (attacker == self) - bprint(Team_ColoredFullName(self.team), " generator spontaneously exploded due to overtime!\n"); - else + self.health = self.max_health; + self.count = autocvar_g_onslaught_cp_regen * ONS_CP_THINKRATE; // slow repair rate from now on + self.think = ons_ControlPoint_Icon_Think; + sound(self, CH_TRIGGER, "onslaught/controlpoint_built.wav", VOL_BASE, ATTEN_NORM); + self.owner.iscaptured = TRUE; + self.solid = SOLID_BBOX; + + float eff_team; + switch(self.owner.team) { - string t; - t = Team_ColoredFullName(attacker.team); - bprint(Team_ColoredFullName(self.team), " generator destroyed by ", t, "!\n"); + case NUM_TEAM_1: eff_team = EFFECT_RED_CAP; break; + case NUM_TEAM_2: eff_team = EFFECT_BLUE_CAP; break; + case NUM_TEAM_3: eff_team = EFFECT_YELLOW_CAP; break; + case NUM_TEAM_4: eff_team = EFFECT_PINK_CAP; break; + default: eff_team = EFFECT_SPAWN_NEUTRAL; break; } - self.iscaptured = FALSE; - self.islinked = FALSE; - self.isshielded = FALSE; - self.takedamage = DAMAGE_NO; // can't be hurt anymore - self.event_damage = func_null; // won't do anything if hurt - self.count = 0; // reset counter - self.think = onslaught_generator_deaththink; // explosion sequence - self.nextthink = time; // start exploding immediately - self.think(); // do the first explosion now - WaypointSprite_UpdateMaxHealth(self.sprite, 0); + Send_Effect(eff_team, self.owner.origin, '0 0 0', 1); - onslaught_updatelinks(); - } + WaypointSprite_UpdateMaxHealth(self.owner.sprite, self.max_health); + WaypointSprite_UpdateHealth(self.owner.sprite, self.health); - if(self.health <= 0) - setmodel(self, "models/onslaught/generator_dead.md3"); - else if(self.health < self.max_health * 0.10) - setmodel(self, "models/onslaught/generator_dmg9.md3"); - else if(self.health < self.max_health * 0.20) - setmodel(self, "models/onslaught/generator_dmg8.md3"); - else if(self.health < self.max_health * 0.30) - setmodel(self, "models/onslaught/generator_dmg7.md3"); - else if(self.health < self.max_health * 0.40) - setmodel(self, "models/onslaught/generator_dmg6.md3"); - else if(self.health < self.max_health * 0.50) - setmodel(self, "models/onslaught/generator_dmg5.md3"); - else if(self.health < self.max_health * 0.60) - setmodel(self, "models/onslaught/generator_dmg4.md3"); - else if(self.health < self.max_health * 0.70) - setmodel(self, "models/onslaught/generator_dmg3.md3"); - else if(self.health < self.max_health * 0.80) - setmodel(self, "models/onslaught/generator_dmg2.md3"); - else if(self.health < self.max_health * 0.90) - setmodel(self, "models/onslaught/generator_dmg1.md3"); - setsize(self, '-52 -52 -14', '52 52 75'); + if(IS_PLAYER(self.owner.ons_toucher)) + { + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ONSLAUGHT_CAPTURE, self.owner.ons_toucher.netname, self.owner.message); + Send_Notification(NOTIF_ALL_EXCEPT, self.owner.ons_toucher, MSG_CENTER, APP_TEAM_ENT_4(self.owner.ons_toucher, CENTER_ONS_CAPTURE_), self.owner.message); + Send_Notification(NOTIF_ONE, self.owner.ons_toucher, MSG_CENTER, CENTER_ONS_CAPTURE, self.owner.message); + PlayerScore_Add(self.owner.ons_toucher, SP_ONS_CAPS, 1); + PlayerTeamScore_AddScore(self.owner.ons_toucher, 10); + } + + self.owner.ons_toucher = world; - // Throw some flaming gibs on damage, more damage = more chance for gib - if(random() < damage/220) - { - sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); - i = random(); - if(i < 0.3) - ons_throwgib(hitloc + '0 0 20', force * -1, "models/onslaught/gen_gib1.md3", 5, TRUE); - else if(i > 0.7) - ons_throwgib(hitloc + '0 0 20', force * -1, "models/onslaught/gen_gib2.md3", 5, TRUE); - else - ons_throwgib(hitloc + '0 0 20', force * -1, "models/onslaught/gen_gib3.md3", 5, TRUE); - } - else - { - // particles on every hit - pointparticles(particleeffectnum("sparks"), hitloc, force * -1, 1); + onslaught_updatelinks(); - //sound on every hit - if (random() < 0.5) - sound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE, ATTEN_NORM); - else - sound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM); + // Use targets now (somebody make sure this is in the right place..) + oself = self; + self = self.owner; + activator = self; + SUB_UseTargets (); + self = oself; + + self.SendFlags |= CPSF_SETUP; } - - //throw some gibs on damage - if(random() < damage/200+0.2) - if(random() < 0.5) - ons_throwgib(hitloc + '0 0 20', randomvec()*360, "models/onslaught/gen_gib1.md3", 5, FALSE); + if(self.owner.model != "models/onslaught/controlpoint_pad2.md3") + setmodel_fixsize(self.owner, "models/onslaught/controlpoint_pad2.md3"); + + if(random() < 0.9 - self.health / self.max_health) + Send_Effect(EFFECT_RAGE, self.origin + 10 * randomvec(), '0 0 -1', 1); } -// update links after a delay -void onslaught_generator_delayed() +void ons_ControlPoint_Icon_Spawn(entity cp, entity player) { - onslaught_updatelinks(); - // now begin normal thinking - self.think = onslaught_generator_think; - self.nextthink = time; -} + entity e = spawn(); + + setsize(e, CPICON_MIN, CPICON_MAX); + setorigin(e, cp.origin + CPICON_OFFSET); + + e.classname = "onslaught_controlpoint_icon"; + e.owner = cp; + e.max_health = autocvar_g_onslaught_cp_health; + e.health = autocvar_g_onslaught_cp_buildhealth; + e.solid = SOLID_NOT; + e.takedamage = DAMAGE_AIM; + e.bot_attack = TRUE; + e.event_damage = ons_ControlPoint_Icon_Damage; + e.team = player.team; + e.colormap = 1024 + (e.team - 1) * 17; + e.count = (e.max_health - e.health) * ONS_CP_THINKRATE / autocvar_g_onslaught_cp_buildtime; // how long it takes to build + + sound(e, CH_TRIGGER, "onslaught/controlpoint_build.wav", VOL_BASE, ATTEN_NORM); + + cp.goalentity = e; + cp.team = e.team; + cp.colormap = e.colormap; -string onslaught_generator_waypointsprite_for_team(entity e, float t) -{ - if(t == e.team) + float eff_team; + switch(player.team) { - if(e.team == NUM_TEAM_1) - return "ons-gen-red"; - else if(e.team == NUM_TEAM_2) - return "ons-gen-blue"; + case NUM_TEAM_1: eff_team = EFFECT_FLAG_RED_TOUCH; break; + case NUM_TEAM_2: eff_team = EFFECT_FLAG_BLUE_TOUCH; break; + case NUM_TEAM_3: eff_team = EFFECT_FLAG_YELLOW_TOUCH; break; + case NUM_TEAM_4: eff_team = EFFECT_FLAG_PINK_TOUCH; break; + default: eff_team = EFFECT_FLAG_NEUTRAL_TOUCH; break; } - if(e.isshielded) - return "ons-gen-shielded"; - if(e.team == NUM_TEAM_1) - return "ons-gen-red"; - else if(e.team == NUM_TEAM_2) - return "ons-gen-blue"; - return ""; -} -void onslaught_generator_updatesprite(entity e) -{ - string s1, s2, s3; - s1 = onslaught_generator_waypointsprite_for_team(e, NUM_TEAM_1); - s2 = onslaught_generator_waypointsprite_for_team(e, NUM_TEAM_2); - s3 = onslaught_generator_waypointsprite_for_team(e, -1); - WaypointSprite_UpdateSprites(e.sprite, s1, s2, s3); + Send_Effect(eff_team, self.owner.origin, '0 0 0', 1); - if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded) - { - e.lastteam = e.team + 2; - e.lastshielded = e.isshielded; - if(e.lastshielded) - { - if(e.team == NUM_TEAM_1 || e.team == NUM_TEAM_2) - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, FALSE)); - else - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5'); - } - else - { - if(e.team == NUM_TEAM_1 || e.team == NUM_TEAM_2) - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, FALSE)); - else - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75'); - } - WaypointSprite_Ping(e.sprite); - } + WaypointSprite_UpdateBuildFinished(cp.sprite, time + (e.max_health - e.health) / (e.count / ONS_CP_THINKRATE)); + WaypointSprite_UpdateRule(cp.sprite,cp.team,SPRITERULE_TEAMPLAY); + cp.sprite.SendFlags |= 16; + + onslaught_controlpoint_icon_link(e, ons_ControlPoint_Icon_BuildThink); } -string onslaught_controlpoint_waypointsprite_for_team(entity e, float t) +string ons_ControlPoint_Waypoint(entity e) { float a; - if(t != -1) + if(e.team) { - a = onslaught_controlpoint_attackable(e, t); - if(a == 3 || a == 4) // ATTACK/TOUCH THIS ONE NOW - { - if(e.team == NUM_TEAM_1) - return "ons-cp-atck-red"; - else if(e.team == NUM_TEAM_2) - return "ons-cp-atck-blue"; - else - return "ons-cp-atck-neut"; - } - else if(a == -2) // DEFEND THIS ONE NOW - { - if(e.team == NUM_TEAM_1) - return "ons-cp-dfnd-red"; - else if(e.team == NUM_TEAM_2) - return "ons-cp-dfnd-blue"; - } - else if(e.team == t || a == -1 || a == 1) // own point, or fire at it - { - if(e.team == NUM_TEAM_1) - return "ons-cp-red"; - else if(e.team == NUM_TEAM_2) - return "ons-cp-blue"; - } - else if(a == 2) // touch it - return "ons-cp-neut"; + a = ons_ControlPoint_Attackable(e, e.team); + + if(a == -2) { return "ons-cp-dfnd"; } // defend now + if(a == -1 || a == 1 || a == 2) { return "ons-cp"; } // touch + if(a == 3 || a == 4) { return "ons-cp-atck"; } // attack } else - { - if(e.team == NUM_TEAM_1) - return "ons-cp-red"; - else if(e.team == NUM_TEAM_2) - return "ons-cp-blue"; - else - return "ons-cp-neut"; - } + return "ons-cp"; + return ""; } -void onslaught_controlpoint_updatesprite(entity e) +void ons_ControlPoint_UpdateSprite(entity e) { - string s1, s2, s3; - s1 = onslaught_controlpoint_waypointsprite_for_team(e, NUM_TEAM_1); - s2 = onslaught_controlpoint_waypointsprite_for_team(e, NUM_TEAM_2); - s3 = onslaught_controlpoint_waypointsprite_for_team(e, -1); - WaypointSprite_UpdateSprites(e.sprite, s1, s2, s3); + string s1; + s1 = ons_ControlPoint_Waypoint(e); + WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1); float sh; - sh = !(onslaught_controlpoint_can_be_linked(e, NUM_TEAM_1) || onslaught_controlpoint_can_be_linked(e, NUM_TEAM_2)); + sh = !(ons_ControlPoint_CanBeLinked(e, NUM_TEAM_1) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_2) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_3) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_4)); if(e.lastteam != e.team + 2 || e.lastshielded != sh || e.iscaptured != e.lastcaptured) { @@ -818,14 +645,14 @@ void onslaught_controlpoint_updatesprite(entity e) } if(e.lastshielded) { - if(e.team == NUM_TEAM_1 || e.team == NUM_TEAM_2) + if(e.team) WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, FALSE)); else WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5'); } else { - if(e.team == NUM_TEAM_1 || e.team == NUM_TEAM_2) + if(e.team) WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, FALSE)); else WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75'); @@ -838,870 +665,1517 @@ void onslaught_controlpoint_updatesprite(entity e) } } -void onslaught_generator_reset() +void ons_ControlPoint_Touch() { - self.team = self.team_saved; - self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health; - self.takedamage = DAMAGE_AIM; - self.bot_attack = TRUE; - self.iscaptured = TRUE; - self.islinked = TRUE; - self.isshielded = TRUE; - self.enemy.solid = SOLID_NOT; - self.think = onslaught_generator_delayed; - self.nextthink = time + 0.2; - setmodel(self, "models/onslaught/generator.md3"); - setsize(self, '-52 -52 -14', '52 52 75'); - - if(!self.noalign) + entity toucher = other; + float attackable; + + if(IS_VEHICLE(toucher) && toucher.owner) + if(autocvar_g_onslaught_allow_vehicle_touch) + toucher = toucher.owner; + else + return; + + if(!IS_PLAYER(toucher)) { return; } + if(toucher.frozen) { return; } + if(toucher.deadflag != DEAD_NO) { return; } + + if ( SAME_TEAM(self,toucher) ) + if ( self.iscaptured ) { - setorigin(self, self.origin + '0 0 20'); - droptofloor(); + if(time <= toucher.teleport_antispam) + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT_ANTISPAM, rint(toucher.teleport_antispam - time)); + else + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT); } + + attackable = ons_ControlPoint_Attackable(self, toucher.team); + if(attackable != 2 && attackable != 4) + return; + // we've verified that this player has a legitimate claim to this point, + // so start building the captured point icon (which only captures this + // point if it successfully builds without being destroyed first) + ons_ControlPoint_Icon_Spawn(self, toucher); + + self.ons_toucher = toucher; - WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); - WaypointSprite_UpdateHealth(self.sprite, self.health); + onslaught_updatelinks(); } -/*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64) - Base generator. - - spawnfunc_onslaught_link entities can target this. - -keys: -"team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET. -"targetname" - name that spawnfunc_onslaught_link entities will use to target this. - */ -void spawnfunc_onslaught_generator() +void ons_ControlPoint_Think() { - if (!g_onslaught) - { - remove(self); - return; - } - - //entity e; - precache_model("models/onslaught/generator.md3"); - precache_model("models/onslaught/generator_shield.md3"); - precache_model("models/onslaught/generator_dmg1.md3"); - precache_model("models/onslaught/generator_dmg2.md3"); - precache_model("models/onslaught/generator_dmg3.md3"); - precache_model("models/onslaught/generator_dmg4.md3"); - precache_model("models/onslaught/generator_dmg5.md3"); - precache_model("models/onslaught/generator_dmg6.md3"); - precache_model("models/onslaught/generator_dmg7.md3"); - precache_model("models/onslaught/generator_dmg8.md3"); - precache_model("models/onslaught/generator_dmg9.md3"); - precache_model("models/onslaught/generator_dead.md3"); - precache_model("models/onslaught/shockwave.md3"); - precache_model("models/onslaught/shockwavetransring.md3"); - precache_model("models/onslaught/gen_gib1.md3"); - precache_model("models/onslaught/gen_gib2.md3"); - precache_model("models/onslaught/gen_gib3.md3"); - precache_model("models/onslaught/ons_ray.md3"); - precache_sound("onslaught/generator_decay.wav"); - precache_sound("weapons/grenade_impact.wav"); - precache_sound("weapons/rocket_impact.wav"); - precache_sound("onslaught/generator_underattack.wav"); - precache_sound("onslaught/shockwave.wav"); - precache_sound("onslaught/ons_hit1.wav"); - precache_sound("onslaught/ons_hit2.wav"); - precache_sound("onslaught/electricity_explode.wav"); - if (!self.team) - objerror("team must be set"); - - if(self.team == NUM_TEAM_1) - ons_red_generator = self; + self.nextthink = time + ONS_CP_THINKRATE; + CSQCMODEL_AUTOUPDATE(); +} - if(self.team == NUM_TEAM_2) - ons_blue_generator = self; +void ons_ControlPoint_Reset() +{ + if(self.goalentity) + remove(self.goalentity); - self.team_saved = self.team; - self.colormap = 1024 + (self.team - 1) * 17; - self.solid = SOLID_BBOX; - self.movetype = MOVETYPE_NONE; - self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health; - setmodel(self, "models/onslaught/generator.md3"); - setsize(self, '-52 -52 -14', '52 52 75'); - setorigin(self, self.origin); - self.takedamage = DAMAGE_AIM; - self.bot_attack = TRUE; - self.event_damage = onslaught_generator_damage; - self.iscaptured = TRUE; - self.islinked = TRUE; + self.goalentity = world; + self.team = 0; + self.colormap = 1024; + self.iscaptured = FALSE; + self.islinked = FALSE; self.isshielded = TRUE; - // helper entity that create fx when generator is damaged - onslaught_generator_damage_spawn(self); - // spawn shield model which indicates whether this can be damaged - self.enemy = spawn(); - setattachment(self.enemy , self, ""); - self.enemy.classname = "onslaught_generator_shield"; - self.enemy.solid = SOLID_NOT; - self.enemy.movetype = MOVETYPE_NONE; - self.enemy.effects = EF_ADDITIVE; - setmodel(self.enemy, "models/onslaught/generator_shield.md3"); - //setorigin(e, self.origin); - self.enemy.colormap = self.colormap; - self.enemy.team = self.team; - //self.think = onslaught_generator_delayed; - //self.nextthink = time + 0.2; - InitializeEntity(self, onslaught_generator_delayed, INITPRIO_LAST); - - WaypointSprite_SpawnFixed(string_null, self.origin + '0 0 128', self, sprite, RADARICON_NONE, '0 0 0'); - WaypointSprite_UpdateRule(self.sprite, NUM_TEAM_2, SPRITERULE_TEAMPLAY); - WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); - WaypointSprite_UpdateHealth(self.sprite, self.health); + self.think = ons_ControlPoint_Think; + self.ons_toucher = world; + self.nextthink = time + ONS_CP_THINKRATE; + setmodel_fixsize(self, "models/onslaught/controlpoint_pad.md3"); - waypoint_spawnforitem(self); + WaypointSprite_UpdateMaxHealth(self.sprite, 0); + WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY); onslaught_updatelinks(); - self.reset = onslaught_generator_reset; + activator = self; + SUB_UseTargets(); // to reset the structures, playerspawns etc. + + CSQCMODEL_AUTOUPDATE(); } -.float waslinked; -.float cp_bob_spd; -.vector cp_origin, cp_bob_origin, cp_bob_dmg; +void ons_DelayedControlPoint_Setup(void) +{ + onslaught_updatelinks(); + + // captureshield setup + ons_CaptureShield_Spawn(self, FALSE); -float ons_notification_time_team1; -float ons_notification_time_team2; + CSQCMODEL_AUTOINIT(); +} -void onslaught_controlpoint_icon_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +void ons_ControlPoint_Setup(entity cp) { - entity oself; - float nag; + // declarations + self = cp; // for later usage with droptofloor() + + // main setup + cp.ons_worldcpnext = ons_worldcplist; // link control point into ons_worldcplist + ons_worldcplist = cp; + + cp.netname = "Control point"; + cp.team = 0; + cp.solid = SOLID_BBOX; + cp.movetype = MOVETYPE_NONE; + cp.touch = ons_ControlPoint_Touch; + cp.think = ons_ControlPoint_Think; + cp.nextthink = time + ONS_CP_THINKRATE; + cp.reset = ons_ControlPoint_Reset; + cp.colormap = 1024; + cp.iscaptured = FALSE; + cp.islinked = FALSE; + cp.isshielded = TRUE; + + if(cp.message == "") { cp.message = "a"; } - if (damage <= 0) - return; - if (self.owner.isshielded) + // precache - TODO: clean up! + precache_model("models/onslaught/controlpoint_pad.md3"); + precache_model("models/onslaught/controlpoint_pad2.md3"); + precache_model("models/onslaught/controlpoint_shield.md3"); + precache_model("models/onslaught/controlpoint_icon.md3"); + precache_model("models/onslaught/controlpoint_icon_dmg1.md3"); + precache_model("models/onslaught/controlpoint_icon_dmg2.md3"); + precache_model("models/onslaught/controlpoint_icon_dmg3.md3"); + precache_model("models/onslaught/controlpoint_icon_gib1.md3"); + precache_model("models/onslaught/controlpoint_icon_gib2.md3"); + precache_model("models/onslaught/controlpoint_icon_gib4.md3"); + precache_sound("onslaught/controlpoint_build.wav"); + precache_sound("onslaught/controlpoint_built.wav"); + precache_sound(W_Sound("grenade_impact")); + precache_sound("onslaught/damageblockedbyshield.wav"); + precache_sound("onslaught/controlpoint_underattack.wav"); + precache_sound("onslaught/ons_spark1.wav"); + precache_sound("onslaught/ons_spark2.wav"); + + // appearence + setmodel_fixsize(cp, "models/onslaught/controlpoint_pad.md3"); + + // control point placement + if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location { - // this is protected by a shield, so ignore the damage - if (time > self.pain_finished) - if (IS_PLAYER(attacker)) - { - play2(attacker, "onslaught/damageblockedbyshield.wav"); - self.pain_finished = time + 1; - } - return; + cp.noalign = TRUE; + cp.movetype = MOVETYPE_NONE; + } + else // drop to floor, automatically find a platform and set that as spawn origin + { + setorigin(cp, cp.origin + '0 0 20'); + cp.noalign = FALSE; + self = cp; + droptofloor(); + cp.movetype = MOVETYPE_TOSS; } + + // waypointsprites + WaypointSprite_SpawnFixed(string_null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE, '0 0 0'); + WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY); + + InitializeEntity(cp, ons_DelayedControlPoint_Setup, INITPRIO_SETLOCATION); +} + + +// ========================= +// Main Generator Functions +// ========================= + +string ons_Generator_Waypoint(entity e) +{ + if(e.isshielded) + return "ons-gen-shielded"; + return "ons-gen"; +} + +void ons_Generator_UpdateSprite(entity e) +{ + string s1; + s1 = ons_Generator_Waypoint(e); + WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1); - if (IS_PLAYER(attacker)) + if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded) { - nag = FALSE; - if(self.team == NUM_TEAM_1) + e.lastteam = e.team + 2; + e.lastshielded = e.isshielded; + if(e.lastshielded) { - if(time - ons_notification_time_team1 > 10) - { - nag = TRUE; - ons_notification_time_team1 = time; - } + if(e.team) + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, FALSE)); + else + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5'); } - else if(self.team == NUM_TEAM_2) + else { - if(time - ons_notification_time_team2 > 10) - { - nag = TRUE; - ons_notification_time_team2 = time; - } + if(e.team) + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, FALSE)); + else + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75'); } - else - nag = TRUE; - - if(nag) - play2team(self.team, "onslaught/controlpoint_underattack.wav"); + WaypointSprite_Ping(e.sprite); } +} +void ons_GeneratorDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if(damage <= 0) { return; } + if(warmup_stage || gameover) { return; } + if(!round_handler_IsRoundStarted()) { return; } + + if (attacker != self) + { + if (self.isshielded) + { + // this is protected by a shield, so ignore the damage + if (time > self.pain_finished) + if (IS_PLAYER(attacker)) + { + play2(attacker, "onslaught/damageblockedbyshield.wav"); + attacker.typehitsound += 1; + self.pain_finished = time + 1; + } + return; + } + if (time > self.pain_finished) + { + self.pain_finished = time + 10; + entity head; + FOR_EACH_REALPLAYER(head) if(SAME_TEAM(head, self)) { Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_GENERATOR_UNDERATTACK); } + play2team(self.team, "onslaught/generator_underattack.wav"); + } + } self.health = self.health - damage; - if(self.owner.iscaptured) - WaypointSprite_UpdateHealth(self.owner.sprite, self.health); - else - WaypointSprite_UpdateBuildFinished(self.owner.sprite, time + (self.max_health - self.health) / (self.count / sys_frametime)); - self.pain_finished = time + 1; - self.punchangle = (2 * randomvec() - '1 1 1') * 45; - self.cp_bob_dmg_z = (2 * random() - 1) * 15; - // colormod flash when shot - self.colormod = '2 2 2'; - // particles on every hit - pointparticles(particleeffectnum("sparks"), hitloc, force*-1, 1); - //sound on every hit - if (random() < 0.5) - sound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE+0.3, ATTEN_NORM); + WaypointSprite_UpdateHealth(self.sprite, self.health); + // choose an animation frame based on health + self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1); + // see if the generator is still functional, or dying + if (self.health > 0) + { + self.lasthealth = self.health; + } else - sound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE+0.3, ATTEN_NORM); - - if (self.health < 0) { - sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM); - pointparticles(particleeffectnum("rocket_explode"), self.origin, '0 0 0', 1); + if (attacker == self) + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_OVERTIME_)); + else { - string t; - t = Team_ColoredFullName(attacker.team); - bprint(Team_ColoredFullName(self.team), " ", self.message, " control point destroyed by ", t, "\n"); - ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 25, "models/onslaught/controlpoint_icon_gib1.md3", 3, FALSE); - ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 45, "models/onslaught/controlpoint_icon_gib2.md3", 3, FALSE); - ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 45, "models/onslaught/controlpoint_icon_gib2.md3", 3, FALSE); - ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, FALSE); - ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, FALSE); - ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, FALSE); - ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, FALSE); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_)); + PlayerScore_Add(attacker, SP_SCORE, 100); } - self.owner.goalentity = world; - self.owner.islinked = FALSE; - self.owner.iscaptured = FALSE; - self.owner.team = 0; - self.owner.colormap = 1024; + self.iscaptured = FALSE; + self.islinked = FALSE; + self.isshielded = FALSE; + self.takedamage = DAMAGE_NO; // can't be hurt anymore + self.event_damage = func_null; // won't do anything if hurt + self.count = 0; // reset counter + self.think = func_null; + self.nextthink = 0; + //self.think(); // do the first explosion now - WaypointSprite_UpdateMaxHealth(self.owner.sprite, 0); + WaypointSprite_UpdateMaxHealth(self.sprite, 0); + WaypointSprite_Ping(self.sprite); + //WaypointSprite_Kill(self.sprite); // can't do this yet, code too poor onslaught_updatelinks(); + } - // Use targets now (somebody make sure this is in the right place..) - oself = self; - self = self.owner; - activator = self; - SUB_UseTargets (); - self = oself; - - - self.owner.waslinked = self.owner.islinked; - if(self.owner.model != "models/onslaught/controlpoint_pad.md3") - setmodel(self.owner, "models/onslaught/controlpoint_pad.md3"); - //setsize(self, '-32 -32 0', '32 32 8'); + // Throw some flaming gibs on damage, more damage = more chance for gib + if(random() < damage/220) + { + sound(self, CH_TRIGGER, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); + } + else + { + // particles on every hit + Send_Effect(EFFECT_SPARKS, hitloc, force * -1, 1); - remove(self); + //sound on every hit + if (random() < 0.5) + sound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE, ATTEN_NORM); + else + sound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM); } + + self.SendFlags |= GSF_STATUS; } -void onslaught_controlpoint_icon_think() +void ons_GeneratorThink() { - entity oself; - self.nextthink = time + sys_frametime; - - if(autocvar_g_onslaught_cp_proxydecap) + entity e; + self.nextthink = time + GEN_THINKRATE; + if (!gameover) { - float _enemy_count = 0; - float _friendly_count = 0; - float _dist; - entity _player; - - FOR_EACH_PLAYER(_player) + if(!self.isshielded && self.wait < time) { - if(!_player.deadflag) + self.wait = time + 5; + FOR_EACH_REALPLAYER(e) { - _dist = vlen(_player.origin - self.origin); - if(_dist < autocvar_g_onslaught_cp_proxydecap_distance) - { - if(_player.team == self.team) - ++_friendly_count; - else - ++_enemy_count; + if(SAME_TEAM(e, self)) + { + Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM); + soundto(MSG_ONE, e, CHAN_AUTO, "kh/alarm.wav", VOL_BASE, ATTEN_NONE); // FIXME: unique sound? } + else + Send_Notification(NOTIF_ONE, e, MSG_CENTER, APP_TEAM_NUM_4(self.team, CENTER_ONS_NOTSHIELDED_)); } } + } +} - _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * sys_frametime); - _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * sys_frametime); +void ons_GeneratorReset() +{ + self.team = self.team_saved; + self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health; + self.takedamage = DAMAGE_AIM; + self.bot_attack = TRUE; + self.iscaptured = TRUE; + self.islinked = TRUE; + self.isshielded = TRUE; + self.event_damage = ons_GeneratorDamage; + self.think = ons_GeneratorThink; + self.nextthink = time + GEN_THINKRATE; + + Net_LinkEntity(self, FALSE, 0, generator_send); + + self.SendFlags = GSF_SETUP; // just incase + self.SendFlags |= GSF_STATUS; - self.health = bound(0, self.health + (_friendly_count - _enemy_count), self.max_health); - if(self.health <= 0) - { - onslaught_controlpoint_icon_damage(self, self, 1, 0, self.origin, '0 0 0'); - return; - } - } + WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); + WaypointSprite_UpdateHealth(self.sprite, self.health); + WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY); + + onslaught_updatelinks(); +} - if (time > self.pain_finished + 5) +void ons_DelayedGeneratorSetup() +{ + // bot waypoints + waypoint_spawnforitem_force(self, self.origin); + self.nearestwaypointtimeout = 0; // activate waypointing again + self.bot_basewaypoint = self.nearestwaypoint; + + // captureshield setup + ons_CaptureShield_Spawn(self, TRUE); + + onslaught_updatelinks(); + + Net_LinkEntity(self, FALSE, 0, generator_send); +} + + +void onslaught_generator_touch() +{ + if ( IS_PLAYER(other) ) + if ( SAME_TEAM(self,other) ) + if ( self.iscaptured ) { - if(self.health < self.max_health) - { - self.health = self.health + self.count; - if (self.health >= self.max_health) - self.health = self.max_health; - WaypointSprite_UpdateHealth(self.owner.sprite, self.health); - } + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_TELEPORT); } - if (self.health < self.max_health * 0.25) - setmodel(self, "models/onslaught/controlpoint_icon_dmg3.md3"); - else if (self.health < self.max_health * 0.50) - setmodel(self, "models/onslaught/controlpoint_icon_dmg2.md3"); - else if (self.health < self.max_health * 0.75) - setmodel(self, "models/onslaught/controlpoint_icon_dmg1.md3"); - else if (self.health < self.max_health * 0.90) - setmodel(self, "models/onslaught/controlpoint_icon.md3"); - // colormod flash when shot - self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1)); +} - if(self.owner.islinked != self.owner.waslinked) - { - // unteam the spawnpoint if needed - float t; - t = self.owner.team; - if(!self.owner.islinked) - self.owner.team = 0; +void ons_GeneratorSetup(entity gen) // called when spawning a generator entity on the map as a spawnfunc +{ + // declarations + float teamnumber = gen.team; + self = gen; // for later usage with droptofloor() + + // main setup + gen.ons_worldgeneratornext = ons_worldgeneratorlist; // link generator into ons_worldgeneratorlist + ons_worldgeneratorlist = gen; + + gen.netname = sprintf("%s generator", Team_ColoredFullName(teamnumber)); + gen.classname = "onslaught_generator"; + gen.solid = SOLID_BBOX; + gen.team_saved = teamnumber; + gen.movetype = MOVETYPE_NONE; + gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health; + gen.takedamage = DAMAGE_AIM; + gen.bot_attack = TRUE; + gen.event_damage = ons_GeneratorDamage; + gen.reset = ons_GeneratorReset; + gen.think = ons_GeneratorThink; + gen.nextthink = time + GEN_THINKRATE; + gen.iscaptured = TRUE; + gen.islinked = TRUE; + gen.isshielded = TRUE; + gen.touch = onslaught_generator_touch; + + // precache - TODO: clean up! + precache_model("models/onslaught/generator_shield.md3"); + precache_model("models/onslaught/gen_gib1.md3"); + precache_model("models/onslaught/gen_gib2.md3"); + precache_model("models/onslaught/gen_gib3.md3"); + precache_sound("onslaught/generator_decay.wav"); + precache_sound(W_Sound("grenade_impact")); + precache_sound(W_Sound("rocket_impact")); + precache_sound("onslaught/generator_underattack.wav"); + precache_sound("onslaught/shockwave.wav"); + precache_sound("onslaught/ons_hit1.wav"); + precache_sound("onslaught/ons_hit2.wav"); + precache_sound("onslaught/generator_underattack.wav"); + + // appearence + // model handled by CSQC + setsize(gen, GENERATOR_MIN, GENERATOR_MAX); + setorigin(gen, (gen.origin + CPGEN_SPAWN_OFFSET)); + gen.colormap = 1024 + (teamnumber - 1) * 17; + + // generator placement + self = gen; + droptofloor(); + + // waypointsprites + WaypointSprite_SpawnFixed(string_null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE, '0 0 0'); + WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY); + WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); + WaypointSprite_UpdateHealth(self.sprite, self.health); + + InitializeEntity(gen, ons_DelayedGeneratorSetup, INITPRIO_SETLOCATION); +} - oself = self; - self = self.owner; - activator = self; - SUB_UseTargets (); - self = oself; - self.owner.team = t; +// =============== +// Round Handler +// =============== - self.owner.waslinked = self.owner.islinked; +float total_generators, redowned, blueowned, yellowowned, pinkowned; +void Onslaught_count_generators() +{ + entity e; + total_generators = redowned = blueowned = yellowowned = pinkowned = 0; + for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext) + { + ++total_generators; + redowned += (e.team == NUM_TEAM_1 && e.health > 0); + blueowned += (e.team == NUM_TEAM_2 && e.health > 0); + yellowowned += (e.team == NUM_TEAM_3 && e.health > 0); + pinkowned += (e.team == NUM_TEAM_4 && e.health > 0); } +} - if (self.punchangle_x > 0) +float Onslaught_GetWinnerTeam() +{ + float winner_team = 0; + if(redowned > 0) + winner_team = NUM_TEAM_1; + if(blueowned > 0) { - self.punchangle_x = self.punchangle_x - 60 * sys_frametime; - if (self.punchangle_x < 0) - self.punchangle_x = 0; + if(winner_team) return 0; + winner_team = NUM_TEAM_2; } - else if (self.punchangle_x < 0) + if(yellowowned > 0) { - self.punchangle_x = self.punchangle_x + 60 * sys_frametime; - if (self.punchangle_x > 0) - self.punchangle_x = 0; + if(winner_team) return 0; + winner_team = NUM_TEAM_3; } - - if (self.punchangle_y > 0) + if(pinkowned > 0) { - self.punchangle_y = self.punchangle_y - 60 * sys_frametime; - if (self.punchangle_y < 0) - self.punchangle_y = 0; + if(winner_team) return 0; + winner_team = NUM_TEAM_4; } - else if (self.punchangle_y < 0) + if(winner_team) + return winner_team; + return -1; // no generators left? +} + +#define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0)) +#define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1) +float Onslaught_CheckWinner() +{ + entity e; + + if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)) { - self.punchangle_y = self.punchangle_y + 60 * sys_frametime; - if (self.punchangle_y > 0) - self.punchangle_y = 0; + ons_stalemate = TRUE; + + if (!wpforenemy_announced) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); + sound(world, CH_INFO, "onslaught/generator_decay.wav", VOL_BASE, ATTEN_NONE); + + wpforenemy_announced = TRUE; + } + + entity tmp_entity; // temporary entity + float d; + for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) if(time >= tmp_entity.ons_overtime_damagedelay) + { + // tmp_entity.max_health / 300 gives 5 minutes of overtime. + // control points reduce the overtime duration. + d = 1; + for(e = ons_worldcplist; e; e = e.ons_worldcpnext) + { + if(DIFF_TEAM(e, tmp_entity)) + if(e.islinked) + d = d + 1; + } + + if(autocvar_g_campaign && autocvar__campaign_testrun) + d = d * tmp_entity.max_health; + else + d = d * tmp_entity.max_health / max(30, 60 * autocvar_timelimit_suddendeath); + + Damage(tmp_entity, tmp_entity, tmp_entity, d, DEATH_HURTTRIGGER, tmp_entity.origin, '0 0 0'); + + tmp_entity.sprite.SendFlags |= 16; + + tmp_entity.ons_overtime_damagedelay = time + 1; + } } + else { wpforenemy_announced = FALSE; ons_stalemate = FALSE; } - if (self.punchangle_z > 0) + Onslaught_count_generators(); + + if(ONS_OWNED_GENERATORS_OK()) + return 0; + + float winner_team = Onslaught_GetWinnerTeam(); + + if(winner_team > 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_)); + TeamScore_AddToTeam(winner_team, ST_ONS_CAPS, +1); + } + else if(winner_team == -1) { - self.punchangle_z = self.punchangle_z - 60 * sys_frametime; - if (self.punchangle_z < 0) - self.punchangle_z = 0; + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED); } - else if (self.punchangle_z < 0) + + ons_stalemate = FALSE; + + play2all(sprintf("ctf/%s_capture.wav", Static_Team_ColorName_Lower(winner_team))); + + round_handler_Init(7, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit); + + FOR_EACH_PLAYER(e) { - self.punchangle_z = self.punchangle_z + 60 * sys_frametime; - if (self.punchangle_z > 0) - self.punchangle_z = 0; + e.ons_roundlost = TRUE; + e.player_blocked = TRUE; } + + nades_Clear(world, TRUE); + + return 1; +} - self.angles_x = self.punchangle_x; - self.angles_y = self.punchangle_y + self.mangle_y; - self.angles_z = self.punchangle_z; - self.mangle_y = self.mangle_y + 45 * sys_frametime; +float Onslaught_CheckPlayers() +{ + return 1; +} - self.cp_bob_origin_z = 4 * PI * (1 - cos(self.cp_bob_spd)); - self.cp_bob_spd = self.cp_bob_spd + 1.875 * sys_frametime; - if(self.cp_bob_dmg_z > 0) - self.cp_bob_dmg_z = self.cp_bob_dmg_z - 3 * sys_frametime; - else - self.cp_bob_dmg_z = 0; - setorigin(self,self.cp_origin + self.cp_bob_origin + self.cp_bob_dmg); +void Onslaught_RoundStart() +{ + entity tmp_entity; + FOR_EACH_PLAYER(tmp_entity) { tmp_entity.player_blocked = FALSE; } - // damaged fx - if(random() < 0.6 - self.health / self.max_health) + for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext) + tmp_entity.sprite.SendFlags |= 16; + + for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) + tmp_entity.sprite.SendFlags |= 16; +} + + +// ================ +// Bot player logic +// ================ + +// NOTE: LEGACY CODE, needs to be re-written! + +void havocbot_goalrating_ons_offenseitems(float ratingscale, vector org, float sradius) +{ + entity head; + float t, i, c, needarmor = FALSE, needweapons = FALSE; + + // Needs armor/health? + if(self.health<100) + needarmor = TRUE; + + // Needs weapons? + c = 0; + for(i = WEP_FIRST; i <= WEP_LAST ; ++i) { - pointparticles(particleeffectnum("electricity_sparks"), self.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1); + // Find weapon + if(self.weapons & WepSet_FromWeapon(i)) + if(++c>=4) + break; + } - if(random() > 0.8) - sound(self, CH_PAIN, "onslaught/ons_spark1.wav", VOL_BASE, ATTEN_NORM); - else if (random() > 0.5) - sound(self, CH_PAIN, "onslaught/ons_spark2.wav", VOL_BASE, ATTEN_NORM); + if(c<4) + needweapons = TRUE; + + if(!needweapons && !needarmor) + return; + + ons_debug(strcat(self.netname, " needs weapons ", ftos(needweapons) , "\n")); + ons_debug(strcat(self.netname, " needs armor ", ftos(needarmor) , "\n")); + + // See what is around + head = findchainfloat(bot_pickup, TRUE); + while (head) + { + // gather health and armor only + if (head.solid) + if ( ((head.health || head.armorvalue) && needarmor) || (head.weapons && needweapons ) ) + if (vlen(head.origin - org) < sradius) + { + t = head.bot_pickupevalfunc(self, head); + if (t > 0) + navigation_routerating(head, t * ratingscale, 500); + } + head = head.chain; } } -void onslaught_controlpoint_icon_buildthink() +void havocbot_role_ons_setrole(entity bot, float role) { - entity oself; - float a; + ons_debug(strcat(bot.netname," switched to ")); + switch(role) + { + case HAVOCBOT_ONS_ROLE_DEFENSE: + ons_debug("defense"); + bot.havocbot_role = havocbot_role_ons_defense; + bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE; + bot.havocbot_role_timeout = 0; + break; + case HAVOCBOT_ONS_ROLE_ASSISTANT: + ons_debug("assistant"); + bot.havocbot_role = havocbot_role_ons_assistant; + bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT; + bot.havocbot_role_timeout = 0; + break; + case HAVOCBOT_ONS_ROLE_OFFENSE: + ons_debug("offense"); + bot.havocbot_role = havocbot_role_ons_offense; + bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE; + bot.havocbot_role_timeout = 0; + break; + } + ons_debug("\n"); +} - self.nextthink = time + sys_frametime; +float havocbot_ons_teamcount(entity bot, float role) +{ + float c = 0; + entity head; - // only do this if there is power - a = onslaught_controlpoint_can_be_linked(self.owner, self.owner.team); - if(!a) - return; + FOR_EACH_PLAYER(head) + if(SAME_TEAM(head, self)) + if(head.havocbot_role_flags & role) + ++c; - self.health = self.health + self.count; + return c; +} - if (self.health >= self.max_health) +void havocbot_goalrating_ons_controlpoints_attack(float ratingscale) +{ + entity cp, cp1, cp2, best, pl, wp; + float radius, found, bestvalue, c; + + // Filter control points + for(cp2 = ons_worldcplist; cp2; cp2 = cp2.ons_worldcpnext) { - self.health = self.max_health; - self.count = autocvar_g_onslaught_cp_regen * sys_frametime; // slow repair rate from now on - self.think = onslaught_controlpoint_icon_think; - sound(self, CH_TRIGGER, "onslaught/controlpoint_built.wav", VOL_BASE, ATTEN_NORM); - bprint(Team_ColoredFullName(self.team), " captured ", self.owner.message, " control point\n"); - self.owner.iscaptured = TRUE; + cp2.wpcost = c = 0; + cp2.wpconsidered = FALSE; - WaypointSprite_UpdateMaxHealth(self.owner.sprite, self.max_health); - WaypointSprite_UpdateHealth(self.owner.sprite, self.health); + if(cp2.isshielded) + continue; - onslaught_updatelinks(); + // Ignore owned controlpoints + if(!(cp2.isgenneighbor[self.team] || cp2.iscpneighbor[self.team])) + continue; - // Use targets now (somebody make sure this is in the right place..) - oself = self; - self = self.owner; - activator = self; - SUB_UseTargets (); - self = oself; - self.cp_origin = self.origin; - self.cp_bob_origin = '0 0 0.1'; - self.cp_bob_spd = 0; + // Count team mates interested in this control point + // (easier and cleaner than keeping counters per cp and teams) + FOR_EACH_PLAYER(pl) + if(SAME_TEAM(pl, self)) + if(pl.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE) + if(pl.havocbot_ons_target==cp2) + ++c; + + // NOTE: probably decrease the cost of attackable control points + cp2.wpcost = c; + cp2.wpconsidered = TRUE; } - self.alpha = self.health / self.max_health; - // colormod flash when shot - self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1)); - if(self.owner.model != "models/onslaught/controlpoint_pad2.md3") - setmodel(self.owner, "models/onslaught/controlpoint_pad2.md3"); - //setsize(self, '-32 -32 0', '32 32 8'); - if(random() < 0.9 - self.health / self.max_health) - pointparticles(particleeffectnum("rage"), self.origin + 10 * randomvec(), '0 0 -1', 1); + // We'll consider only the best case + bestvalue = 99999999999; + cp = world; + for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext) + { + if (!cp1.wpconsidered) + continue; + + if(cp1.wpcost self.havocbot_role_timeout) + { + havocbot_ons_reset_role(self); return; - // we've verified that this player has a legitimate claim to this point, - // so start building the captured point icon (which only captures this - // point if it successfully builds without being destroyed first) - self.goalentity = e = spawn(); - e.classname = "onslaught_controlpoint_icon"; - e.owner = self; - e.max_health = autocvar_g_onslaught_cp_health; - e.health = autocvar_g_onslaught_cp_buildhealth; - e.solid = SOLID_BBOX; - e.movetype = MOVETYPE_NONE; - setmodel(e, "models/onslaught/controlpoint_icon.md3"); - setsize(e, '-32 -32 -32', '32 32 32'); - setorigin(e, self.origin + '0 0 96'); - e.takedamage = DAMAGE_AIM; - e.bot_attack = TRUE; - e.event_damage = onslaught_controlpoint_icon_damage; - e.team = other.team; - e.colormap = 1024 + (e.team - 1) * 17; - e.think = onslaught_controlpoint_icon_buildthink; - e.nextthink = time + sys_frametime; - e.count = (e.max_health - e.health) * sys_frametime / autocvar_g_onslaught_cp_buildtime; // how long it takes to build - sound(e, CH_TRIGGER, "onslaught/controlpoint_build.wav", VOL_BASE, ATTEN_NORM); - self.team = e.team; - self.colormap = e.colormap; - WaypointSprite_UpdateBuildFinished(self.sprite, time + (e.max_health - e.health) / (e.count / sys_frametime)); - onslaught_updatelinks(); + } + + if(self.havocbot_attack_time>time) + return; + + if (self.bot_strategytime < time) + { + navigation_goalrating_start(); + havocbot_goalrating_enemyplayers(20000, self.origin, 650); + if(!havocbot_goalrating_ons_generator_attack(20000)) + havocbot_goalrating_ons_controlpoints_attack(20000); + havocbot_goalrating_ons_offenseitems(10000, self.origin, 10000); + navigation_goalrating_end(); + + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + } } -void onslaught_controlpoint_think() +void havocbot_role_ons_assistant() { - self.nextthink = time; - CSQCMODEL_AUTOUPDATE(); + havocbot_ons_reset_role(self); } -void onslaught_controlpoint_reset() +void havocbot_role_ons_defense() { - if(self.goalentity && self.goalentity != world) - remove(self.goalentity); - self.goalentity = world; - self.team = 0; - self.colormap = 1024; - self.iscaptured = FALSE; - self.islinked = FALSE; - self.isshielded = TRUE; - self.enemy.solid = SOLID_NOT; - self.enemy.colormap = self.colormap; - self.think = onslaught_controlpoint_think; - self.enemy.think = func_null; - self.nextthink = time; // don't like func_null :P - setmodel(self, "models/onslaught/controlpoint_pad.md3"); - //setsize(self, '-32 -32 0', '32 32 8'); - - WaypointSprite_UpdateMaxHealth(self.sprite, 0); + havocbot_ons_reset_role(self); +} - onslaught_updatelinks(); +void havocbot_ons_reset_role(entity bot) +{ + entity head; + float c; - activator = self; - SUB_UseTargets(); // to reset the structures, playerspawns etc. - - CSQCMODEL_AUTOUPDATE(); -} + if(self.deadflag != DEAD_NO) + return; -/*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128) - Control point. Be sure to give this enough clearance so that the shootable part has room to exist + bot.havocbot_ons_target = world; - This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity. + // TODO: Defend control points or generator if necessary -keys: -"targetname" - name that spawnfunc_onslaught_link entities will use to target this. -"target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities. -"message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc) - */ + // if there is only me on the team switch to offense + c = 0; + FOR_EACH_PLAYER(head) + if(SAME_TEAM(head, self)) + ++c; -void spawnfunc_onslaught_controlpoint() -{ - //entity e; - if (!g_onslaught) + if(c==1) { - remove(self); + havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE); return; } - precache_model("models/onslaught/controlpoint_pad.md3"); - precache_model("models/onslaught/controlpoint_pad2.md3"); - precache_model("models/onslaught/controlpoint_shield.md3"); - precache_model("models/onslaught/controlpoint_icon.md3"); - precache_model("models/onslaught/controlpoint_icon_dmg1.md3"); - precache_model("models/onslaught/controlpoint_icon_dmg2.md3"); - precache_model("models/onslaught/controlpoint_icon_dmg3.md3"); - precache_model("models/onslaught/controlpoint_icon_gib1.md3"); - precache_model("models/onslaught/controlpoint_icon_gib2.md3"); - precache_model("models/onslaught/controlpoint_icon_gib4.md3"); - precache_sound("onslaught/controlpoint_build.wav"); - precache_sound("onslaught/controlpoint_built.wav"); - precache_sound("weapons/grenade_impact.wav"); - precache_sound("onslaught/damageblockedbyshield.wav"); - precache_sound("onslaught/controlpoint_underattack.wav"); - precache_sound("onslaught/ons_spark1.wav"); - precache_sound("onslaught/ons_spark2.wav"); - self.solid = SOLID_BBOX; - self.movetype = MOVETYPE_NONE; - setmodel(self, "models/onslaught/controlpoint_pad.md3"); - //setsize(self, '-32 -32 0', '32 32 8'); - if(!self.noalign) - { - setorigin(self, self.origin + '0 0 20'); - droptofloor(); - } - self.touch = onslaught_controlpoint_touch; - self.team = 0; - self.colormap = 1024; - self.iscaptured = FALSE; - self.islinked = FALSE; - self.isshielded = TRUE; + havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE); +} - // spawn shield model which indicates whether this can be damaged - self.enemy = spawn(); - self.enemy.classname = "onslaught_controlpoint_shield"; - self.enemy.solid = SOLID_NOT; - self.enemy.movetype = MOVETYPE_NONE; - self.enemy.effects = EF_ADDITIVE; - setmodel(self.enemy , "models/onslaught/controlpoint_shield.md3"); - setattachment(self.enemy , self, ""); - //setsize(e, '-32 -32 0', '32 32 128'); +/* + * Find control point or generator owned by the same team self which is nearest to pos + * if max_dist is positive, only control points within this range will be considered + */ +entity ons_Nearest_ControlPoint(vector pos, float max_dist) +{ + entity tmp_entity, closest_target = world; + tmp_entity = findchain(classname, "onslaught_controlpoint"); + while(tmp_entity) + { + if(SAME_TEAM(tmp_entity, self)) + if(tmp_entity.iscaptured) + if(max_dist <= 0 || vlen(tmp_entity.origin - pos) <= max_dist) + if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world) + closest_target = tmp_entity; + tmp_entity = tmp_entity.chain; + } + tmp_entity = findchain(classname, "onslaught_generator"); + while(tmp_entity) + { + if(SAME_TEAM(tmp_entity, self)) + if(max_dist <= 0 || vlen(tmp_entity.origin - pos) < max_dist) + if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world) + closest_target = tmp_entity; + tmp_entity = tmp_entity.chain; + } + + return closest_target; +} - //setorigin(e, self.origin); - self.enemy.colormap = self.colormap; +/* + * Find control point or generator owned by the same team self which is nearest to pos + * if max_dist is positive, only control points within this range will be considered + * This function only check distances on the XY plane, disregarding Z + */ +entity ons_Nearest_ControlPoint_2D(vector pos, float max_dist) +{ + entity tmp_entity, closest_target = world; + vector delta; + float smallest_distance = 0, distance; + + tmp_entity = findchain(classname, "onslaught_controlpoint"); + while(tmp_entity) + { + delta = tmp_entity.origin - pos; + delta_z = 0; + distance = vlen(delta); + + if(SAME_TEAM(tmp_entity, self)) + if(tmp_entity.iscaptured) + if(max_dist <= 0 || distance <= max_dist) + if(closest_target == world || distance <= smallest_distance ) + { + closest_target = tmp_entity; + smallest_distance = distance; + } + + tmp_entity = tmp_entity.chain; + } + tmp_entity = findchain(classname, "onslaught_generator"); + while(tmp_entity) + { + delta = tmp_entity.origin - pos; + delta_z = 0; + distance = vlen(delta); + + if(SAME_TEAM(tmp_entity, self)) + if(max_dist <= 0 || distance <= max_dist) + if(closest_target == world || distance <= smallest_distance ) + { + closest_target = tmp_entity; + smallest_distance = distance; + } + + tmp_entity = tmp_entity.chain; + } + + return closest_target; +} +/** + * find the number of control points and generators in the same team as self + */ +float ons_Count_SelfControlPoints() +{ + entity tmp_entity; + tmp_entity = findchain(classname, "onslaught_controlpoint"); + float n = 0; + while(tmp_entity) + { + if(SAME_TEAM(tmp_entity, self)) + if(tmp_entity.iscaptured) + n++; + tmp_entity = tmp_entity.chain; + } + tmp_entity = findchain(classname, "onslaught_generator"); + while(tmp_entity) + { + if(SAME_TEAM(tmp_entity, self)) + n++; + tmp_entity = tmp_entity.chain; + } + return n; +} - waypoint_spawnforitem(self); +/** + * Teleport player to a random position near tele_target + * if tele_effects is true, teleport sound+particles are created + * return FALSE on failure + */ +float ons_Teleport(entity player, entity tele_target, float range, float tele_effects) +{ + if ( !tele_target ) + return FALSE; - self.think = onslaught_controlpoint_think; - self.nextthink = time; + float i; + vector loc; + float theta; + for(i = 0; i < 16; ++i) + { + theta = random() * 2 * M_PI; + loc_y = sin(theta); + loc_x = cos(theta); + loc_z = 0; + loc *= random() * range; + + loc += tele_target.origin + '0 0 128'; + + tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, player); + if(trace_fraction == 1.0 && !trace_startsolid) + { + traceline(tele_target.origin, loc, MOVE_NOMONSTERS, tele_target); // double check to make sure we're not spawning outside the world + if(trace_fraction == 1.0 && !trace_startsolid) + { + if ( tele_effects ) + { + Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1); + sound (player, CH_TRIGGER, "misc/teleport.wav", VOL_BASE, ATTEN_NORM); + } + setorigin(player, loc); + player.angles = '0 1 0' * ( theta * RAD2DEG + 180 ); + makevectors(player.angles); + player.fixangle = TRUE; + player.teleport_antispam = time + autocvar_g_onslaught_teleport_wait; + + if ( tele_effects ) + Send_Effect(EFFECT_TELEPORT, player.origin + v_forward * 32, '0 0 0', 1); + return TRUE; + } + } + } + + return FALSE; +} - WaypointSprite_SpawnFixed(string_null, self.origin + '0 0 128', self, sprite, RADARICON_NONE, '0 0 0'); - WaypointSprite_UpdateRule(self.sprite, NUM_TEAM_2, SPRITERULE_TEAMPLAY); +// ============== +// Hook Functions +// ============== - onslaught_updatelinks(); +MUTATOR_HOOKFUNCTION(ons_ResetMap) +{ + FOR_EACH_PLAYER(self) + { + self.ons_roundlost = FALSE; + self.ons_deathloc = '0 0 0'; + PutClientInServer(); + } + return FALSE; +} - self.reset = onslaught_controlpoint_reset; - - CSQCMODEL_AUTOINIT(); +MUTATOR_HOOKFUNCTION(ons_RemovePlayer) +{ + self.ons_deathloc = '0 0 0'; + return FALSE; } -float onslaught_link_send(entity to, float sendflags) +MUTATOR_HOOKFUNCTION(ons_PlayerSpawn) { - WriteByte(MSG_ENTITY, ENT_CLIENT_RADARLINK); - WriteByte(MSG_ENTITY, sendflags); - if(sendflags & 1) + if(!round_handler_IsRoundStarted()) { - WriteCoord(MSG_ENTITY, self.goalentity.origin_x); - WriteCoord(MSG_ENTITY, self.goalentity.origin_y); - WriteCoord(MSG_ENTITY, self.goalentity.origin_z); + self.player_blocked = TRUE; + return FALSE; } - if(sendflags & 2) + + entity l; + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) { - WriteCoord(MSG_ENTITY, self.enemy.origin_x); - WriteCoord(MSG_ENTITY, self.enemy.origin_y); - WriteCoord(MSG_ENTITY, self.enemy.origin_z); + l.sprite.SendFlags |= 16; } - if(sendflags & 4) + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) { - WriteByte(MSG_ENTITY, self.clientcolors); // which is goalentity's color + enemy's color * 16 + l.sprite.SendFlags |= 16; } - return TRUE; -} -void onslaught_link_checkupdate() -{ - // TODO check if the two sides have moved (currently they won't move anyway) - float redpower, bluepower; + if(ons_stalemate) { Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); } - redpower = bluepower = 0; - if(self.goalentity.islinked) + if ( autocvar_g_onslaught_spawn_choose ) + if ( self.ons_spawn_by ) + if ( ons_Teleport(self,self.ons_spawn_by,autocvar_g_onslaught_teleport_radius,FALSE) ) { - if(self.goalentity.team == NUM_TEAM_1) - redpower = 1; - else if(self.goalentity.team == NUM_TEAM_2) - bluepower = 1; + self.ons_spawn_by = world; + return FALSE; } - if(self.enemy.islinked) + + if(autocvar_g_onslaught_spawn_at_controlpoints) + if(random() <= autocvar_g_onslaught_spawn_at_controlpoints_chance) { - if(self.enemy.team == NUM_TEAM_1) - redpower = 2; - else if(self.enemy.team == NUM_TEAM_2) - bluepower = 2; - } - - float cc; - if(redpower == 1 && bluepower == 2) - cc = (NUM_TEAM_1 - 1) * 0x01 + (NUM_TEAM_2 - 1) * 0x10; - else if(redpower == 2 && bluepower == 1) - cc = (NUM_TEAM_1 - 1) * 0x10 + (NUM_TEAM_2 - 1) * 0x01; - else if(redpower) - cc = (NUM_TEAM_1 - 1) * 0x11; - else if(bluepower) - cc = (NUM_TEAM_2 - 1) * 0x11; - else - cc = 0; + float random_target = autocvar_g_onslaught_spawn_at_controlpoints_random; + entity tmp_entity, closest_target = world; + vector spawn_loc = self.ons_deathloc; + + // new joining player or round reset, don't bother checking + if(spawn_loc == '0 0 0') { return FALSE; } - //print(etos(self), " rp=", ftos(redpower), " bp=", ftos(bluepower), " "); - //print("cc=", ftos(cc), "\n"); + if(random_target) { RandomSelection_Init(); } - if(cc != self.clientcolors) - { - self.clientcolors = cc; - self.SendFlags |= 4; + for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext) + { + if(SAME_TEAM(tmp_entity, self)) + if(random_target) + RandomSelection_Add(tmp_entity, 0, string_null, 1, 1); + else if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world) + closest_target = tmp_entity; + } + + if(random_target) { closest_target = RandomSelection_chosen_ent; } + + if(closest_target) + { + float i; + vector loc; + for(i = 0; i < 10; ++i) + { + loc = closest_target.origin + '0 0 96'; + loc += ('0 1 0' * random()) * 128; + tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self); + if(trace_fraction == 1.0 && !trace_startsolid) + { + traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world + if(trace_fraction == 1.0 && !trace_startsolid) + { + setorigin(self, loc); + self.angles = normalize(loc - closest_target.origin) * RAD2DEG; + return FALSE; + } + } + } + } + } + + if(autocvar_g_onslaught_spawn_at_generator) + if(random() <= autocvar_g_onslaught_spawn_at_generator_chance) + { + float random_target = autocvar_g_onslaught_spawn_at_generator_random; + entity tmp_entity, closest_target = world; + vector spawn_loc = self.ons_deathloc; + + // new joining player or round reset, don't bother checking + if(spawn_loc == '0 0 0') { return FALSE; } + + if(random_target) { RandomSelection_Init(); } + + for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) + { + if(random_target) + RandomSelection_Add(tmp_entity, 0, string_null, 1, 1); + else + { + if(SAME_TEAM(tmp_entity, self)) + if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world) + closest_target = tmp_entity; + } + } + + if(random_target) { closest_target = RandomSelection_chosen_ent; } + + if(closest_target) + { + float i; + vector loc; + for(i = 0; i < 10; ++i) + { + loc = closest_target.origin + '0 0 128'; + loc += ('0 1 0' * random()) * 256; + tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self); + if(trace_fraction == 1.0 && !trace_startsolid) + { + traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world + if(trace_fraction == 1.0 && !trace_startsolid) + { + setorigin(self, loc); + self.angles = normalize(loc - closest_target.origin) * RAD2DEG; + return FALSE; + } + } + } + } } - self.nextthink = time; + return FALSE; } -void onslaught_link_delayed() +MUTATOR_HOOKFUNCTION(ons_PlayerDies) { - self.goalentity = find(world, targetname, self.target); - self.enemy = find(world, targetname, self.target2); - if (!self.goalentity) - objerror("can not find target\n"); - if (!self.enemy) - objerror("can not find target2\n"); - dprint(etos(self.goalentity), " linked with ", etos(self.enemy), "\n"); - self.SendFlags |= 3; - self.think = onslaught_link_checkupdate; - self.nextthink = time; + frag_target.ons_deathloc = frag_target.origin; + entity l; + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) + { + l.sprite.SendFlags |= 16; + } + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) + { + l.sprite.SendFlags |= 16; + } + + if ( autocvar_g_onslaught_spawn_choose ) + if ( ons_Count_SelfControlPoints() > 1 ) + stuffcmd(self, "qc_cmd_cl hud clickradar\n"); + + return FALSE; } -/*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16) - Link between control points. +MUTATOR_HOOKFUNCTION(ons_MonsterThink) +{ + entity e = find(world, targetname, self.target); + if (e != world) + self.team = e.team; - This entity targets two different spawnfunc_onslaught_controlpoint or spawnfunc_onslaught_generator entities, and suppresses shielding on both if they are owned by different teams. + return FALSE; +} -keys: -"target" - first control point. -"target2" - second control point. - */ -void spawnfunc_onslaught_link() +void ons_MonsterSpawn_Delayed() { - if (!g_onslaught) + entity e, own = self.owner; + + if(!own) { remove(self); return; } + + if(own.targetname) { - remove(self); - return; + e = find(world, target, own.targetname); + if(e != world) + { + own.team = e.team; + + activator = e; + own.use(); + } } - if (self.target == "" || self.target2 == "") - objerror("target and target2 must be set\n"); - InitializeEntity(self, onslaught_link_delayed, INITPRIO_FINDTARGET); - Net_LinkEntity(self, FALSE, 0, onslaught_link_send); + + remove(self); } -MUTATOR_HOOKFUNCTION(ons_BuildMutatorsString) +MUTATOR_HOOKFUNCTION(ons_MonsterSpawn) { - ret_string = strcat(ret_string, ":ONS"); - return 0; + entity e = spawn(); + e.owner = self; + InitializeEntity(e, ons_MonsterSpawn_Delayed, INITPRIO_FINDTARGET); + + return FALSE; } -MUTATOR_HOOKFUNCTION(ons_BuildMutatorsPrettyString) +void ons_TurretSpawn_Delayed() { - ret_string = strcat(ret_string, ", Onslaught"); - return 0; + entity e, own = self.owner; + + if(!own) { remove(self); return; } + + if(own.targetname) + { + e = find(world, target, own.targetname); + if(e != world) + { + own.team = e.team; + own.active = ACTIVE_NOT; + + activator = e; + own.use(); + } + } + + remove(self); } -MUTATOR_HOOKFUNCTION(ons_Spawn_Score) +MUTATOR_HOOKFUNCTION(ons_TurretSpawn) { + entity e = spawn(); + e.owner = self; + InitializeEntity(e, ons_TurretSpawn_Delayed, INITPRIO_FINDTARGET); - /* - float _neer_home = (random() > 0.5 ? TRUE : FALSE); + return FALSE; +} - RandomSelection_Init(); +MUTATOR_HOOKFUNCTION(ons_BotRoles) +{ + havocbot_ons_reset_role(self); + return TRUE; +} - if(self.team == NUM_TEAM_1) - RandomSelection_Add(ons_red_generator, 0, string_null, 1, 1); +MUTATOR_HOOKFUNCTION(ons_GetTeamCount) +{ + // onslaught is special + entity tmp_entity; + for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) + { + switch(tmp_entity.team) + { + case NUM_TEAM_1: c1 = 0; break; + case NUM_TEAM_2: c2 = 0; break; + case NUM_TEAM_3: c3 = 0; break; + case NUM_TEAM_4: c4 = 0; break; + } + } - if(self.team == NUM_TEAM_2) - RandomSelection_Add(ons_blue_generator, 0, string_null, 1, 1); + return TRUE; +} - entity _cp = findchain(classname, "onslaught_controlpoint"): - while _cp; - { - if(_cp.team == self.team) - RandomSelection_Add(_cp, 0, string_null, 1, 1); +MUTATOR_HOOKFUNCTION(ons_SpectateCopy) +{ + self.ons_roundlost = other.ons_roundlost; // make spectators see it too + return FALSE; +} - _cp = _cp.chain; +MUTATOR_HOOKFUNCTION(ons_SV_ParseClientCommand) +{ + if(MUTATOR_RETURNVALUE) // command was already handled? + return FALSE; + + if ( cmd_name == "ons_spawn" ) + { + vector pos = self.origin; + if(cmd_argc > 1) + pos_x = stof(argv(1)); + if(cmd_argc > 2) + pos_y = stof(argv(2)); + if(cmd_argc > 3) + pos_z = stof(argv(3)); + + if ( IS_PLAYER(self) ) + { + if ( !self.frozen ) + { + entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius); + + if ( !source_point && self.health > 0 ) + { + sprint(self, "\nYou need to be next to a control point\n"); + return 1; + } + + + entity closest_target = ons_Nearest_ControlPoint_2D(pos, autocvar_g_onslaught_click_radius); + + if ( closest_target == world ) + { + sprint(self, "\nNo control point found\n"); + return 1; + } + + if ( self.health <= 0 ) + { + self.ons_spawn_by = closest_target; + self.respawn_flags = self.respawn_flags | RESPAWN_FORCE; + } + else + { + if ( source_point == closest_target ) + { + sprint(self, "\nTeleporting to the same point\n"); + return 1; + } + + if ( !ons_Teleport(self,closest_target,autocvar_g_onslaught_teleport_radius,TRUE) ) + sprint(self, "\nUnable to teleport there\n"); + } + + return 1; + } + + sprint(self, "\nNo teleportation for you\n"); + } + + return 1; } + return 0; +} - if(RandomSelection_chosen_ent) +MUTATOR_HOOKFUNCTION(ons_PlayerUseKey) +{ + if(MUTATOR_RETURNVALUE || gameover) { return FALSE; } + + if((time > self.teleport_antispam) && (self.deadflag == DEAD_NO) && !self.vehicle) { - self.tur_head = RandomSelection_chosen_ent; - spawn_score_x += SPAWN_PRIO_NEAR_TEAMMATE_FOUND; + entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius); + if ( source_point ) + { + stuffcmd(self, "qc_cmd_cl hud clickradar\n"); + return TRUE; + } } - else if(self.team == spawn_spot.team) - spawn_score_x += SPAWN_PRIO_NEAR_TEAMMATE_SAMETEAM; // prefer same team, if we can't find a spawn near teammate + + return FALSE; +} - */ +// ========== +// Spawnfuncs +// ========== - return 0; -} +/*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16) + Link between control points. -MUTATOR_HOOKFUNCTION(ons_PlayerSpawn) + This entity targets two different spawnfunc_onslaught_controlpoint or spawnfunc_onslaught_generator entities, and suppresses shielding on both if they are owned by different teams. + +keys: +"target" - first control point. +"target2" - second control point. + */ +void spawnfunc_onslaught_link() { - if(!autocvar_g_onslaught_spawn_at_controlpoints) - return 0; + if(!g_onslaught) { remove(self); return; } - if(random() < 0.5) // 50/50 chane to use default spawnsystem. - return 0; + if (self.target == "" || self.target2 == "") + objerror("target and target2 must be set\n"); - float _close_to_home = ((random() > 0.5) ? TRUE : FALSE); - entity _best = world, _trg_gen = world; - float _score, _best_score = MAX_SHOT_DISTANCE; + self.ons_worldlinknext = ons_worldlinklist; // link into ons_worldlinklist + ons_worldlinklist = self; - RandomSelection_Init(); + InitializeEntity(self, ons_DelayedLinkSetup, INITPRIO_FINDTARGET); + Net_LinkEntity(self, FALSE, 0, ons_Link_Send); +} - if(self.team == NUM_TEAM_1) - { - if(!_close_to_home) - _trg_gen = ons_blue_generator; - else - _trg_gen = ons_red_generator; - } +/*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128) + Control point. Be sure to give this enough clearance so that the shootable part has room to exist - if(self.team == NUM_TEAM_2) - { - if(_close_to_home) - _trg_gen = ons_blue_generator; - else - _trg_gen = ons_red_generator; - } + This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity. - entity _cp = findchain(classname, "onslaught_controlpoint"); - while(_cp) - { - if(_cp.team == self.team) - { - _score = vlen(_trg_gen.origin - _cp.origin); - if(_score < _best_score) - { - _best = _cp; - _best_score = _score; - } - } - _cp = _cp.chain; - } +keys: +"targetname" - name that spawnfunc_onslaught_link entities will use to target this. +"target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities. +"message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc) + */ - vector _loc; - float i; - if(_best) - { - for(i = 0; i < 10; ++i) - { - _loc = _best.origin + '0 0 96'; - _loc += ('0 1 0' * random()) * 128; - tracebox(_loc, PL_MIN, PL_MAX, _loc, MOVE_NORMAL, self); - if(trace_fraction == 1.0 && !trace_startsolid) - { - setorigin(self, _loc); - self.angles = normalize(_loc - _best.origin) * RAD2DEG; - return 0; - } - } - } - else - { - if(!autocvar_g_onslaught_spawn_at_generator) - return 0; +void spawnfunc_onslaught_controlpoint() +{ + if(!g_onslaught) { remove(self); return; } + + ons_ControlPoint_Setup(self); +} - _trg_gen = ((self.team == NUM_TEAM_1) ? ons_red_generator : ons_blue_generator); +/*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64) + Base generator. - for(i = 0; i < 10; ++i) - { - _loc = _trg_gen.origin + '0 0 96'; - _loc += ('0 1 0' * random()) * 128; - tracebox(_loc, PL_MIN, PL_MAX, _loc, MOVE_NORMAL, self); - if(trace_fraction == 1.0 && !trace_startsolid) - { - setorigin(self, _loc); - self.angles = normalize(_loc - _trg_gen.origin) * RAD2DEG; - return 0; - } - } - } + spawnfunc_onslaught_link entities can target this. - return 0; +keys: +"team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET. +"targetname" - name that spawnfunc_onslaught_link entities will use to target this. + */ +void spawnfunc_onslaught_generator() +{ + if(!g_onslaught) { remove(self); return; } + if(!self.team) { objerror("team must be set"); } + + ons_GeneratorSetup(self); } -MUTATOR_HOOKFUNCTION(ons_MonsterThink) + +// scoreboard setup +void ons_ScoreRules() { - entity e = find(world, targetname, self.target); - if (e != world) - self.team = e.team; + CheckAllowedTeams(world); + ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), SFL_SORT_PRIO_PRIMARY, 0, TRUE); + ScoreInfo_SetLabel_TeamScore (ST_ONS_CAPS, "destroyed", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_ONS_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); + ScoreInfo_SetLabel_PlayerScore(SP_ONS_TAKES, "takes", 0); + ScoreRules_basics_end(); +} - return FALSE; +void ons_DelayedInit() // Do this check with a delay so we can wait for teams to be set up +{ + ons_ScoreRules(); + + round_handler_Spawn(Onslaught_CheckPlayers, Onslaught_CheckWinner, Onslaught_RoundStart); + round_handler_Init(5, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit); } -MUTATOR_HOOKFUNCTION(ons_MonsterSpawn) +void ons_Initialize() { - entity e, ee = world; + precache_sound("ctf/red_capture.wav"); + precache_sound("ctf/blue_capture.wav"); + precache_sound("ctf/yellow_capture.wav"); + precache_sound("ctf/pink_capture.wav"); + + ons_captureshield_force = autocvar_g_onslaught_shield_force; - if(self.targetname) - { - e = find(world,target,self.targetname); - if(e != world) - { - self.team = e.team; - ee = e; - } - } + addstat(STAT_ROUNDLOST, AS_INT, ons_roundlost); - if(ee) - { - activator = ee; - self.use(); - } - - return FALSE; + InitializeEntity(world, ons_DelayedInit, INITPRIO_GAMETYPE); } MUTATOR_DEFINITION(gamemode_onslaught) { - MUTATOR_HOOK(BuildMutatorsPrettyString, ons_BuildMutatorsPrettyString, CBC_ORDER_ANY); - MUTATOR_HOOK(BuildMutatorsString, ons_BuildMutatorsString, CBC_ORDER_ANY); + MUTATOR_HOOK(reset_map_global, ons_ResetMap, CBC_ORDER_ANY); + MUTATOR_HOOK(MakePlayerObserver, ons_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(ClientDisconnect, ons_RemovePlayer, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerSpawn, ons_PlayerSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDies, ons_PlayerDies, CBC_ORDER_ANY); MUTATOR_HOOK(MonsterMove, ons_MonsterThink, CBC_ORDER_ANY); MUTATOR_HOOK(MonsterSpawn, ons_MonsterSpawn, CBC_ORDER_ANY); - //MUTATOR_HOOK(Spawn_Score, ons_Spawn_Score, CBC_ORDER_ANY); + MUTATOR_HOOK(TurretSpawn, ons_TurretSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(HavocBot_ChooseRole, ons_BotRoles, CBC_ORDER_ANY); + MUTATOR_HOOK(GetTeamCount, ons_GetTeamCount, CBC_ORDER_ANY); + MUTATOR_HOOK(SpectateCopy, ons_SpectateCopy, CBC_ORDER_ANY); + MUTATOR_HOOK(SV_ParseClientCommand, ons_SV_ParseClientCommand, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerUseKey, ons_PlayerUseKey, CBC_ORDER_ANY); MUTATOR_ONADD { if(time > 1) // game loads at time 1 error("This is a game type and it cannot be added at runtime."); + ons_Initialize(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back ons_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. } MUTATOR_ONREMOVE @@ -1710,5 +2184,5 @@ MUTATOR_DEFINITION(gamemode_onslaught) return -1; } - return 0; + return FALSE; } diff --git a/qcsrc/server/mutators/gamemode_onslaught.qh b/qcsrc/server/mutators/gamemode_onslaught.qh new file mode 100644 index 000000000..cb4aeb4cf --- /dev/null +++ b/qcsrc/server/mutators/gamemode_onslaught.qh @@ -0,0 +1,94 @@ +// these are needed since mutators are compiled last + +#ifdef SVQC + +.entity ons_toucher; // player who touched the control point + +// control point / generator constants +#define ONS_CP_THINKRATE 0.2 +#define GEN_THINKRATE 1 +#define CPGEN_SPAWN_OFFSET ('0 0 1' * (PL_MAX_z - 13)) +#define CPGEN_WAYPOINT_OFFSET ('0 0 128') +#define CPICON_OFFSET ('0 0 96') + +// list of generators on the map +entity ons_worldgeneratorlist; +.entity ons_worldgeneratornext; +.entity ons_stalegeneratornext; + +// list of control points on the map +entity ons_worldcplist; +.entity ons_worldcpnext; +.entity ons_stalecpnext; + +// list of links on the map +entity ons_worldlinklist; +.entity ons_worldlinknext; +.entity ons_stalelinknext; + +// definitions +.entity sprite; +.string target2; +.float iscaptured; +.float islinked; +.float isshielded; +.float lasthealth; +.float lastteam; +.float lastshielded; +.float lastcaptured; + +.float waslinked; + +float ons_stalemate; + +.float teleport_antispam; + +.float ons_roundlost; + +// waypoint sprites +.entity bot_basewaypoint; // generator waypointsprite +float wpforenemy_announced; + +.float isgenneighbor[17]; +.float iscpneighbor[17]; +float ons_notification_time[17]; + +.float ons_overtime_damagedelay; + +.vector ons_deathloc; + +.entity ons_spawn_by; + +// declarations for functions used outside gamemode_onslaught.qc +void ons_Generator_UpdateSprite(entity e); +void ons_ControlPoint_UpdateSprite(entity e); +float ons_ControlPoint_Attackable(entity cp, float teamnumber); + +// CaptureShield: Prevent capturing or destroying control point/generator if it is not available yet +float ons_captureshield_force; // push force of the shield + +// bot player logic +#define HAVOCBOT_ONS_ROLE_NONE 0 +#define HAVOCBOT_ONS_ROLE_DEFENSE 2 +#define HAVOCBOT_ONS_ROLE_ASSISTANT 4 +#define HAVOCBOT_ONS_ROLE_OFFENSE 8 + +.entity havocbot_ons_target; + +.float havocbot_role_flags; +.float havocbot_attack_time; + +void havocbot_role_ons_defense(); +void havocbot_role_ons_offense(); +void havocbot_role_ons_assistant(); + +void havocbot_ons_reset_role(entity bot); +void havocbot_goalrating_items(float ratingscale, vector org, float sradius); +void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradius); + +// score rule declarations +#define ST_ONS_CAPS 1 +#define SP_ONS_CAPS 4 +#define SP_ONS_TAKES 6 + +#endif diff --git a/qcsrc/server/mutators/gamemode_race.qc b/qcsrc/server/mutators/gamemode_race.qc index da5ca4c10..dacb24b80 100644 --- a/qcsrc/server/mutators/gamemode_race.qc +++ b/qcsrc/server/mutators/gamemode_race.qc @@ -266,6 +266,21 @@ MUTATOR_HOOKFUNCTION(race_GetTeamCount) return FALSE; } +MUTATOR_HOOKFUNCTION(race_CountFrags) +{ + // announce remaining frags if not in qualifying mode + if(!g_race_qualifying) + return TRUE; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(race_AllowMobSpawning) +{ + ret_string = "No monsters in a race!"; + return TRUE; +} + void race_Initialize() { race_ScoreRules(); @@ -287,6 +302,8 @@ MUTATOR_DEFINITION(gamemode_race) MUTATOR_HOOK(GetPressedKeys, race_PlayerPostThink, CBC_ORDER_ANY); MUTATOR_HOOK(ForbidPlayerScore_Clear, race_ForbidClearPlayerScore, CBC_ORDER_ANY); MUTATOR_HOOK(GetTeamCount, race_GetTeamCount, CBC_ORDER_ANY); + MUTATOR_HOOK(Scores_CountFragsRemaining, race_CountFrags, CBC_ORDER_ANY); + MUTATOR_HOOK(AllowMobSpawning, race_AllowMobSpawning, CBC_ORDER_ANY); MUTATOR_ONADD { diff --git a/qcsrc/server/mutators/gamemode_tdm.qc b/qcsrc/server/mutators/gamemode_tdm.qc index 59f0927e8..a4266fa48 100644 --- a/qcsrc/server/mutators/gamemode_tdm.qc +++ b/qcsrc/server/mutators/gamemode_tdm.qc @@ -51,9 +51,16 @@ MUTATOR_HOOKFUNCTION(tdm_GetTeamCount) return TRUE; } +MUTATOR_HOOKFUNCTION(tdm_CountFrags) +{ + // announce remaining frags + return TRUE; +} + MUTATOR_DEFINITION(gamemode_tdm) { MUTATOR_HOOK(GetTeamCount, tdm_GetTeamCount, CBC_ORDER_ANY); + MUTATOR_HOOK(Scores_CountFragsRemaining, tdm_CountFrags, CBC_ORDER_ANY); MUTATOR_ONADD { diff --git a/qcsrc/server/mutators/gamemode_vip.qc b/qcsrc/server/mutators/gamemode_vip.qc new file mode 100644 index 000000000..39b755712 --- /dev/null +++ b/qcsrc/server/mutators/gamemode_vip.qc @@ -0,0 +1,716 @@ +void VIP_count_players() +{ + total_players = red_players = blue_players = yellow_players = pink_players = 0; + entity head; + + for(head = world; (head = find(head, classname, "player")); ) + { + ++total_players; + red_players += (head.team == NUM_TEAM_1); + blue_players += (head.team == NUM_TEAM_2); + yellow_players += (head.team == NUM_TEAM_3); + pink_players += (head.team == NUM_TEAM_4); + } +} + +void vip_ClearVIPs() +{ + entity head; + float i; + + FOR_EACH_PLAYER(head) + { + if(head.wps_vip) + { + WaypointSprite_Ping(head.wps_vip); + WaypointSprite_Kill(head.wps_vip); + } + head.isvip = FALSE; + head.gem_dropped = FALSE; + } + + for(i = 0; i < VIP_TEAMS; ++i) + vip_count[i] = 0; +} + +#define VIP_TEAMS_COUNT() ((red_players > 0) + (blue_players > 0) + (yellow_players > 0) + (pink_players > 0)) +#define VIP_TEAMS_OK() (VIP_TEAMS_COUNT() == vip_teams) +#define VIP_COUNT() ((vip_count[NUM_TEAM_1] > 0) + (vip_count[NUM_TEAM_2] > 0) + (vip_count[NUM_TEAM_3] > 0) + (vip_count[NUM_TEAM_4] > 0)) + +float VIP_GetWinnerTeam() +{ + float winner_team = 0; + if(vip_count[NUM_TEAM_1] > 0) + winner_team = NUM_TEAM_1; + if(vip_count[NUM_TEAM_2] > 0) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_2; + } + if(vip_count[NUM_TEAM_3] > 0) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_3; + } + if(vip_count[NUM_TEAM_4] > 0) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_4; + } + if(winner_team) + return winner_team; + return -1; // no player left +} + +float VIP_CheckWinner() +{ + entity head; + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); + + nades_Clear(world, TRUE); + + FOR_EACH_PLAYER(head) if(head.isvip && head.health > 0) + PlayerScore_Add(head, SP_VIP_SURVIVALS, 1); + + vip_ClearVIPs(); + allowed_to_spawn = FALSE; + round_handler_Init(5, autocvar_g_vip_warmup, autocvar_g_vip_round_timelimit); + return 1; + } + + VIP_count_players(); + if(VIP_COUNT() > 1) + return 0; + + float winner_team = VIP_GetWinnerTeam(); + if(winner_team > 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_)); + TeamScore_AddToTeam(winner_team, ST_VIP_SCORE, +1); + + entity head; + FOR_EACH_PLAYER(head) + if(head.isvip && head.health > 0 && head.team == winner_team) + PlayerScore_Add(head, SP_VIP_SURVIVALS, 1); + + switch(winner_team) + { + case NUM_TEAM_1: play2all("ctf/red_capture.wav"); break; + case NUM_TEAM_2: play2all("ctf/blue_capture.wav"); break; + case NUM_TEAM_3: play2all("ctf/yellow_capture.wav"); break; + case NUM_TEAM_4: play2all("ctf/pink_capture.wav"); break; + } + } + else if(winner_team == -1) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED); + + entity head; + FOR_EACH_PLAYER(head) + if(head.isvip && head.health > 0) + PlayerScore_Add(head, SP_VIP_SURVIVALS, 1); + } + + vip_ClearVIPs(); + + allowed_to_spawn = FALSE; + + round_handler_Init(5, autocvar_g_vip_warmup, autocvar_g_vip_round_timelimit); + + nades_Clear(world, TRUE); + + return 1; +} + +void VIP_RoundStart() +{ + entity oldself = self; + FOR_EACH_PLAYER(self) + { + if(self.isvip) + Send_Notification(NOTIF_ONE, self, MSG_CENTER, APP_TEAM_ENT_4(self, CENTER_VIP_BEGIN_)); + PutClientInServer(); + } + self = oldself; +} + +void vip_SelectVIPs() +{ + float usegems = g_vip_soulgems; + entity head; + RandomSelection_Init(); + FOR_EACH_PLAYER(head) if(head.health > 0) + if((redgem_count < 1 || !usegems) && head.team == NUM_TEAM_1 && vip_count[NUM_TEAM_1] < 1) + RandomSelection_Add(head, 0, string_null, 1, ((IS_REAL_CLIENT(head) && !head.gem_dropped) ? 1 : 0.5)); + + vip_SetVIP(RandomSelection_chosen_ent, NUM_TEAM_1); + + RandomSelection_Init(); + FOR_EACH_PLAYER(head) if(head.health > 0) + if((bluegem_count < 1 || !usegems) && head.team == NUM_TEAM_2 && vip_count[NUM_TEAM_2] < 1) + RandomSelection_Add(head, 0, string_null, 1, ((IS_REAL_CLIENT(head) && !head.gem_dropped) ? 1 : 0.5)); + + vip_SetVIP(RandomSelection_chosen_ent, NUM_TEAM_2); + + RandomSelection_Init(); + FOR_EACH_PLAYER(head) if(head.health > 0) + if((yellowgem_count < 1 || !usegems) && head.team == NUM_TEAM_3 && vip_count[NUM_TEAM_3] < 1) + RandomSelection_Add(head, 0, string_null, 1, ((IS_REAL_CLIENT(head) && !head.gem_dropped) ? 1 : 0.5)); + + vip_SetVIP(RandomSelection_chosen_ent, NUM_TEAM_3); + + RandomSelection_Init(); + FOR_EACH_PLAYER(head) if(head.health > 0) + if((pinkgem_count < 1 || !usegems) && head.team == NUM_TEAM_4 && vip_count[NUM_TEAM_4] < 1) + RandomSelection_Add(head, 0, string_null, 1, ((IS_REAL_CLIENT(head) && !head.gem_dropped) ? 1 : 0.5)); + + vip_SetVIP(RandomSelection_chosen_ent, NUM_TEAM_4); +} + +float prev_missing_teams_mask; +float VIP_CheckTeams() +{ + float rc, bc, yc, pc; + rc = ((redgem_count) ? redgem_count : 1); + bc = ((bluegem_count) ? bluegem_count : 1); + yc = ((yellowgem_count) ? yellowgem_count : 1); + pc = ((pinkgem_count) ? pinkgem_count : 1); + allowed_to_spawn = TRUE; + VIP_count_players(); + if(vip_count[NUM_TEAM_1] < rc || vip_count[NUM_TEAM_2] < bc || (vip_teams >= 3 && vip_count[NUM_TEAM_3] < yc) || (vip_teams >= 4 && vip_count[NUM_TEAM_4] < pc)) + { + entity head; + if(!g_vip_soulgems || redgem_count < 1 || bluegem_count < 1 || (vip_teams >= 3 && yellowgem_count < 1) || (vip_teams >= 4 && pinkgem_count < 1)) + vip_SelectVIPs(); + + FOR_EACH_PLAYER(head) + { + if((head.team == NUM_TEAM_1 && vip_count[NUM_TEAM_1] > 0) || (head.team == NUM_TEAM_2 && vip_count[NUM_TEAM_2] > 0) || (vip_teams >= 3 && head.team == NUM_TEAM_3 && vip_count[NUM_TEAM_3] > 0) || (vip_teams >= 4 && head.team == NUM_TEAM_4 && vip_count[NUM_TEAM_4] > 0)) + Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_VIP_MISSING_ENEMY); + else + Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_VIP_MISSING); + } + return 0; + } + if(VIP_TEAMS_OK()) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 1; + } + if(total_players == 0) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 0; + } + float missing_teams_mask = (!redalive) + (!bluealive) * 2; + if(vip_teams >= 3) missing_teams_mask += (!yellowalive) * 4; + if(vip_teams >= 4) missing_teams_mask += (!pinkalive) * 8; + if(prev_missing_teams_mask != missing_teams_mask) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); + prev_missing_teams_mask = missing_teams_mask; + } + return 0; +} + +void vip_DropSoulGem(entity player) +{ + if(!player.isvip) + return; + + if(player.wps_vip) + { + WaypointSprite_Ping(player.wps_vip); + WaypointSprite_Kill(player.wps_vip); + } + + player.health = 100; // reset these now? + player.armorvalue = 0; + player.isvip = FALSE; + player.gem_dropped = TRUE; + vip_count[player.team] -= 1; + + if(IS_REAL_CLIENT(player)) + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_VIP_DROP_SELF); + + Send_Notification(NOTIF_ALL_EXCEPT, player, MSG_CENTER, CENTER_VIP_DROP, player.netname); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_VIP_DROP, player.netname); + + play2all("kh/destroy.wav"); +} + +MUTATOR_HOOKFUNCTION(vip_PlayerDies) +{ + if(frag_target.isvip) + { + if(IS_PLAYER(frag_attacker)) + { + PlayerScore_Add(frag_attacker, SP_VIP_VIPKILLS, 1); + PlayerScore_Add(frag_attacker, SP_SCORE, 1); + } + + vip_count[frag_target.team] -= 1; + frag_target.isvip = FALSE; + + play2all("kh/destroy.wav"); + + WaypointSprite_Ping(frag_target.wps_vip); + WaypointSprite_Kill(frag_target.wps_vip); + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(vip_RemovePlayer) +{ + if(self.isvip) + { + vip_count[self.team] -= 1; + self.isvip = FALSE; + WaypointSprite_Kill(self.wps_vip); + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(vip_PlayerThink) +{ + if(self.wps_vip) + WaypointSprite_UpdateHealth(self.wps_vip, ((g_instagib) ? self.armorvalue : '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON))); + + self.stat_vip_red = vip_count[NUM_TEAM_1]; + self.stat_vip_blue = vip_count[NUM_TEAM_2]; + self.stat_vip_yellow = vip_count[NUM_TEAM_3]; + self.stat_vip_pink = vip_count[NUM_TEAM_4]; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(vip_ResetMap) +{ + FOR_EACH_PLAYER(self) + PutClientInServer(); + + return TRUE; +} + +MUTATOR_HOOKFUNCTION(vip_PlayerSpawn) +{ + if(self.isvip) + { + if(!g_instagib) + self.health = 200; + self.armorvalue = ((g_instagib) ? 5 : 200); // 5 lives in instagib + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(vip_PlayerRegen) +{ + // VIP regens to 200 + if(self.isvip) + { + regen_mod_max *= 2; + regen_mod_regen = 0.7; // but with slower regen speed + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(vip_PlayerUseKey) +{ + if(MUTATOR_RETURNVALUE || gameover || round_handler_IsRoundStarted()) { return FALSE; } + + entity player = self; + + if((time > player.pickup_antispam) && (player.deadflag == DEAD_NO) && !player.vehicle) //&& (!player.vehicle || autocvar_g_vip_allow_vehicle_touch)) vehicle support coming soon(?) + { + if(autocvar_g_vip_drop && player.isvip) + { + if(player.drop_count == -1) + { + if(time > player.drop_prevtime + autocvar_g_vip_drop_punish_delay) + { + player.drop_prevtime = time; + player.drop_count = 1; + vip_DropSoulGem(player); + return TRUE; + } + else + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_VIP_DROP_PUNISH, rint((player.drop_prevtime + autocvar_g_vip_drop_punish_delay) - time)); + return FALSE; + } + } + else + { + if(time > player.drop_prevtime + autocvar_g_vip_drop_punish_time) { player.drop_count = 1; } + else { player.drop_count += 1; } + if(player.drop_count >= autocvar_g_vip_drop_punish_count) { player.drop_count = -1; } + + player.drop_prevtime = time; + vip_DropSoulGem(player); + return TRUE; + } + } + } + + return TRUE; +} + +MUTATOR_HOOKFUNCTION(vip_OnEntityPreSpawn) +{ + if(!g_vip_soulgems) + return FALSE; + + if(self.classname == "item_flag_team1") + { + vip_SpawnSoulGem(self.origin, NUM_TEAM_1); + return TRUE; + } + + if(self.classname == "item_flag_team2") + { + vip_SpawnSoulGem(self.origin, NUM_TEAM_2); + return TRUE; + } + + if(self.classname == "item_flag_team3") + { + vip_SpawnSoulGem(self.origin, NUM_TEAM_3); + return TRUE; + } + + if(self.classname == "item_flag_team4") + { + vip_SpawnSoulGem(self.origin, NUM_TEAM_4); + return TRUE; + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(vip_GetTeamCount) +{ + ret_float = vip_teams; + return FALSE; +} + +vector vip_SoulGemColormod(float theteam, float iseffect) +{ + if(iseffect) + { + switch(theteam) + { + case NUM_TEAM_1: return '1 0.4 0.4'; + case NUM_TEAM_2: return '0.4 0.4 1'; + case NUM_TEAM_3: return '1 1 0.4'; + case NUM_TEAM_4: return '1 0.4 1'; + } + } + else + { + switch(theteam) + { + case NUM_TEAM_1: return '1 0.8 0.8'; + case NUM_TEAM_2: return '0.8 0.8 1'; + case NUM_TEAM_3: return '1 1 0.8'; + case NUM_TEAM_4: return '1 0.8 1'; + } + } + + return (iseffect) ? '0.2 0.2 0.2' : '0.1 0.1 0.1'; +} + +void vip_VIPWaypoints(entity player) +{ + WaypointSprite_Spawn("vip", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, 0, player, wps_vip, TRUE, RADARICON_FLAG, Team_ColorRGB(player.team)); + WaypointSprite_UpdateMaxHealth(player.wps_vip, ((g_instagib) ? 5 : '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2)); + WaypointSprite_UpdateHealth(player.wps_vip, ((g_instagib) ? self.armorvalue : '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON))); + WaypointSprite_UpdateTeamRadar(player.wps_vip, RADARICON_FLAGCARRIER, Team_ColorRGB(player.team)); +} + +void vip_SetVIP(entity player, float tm) +{ + if(!IS_PLAYER(player) || player == world || player.isvip) + return; // TODO: check how this can be called at all for non players + player.isvip = TRUE; + if(!g_instagib) + player.health = 200; + player.armorvalue = ((g_instagib) ? 5 : 200); + player.pickup_antispam = time + autocvar_g_vip_pickup_wait; + + vip_count[tm] += 1; + + vip_VIPWaypoints(player); + + if(IS_REAL_CLIENT(player)) + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_VIP_PICKUP_SELF); + + Send_Notification(NOTIF_ALL_EXCEPT, player, MSG_CENTER, CENTER_VIP_PICKUP, player.netname); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_VIP_PICKUP, player.netname); +} + +void vip_SoulGem_touch() +{ + if(!IS_PLAYER(other)) + return; + + if(self.gem_pickedup) + return; + + if(other.isvip) + return; + + if(other.frozen) + return; + + if(!allowed_to_spawn) + return; + + if(other.deadflag != DEAD_NO) + return; + + if(DIFF_TEAM(other, self)) + return; + + if(round_handler_IsRoundStarted()) + return; + + self.gem_pickedup = 1; + self.owner = other; + self.alpha = 0; + self.enemy.alpha = 0; + self.colormod = vip_SoulGemColormod(-self.team, FALSE); + self.enemy.colormod = vip_SoulGemColormod(-self.team, TRUE); + + WaypointSprite_Kill(self.waypointsprite_attached); + + play2all("kh/capture.wav"); + + vip_SetVIP(other, other.team); +} + +void vip_SoulGem_think() +{ + self.glow_size = 256 + cos(time * 2) * 64; + self.nextthink = time + 0.1; + + if(vip_count[self.team] < 1 && self.gem_pickedup && allowed_to_spawn && self.owner && !self.owner.isvip && !round_handler_IsRoundStarted()) + self.reset(); +} + +void vip_SoulGem_reset() +{ + self.gem_pickedup = 0; + self.alpha = self.enemy.alpha = 0.8; + self.colormod = vip_SoulGemColormod(self.team, FALSE); + self.enemy.colormod = vip_SoulGemColormod(self.team, TRUE); + self.owner = world; + + // "run here" waypoint + if(!self.waypointsprite_attached) + WaypointSprite_Spawn("keycarrier-finish", 0, 0, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, waypointsprite_attached, TRUE, RADARICON_FLAG, '0 1 1'); +} + +void vip_DelayedGemSetup() // called after a gem is placed on a map +{ + // waypointsprites + WaypointSprite_Spawn("keycarrier-finish", 0, 0, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, waypointsprite_attached, TRUE, RADARICON_FLAG, '0 1 1'); +} + +void vip_SpawnSoulGem(vector orig, float theteam) +{ + entity e = spawn(); + e.classname = "vip_soulgem"; + e.target = "###item###"; + e.team = theteam; + e.movetype = MOVETYPE_NONE; + e.touch = vip_SoulGem_touch; + e.think = vip_SoulGem_think; + e.nextthink = time + random(); + e.solid = SOLID_TRIGGER; + e.flags = FL_ITEM; + e.reset = vip_SoulGem_reset; + setmodel(e, "models/runematch/rune.mdl"); + setsize(e, '0 0 -35', '0 0 0'); + + e.glow_size = 256; + e.glow_color = (theteam == NUM_TEAM_1) ? 251 : 250; + e.glow_trail = 1; + + e.enemy = spawn(); + e.enemy.enemy = e; + e.enemy.classname = "vip_soulgem_effect"; + setmodel(e.enemy, "models/runematch/curse.mdl"); + e.origin = orig; + e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP; + setorigin (e, e.origin); + setattachment(e.enemy, e, ""); + + move_out_of_solid(e); + + entity oldself; + oldself = self; + self = e; + droptofloor(); + self = oldself; + + e.colormod = vip_SoulGemColormod(theteam, FALSE); + e.enemy.colormod = vip_SoulGemColormod(theteam, TRUE); + + e.alpha = e.enemy.alpha = 0.8; + e.effects = e.enemy.effects = (EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION); + + // update gem counts + switch(theteam) + { + case NUM_TEAM_1: redgem_count += 1; break; + case NUM_TEAM_2: bluegem_count += 1; break; + case NUM_TEAM_3: yellowgem_count += 1; break; + case NUM_TEAM_4: pinkgem_count += 1; break; + } + + InitializeEntity(e, vip_DelayedGemSetup, INITPRIO_SETLOCATION); +} + +// for maps without flags +void spawnfunc_item_soulgem_team1() +{ + if(!g_vip || !g_vip_soulgems) { remove(self); return; } + + vip_SpawnSoulGem(self.origin, NUM_TEAM_1); + + self.think = SUB_Remove; + self.nextthink = time + 0.1; +} + +void spawnfunc_item_soulgem_team2() +{ + if(!g_vip || !g_vip_soulgems) { remove(self); return; } + + vip_SpawnSoulGem(self.origin, NUM_TEAM_2); + + self.think = SUB_Remove; + self.nextthink = time + 0.1; +} + +void spawnfunc_item_soulgem_team3() +{ + if(!g_vip || !g_vip_soulgems) { remove(self); return; } + + vip_SpawnSoulGem(self.origin, NUM_TEAM_3); + + self.think = SUB_Remove; + self.nextthink = time + 0.1; +} + +void spawnfunc_item_soulgem_team4() +{ + if(!g_vip || !g_vip_soulgems) { remove(self); return; } + + vip_SpawnSoulGem(self.origin, NUM_TEAM_4); + + self.think = SUB_Remove; + self.nextthink = time + 0.1; +} + +// scoreboard setup +void vip_ScoreRules() +{ + CheckAllowedTeams(world); + ScoreRules_basics(vip_teams, SFL_SORT_PRIO_PRIMARY, 0, TRUE); + ScoreInfo_SetLabel_PlayerScore(SP_VIP_SURVIVALS, "survivals", 0); + ScoreInfo_SetLabel_PlayerScore(SP_VIP_VIPKILLS, "vipkills", SFL_SORT_PRIO_SECONDARY); + ScoreInfo_SetLabel_TeamScore( ST_VIP_SCORE, "scores", SFL_SORT_PRIO_PRIMARY); + ScoreRules_basics_end(); +} + +void vip_DelayedInit() // Do this check with a delay so we can wait for teams to be set up. +{ + vip_teams = 2; + + if(yellowgem_count > 0) vip_teams = max(3, vip_teams); + if(pinkgem_count > 0) vip_teams = max(4, vip_teams); + + if(autocvar_g_vip_teams >= 2) + vip_teams = autocvar_g_vip_teams; + + vip_teams = bound(2, vip_teams, 4); + + vip_ScoreRules(); +} + +void vip_Initialize() +{ + allowed_to_spawn = TRUE; + + g_vip_soulgems = cvar("g_vip_soulgems"); + + round_handler_Spawn(VIP_CheckTeams, VIP_CheckWinner, VIP_RoundStart); + round_handler_Init(5, autocvar_g_vip_warmup, autocvar_g_vip_round_timelimit); + + precache_sound("kh/capture.wav"); + precache_sound("kh/destroy.wav"); + precache_sound("ctf/red_capture.wav"); + precache_sound("ctf/blue_capture.wav"); + precache_sound("ctf/yellow_capture.wav"); + precache_sound("ctf/pink_capture.wav"); + + precache_model("models/runematch/rune.mdl"); + precache_model("models/runematch/curse.mdl"); + + addstat(STAT_VIP, AS_INT, isvip); + addstat(STAT_VIP_RED, AS_FLOAT, stat_vip_red); + addstat(STAT_VIP_BLUE, AS_FLOAT, stat_vip_blue); + addstat(STAT_VIP_YELLOW, AS_FLOAT, stat_vip_yellow); + addstat(STAT_VIP_PINK, AS_FLOAT, stat_vip_pink); + + InitializeEntity(world, vip_DelayedInit, INITPRIO_GAMETYPE); +} + +MUTATOR_DEFINITION(gamemode_vip) +{ + MUTATOR_HOOK(PlayerDies, vip_PlayerDies, CBC_ORDER_ANY); + MUTATOR_HOOK(ClientDisconnect, vip_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(MakePlayerObserver, vip_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerPreThink, vip_PlayerThink, CBC_ORDER_ANY); + MUTATOR_HOOK(reset_map_players, vip_ResetMap, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerSpawn, vip_PlayerSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerRegen, vip_PlayerRegen, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerUseKey, vip_PlayerUseKey, CBC_ORDER_ANY); + MUTATOR_HOOK(OnEntityPreSpawn, vip_OnEntityPreSpawn, CBC_ORDER_FIRST); + MUTATOR_HOOK(GetTeamCount, vip_GetTeamCount, CBC_ORDER_ANY); + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + vip_Initialize(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back vip_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + print("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} diff --git a/qcsrc/server/mutators/gamemode_vip.qh b/qcsrc/server/mutators/gamemode_vip.qh new file mode 100644 index 000000000..33e9483ff --- /dev/null +++ b/qcsrc/server/mutators/gamemode_vip.qh @@ -0,0 +1,34 @@ +void vip_SpawnSoulGem(vector, float); +void vip_SetVIP(entity player, float tm); + +float g_vip_soulgems; + +.float gem_pickedup; +.float gem_dropped; + +float allowed_to_spawn; +float red_players, blue_players, yellow_players, pink_players; +float prev_total_players, total_players; +.float isvip; +.entity wps_vip; +float redgem_count, bluegem_count, yellowgem_count, pinkgem_count; + +.float drop_count; +.float drop_prevtime; +.float pickup_antispam; + +const float VIP_TEAMS = 17; +float vip_count[VIP_TEAMS]; + +// score rule declarations +#define SP_VIP_VIPKILLS 4 +#define SP_VIP_SURVIVALS 5 + +#define ST_VIP_SCORE 1 + +.float stat_vip_red; +.float stat_vip_blue; +.float stat_vip_yellow; +.float stat_vip_pink; + +float vip_teams; diff --git a/qcsrc/server/mutators/mutator_bloodloss.qc b/qcsrc/server/mutators/mutator_bloodloss.qc index 4d990b3cf..fb411ee33 100644 --- a/qcsrc/server/mutators/mutator_bloodloss.qc +++ b/qcsrc/server/mutators/mutator_bloodloss.qc @@ -9,6 +9,8 @@ MUTATOR_HOOKFUNCTION(bloodloss_PlayerThink) if(time >= self.bloodloss_timer) { + if(self.vehicle) + vehicles_exit(VHEF_RELEASE); self.event_damage(self, self, 1, DEATH_ROT, self.origin, '0 0 0'); self.bloodloss_timer = time + 0.5 + random() * 0.5; } diff --git a/qcsrc/server/mutators/mutator_buffs.qc b/qcsrc/server/mutators/mutator_buffs.qc index 765619df7..60bd1d1a0 100644 --- a/qcsrc/server/mutators/mutator_buffs.qc +++ b/qcsrc/server/mutators/mutator_buffs.qc @@ -10,6 +10,10 @@ float buffs_BuffModel_Customize() if(myowner.alpha <= 0.5 && !same_team && myowner.alpha != 0) return FALSE; + if(cvar("g_piggyback")) + if(player.pbhost == myowner || myowner.piggybacker) + return FALSE; // don't show to piggybacker's carrier, and don't show if carrier is carrying someone else + if(player == myowner || (IS_SPEC(other) && other.enemy == myowner)) { // somewhat hide the model, but keep the glow @@ -24,6 +28,12 @@ float buffs_BuffModel_Customize() return TRUE; } +vector buff_GlowColor(entity buff) +{ + //if(buff.team) { return Team_ColorRGB(buff.team); } + return buff.color; +} + // buff item float buff_Waypoint_visible_for_player(entity plr) { @@ -88,8 +98,8 @@ void buff_Respawn(entity ent) if(autocvar_g_buffs_random_lifetime > 0) ent.lifetime = time + autocvar_g_buffs_random_lifetime; - pointparticles(particleeffectnum("electro_combo"), oldbufforigin + ((ent.mins + ent.maxs) * 0.5), '0 0 0', 1); - pointparticles(particleeffectnum("electro_combo"), CENTER_OR_VIEWOFS(ent), '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_COMBO, oldbufforigin + ((ent.mins + ent.maxs) * 0.5), '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_COMBO, CENTER_OR_VIEWOFS(ent), '0 0 0', 1); WaypointSprite_Ping(ent.buff_waypoint); @@ -107,7 +117,7 @@ void buff_Touch() } if((self.team && DIFF_TEAM(other, self)) - || (other.frozen) + || (Player_Trapped(other)) || (other.vehicle) || (!IS_PLAYER(other)) || (!self.buff_active) @@ -117,6 +127,22 @@ void buff_Touch() return; } + if(cvar("g_piggyback")) + if(other.buffs) + if(other.buffs == self.buffs || !other.cvar_cl_buffs_autoreplace) + { + entity p = other; + while(p.piggybacker) + { + if(!p.buffs || (!p.piggybacker && (p.cvar_cl_buffs_autoreplace || p.buffs != self.buffs))) + { + other = p; + break; + } + p = p.piggybacker; + } + } + if(other.buffs) { if(other.cvar_cl_buffs_autoreplace && other.buffs != self.buffs) @@ -137,7 +163,7 @@ void buff_Touch() Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_GOT, self.buffs); Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_INFO, INFO_ITEM_BUFF, other.netname, self.buffs); - pointparticles(particleeffectnum("item_pickup"), CENTER_OR_VIEWOFS(self), '0 0 0', 1); + Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(self), '0 0 0', 1); sound(other, CH_TRIGGER, "misc/shield_respawn.wav", VOL_BASE, ATTN_NORM); other.buffs |= (self.buffs); } @@ -174,7 +200,7 @@ void buff_Think() if(self.buffs != self.oldbuffs) { self.color = Buff_Color(self.buffs); - self.glowmod = ((self.team) ? Team_ColorRGB(self.team) + '0.1 0.1 0.1' : self.color); + self.glowmod = buff_GlowColor(self); self.skin = Buff_Skin(self.buffs); setmodel(self, "models/relics/relic.md3"); @@ -200,7 +226,7 @@ void buff_Think() } if(!self.buff_active && !self.buff_activetime) - if(!self.owner || self.owner.frozen || self.owner.deadflag != DEAD_NO || !self.owner.iscreature || !(self.owner.buffs & self.buffs)) + if(!self.owner || Player_Trapped(self.owner) || !self.owner.iscreature || !(self.owner.buffs & self.buffs)) { buff_SetCooldown(autocvar_g_buffs_cooldown_respawn + frametime); self.owner = world; @@ -221,23 +247,12 @@ void buff_Think() { self.buff_active = TRUE; sound(self, CH_TRIGGER, "misc/strength_respawn.wav", VOL_BASE, ATTN_NORM); - pointparticles(particleeffectnum("item_respawn"), CENTER_OR_VIEWOFS(self), '0 0 0', 1); + Send_Effect(EFFECT_ITEM_RESPAWN, CENTER_OR_VIEWOFS(self), '0 0 0', 1); } } - if(!self.buff_active) + if(self.buff_active) { - self.alpha = 0.3; - self.effects &= ~(EF_FULLBRIGHT); - self.pflags = 0; - } - else - { - self.alpha = 1; - self.effects |= EF_FULLBRIGHT; - self.light_lev = 220 + 36 * sin(time); - self.pflags = PFLAGS_FULLDYNAMIC; - if(self.team && !self.buff_waypoint) buff_Waypoint_Spawn(self); @@ -270,6 +285,25 @@ void buff_Reset() buff_Respawn(self); } +float buff_Customize() +{ + entity player = WaypointSprite_getviewentity(other); + if(!self.buff_active || (self.team && DIFF_TEAM(player, self))) + { + self.alpha = 0.3; + if(self.effects & EF_FULLBRIGHT) { self.effects &= ~(EF_FULLBRIGHT); } + self.pflags = 0; + } + else + { + self.alpha = 1; + if(!(self.effects & EF_FULLBRIGHT)) { self.effects |= EF_FULLBRIGHT; } + self.light_lev = 220 + 36 * sin(time); + self.pflags = PFLAGS_FULLDYNAMIC; + } + return TRUE; +} + void buff_Init(entity ent) { if(!cvar("g_buffs")) { remove(self); return; } @@ -294,9 +328,10 @@ void buff_Init(entity ent) self.skin = Buff_Skin(self.buffs); self.effects = EF_FULLBRIGHT | EF_STARDUST | EF_NOSHADOW; self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY; + self.customizeentityforclient = buff_Customize; //self.gravity = 100; self.color = Buff_Color(self.buffs); - self.glowmod = ((self.team) ? Team_ColorRGB(self.team) + '0.1 0.1 0.1' : self.color); + self.glowmod = buff_GlowColor(self); buff_SetCooldown(autocvar_g_buffs_cooldown_activate + game_starttime); self.buff_active = !self.buff_activetime; self.pflags = PFLAGS_FULLDYNAMIC; @@ -337,11 +372,15 @@ void buff_SpawnReplacement(entity ent, entity old) // mutator hooks MUTATOR_HOOKFUNCTION(buffs_PlayerDamage_SplitHealthArmor) { - if(frag_deathtype == DEATH_BUFF_VENGEANCE) { return FALSE; } // oh no you don't + if(frag_deathtype == DEATH_BUFF) { return FALSE; } if(frag_target.buffs & BUFF_RESISTANCE) { - vector v = healtharmor_applydamage(50, autocvar_g_buffs_resistance_blockpercent, frag_deathtype, frag_damage); + float blockpercent = autocvar_g_buffs_resistance_blockpercent; +#ifdef CHAOS + if(DEATH_ISWEAPON(frag_deathtype, WEP_LIGHTSABRE) && autocvar_g_buffs_resistance_blockpercent > 0) { blockpercent *= 0.3; } +#endif + vector v = healtharmor_applydamage(50, blockpercent, frag_deathtype, frag_damage, autocvar_g_balance_armor_block_bycount); damage_take = v_x; damage_save = v_y; } @@ -352,7 +391,7 @@ MUTATOR_HOOKFUNCTION(buffs_PlayerDamage_SplitHealthArmor) void buff_Vengeance_DelayedDamage() { if(self.enemy) - Damage(self.enemy, self.owner, self.owner, self.dmg, DEATH_BUFF_VENGEANCE, self.enemy.origin, '0 0 0'); + Damage(self.enemy, self.owner, self.owner, self.dmg, DEATH_BUFF, self.enemy.origin, '0 0 0'); remove(self); return; @@ -360,7 +399,7 @@ void buff_Vengeance_DelayedDamage() MUTATOR_HOOKFUNCTION(buffs_PlayerDamage_Calculate) { - if(frag_deathtype == DEATH_BUFF_VENGEANCE) { return FALSE; } // oh no you don't + if(frag_deathtype == DEATH_BUFF) { return FALSE; } if(frag_target.buffs & BUFF_SPEED) if(frag_target != frag_attacker) @@ -405,6 +444,7 @@ MUTATOR_HOOKFUNCTION(buffs_PlayerDamage_Calculate) frag_target.buff_disability_time = time + autocvar_g_buffs_disability_time; if(frag_attacker.buffs & BUFF_MEDIC) + if(DEATH_WEAPONOF(frag_deathtype) != WEP_ARC) if(SAME_TEAM(frag_attacker, frag_target)) if(frag_attacker != frag_target) { @@ -415,12 +455,12 @@ MUTATOR_HOOKFUNCTION(buffs_PlayerDamage_Calculate) // this... is ridiculous (TODO: fix!) if(frag_attacker.buffs & BUFF_VAMPIRE) if(!frag_target.vehicle) + if(DEATH_WEAPONOF(frag_deathtype) != WEP_ARC) if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) - if(frag_target.deadflag == DEAD_NO) - if(IS_PLAYER(frag_target) || (frag_target.flags & FL_MONSTER)) + if(IS_PLAYER(frag_target) || IS_MONSTER(frag_target)) if(frag_attacker != frag_target) - if(!frag_target.frozen) if(frag_target.takedamage) + if(!Player_Trapped(frag_target)) if(DIFF_TEAM(frag_attacker, frag_target)) frag_attacker.health = bound(0, frag_attacker.health + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.health), g_pickup_healthsmall_max); @@ -450,6 +490,12 @@ MUTATOR_HOOKFUNCTION(buffs_PlayerPhysics) self.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_disability_speed; } + if(self.buffs & BUFF_JUMP) + { + // automatically reset, no need to worry + self.stat_sv_jumpvelocity = autocvar_g_buffs_jump_height; + } + return FALSE; } @@ -457,7 +503,6 @@ MUTATOR_HOOKFUNCTION(buffs_PlayerJump) { if(self.buffs & BUFF_JUMP) player_jumpheight = autocvar_g_buffs_jump_height; - self.stat_jumpheight = player_jumpheight; return FALSE; } @@ -542,6 +587,7 @@ MUTATOR_HOOKFUNCTION(buffs_OnEntityPreSpawn) { entity e = spawn(); buff_SpawnReplacement(e, self); + self.classname = "item_removing"; return TRUE; } } @@ -566,11 +612,11 @@ MUTATOR_HOOKFUNCTION(buffs_PlayerThink) if(time < self.buff_disability_time) if(time >= self.buff_disability_effect_time) { - pointparticles(particleeffectnum("smoking"), self.origin + ((self.mins + self.maxs) * 0.5), '0 0 0', 1); + Send_Effect(EFFECT_SMOKING, self.origin + ((self.mins + self.maxs) * 0.5), '0 0 0', 1); self.buff_disability_effect_time = time + 0.5; } - - if(self.frozen) + + if(Player_Trapped(self)) { if(self.buffs) { @@ -578,7 +624,7 @@ MUTATOR_HOOKFUNCTION(buffs_PlayerThink) self.buffs = 0; } } - + if((self.buffs & BUFF_INVISIBLE) && (self.oldbuffs & BUFF_INVISIBLE)) if(self.alpha != autocvar_g_buffs_invisible_alpha) self.alpha = autocvar_g_buffs_invisible_alpha; @@ -602,20 +648,14 @@ MUTATOR_HOOKFUNCTION(buffs_PlayerThink) if(!self.ammo_nails) { self.ammo_nails = 20; } if(!self.ammo_fuel) { self.ammo_fuel = 20; } } - + if(self.oldbuffs & BUFF_INVISIBLE) { - if(time < self.strength_finished && g_instagib) - self.alpha = autocvar_g_instagib_invis_alpha; - else - self.alpha = self.buff_invisible_prev_alpha; + self.alpha = self.buff_invisible_prev_alpha; } else if(self.buffs & BUFF_INVISIBLE) { - if(time < self.strength_finished && g_instagib) - self.buff_invisible_prev_alpha = default_player_alpha; - else - self.buff_invisible_prev_alpha = self.alpha; + self.buff_invisible_prev_alpha = self.alpha; self.alpha = autocvar_g_buffs_invisible_alpha; } @@ -644,7 +684,7 @@ MUTATOR_HOOKFUNCTION(buffs_PlayerThink) self.buff_model.customizeentityforclient = buffs_BuffModel_Customize; } self.buff_model.color = Buff_Color(self.buffs); - self.buff_model.glowmod = ((self.buff_model.team) ? Team_ColorRGB(self.buff_model.team) + '0.1 0.1 0.1' : self.buff_model.color); + self.buff_model.glowmod = buff_GlowColor(self.buff_model); self.buff_model.skin = Buff_Skin(self.buffs); self.effects |= EF_NOSHADOW; @@ -745,11 +785,10 @@ void buffs_Initialize() precache_sound("misc/strength_respawn.wav"); precache_sound("misc/shield_respawn.wav"); precache_sound("relics/relic_effect.wav"); - precache_sound("weapons/rocket_impact.wav"); + precache_sound(W_Sound("rocket_impact")); precache_sound("keepaway/respawn.wav"); addstat(STAT_BUFFS, AS_INT, buffs); - addstat(STAT_MOVEVARS_JUMPVELOCITY, AS_FLOAT, stat_jumpheight); InitializeEntity(world, buffs_DelayedInit, INITPRIO_FINDTARGET); } @@ -767,10 +806,10 @@ MUTATOR_DEFINITION(mutator_buffs) MUTATOR_HOOK(VehicleExit, buffs_VehicleExit, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerRegen, buffs_PlayerRegen, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerDies, buffs_PlayerDies, CBC_ORDER_ANY); - MUTATOR_HOOK(PlayerUseKey, buffs_PlayerUseKey, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerUseKey, buffs_PlayerUseKey, CBC_ORDER_FIRST); MUTATOR_HOOK(MakePlayerObserver, buffs_RemovePlayer, CBC_ORDER_ANY); MUTATOR_HOOK(ClientDisconnect, buffs_RemovePlayer, CBC_ORDER_ANY); - MUTATOR_HOOK(OnEntityPreSpawn, buffs_OnEntityPreSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(OnEntityPreSpawn, buffs_OnEntityPreSpawn, CBC_ORDER_LAST); MUTATOR_HOOK(CustomizeWaypoint, buffs_CustomizeWaypoint, CBC_ORDER_ANY); MUTATOR_HOOK(WeaponRateFactor, buffs_WeaponRate, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerPreThink, buffs_PlayerThink, CBC_ORDER_ANY); diff --git a/qcsrc/server/mutators/mutator_buffs.qh b/qcsrc/server/mutators/mutator_buffs.qh index 66540b876..d8fc740d2 100644 --- a/qcsrc/server/mutators/mutator_buffs.qh +++ b/qcsrc/server/mutators/mutator_buffs.qh @@ -6,9 +6,6 @@ .float buff_invisible_prev_alpha; // flight .float buff_flight_prev_gravity; -// jump -.float stat_jumpheight; -const float STAT_MOVEVARS_JUMPVELOCITY = 250; // engine hack // disability .float buff_disability_time; .float buff_disability_effect_time; diff --git a/qcsrc/server/mutators/mutator_campcheck.qc b/qcsrc/server/mutators/mutator_campcheck.qc index 2ec584db4..59a7cc308 100644 --- a/qcsrc/server/mutators/mutator_campcheck.qc +++ b/qcsrc/server/mutators/mutator_campcheck.qc @@ -3,7 +3,7 @@ MUTATOR_HOOKFUNCTION(campcheck_PlayerDies) { - Kill_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER_CPID, CPID_CAMPCHECK); + Kill_Notification(NOTIF_ONE, self, MSG_CENTER_CPID, CPID_CAMPCHECK); return FALSE; } @@ -23,9 +23,13 @@ MUTATOR_HOOKFUNCTION(campcheck_PlayerDamage) MUTATOR_HOOKFUNCTION(campcheck_PlayerThink) { + if(!gameover) + if(!warmup_stage) // don't consider it camping during warmup? + if(time >= game_starttime) if(IS_PLAYER(self)) - if(self.deadflag == DEAD_NO) - if(!self.frozen) + if(IS_REAL_CLIENT(self)) // bots may camp, but that's no reason to constantly kill them + if(!Player_Trapped(self)) + if(!self.BUTTON_CHAT) if(autocvar_g_campcheck_interval) { vector dist; @@ -54,8 +58,12 @@ MUTATOR_HOOKFUNCTION(campcheck_PlayerThink) self.campcheck_nextcheck = time + autocvar_g_campcheck_interval; self.campcheck_traveled_distance = 0; } + + return FALSE; } + self.campcheck_nextcheck = time + autocvar_g_campcheck_interval; // one of the above checks failed, so keep the timer up to date + return FALSE; } diff --git a/qcsrc/server/mutators/mutator_dodging.qc b/qcsrc/server/mutators/mutator_dodging.qc index b26fe1b9f..5f8f09564 100644 --- a/qcsrc/server/mutators/mutator_dodging.qc +++ b/qcsrc/server/mutators/mutator_dodging.qc @@ -1,4 +1,4 @@ - +.float cvar_cl_dodging; .float cvar_cl_dodging_timeout; @@ -22,6 +22,7 @@ .float dodging_velocity_gain; MUTATOR_HOOKFUNCTION(dodging_GetCvars) { + GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_dodging, "cl_dodging"); GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_dodging_timeout, "cl_dodging_timeout"); return 0; } @@ -47,6 +48,18 @@ MUTATOR_HOOKFUNCTION(dodging_PlayerPhysics) { if (g_dodging == 0) clean_up_and_do_nothing = 1; + if(!self.cvar_cl_dodging) + clean_up_and_do_nothing = 1; + + if(cvar("g_overkill")) + clean_up_and_do_nothing = 0; // always enabled in overkill + + if(g_dodging == 2) + clean_up_and_do_nothing = 0; // forced enabled + + if(self.frozen) + clean_up_and_do_nothing = 0; // also enabled in freezetag + // when swimming, no dodging allowed.. if (self.waterlevel >= WATERLEVEL_SWIMMING) clean_up_and_do_nothing = 1; @@ -81,8 +94,7 @@ MUTATOR_HOOKFUNCTION(dodging_PlayerPhysics) { //disable jump key during dodge accel phase if (self.movement_z > 0) self.movement_z = 0; - self.velocity = - self.velocity + self.velocity += + ((self.dodging_direction_y * velocity_difference) * v_right) + ((self.dodging_direction_x * velocity_difference) * v_forward); @@ -93,9 +105,7 @@ MUTATOR_HOOKFUNCTION(dodging_PlayerPhysics) { if (self.dodging_single_action == 1) { self.flags &= ~FL_ONGROUND; - self.velocity = - self.velocity - + (autocvar_sv_dodging_up_speed * v_up); + self.velocity += (autocvar_sv_dodging_up_speed * v_up); if (autocvar_sv_dodging_sound == 1) PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND); @@ -170,14 +180,28 @@ MUTATOR_HOOKFUNCTION(dodging_GetPressedKeys) { tap_direction_y = 0; float frozen_dodging, frozen_no_doubletap; + float do_nothing = 0; frozen_dodging = (self.frozen && autocvar_sv_dodging_frozen); frozen_no_doubletap = (frozen_dodging && !autocvar_sv_dodging_frozen_doubletap); - float dodge_detected; + float dodge_detected = 0; if (g_dodging == 0) - return 0; + do_nothing = 1; + + if(!self.cvar_cl_dodging) + do_nothing = 1; + + if(cvar("g_overkill")) + do_nothing = 0; - dodge_detected = 0; + if(frozen_dodging) + do_nothing = 0; + + if(g_dodging == 2) + do_nothing = 0; + + if(do_nothing != 0) + return 0; // first check if the last dodge is far enough back in time so we can dodge again if ((time - self.last_dodging_time) < autocvar_sv_dodging_delay) @@ -266,10 +290,9 @@ MUTATOR_DEFINITION(mutator_dodging) // get timeout information from the client, so the client can configure it.. MUTATOR_HOOK(GetCvars, dodging_GetCvars, CBC_ORDER_ANY); - // this just turns on the cvar. MUTATOR_ONADD { - g_dodging = 1; + g_dodging = cvar("g_dodging"); } // this just turns off the cvar. diff --git a/qcsrc/server/mutators/mutator_freeze.qc b/qcsrc/server/mutators/mutator_freeze.qc new file mode 100644 index 000000000..4074a005b --- /dev/null +++ b/qcsrc/server/mutators/mutator_freeze.qc @@ -0,0 +1,303 @@ +void freeze_Unfreeze(entity ent) +{ + ent.freeze_flag = FROZEN_THAWING; // thawing is fine, as this is only checked when the player is frozen + + self.freezetag_frozen_time = 0; + self.freezetag_frozen_timeout = 0; + + Kill_Notification(NOTIF_ONE_ONLY, ent, MSG_CENTER_CPID, CPID_FREEZE); + + Unfreeze(ent); +} + + +MUTATOR_HOOKFUNCTION(freeze_PlayerSpawn) +{ + freeze_Unfreeze(self); + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(freeze_PlayerDies) +{ + if(frag_deathtype == DEATH_HURTTRIGGER) + return FALSE; + + calculate_player_respawn_time(); // player doesn't actually die + + float can_freeze = FALSE; + + if(frag_target.cvar_cl_freeze) + if(cvar("g_freeze") == 1) + can_freeze = TRUE; + + if(cvar("g_freeze") == 2) + can_freeze = TRUE; + + if(cvar("g_freeze") == 3) + can_freeze = FALSE; // force off (easier than disabling & restarting, will fix later) + + if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)) + { + if(self.frozen) + self.freeze_flag = FROZEN_RESPAWNING; + return TRUE; + } + + if(can_freeze) + if(!frag_target.frozen) + { + if(!(teamplay && autocvar_g_freeze_noauto)) + if(IS_REAL_CLIENT(frag_target)) + Send_Notification(NOTIF_ONE_ONLY, frag_target, MSG_CENTER, CENTER_FREEZE_THAWING); + frag_target.health = 1; // "respawn" the player :P + + if(g_instagib) + frag_target.ammo_cells = start_ammo_cells; // we need more ammo in instagib, otherwise the player will defrost & die again + + if(frag_deathtype == DEATH_TEAMCHANGE || frag_deathtype == DEATH_AUTOTEAMCHANGE) + { + // let the player die, he will be automatically frozen when he respawns + if(self.frozen == 1) + freeze_Unfreeze(self); // remove ice + return 1; + } + + Freeze(frag_target, 0, 1, (teamplay && autocvar_g_freeze_noauto)); + if(autocvar_g_freeze_frozen_maxtime > 0) + self.freezetag_frozen_timeout = time + autocvar_g_freeze_frozen_maxtime; + self.freezetag_frozen_time = time; + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(freeze_PlayerPreThink) +{ + if(self.deadflag != DEAD_NO || !IS_PLAYER(self) || gameover) + return FALSE; + + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + return FALSE; + + float button_pressed = FALSE, n; + + if(teamplay && autocvar_g_freeze_noauto) + { + // copied from freezetag + if(self.frozen == 1) + { + // keep health = 1 + self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen; + } + + if(self.frozen == 1) + { + button_pressed = (!autocvar_g_freeze_norespawn && (self.BUTTON_JUMP || self.BUTTON_USE)); // only detect jumps + if(g_lms || g_ca || g_jailbreak) { self.freeze_flag = FROZEN_RESPAWNING; } // these modes require player to die + if(self.freeze_flag == FROZEN_THAWING) + { + if(!button_pressed) + self.freeze_flag = FROZEN_WILLRESPAWN; + } + else if(self.freeze_flag == FROZEN_WILLRESPAWN) + { + if(button_pressed) + self.freeze_flag = FROZEN_RESPAWNING; + } + else if(self.freeze_flag == FROZEN_RESPAWNING) + { + if(time > self.respawn_time) + { + self.respawn_time = time + 1; // only retry once a second + if(self.nade) + toss_nade(self, '0 0 100', max(self.nade.wait, time + 0.05)); + PutClientInServer(); + } + + return 1; // if we get here, normal revivals have been cancelled + } + } + + if(self.frozen == 1) + { + float frozen_count = 0, nplayers = 0; + FOR_EACH_PLAYER(other) if(SAME_TEAM(self, other)) + { + ++nplayers; + if(other.frozen == 1) + ++frozen_count; + } + if(nplayers == frozen_count) + { + FOR_EACH_PLAYER(other) if(SAME_TEAM(self, other)) + { + other.freeze_flag = FROZEN_RESPAWNING; + other.respawn_time = time + autocvar_g_freeze_respawn_time; + PlayerScore_Add(other, SP_SCORE, -1); // lose score for this + } + + return 1; + } + } + + entity o; + o = world; + //if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout) + //if(self.iceblock) + //self.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (self.freezetag_frozen_timeout - time) / (self.freezetag_frozen_timeout - self.freezetag_frozen_time); + + if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout) + n = -1; + else + { + vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; + n = 0; + FOR_EACH_PLAYER(other) if(self != other) + { + if(other.deadflag == DEAD_NO) + if(other.frozen == 0) + { + if(SAME_TEAM(other, self)) + { + if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) + { + if(!o) + o = other; + if(self.frozen == 1) + other.reviving = TRUE; + ++n; + } + } + } + } + } + + if(n && self.frozen == 1) // OK, there is at least one teammate reviving us + { + self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); + self.health = max(1, self.revive_progress * start_health); + + if(self.revive_progress >= 1) + { + freeze_Unfreeze(self); + + if(n == -1) + { + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freeze_frozen_maxtime); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, self.netname, autocvar_g_freeze_frozen_maxtime); + return 1; + } + + // EVERY team mate nearby gets a point (even if multiple!) + FOR_EACH_PLAYER(other) + { + if(other.reviving) + { + PlayerScore_Add(other, SP_SCORE, +1); + + nades_GiveBonus(other,autocvar_g_nades_bonus_score_low); + } + } + + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname); + Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED, self.netname, o.netname); + } + + FOR_EACH_PLAYER(other) + { + if(other.reviving) + { + other.revive_progress = self.revive_progress; + other.reviving = FALSE; + } + } + } + else if(!n && self.frozen == 1) // only if no teammate is nearby will we reset + { + self.revive_progress = bound(0, self.revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1); + self.health = max(1, self.revive_progress * start_health); + } + else if(!n && !self.frozen) + { + self.revive_progress = 0; // thawing nobody + } + } + else if(self.frozen == 1) // auto revive + { + float rspeed = autocvar_g_freeze_revive_speed; + if(autocvar_g_freeze_revive_speed_random) + rspeed *= (random() * autocvar_g_freeze_revive_speed_random); + + self.revive_progress = bound(0, self.revive_progress + frametime * rspeed, 1); + self.health = max(1, self.revive_progress * start_health); + + if(self.health >= autocvar_g_freeze_revive_minhealth) + button_pressed = (self.BUTTON_JUMP || self.BUTTON_USE); // we're about to defrost, only detect jumps + else + button_pressed = (self.BUTTON_ATCK || self.BUTTON_JUMP || self.BUTTON_ATCK2 || self.BUTTON_HOOK || self.BUTTON_USE); + + if(autocvar_g_freeze_norespawn) + button_pressed = 0; // don't allow re-spawning via jump/attack + + if(self.freeze_flag == FROZEN_THAWING) + { + if(!button_pressed) + self.freeze_flag = FROZEN_WILLRESPAWN; + } + else if(self.freeze_flag == FROZEN_WILLRESPAWN) + { + if(button_pressed) + self.freeze_flag = FROZEN_RESPAWNING; + } + else if(self.freeze_flag == FROZEN_RESPAWNING) + { + if(time > self.respawn_time) + { + self.respawn_time = time + 1; // only retry once a second + if(self.nade) + toss_nade(self, '0 0 100', max(self.nade.wait, time + 0.05)); + PutClientInServer(); + return 1; + } + } + + if(self.revive_progress >= 1) + freeze_Unfreeze(self); + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(freeze_GetCvars) +{ + GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_freeze, "cl_freeze"); + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(freeze_BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":Freeze"); + return FALSE; +} + +MUTATOR_HOOKFUNCTION(freeze_BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Freeze"); + return FALSE; +} + +MUTATOR_DEFINITION(mutator_freeze) +{ + MUTATOR_HOOK(PlayerSpawn, freeze_PlayerSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDies, freeze_PlayerDies, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerPreThink, freeze_PlayerPreThink, CBC_ORDER_FIRST); + MUTATOR_HOOK(GetCvars, freeze_GetCvars, CBC_ORDER_ANY); + MUTATOR_HOOK(BuildMutatorsString, freeze_BuildMutatorsString, CBC_ORDER_ANY); + MUTATOR_HOOK(BuildMutatorsPrettyString, freeze_BuildMutatorsPrettyString, CBC_ORDER_ANY); + + return FALSE; +} diff --git a/qcsrc/server/mutators/mutator_freeze.qh b/qcsrc/server/mutators/mutator_freeze.qh new file mode 100644 index 000000000..ad307ae65 --- /dev/null +++ b/qcsrc/server/mutators/mutator_freeze.qh @@ -0,0 +1,6 @@ +.float freeze_flag; // used to check if the player is going to respawn or thaw out +const float FROZEN_THAWING = 0; // player is thawing, don't take any action +const float FROZEN_WILLRESPAWN = 1; // waiting for player to release the trigger key +const float FROZEN_RESPAWNING = 2; // player released the key, respawn when we can + +.float cvar_cl_freeze; \ No newline at end of file diff --git a/qcsrc/server/mutators/mutator_hats.qc b/qcsrc/server/mutators/mutator_hats.qc new file mode 100644 index 000000000..de107be52 --- /dev/null +++ b/qcsrc/server/mutators/mutator_hats.qc @@ -0,0 +1,180 @@ +.entity hatentity; +.string cvar_cl_hat; +.float cvar_cl_nohats; +.string hatname; // only update when the player spawns + +void hats_Precache(string pattern) +{ + float globhandle, i, n; + string f; + + globhandle = search_begin(pattern, TRUE, FALSE); + if (globhandle < 0) + return; + n = search_getsize(globhandle); + for (i = 0; i < n; ++i) + { + f = search_getfilename(globhandle, i); + precache_model(f); + } + search_end(globhandle); +} + +float hats_getscale(entity e) +{ + float s; + get_model_parameters(e.model, e.skin); + s = get_model_parameters_hat_scale; + get_model_parameters(string_null, 0); + + return s; +} + +vector hats_getheight(entity e) +{ + vector s; + get_model_parameters(e.model, e.skin); + s = get_model_parameters_hat_height; + get_model_parameters(string_null, 0); + + return s; +} +vector hats_getangles(entity e) +{ + vector s; + get_model_parameters(e.model, e.skin); + s = get_model_parameters_hat_angles; + get_model_parameters(string_null, 0); + + return s; +} + +void hat_Think() +{ + float tag_found; + self.nextthink = time; + if (self.owner.hatentity != self) + { + remove(self); + return; + } + if(Player_Trapped(self.owner)) + { + self.model = ""; + return; + } + if (self.hatname != self.owner.hatname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag) + { + self.hatname = self.owner.hatname; + self.dmg = self.owner.modelindex; + self.deadflag = self.owner.deadflag; + if (self.owner.hatname != "" && fexists(strcat("models/hats/", self.owner.hatname, ".md3"))) + setmodel(self, strcat("models/hats/", self.owner.hatname, ".md3")); // precision set below + else + self.model = ""; + + if((tag_found = gettagindex(self.owner, "tag_head"))) + { + self.tag_index = tag_found; + self.tag_entity = self.owner; + setorigin(self, hats_getheight(self.owner)); + self.scale = hats_getscale(self.owner); + self.angles = hats_getangles(self.owner); + } + else + { + setattachment(self, self.owner, "head"); + setorigin(self, hats_getheight(self.owner)); + self.scale = hats_getscale(self.owner); + self.angles = hats_getangles(self.owner); + } + } + self.effects = self.owner.effects; + self.effects |= EF_LOWPRECISION; + self.effects = self.effects & EFMASK_CHEAP; // eat performance + if(self.scale < -1) + self.alpha = -1; + else 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.glowmod; + self.colormap = self.owner.colormap; + + CSQCMODEL_AUTOUPDATE(); +} + +float hats_Customize() +{ + if(other.cvar_cl_nohats) { return FALSE; } + return TRUE; +} + +void hats_SpawnHat() +{ + self.hatentity = spawn(); + self.hatentity.classname = "hatentity"; + self.hatentity.solid = SOLID_NOT; + self.hatentity.owner = self; + self.hatentity.hatentity = self.hatentity; + setorigin(self.hatentity, '0 0 0'); + self.hatentity.angles = '0 0 0'; + self.hatentity.think = hat_Think; + self.hatentity.nextthink = time; + self.hatentity.customizeentityforclient = hats_Customize; + + { + entity oldself = self; + self = self.hatentity; + CSQCMODEL_AUTOINIT(); + self = oldself; + } +} + +MUTATOR_HOOKFUNCTION(hats_PlayerSpawn) +{ + hats_SpawnHat(); + self.hatentity.alpha = default_weapon_alpha; +#ifdef ADAY + self.hatname = strzone("cowboy"); +#elif defined(XMAS) + self.hatname = strzone("santa"); +#else + self.hatname = strzone(self.cvar_cl_hat); +#endif + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(hats_GetCvars) +{ + GetCvars_handleString(get_cvars_s, get_cvars_f, cvar_cl_hat, "cl_magical_hax"); + GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_nohats, "cl_nohats"); + return FALSE; +} + +MUTATOR_HOOKFUNCTION(hats_RemovePlayer) +{ + self.hatentity = world; + self.hatname = ""; + + return FALSE; +} + +MUTATOR_DEFINITION(mutator_hats) +{ + MUTATOR_HOOK(GetCvars, hats_GetCvars, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerSpawn, hats_PlayerSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(ClientDisconnect, hats_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(MakePlayerObserver, hats_RemovePlayer, CBC_ORDER_ANY); + + MUTATOR_ONADD + { + hats_Precache("models/hats/*.md3"); + } + + return FALSE; +} diff --git a/qcsrc/server/mutators/mutator_instagib.qc b/qcsrc/server/mutators/mutator_instagib.qc index 4200b2207..671d49894 100644 --- a/qcsrc/server/mutators/mutator_instagib.qc +++ b/qcsrc/server/mutators/mutator_instagib.qc @@ -4,15 +4,38 @@ void spawnfunc_item_minst_cells (void) if (!self.ammo_cells) self.ammo_cells = autocvar_g_instagib_ammo_drop; - StartItem ("models/items/a_cells.md3", + StartItem (Item_Model("a_cells.md3"), "misc/itempickup.wav", 45, 0, "Vaporizer Ammo", IT_CELLS, 0, 0, generic_pickupevalfunc, 100); } +void spawnfunc_item_minst_rockets (void) +{ + if (!g_instagib) { remove(self); return; } + if(!cvar("g_instagib_withmines")) { remove(self); return; } + if (!self.ammo_rockets) + self.ammo_rockets = autocvar_g_instagib_ammo_rockets; + + StartItem (Item_Model("a_rockets.md3"), + "misc/itempickup.wav", 45, 0, + "Vaporizer Ammo", IT_ROCKETS, 0, 0, generic_pickupevalfunc, 100); +} + +void instagib_item_supercells() +{ + if(!g_instagib) { remove(self); return; } + if(!self.ammo_supercells) + self.ammo_supercells = 2; + + StartItem (Item_Model("a_supercells.md3"), + "misc/itempickup.wav", 45, 0, + "supercells", IT_SUPERCELLS, 0, 0, generic_pickupevalfunc, 100); +} + void instagib_health_mega() { self.max_health = 1; - StartItem ("models/items/g_h100.md3", + StartItem (Item_Model("g_h100.md3"), "misc/megahealth.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup, "Extralife", IT_NAILS, 0, FL_POWERUP, generic_pickupevalfunc, BOT_PICKUP_RATING_HIGH); } @@ -37,6 +60,14 @@ void instagib_ammocheck() instagib_stop_countdown(self); else if (self.ammo_cells > 0 || (self.items & IT_UNLIMITED_WEAPON_AMMO) || (self.flags & FL_GODMODE)) instagib_stop_countdown(self); + else if(autocvar_g_rm) + { + if(!self.instagib_needammo) + { + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_INSTAGIB_DOWNGRADE); + self.instagib_needammo = TRUE; + } + } else { self.instagib_needammo = TRUE; @@ -111,7 +142,7 @@ MUTATOR_HOOKFUNCTION(instagib_MatchEnd) MUTATOR_HOOKFUNCTION(instagib_MonsterLoot) { - other.monster_loot = spawnfunc_item_minst_cells; + other.monster_loot = ((random() > 0.5 && autocvar_g_instagib_use_normal_ammo && cvar("g_instagib_withmines")) ? spawnfunc_item_minst_rockets : spawnfunc_item_minst_cells); return FALSE; } @@ -125,14 +156,6 @@ MUTATOR_HOOKFUNCTION(instagib_MonsterSpawn) return FALSE; } -MUTATOR_HOOKFUNCTION(instagib_BotShouldAttack) -{ - if(checkentity.items & IT_STRENGTH) - return TRUE; - - return FALSE; -} - MUTATOR_HOOKFUNCTION(instagib_MakePlayerObserver) { instagib_stop_countdown(self); @@ -161,56 +184,6 @@ MUTATOR_HOOKFUNCTION(instagib_PlayerPowerups) { if (!(self.effects & EF_FULLBRIGHT)) self.effects |= EF_FULLBRIGHT; - - if (self.items & IT_STRENGTH) - { - play_countdown(self.strength_finished, "misc/poweroff.wav"); - if (time > self.strength_finished) - { - self.alpha = default_player_alpha; - self.exteriorweaponentity.alpha = default_weapon_alpha; - self.items &= ~IT_STRENGTH; - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERDOWN_INVISIBILITY); - } - } - else - { - if (time < self.strength_finished) - { - self.alpha = autocvar_g_instagib_invis_alpha; - self.exteriorweaponentity.alpha = autocvar_g_instagib_invis_alpha; - self.items |= IT_STRENGTH; - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERUP_INVISIBILITY, self.netname); - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERUP_INVISIBILITY); - } - } - - if (self.items & IT_INVINCIBLE) - { - play_countdown(self.invincible_finished, "misc/poweroff.wav"); - if (time > self.invincible_finished) - { - self.items &= ~IT_INVINCIBLE; - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERDOWN_SPEED); - } - } - else - { - if (time < self.invincible_finished) - { - self.items |= IT_INVINCIBLE; - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERUP_SPEED, self.netname); - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERUP_SPEED); - } - } - return FALSE; -} - -MUTATOR_HOOKFUNCTION(instagib_PlayerPhysics) -{ - if(self.items & IT_INVINCIBLE) - self.stat_sv_maxspeed = self.stat_sv_maxspeed * autocvar_g_instagib_speed_highspeed; - return FALSE; } @@ -231,17 +204,32 @@ MUTATOR_HOOKFUNCTION(instagib_ForbidThrowing) MUTATOR_HOOKFUNCTION(instagib_PlayerDamage) { - if(autocvar_g_friendlyfire == 0 && SAME_TEAM(frag_target, frag_attacker) && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker)) + if(frag_deathtype == DEATH_NOAMMO) + return FALSE; + + if(autocvar_g_rm) + if(DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR)) + if(frag_attacker == frag_target || frag_target.classname == "nade") + frag_damage = 0; + + if((autocvar_g_rm && autocvar_g_rm_laser == 1) || autocvar_g_rm_laser == 2) + if(DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO)) + if(frag_attacker == frag_target || forbidWeaponUse(frag_attacker) == 2) frag_damage = 0; if(IS_PLAYER(frag_target)) { - if ((frag_deathtype == DEATH_FALL) || - (frag_deathtype == DEATH_DROWN) || - (frag_deathtype == DEATH_SLIME) || - (frag_deathtype == DEATH_LAVA)) + if(frag_deathtype == DEATH_FALL) + frag_damage = 0; // never count fall damage + + if(!autocvar_g_instagib_damagedbycontents) + switch(frag_deathtype) { - frag_damage = 0; + case DEATH_DROWN: + case DEATH_SLIME: + case DEATH_LAVA: + frag_damage = 0; + break; } if(IS_PLAYER(frag_attacker)) @@ -249,12 +237,14 @@ MUTATOR_HOOKFUNCTION(instagib_PlayerDamage) { if(frag_deathtype & HITTYPE_SECONDARY) { - frag_damage = frag_mirrordamage = 0; + if(!autocvar_g_instagib_blaster_keepdamage) + frag_damage = frag_mirrordamage = 0; if(frag_target != frag_attacker) { - if(frag_target.health > 0) { Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); } - frag_force = '0 0 0'; + if(frag_damage <= 0 && frag_target.health > 0) { Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); } + if(!autocvar_g_instagib_blaster_keepforce) + frag_force = '0 0 0'; } } else if(frag_target.armorvalue) @@ -276,12 +266,12 @@ MUTATOR_HOOKFUNCTION(instagib_PlayerDamage) { frag_attacker.armorvalue -= 1; Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, frag_attacker.armorvalue); - frag_attacker.damage_dealt += 1; + frag_attacker.damage_dealt += frag_mirrordamage; } frag_mirrordamage = 0; } - if(frag_target.items & IT_STRENGTH) + if(frag_target.buffs & BUFF_INVISIBLE) yoda = 1; return FALSE; @@ -296,10 +286,10 @@ MUTATOR_HOOKFUNCTION(instagib_SetStartItems) start_ammo_nails = warmup_start_ammo_nails = 0; start_ammo_cells = warmup_start_ammo_cells = cvar("g_instagib_ammo_start"); start_ammo_plasma = warmup_start_ammo_plasma = 0; - start_ammo_rockets = warmup_start_ammo_rockets = 0; + start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_instagib_withmines_ammo_start");; start_ammo_fuel = warmup_start_ammo_fuel = 0; - start_weapons = warmup_start_weapons = WEPSET_VAPORIZER; + start_weapons = warmup_start_weapons = ((cvar("g_instagib_withmines")) ? (WEPSET_VAPORIZER | WEPSET_MINE_LAYER) : WEPSET_VAPORIZER); start_items |= IT_UNLIMITED_SUPERWEAPONS; return FALSE; @@ -308,7 +298,12 @@ MUTATOR_HOOKFUNCTION(instagib_SetStartItems) MUTATOR_HOOKFUNCTION(instagib_FilterItem) { if(self.classname == "item_cells") - return TRUE; // no normal cells? + if(!autocvar_g_instagib_use_normal_ammo) + return TRUE; + + if(self.classname == "item_rockets") + if(!autocvar_g_instagib_use_normal_ammo || !cvar("g_instagib_withmines")) + return TRUE; if(self.weapon == WEP_VAPORIZER && self.classname == "droppedweapon") { @@ -318,11 +313,13 @@ MUTATOR_HOOKFUNCTION(instagib_FilterItem) if(self.weapon == WEP_DEVASTATOR || self.weapon == WEP_VORTEX) { - entity e = spawn(); + entity e = spawn(), oldself; setorigin(e, self.origin); - entity oldself; oldself = self; self = e; + self.noalign = oldself.noalign; + self.cnt = oldself.cnt; + self.team = oldself.team; spawnfunc_item_minst_cells(); self = oldself; return TRUE; @@ -334,39 +331,43 @@ MUTATOR_HOOKFUNCTION(instagib_FilterItem) if(self.ammo_cells > autocvar_g_instagib_ammo_drop && self.classname != "item_minst_cells") self.ammo_cells = autocvar_g_instagib_ammo_drop; - if(self.ammo_cells && !self.weapon) - return FALSE; - - return TRUE; -} + if(self.classname != "item_minst_rockets") + self.ammo_rockets = bound(0, self.ammo_rockets, autocvar_g_instagib_ammo_rockets); -MUTATOR_HOOKFUNCTION(instagib_CustomizeWaypoint) -{ - entity e = WaypointSprite_getviewentity(other); + if((self.ammo_cells || self.ammo_supercells) && !self.weapon) + return FALSE; - // if you have the invisibility powerup, sprites ALWAYS are restricted to your team - // but only apply this to real players, not to spectators - if((self.owner.flags & FL_CLIENT) && (self.owner.items & IT_STRENGTH) && (e == other)) - if(DIFF_TEAM(self.owner, e)) - return TRUE; + if(self.ammo_rockets && !self.weapon) + return FALSE; - return FALSE; + return TRUE; } MUTATOR_HOOKFUNCTION(instagib_ItemCountdown) { switch(self.items) { - case IT_STRENGTH: item_name = "item-invis"; item_color = '0 0 1'; break; case IT_NAILS: item_name = "item-extralife"; item_color = '1 0 0'; break; - case IT_INVINCIBLE: item_name = "item-speed"; item_color = '1 0 1'; break; } return FALSE; } +MUTATOR_HOOKFUNCTION(instagib_PlayerDies) +{ + if( (DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER)) + || (autocvar_g_rm && DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR)) + || (((autocvar_g_rm && autocvar_g_rm_laser == 1) || autocvar_g_rm_laser == 2) && DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO)) + ) + frag_damage = 1000; // always gib if it was a vaporizer death + + (get_weaponinfo(WEP_VAPORIZER)).weaponthrowable = 1; // throwing is forbidden by a mutator hook, enabling this for drop on death + + return FALSE; +} + MUTATOR_HOOKFUNCTION(instagib_ItemTouch) { - if(self.ammo_cells) + if(self.ammo_cells || self.ammo_supercells) { // play some cool sounds ;) if (IS_CLIENT(other)) @@ -395,25 +396,12 @@ MUTATOR_HOOKFUNCTION(instagib_ItemTouch) MUTATOR_HOOKFUNCTION(instagib_OnEntityPreSpawn) { - if (!autocvar_g_powerups) { return FALSE; } - if (!(self.classname == "item_strength" || self.classname == "item_invincible" || self.classname == "item_health_mega")) - return FALSE; - - entity e = spawn(); - - if(random() < 0.3) - e.think = spawnfunc_item_strength; - else if(random() < 0.6) - e.think = instagib_health_mega; - else - e.think = spawnfunc_item_invincible; - - e.nextthink = time + 0.1; - e.spawnflags = self.spawnflags; - e.noalign = self.noalign; - setorigin(e, self.origin); + if(WEP_CVAR_PRI(vaporizer, charge)) + if(self.classname == "item_cells" || self.classname == "item_minst_cells") + if(random() <= 0.5) + instagib_item_supercells(); - return TRUE; + return FALSE; } MUTATOR_HOOKFUNCTION(instagib_BuildMutatorsString) @@ -439,16 +427,14 @@ MUTATOR_DEFINITION(mutator_instagib) MUTATOR_HOOK(MatchEnd, instagib_MatchEnd, CBC_ORDER_ANY); MUTATOR_HOOK(MonsterDropItem, instagib_MonsterLoot, CBC_ORDER_ANY); MUTATOR_HOOK(MonsterSpawn, instagib_MonsterSpawn, CBC_ORDER_ANY); - MUTATOR_HOOK(BotShouldAttack, instagib_BotShouldAttack, CBC_ORDER_ANY); - MUTATOR_HOOK(PlayerPhysics, instagib_PlayerPhysics, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerSpawn, instagib_PlayerSpawn, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerDamage_Calculate, instagib_PlayerDamage, CBC_ORDER_ANY); MUTATOR_HOOK(MakePlayerObserver, instagib_MakePlayerObserver, CBC_ORDER_ANY); MUTATOR_HOOK(SetStartItems, instagib_SetStartItems, CBC_ORDER_ANY); MUTATOR_HOOK(ItemTouch, instagib_ItemTouch, CBC_ORDER_ANY); MUTATOR_HOOK(FilterItem, instagib_FilterItem, CBC_ORDER_ANY); - MUTATOR_HOOK(CustomizeWaypoint, instagib_CustomizeWaypoint, CBC_ORDER_ANY); MUTATOR_HOOK(Item_RespawnCountdown, instagib_ItemCountdown, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDies, instagib_PlayerDies, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerDamage_SplitHealthArmor, instagib_SplitHealthArmor, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerPowerups, instagib_PlayerPowerups, CBC_ORDER_ANY); MUTATOR_HOOK(ForbidThrowCurrentWeapon, instagib_ForbidThrowing, CBC_ORDER_ANY); @@ -459,5 +445,23 @@ MUTATOR_DEFINITION(mutator_instagib) MUTATOR_HOOK(BuildMutatorsPrettyString, instagib_BuildMutatorsPrettyString, CBC_ORDER_ANY); MUTATOR_HOOK(SetModname, instagib_SetModname, CBC_ORDER_ANY); + MUTATOR_ONADD + { + precache_sound(W_Sound("rocket_impact")); + + if(cvar("g_instagib_withmines")) + (get_weaponinfo(WEP_MINE_LAYER)).spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; + + g_instagib = cvar("g_instagib"); + } + + MUTATOR_ONREMOVE + { + if(cvar("g_instagib_withmines")) + (get_weaponinfo(WEP_MINE_LAYER)).spawnflags |= WEP_FLAG_MUTATORBLOCKED; + + g_instagib = 0; + } + return FALSE; } diff --git a/qcsrc/server/mutators/mutator_instagib.qh b/qcsrc/server/mutators/mutator_instagib.qh new file mode 100644 index 000000000..0fbd3f47e --- /dev/null +++ b/qcsrc/server/mutators/mutator_instagib.qh @@ -0,0 +1,3 @@ +float g_instagib; + +void() spawnfunc_item_minst_cells; \ No newline at end of file diff --git a/qcsrc/server/mutators/mutator_itemeditor.qc b/qcsrc/server/mutators/mutator_itemeditor.qc new file mode 100644 index 000000000..10276843b --- /dev/null +++ b/qcsrc/server/mutators/mutator_itemeditor.qc @@ -0,0 +1,419 @@ +float ie_itemcount; +.string ie_itemname; + +float ie_enabled; + +float ie_database_loaded; + +string ie_port_string; + +vector rintvec(vector vec) +{ + vector rinted; + rinted_x = rint(vec_x); + rinted_y = rint(vec_y); + rinted_z = rint(vec_z); + return rinted; +} + +void ie_Debug(string input) +{ + switch(autocvar_g_itemeditor_debug) + { + case 1: dprint(input); break; + case 2: print(input); break; + } +} + +entity ie_SpawnItem(float database); +string ie_ItemPort_Save(entity e, float database) +{ + // save item properties, and return them as a string + string s; + entity head = e; + + if(head) + { + // ---------------- ITEM PROPERTY STORAGE: SAVE ---------------- + if(database) { ie_port_string = strcat(ie_port_string, sprintf("\"%.9v\"", rintvec(head.origin)), " "); } + ie_port_string = strcat(ie_port_string, sprintf("\"%.9f\"", head.team), " "); + ie_port_string = strcat(ie_port_string, sprintf("\"%.9f\"", head.cnt), " "); + ie_port_string = strcat(ie_port_string, sprintf("\"%.9f\"", head.noalign), " "); + ie_port_string = strcat(ie_port_string, sprintf("\"%s\"", head.ie_itemname), " "); + } + + // now apply the array to a simple string, with the ; symbol separating items + s = ""; + if(ie_port_string) { s = strcat(s, ie_port_string, "; "); ie_port_string = string_null; } + + return s; +} + +// this will be removed when we have ID based item handling +string ie_FixItemName(string itname) +{ + switch(itname) + { + case "item_armor_small": case "smallarmor": case "armor_small": case "armorshard": + return "item_armor_small"; + case "item_armor_medium": case "mediumarmor": case "armor_medium": + return "item_armor_medium"; + case "item_armor_big": case "bigarmor": case "armor_big": + return "item_armor_big"; + case "item_armor_large": case "largearmor": case "armor_large": case "megaarmor": case "ma": + return "item_armor_large"; + case "item_health_small": case "smallhealth": case "health_small": + return "item_health_small"; + case "item_health_medium": case "mediumhealth": case "health_medium": + return "item_health_medium"; + case "item_health_large": case "largehealth": case "health_large": + return "item_health_large"; + case "item_health_mega": case "megahealth": case "health_mega": case "mh": case "megahealth": + return "item_health_mega"; + case "cells": case "item_cells": case "ammo_cells": + return "item_cells"; + case "bullets": case "nails": case "item_bullets": case "item_nails": case "ammo_bullets": case "ammo_nails": + return "item_bullets"; + case "rockets": case "explosives": case "item_rockets": case "item_explosives": case "ammo_rockets": case "ammo_explosives": + return "item_rockets"; + case "strength": case "item_strength": case "powerup_strength": + return "item_strength"; + case "invincible": case "item_invincible": case "powerup_invincible": + return "item_strength"; + default: + { + float i; + for(i = WEP_FIRST; i <= WEP_LAST; ++i) + if((get_weaponinfo(i)).netname == itname || (get_weaponinfo(i)).netname == substring(itname, 7, strlen(itname))) + return ((get_weaponinfo(i)).netname == substring(itname, 7, strlen(itname)) ? strcat("weapon_", itname) : itname); + return ""; + } + } +} + +void ie_SetItemType(entity e) +{ + string fixed_name = ie_FixItemName(e.ie_itemname); + if(fixed_name == "") { return; } + print("Attempting to spawn ", fixed_name, "\n"); + initialize_field_db(); + target_spawn_edit_entity(e, strcat("$ spawnfunc_", fixed_name), world, world, world, world, world); + + e.classname = "itemeditor_item"; +} + +entity ie_ItemPort_Load(string s, float database) +{ + // load item properties, and spawn a new item with them + float n; + entity e = world; + + // separate items between the ; symbols + n = tokenizebyseparator(s, "; "); + ie_port_string = argv(0); + + // now separate and apply the properties of each item + float argv_num = 0; + + tokenize_console(ie_port_string); + e = ie_SpawnItem(database); + + if(database) { setorigin(e, stov(argv(argv_num))); ++argv_num; } + e.team = stof(argv(argv_num)); ++argv_num; + e.cnt = stof(argv(argv_num)); ++argv_num; + e.noalign = stof(argv(argv_num)); ++argv_num; + e.ie_itemname = strzone(argv(argv_num)); ++argv_num; + + print(e.ie_itemname, "\n"); + ie_SetItemType(e); + + ie_port_string = string_null; // fully clear the string + + return e; +} + +void ie_Database_Save() +{ + // saves all items to the database file + entity head; + string file_name; + float file_get; + + file_name = strcat("itemeditor/storage_", autocvar_g_itemeditor_storage_name, "_", GetMapname(), ".txt"); + file_get = fopen(file_name, FILE_WRITE); + fputs(file_get, strcat("// itemeditor storage \"", autocvar_g_itemeditor_storage_name, "\" for map \"", GetMapname(), "\"")); + fputs(file_get, strcat(" containing ", ftos(ie_itemcount), " items\n")); + + for(head = world; (head = find(head, classname, "itemeditor_item")); ) + { + // use a line of text for each item, listing all properties + fputs(file_get, strcat(ie_ItemPort_Save(head, TRUE), "\n")); + } + fclose(file_get); +} + +void ie_Database_Load() +{ + // loads all items from the database file + string file_read, file_name; + float file_get; + + file_name = strcat("itemeditor/storage_", autocvar_g_itemeditor_storage_name, "_", GetMapname(), ".txt"); + file_get = fopen(file_name, FILE_READ); + if(file_get < 0) + { + ie_Debug(strcat("^3ITEMEDITOR - Server: ^7could not find storage file ^3", file_name, "^7, no items were loaded\n")); + } + else + { + for(;;) + { + file_read = fgets(file_get); + if(file_read == "") + break; + if(substring(file_read, 0, 2) == "//") + continue; + if(substring(file_read, 0, 1) == "#") + continue; + + entity e; + e = ie_ItemPort_Load(file_read, TRUE); + } + ie_Debug(strcat("^3ITEMEDITOR - SERVER: ^7successfully loaded storage file ^3", file_name, "\n")); + } + fclose(file_get); + + ie_database_loaded = TRUE; +} + +void ie_Remove(entity e); +void ie_Database_Unload() +{ + entity head; + for(head = world; (head = find(head, classname, "itemeditor_item")); ) + ie_Remove(head); + ie_database_loaded = FALSE; +} + +void ie_Think() +{ + self.nextthink = time; + + // decide if and how this item can be grabbed + if(autocvar_g_itemeditor_readonly) + self.grab = 0; // no grabbing + else + self.grab = 3; // anyone +} + +entity ie_SpawnItem(float database) +{ + entity e = spawn(); + e.classname = "itemeditor_item"; + + if(!database) + { + // set origin and direction based on player position and view angle + makevectors(self.v_angle); + WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * autocvar_g_itemeditor_spawn_distance, MOVE_NORMAL, self); + setorigin(e, trace_endpos); + } + + if(IS_REAL_CLIENT(self)) { print_to(self, "Spawned new item entity"); } + + ie_itemcount += 1; + + return e; +} + +void ie_Remove(entity e) +{ + if(e.ie_itemname) { strunzone(e.ie_itemname); e.ie_itemname = string_null; } + RemoveItem(e); + e = world; + + ie_itemcount -= 1; +} + +float ie_CheckItem(entity e) +{ + if(!e || e.classname != "itemeditor_item") { return FALSE; } + return TRUE; +} + +MUTATOR_HOOKFUNCTION(ie_ClientCommand) +{ + if(cmd_name == "itemeditor") + { + if(!ie_enabled || autocvar_g_itemeditor_readonly) { sprint(self, "Item editing is currently disabled\n"); return TRUE; } + + if(argv(1) == "spawn") + { + if(!argv(2) || argv(2) == "") { sprint(self, "You must specify an item name\n"); return TRUE; } + if(ie_itemcount >= autocvar_g_itemeditor_max) { sprint(self, "Too many items!\n"); return TRUE; } + + string item_name = strzone(argv(2)); + if(ie_FixItemName(item_name) == "") { sprint(self, "Invalid item\n"); strunzone(item_name); return TRUE; } + + WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self); + entity e = ie_SpawnItem(FALSE); + setorigin(e, trace_endpos); + e.ie_itemname = item_name; + ie_SetItemType(e); + return TRUE; + } + + if(argv(1) == "remove") + { + entity e, theitem = world; + for(e = WarpZone_FindRadius(self.origin + self.view_ofs + v_forward * 50, 50, FALSE); e; e = e.chain) + if(e.classname == "itemeditor_item") + { + print("Got one\n"); + theitem = e; + break; + } + + + if(ie_CheckItem(theitem)) + { + ie_Remove(theitem); + sprint(self, "Successfully removed an item\n"); + return TRUE; + } + else + { + sprint(self, "Item not found\n"); + return TRUE; + } + } + + if(argv(1) == "edit") + { + if(!argv(2)) { sprint(self, "You must specify a property edit\n"); return TRUE; } + if(!ie_enabled) { sprint(self, "Editing is not enabled\n"); return TRUE; } + + entity e, theitem = world; + for(e = WarpZone_FindRadius(self.origin + self.view_ofs + v_forward * 50, 50, FALSE); e; e = e.chain) + if(e.classname == "itemeditor_item") + { + theitem = e; + break; + } + + if(ie_CheckItem(theitem)) + if(argv(3)) + switch(argv(2)) + { + case "cnt": theitem.cnt = stof(argv(3)); return TRUE; + case "team": theitem.team = stof(argv(3)); return TRUE; + case "noalign": case "float": theitem.noalign = stof(argv(3)); return TRUE; + default: print_to(self, "Unknown option"); return TRUE; + } + } + + sprint(self, "Command was not handled\n"); + return TRUE; + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(ie_ServerCommand) +{ + if(cmd_name == "itemeditor") + { + switch(argv(1)) + { + case "enable": + case "start": + { + if(!ie_database_loaded) { print("Enabling editing while database is unloaded could cause chaos, stopping\n"); return TRUE; } + ie_enabled = TRUE; + bprint("Item editing has been enabled!\n"); + return TRUE; + } + case "disable": + case "stop": + { + ie_enabled = FALSE; + bprint("Item editing has been disabled!\n"); + return TRUE; + } + case "load": + { + if(ie_itemcount > 0 || ie_database_loaded) { print("Item database has already been loaded\n"); return TRUE; } + + ie_Database_Load(); + bprint("Item database has been loaded!\n"); + return TRUE; + } + case "unload": + { + if(ie_itemcount <= 0 || !ie_database_loaded) { print("Item database has already been unloaded\n"); return TRUE; } + + ie_enabled = FALSE; // we must disable this, so as to not break stuff + ie_Database_Unload(); + bprint("Item database has been unloaded!\n"); + return TRUE; + } + case "removeitems": + { + entity head; + for(head = world; (head = findflags(head, flags, FL_ITEM)); ) + if(head.items || head.weapon) + if((head.classname != "itemeditor_item" && head.classname != "droppedweapon") || argv(2) == "all") + RemoveItem(head); + + bprint("Regular items removed!\n"); + return TRUE; + } + } + print("Command was not handled\n"); + return TRUE; + } + return FALSE; +} + +float ie_autosave_time; +MUTATOR_HOOKFUNCTION(ie_StartFrame) +{ + entity head; + for(head = world; (head = find(head, classname, "itemeditor_item")); ) + head.grab = (autocvar_g_itemeditor_readonly || !ie_enabled) ? 0 : 3; + + if(!ie_enabled) + return FALSE; + if(!ie_database_loaded) + return FALSE; + if(!autocvar_g_itemeditor_storage_autosave) + return FALSE; + if(time < ie_autosave_time) + return FALSE; + ie_autosave_time = time + autocvar_g_itemeditor_storage_autosave; + + ie_Database_Save(); + + return TRUE; +} + +void ie_DelayedInit() +{ + ie_Database_Load(); +} + +MUTATOR_DEFINITION(mutator_itemeditor) +{ + MUTATOR_HOOK(SV_ParseClientCommand, ie_ClientCommand, CBC_ORDER_ANY); + MUTATOR_HOOK(SV_ParseServerCommand, ie_ServerCommand, CBC_ORDER_ANY); + MUTATOR_HOOK(SV_StartFrame, ie_StartFrame, CBC_ORDER_ANY); + + MUTATOR_ONADD + { + ie_autosave_time = time + autocvar_g_itemeditor_storage_autosave; // don't save the first server frame + if(autocvar_g_itemeditor_storage_autoload) + InitializeEntity(world, ie_DelayedInit, INITPRIO_LAST); + } + + return FALSE; +} diff --git a/qcsrc/server/mutators/mutator_multijump.qc b/qcsrc/server/mutators/mutator_multijump.qc index 868ddf246..090bdfe2a 100644 --- a/qcsrc/server/mutators/mutator_multijump.qc +++ b/qcsrc/server/mutators/mutator_multijump.qc @@ -1,24 +1,32 @@ .float multijump_count; .float multijump_ready; +.float cvar_cl_multijump; MUTATOR_HOOKFUNCTION(multijump_PlayerPhysics) { if(self.flags & FL_ONGROUND) - self.multijump_count = 0; + { + if (autocvar_g_multijump > 0) + self.multijump_count = 0; + else + self.multijump_count = -2; // the cvar value for infinite jumps is -1, so this needs to be smaller + } return FALSE; } MUTATOR_HOOKFUNCTION(multijump_PlayerJump) { + if (self.cvar_cl_multijump) if (self.flags & FL_JUMPRELEASED && !(self.flags & FL_ONGROUND)) // jump button pressed this frame and we are in midair self.multijump_ready = TRUE; // this is necessary to check that we released the jump button and pressed it again else self.multijump_ready = FALSE; - if(!player_multijump && self.multijump_ready && (autocvar_g_multijump == -1 || self.multijump_count < autocvar_g_multijump) && self.velocity_z > autocvar_g_multijump_speed) + if(!player_multijump && self.multijump_ready && self.multijump_count < autocvar_g_multijump && self.velocity_z > autocvar_g_multijump_speed && vlen(self.velocity) <= autocvar_g_multijump_maxspeed) { if (autocvar_g_multijump) + if (self.cvar_cl_multijump) { if (autocvar_g_multijump_add == 0) // in this case we make the z velocity == jumpvelocity { @@ -50,7 +58,8 @@ MUTATOR_HOOKFUNCTION(multijump_PlayerJump) self.velocity_y = wishdir_y * curspeed; // keep velocity_z unchanged! } - self.multijump_count += 1; + if (autocvar_g_multijump > 0) + self.multijump_count += 1; } } self.multijump_ready = FALSE; // require releasing and pressing the jump button again for the next jump @@ -59,6 +68,12 @@ MUTATOR_HOOKFUNCTION(multijump_PlayerJump) return FALSE; } +MUTATOR_HOOKFUNCTION(multijump_GetCvars) +{ + GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_multijump, "cl_multijump"); + return FALSE; +} + MUTATOR_HOOKFUNCTION(multijump_BuildMutatorsString) { ret_string = strcat(ret_string, ":multijump"); @@ -75,6 +90,7 @@ MUTATOR_DEFINITION(mutator_multijump) { MUTATOR_HOOK(PlayerPhysics, multijump_PlayerPhysics, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerJump, multijump_PlayerJump, CBC_ORDER_ANY); + MUTATOR_HOOK(GetCvars, multijump_GetCvars, CBC_ORDER_ANY); MUTATOR_HOOK(BuildMutatorsString, multijump_BuildMutatorsString, CBC_ORDER_ANY); MUTATOR_HOOK(BuildMutatorsPrettyString, multijump_BuildMutatorsPrettyString, CBC_ORDER_ANY); diff --git a/qcsrc/server/mutators/mutator_nades.qc b/qcsrc/server/mutators/mutator_nades.qc index 91bd53e5a..72db5fde8 100644 --- a/qcsrc/server/mutators/mutator_nades.qc +++ b/qcsrc/server/mutators/mutator_nades.qc @@ -64,8 +64,7 @@ void napalm_damage(float dist, float damage, float edgedamage, float burntime) 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); + Send_Effect(EFFECT_FIREBALL_LASER, self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1); } } @@ -108,7 +107,7 @@ void nade_napalm_ball() entity proj; vector kick; - spamsound(self, CH_SHOTS, "weapons/fireball_fire.wav", VOL_BASE, ATTEN_NORM); + spamsound(self, CH_SHOTS, W_Sound("fireball_fire"), VOL_BASE, ATTEN_NORM); proj = spawn (); proj.owner = self.owner; @@ -212,7 +211,7 @@ void nade_napalm_boom() void nade_ice_freeze(entity freezefield, entity frost_target, float freeze_time) { frost_target.frozen_by = freezefield.realowner; - pointparticles(particleeffectnum("electro_impact"), frost_target.origin, '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_IMPACT, frost_target.origin, '0 0 0', 1); Freeze(frost_target, 1/freeze_time, 3, FALSE); if(frost_target.ballcarried) if(g_keepaway) { ka_DropEvent(frost_target); } @@ -220,7 +219,8 @@ void nade_ice_freeze(entity freezefield, entity frost_target, float freeze_time) if(frost_target.flagcarried) { ctf_Handle_Throw(frost_target, world, DROP_THROW); } if(frost_target.nade) { toss_nade(frost_target, '0 0 0', time + 0.05); } - kh_Key_DropAll(frost_target, FALSE); + entity key; + KH_FOR_EACH_KEY(key) if(key.owner == frost_target) { kh_Handle_Throw(frost_target, world, key, DROP_THROW); } } void nade_ice_think() @@ -237,17 +237,17 @@ void nade_ice_think() { if ( autocvar_g_nades_ice_explode ) { - string expef; + float expef; switch(self.realowner.team) { - case NUM_TEAM_1: expef = "nade_red_explode"; break; - case NUM_TEAM_2: expef = "nade_blue_explode"; break; - case NUM_TEAM_3: expef = "nade_yellow_explode"; break; - case NUM_TEAM_4: expef = "nade_pink_explode"; break; - default: expef = "nade_neutral_explode"; break; + case NUM_TEAM_1: expef = EFFECT_NADE_RED_EXPLODE; break; + case NUM_TEAM_2: expef = EFFECT_NADE_BLUE_EXPLODE; break; + case NUM_TEAM_3: expef = EFFECT_NADE_YELLOW_EXPLODE; break; + case NUM_TEAM_4: expef = EFFECT_NADE_PINK_EXPLODE; break; + default: expef = EFFECT_NADE_NEUTRAL_EXPLODE; break; } - pointparticles(particleeffectnum(expef), self.origin + '0 0 1', '0 0 0', 1); - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); + Send_Effect(expef, self.origin + '0 0 1', '0 0 0', 1); + sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, autocvar_g_nades_nade_radius, self, world, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy); @@ -271,15 +271,14 @@ void nade_ice_think() randomp_x = randomr*cos(randomw); randomp_y = randomr*sin(randomw); randomp_z = 1; - pointparticles(particleeffectnum("electro_muzzleflash"), self.origin + randomp, '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, self.origin + randomp, '0 0 0', 1); if(time >= self.nade_special_time) { self.nade_special_time = time+0.7; - - pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1); - pointparticles(particleeffectnum("icefield"), self.origin, '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_IMPACT, self.origin, '0 0 0', 1); + Send_Effect(EFFECT_ICEFIELD, self.origin, '0 0 0', 1); } @@ -289,10 +288,10 @@ void nade_ice_think() for(e = findradius(self.origin, autocvar_g_nades_nade_radius); e; e = e.chain) if(e != self) if(!autocvar_g_nades_ice_teamcheck || (DIFF_TEAM(e, self.realowner) || e == self.realowner)) - if(e.takedamage && e.deadflag == DEAD_NO) + if(e.takedamage) if(e.health > 0) + if(!Player_Trapped(e)) if(!e.revival_time || ((time - e.revival_time) >= 1.5)) - if(!e.frozen) if(current_freeze_time > 0) nade_ice_freeze(self, e, current_freeze_time); } @@ -399,7 +398,7 @@ void nade_heal_touch() { float maxhealth; float health_factor; - if(IS_PLAYER(other) || (other.flags & FL_MONSTER)) + if(IS_PLAYER(other) || IS_MONSTER(other)) if(other.deadflag == DEAD_NO) if(!other.frozen) { @@ -413,11 +412,11 @@ void nade_heal_touch() } if ( health_factor > 0 ) { - maxhealth = (other.flags & FL_MONSTER) ? other.max_health : g_pickup_healthmega_max; + maxhealth = (IS_MONSTER(other)) ? other.max_health : 200; // TODO: find a good limiter that works across all modes and mutators if ( other.health < maxhealth ) { if ( self.nade_show_particles ) - pointparticles(particleeffectnum("healing_fx"), other.origin, '0 0 0', 1); + Send_Effect(EFFECT_HEALING, other.origin, '0 0 0', 1); other.health = min(other.health+health_factor, maxhealth); } other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot); @@ -429,9 +428,9 @@ void nade_heal_touch() } - if ( IS_REAL_CLIENT(other) || (other.vehicle_flags & VHF_ISVEHICLE) ) + if ( IS_REAL_CLIENT(other) || IS_VEHICLE(other) ) { - entity show_red = (other.vehicle_flags & VHF_ISVEHICLE) ? other.owner : other; + entity show_red = IS_VEHICLE(other) ? other.owner : other; show_red.stat_healing_orb = time+0.1; show_red.stat_healing_orb_alpha = 0.75 * (self.ltime - time) / self.healer_lifetime; } @@ -474,57 +473,56 @@ void nade_monster_boom() void nade_boom() { - string expef; + float expef; float nade_blast = 1; switch ( self.nade_type ) { case NADE_TYPE_NAPALM: nade_blast = autocvar_g_nades_napalm_blast; - expef = "explosion_medium"; + expef = EFFECT_EXPLOSION_MEDIUM; break; case NADE_TYPE_ICE: nade_blast = 0; - expef = "electro_combo"; // hookbomb_explode electro_combo bigplasma_impact + expef = EFFECT_ELECTRO_COMBO; // hookbomb_explode electro_combo bigplasma_impact break; case NADE_TYPE_TRANSLOCATE: nade_blast = 0; - expef = ""; + expef = 0; break; case NADE_TYPE_MONSTER: case NADE_TYPE_SPAWN: nade_blast = 0; switch(self.realowner.team) { - case NUM_TEAM_1: expef = "spawn_event_red"; break; - case NUM_TEAM_2: expef = "spawn_event_blue"; break; - case NUM_TEAM_3: expef = "spawn_event_yellow"; break; - case NUM_TEAM_4: expef = "spawn_event_pink"; break; - default: expef = "spawn_event_neutral"; break; + case NUM_TEAM_1: expef = EFFECT_SPAWN_RED; break; + case NUM_TEAM_2: expef = EFFECT_SPAWN_BLUE; break; + case NUM_TEAM_3: expef = EFFECT_SPAWN_YELLOW; break; + case NUM_TEAM_4: expef = EFFECT_SPAWN_PINK; break; + default: expef = EFFECT_SPAWN_NEUTRAL; break; } break; case NADE_TYPE_HEAL: nade_blast = 0; - expef = "spawn_event_red"; + expef = EFFECT_SPAWN_RED; break; default: case NADE_TYPE_NORMAL: switch(self.realowner.team) { - case NUM_TEAM_1: expef = "nade_red_explode"; break; - case NUM_TEAM_2: expef = "nade_blue_explode"; break; - case NUM_TEAM_3: expef = "nade_yellow_explode"; break; - case NUM_TEAM_4: expef = "nade_pink_explode"; break; - default: expef = "nade_neutral_explode"; break; + case NUM_TEAM_1: expef = EFFECT_NADE_RED_EXPLODE; break; + case NUM_TEAM_2: expef = EFFECT_NADE_BLUE_EXPLODE; break; + case NUM_TEAM_3: expef = EFFECT_NADE_YELLOW_EXPLODE; break; + case NUM_TEAM_4: expef = EFFECT_NADE_PINK_EXPLODE; break; + default: expef = EFFECT_NADE_NEUTRAL_EXPLODE; break; } } - if(expef != "") - pointparticles(particleeffectnum(expef), findbetterlocation(self.origin, 8), '0 0 0', 1); + if(expef) { Send_Effect(expef, self.origin + '0 0 1', '0 0 0', 1); } sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM); - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); + sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); self.event_damage = func_null; // prevent somehow calling damage in the next call @@ -568,7 +566,7 @@ void nade_touch() //UpdateCSQCProjectile(self); if(self.health == self.max_health) { - spamsound(self, CH_SHOTS, strcat("weapons/grenade_bounce", ftos(1 + rint(random() * 5)), ".wav"), VOL_BASE, ATTEN_NORM); + spamsound(self, CH_SHOTS, W_Sound(strcat("grenade_bounce", ftos(1 + rint(random() * 5)))), VOL_BASE, ATTEN_NORM); return; } @@ -737,6 +735,7 @@ void nades_GiveBonus(entity player, float score) if ( player.bonus_nade_score >= 1 ) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_NADE_BONUS); + Send_Notification(NOTIF_ONE, player, MSG_ANNCE, ANNCE_BONUSNADE); play2(player,"kh/alarm.wav"); player.bonus_nades++; player.bonus_nade_score -= 1; @@ -773,6 +772,10 @@ float nade_customize() void nade_prime() { + if(autocvar_g_nades_bonus_only) + if(!self.bonus_nades) + return; // only allow bonus nades + if(self.nade) remove(self.nade); @@ -784,9 +787,7 @@ void nade_prime() n.classname = "nade"; fn.classname = "fake_nade"; - if(self.items & IT_STRENGTH && autocvar_g_nades_bonus_onstrength) - n.nade_type = self.nade_type; - else if (self.bonus_nades >= 1) + if (self.bonus_nades >= 1) { n.nade_type = self.nade_type; n.pokenade_type = self.pokenade_type; @@ -842,7 +843,7 @@ float CanThrowNade() if (!autocvar_g_nades) return FALSE; // allow turning them off mid match - if(forbidWeaponUse()) + if(forbidWeaponUse(self)) return FALSE; if (!IS_PLAYER(self)) @@ -878,12 +879,26 @@ void nades_CheckThrow() } } -void nades_Clear(entity player) +void nades_Clear(entity player, float allplayers) { - if(player.nade) - remove(player.nade); - if(player.fake_nade) - remove(player.fake_nade); + if(allplayers) + { + entity head; + FOR_EACH_PLAYER(head) if(head != player) + { + if(head.nade) { remove(head.nade); } + if(head.fake_nade) { remove(head.fake_nade); } + head.nade = head.fake_nade = world; + head.nade_timer = 0; + } + + return; + } + + if(!player) { return; } + + if(player.nade) { remove(player.nade); } + if(player.fake_nade) { remove(player.fake_nade); } player.nade = player.fake_nade = world; player.nade_timer = 0; @@ -955,9 +970,9 @@ MUTATOR_HOOKFUNCTION(nades_PlayerPreThink) { entity key; float key_count = 0; - FOR_EACH_KH_KEY(key) if(key.owner == self) { ++key_count; } + KH_FOR_EACH_KEY(key) if(key.owner == self) { ++key_count; } - if(self.flagcarried || self.ballcarried) // this player is important + if(self.flagcarried || self.isvip || self.ballcarried) // this player is important time_score = autocvar_g_nades_bonus_score_time_flagcarrier; else time_score = autocvar_g_nades_bonus_score_time; @@ -1047,6 +1062,7 @@ MUTATOR_HOOKFUNCTION(nades_PlayerSpawn) self.nade_timer = 0; if(self.nade_spawnloc) + if(!Player_Trapped(self)) { setorigin(self, self.nade_spawnloc.origin); self.nade_spawnloc.cnt -= 1; @@ -1064,7 +1080,7 @@ MUTATOR_HOOKFUNCTION(nades_PlayerSpawn) MUTATOR_HOOKFUNCTION(nades_PlayerDies) { if(frag_target.nade) - if(!frag_target.frozen || !autocvar_g_freezetag_revive_nade) + if(!frag_target.frozen || !autocvar_g_freeze_revive_nade) toss_nade(frag_target, '0 0 100', max(frag_target.nade.wait, time + 0.05)); float killcount_bonus = ((frag_attacker.killcount >= 1) ? bound(0, autocvar_g_nades_bonus_score_minor * frag_attacker.killcount, autocvar_g_nades_bonus_score_medium) : autocvar_g_nades_bonus_score_minor); @@ -1098,14 +1114,14 @@ MUTATOR_HOOKFUNCTION(nades_PlayerDies) MUTATOR_HOOKFUNCTION(nades_PlayerDamage) { if(frag_target.frozen) - if(autocvar_g_freezetag_revive_nade) + if(autocvar_g_freeze_revive_nade) if(frag_attacker == frag_target) if(frag_deathtype == DEATH_NADE) if(time - frag_inflictor.toss_time <= 0.1) { Unfreeze(frag_target); - frag_target.health = autocvar_g_freezetag_revive_nade_health; - pointparticles(particleeffectnum("iceorglass"), frag_target.origin, '0 0 0', 3); + frag_target.health = autocvar_g_freeze_revive_nade_health; + Send_Effect(EFFECT_ICEORGLASS, frag_target.origin, '0 0 0', 3); frag_damage = 0; frag_force = '0 0 0'; Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_NADE, frag_target.netname); @@ -1127,7 +1143,7 @@ MUTATOR_HOOKFUNCTION(nades_MonsterDies) MUTATOR_HOOKFUNCTION(nades_RemovePlayer) { - nades_Clear(self); + nades_Clear(self, FALSE); nades_RemoveBonus(self); return FALSE; } @@ -1178,13 +1194,13 @@ void nades_Initialize() precache_model("models/weapons/v_ok_grenade.md3"); precache_model("models/ctf/shield.md3"); - precache_sound("weapons/rocket_impact.wav"); - precache_sound("weapons/grenade_bounce1.wav"); - precache_sound("weapons/grenade_bounce2.wav"); - precache_sound("weapons/grenade_bounce3.wav"); - precache_sound("weapons/grenade_bounce4.wav"); - precache_sound("weapons/grenade_bounce5.wav"); - precache_sound("weapons/grenade_bounce6.wav"); + precache_sound(W_Sound("rocket_impact")); + precache_sound(W_Sound("grenade_bounce1")); + precache_sound(W_Sound("grenade_bounce2")); + precache_sound(W_Sound("grenade_bounce3")); + precache_sound(W_Sound("grenade_bounce4")); + precache_sound(W_Sound("grenade_bounce5")); + precache_sound(W_Sound("grenade_bounce6")); precache_sound("overkill/grenadebip.ogg"); } diff --git a/qcsrc/server/mutators/mutator_nades.qh b/qcsrc/server/mutators/mutator_nades.qh index c6c30c6d5..35c98fca3 100644 --- a/qcsrc/server/mutators/mutator_nades.qh +++ b/qcsrc/server/mutators/mutator_nades.qh @@ -17,8 +17,8 @@ void toss_nade(entity e, vector _velocity, float _time); -// Remove nades that are being thrown -void(entity player) nades_Clear; +// Remove nades that are being thrown, if allplayers is TRUE, all clients EXCEPT player are checked +void(entity player, float allplayers) nades_Clear; // Give a bonus grenade to a player void(entity player, float score) nades_GiveBonus; diff --git a/qcsrc/server/mutators/mutator_new_toys.qc b/qcsrc/server/mutators/mutator_new_toys.qc index 3e41c42fe..5a6342999 100644 --- a/qcsrc/server/mutators/mutator_new_toys.qc +++ b/qcsrc/server/mutators/mutator_new_toys.qc @@ -69,6 +69,7 @@ roflsound "New toys, new toys!" sound. .string new_toys; float autocvar_g_new_toys_autoreplace; +float autocvar_g_new_toys_use_pickupsound; #define NT_AUTOREPLACE_NEVER 0 #define NT_AUTOREPLACE_ALWAYS 1 #define NT_AUTOREPLACE_RANDOM 2 @@ -102,7 +103,7 @@ string nt_GetFullReplacement(string w) case "devastator": return "minelayer"; case "machinegun": return "hlac"; case "vortex": return "rifle"; - case "shotgun": return "shockwave"; + //case "shotgun": return "shockwave"; default: return string_null; } } @@ -185,8 +186,8 @@ MUTATOR_HOOKFUNCTION(nt_SetWeaponreplace) MUTATOR_HOOKFUNCTION(nt_FilterItem) { - if(nt_IsNewToy(self.weapon)) - self.item_pickupsound = "weapons/weaponpickup_new_toys.wav"; + if(nt_IsNewToy(self.weapon) && autocvar_g_new_toys_use_pickupsound) + self.item_pickupsound = strzone(W_Sound("weaponpickup_new_toys")); return 0; } @@ -202,7 +203,7 @@ MUTATOR_DEFINITION(mutator_new_toys) if(time > 1) // game loads at time 1 error("This cannot be added at runtime\n"); - precache_sound("weapons/weaponpickup_new_toys.wav"); + precache_sound(W_Sound("weaponpickup_new_toys")); // mark the guns as ok to use by e.g. impulse 99 float i; diff --git a/qcsrc/server/mutators/mutator_nix.qc b/qcsrc/server/mutators/mutator_nix.qc index 6a980af08..fc4755edd 100644 --- a/qcsrc/server/mutators/mutator_nix.qc +++ b/qcsrc/server/mutators/mutator_nix.qc @@ -184,11 +184,6 @@ MUTATOR_HOOKFUNCTION(nix_FilterItem) if (autocvar_g_nix_with_healtharmor) return 0; break; - case IT_STRENGTH: - case IT_INVINCIBLE: - if (autocvar_g_nix_with_powerups) - return 0; - break; } return 1; // delete all other items diff --git a/qcsrc/server/mutators/mutator_overkill.qc b/qcsrc/server/mutators/mutator_overkill.qc index 7a5e62a7e..7c3a35fe4 100644 --- a/qcsrc/server/mutators/mutator_overkill.qc +++ b/qcsrc/server/mutators/mutator_overkill.qc @@ -18,7 +18,7 @@ void ok_IncreaseCharge(entity ent, float wep) return; // dummy if(ent.ok_use_ammocharge) - if(!ent.BUTTON_ATCK) // not while attacking? + if(!ent.BUTTON_ATCK || autocvar_g_overkill_ammo_charge_attack) // not while attacking? ent.ammo_charge[wep] = min(autocvar_g_overkill_ammo_charge_limit, ent.ammo_charge[wep] + cvar(sprintf("g_overkill_ammo_charge_rate_%s", wepent.netname)) * frametime / W_TICSPERFRAME); } @@ -65,7 +65,7 @@ MUTATOR_HOOKFUNCTION(ok_PlayerDies) { entity oldself = self; - if(self.flags & FL_MONSTER) + if(IS_MONSTER(self)) { remove(other); // remove default item other = world; @@ -130,8 +130,10 @@ MUTATOR_HOOKFUNCTION(ok_PlayerPreThink) ok_IncreaseCharge(self, self.weapon); + float weaponuse_forbidden = forbidWeaponUse(self); + if(self.BUTTON_ATCK2) - if(!forbidWeaponUse() || self.weapon_blocked) // allow if weapon is blocked + if(!weaponuse_forbidden || (weaponuse_forbidden == 4 || weaponuse_forbidden == 2)) // allow if weapon is blocked if(time >= self.jump_interval) { self.jump_interval = time + WEP_CVAR_PRI(blaster, refire) * W_WeaponRateFactor(); @@ -162,7 +164,7 @@ MUTATOR_HOOKFUNCTION(ok_PlayerPreThink) { //Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_OVERKILL_CHARGE); self.ok_notice_time = time + 2; - play2(self, "weapons/dryfire.wav"); + play2(self, W_Sound("dryfire")); } if(self.weaponentity.state != WS_CLEAR) w_ready(); @@ -197,42 +199,41 @@ MUTATOR_HOOKFUNCTION(ok_PlayerSpawn) MUTATOR_HOOKFUNCTION(ok_OnEntityPreSpawn) { - if(autocvar_g_powerups) - if(autocvar_g_overkill_powerups_replace) + // powerups don't actually exist, so we can abuse them as much as we want + if(self.classname == "item_strength") { - if(self.classname == "item_strength") - { - entity wep = spawn(); - setorigin(wep, self.origin); - setmodel(wep, "models/weapons/g_ok_hmg.md3"); - wep.classname = "weapon_hmg"; - wep.ok_item = TRUE; - wep.noalign = self.noalign; - wep.cnt = self.cnt; - wep.team = self.team; - wep.respawntime = autocvar_g_overkill_superguns_respawn_time; - wep.pickup_anyway = TRUE; - wep.think = spawnfunc_weapon_hmg; - wep.nextthink = time + 0.1; - return TRUE; - } + self.classname = "item_removing"; // avoid letting other mutators use it + entity wep = spawn(); + setorigin(wep, self.origin); + setmodel(wep, "models/weapons/g_ok_hmg.md3"); + wep.classname = "weapon_hmg"; + wep.ok_item = TRUE; + wep.noalign = self.noalign; + wep.cnt = self.cnt; + wep.team = self.team; + wep.respawntime = autocvar_g_overkill_superguns_respawn_time; + wep.pickup_anyway = TRUE; + wep.think = spawnfunc_weapon_hmg; + wep.nextthink = time + 0.1; + return TRUE; + } - if(self.classname == "item_invincible") - { - entity wep = spawn(); - setorigin(wep, self.origin); - setmodel(wep, "models/weapons/g_ok_rl.md3"); - wep.classname = "weapon_rpc"; - wep.ok_item = TRUE; - wep.noalign = self.noalign; - wep.cnt = self.cnt; - wep.team = self.team; - wep.respawntime = autocvar_g_overkill_superguns_respawn_time; - wep.pickup_anyway = TRUE; - wep.think = spawnfunc_weapon_rpc; - wep.nextthink = time + 0.1; - return TRUE; - } + if(self.classname == "item_invincible") + { + self.classname = "item_removing"; // avoid letting other mutators use it + entity wep = spawn(); + setorigin(wep, self.origin); + setmodel(wep, "models/weapons/g_ok_rl.md3"); + wep.classname = "weapon_rpc"; + wep.ok_item = TRUE; + wep.noalign = self.noalign; + wep.cnt = self.cnt; + wep.team = self.team; + wep.respawntime = autocvar_g_overkill_superguns_respawn_time; + wep.pickup_anyway = TRUE; + wep.think = spawnfunc_weapon_rpc; + wep.nextthink = time + 0.1; + return TRUE; } return FALSE; @@ -293,11 +294,51 @@ MUTATOR_HOOKFUNCTION(ok_SetModname) void ok_SetCvars() { + // we can't rely on custom balances here yet, as servers may be outdated + // hack to force overkill playermodels cvar_settemp("sv_defaultcharacter", "1"); cvar_settemp("sv_defaultplayermodel", "models/ok_player/okrobot1.dpm models/ok_player/okrobot2.dpm models/ok_player/okrobot3.dpm models/ok_player/okrobot4.dpm models/ok_player/okmale1.dpm models/ok_player/okmale2.dpm models/ok_player/okmale3.dpm models/ok_player/okmale4.dpm"); cvar_settemp("sv_defaultplayermodel_red", "models/ok_player/okrobot1.dpm models/ok_player/okrobot2.dpm models/ok_player/okrobot3.dpm models/ok_player/okrobot4.dpm"); cvar_settemp("sv_defaultplayermodel_blue", "models/ok_player/okmale1.dpm models/ok_player/okmale2.dpm models/ok_player/okmale3.dpm models/ok_player/okmale4.dpm"); + + // shotgun + cvar_settemp("g_balance_shotgun_primary_damage", "17"); + cvar_settemp("g_balance_shotgun_primary_bullets", "10"); + cvar_settemp("g_balance_shotgun_primary_force", "80"); + cvar_settemp("g_balance_shotgun_primary_spread", "0.07"); + cvar_settemp("g_balance_shotgun_primary_animtime", "0.65"); + cvar_settemp("g_balance_shotgun_primary_ammo", "6.25"); + cvar_settemp("g_balance_shotgun_reload_ammo", "50"); + cvar_settemp("g_balance_shotgun_reload_time", "2"); + + // machinegun + cvar_settemp("g_balance_machinegun_sustained_damage", "25"); + cvar_settemp("g_balance_machinegun_sustained_spread", "0.01"); + cvar_settemp("g_balance_machinegun_sustained_force", "5"); + cvar_settemp("g_balance_machinegun_reload_ammo", "30"); + cvar_settemp("g_balance_machinegun_reload_time", "1.5"); + + // vortex + cvar_settemp("g_balance_vortex_primary_damage", "100"); + cvar_settemp("g_balance_vortex_primary_force", "500"); + cvar_settemp("g_balance_vortex_primary_refire", "0.75"); + cvar_settemp("g_balance_vortex_primary_animtime", "0.95"); + cvar_settemp("g_balance_vortex_primary_ammo", "10"); + cvar_settemp("g_balance_vortex_secondary", "1"); + cvar_settemp("g_balance_vortex_charge", "0"); + cvar_settemp("g_balance_vortex_reload_ammo", "50"); + cvar_settemp("g_balance_vortex_reload_time", "2"); + + // laser + cvar_settemp("g_balance_vaporizer_secondary_force", "300"); + + // misc + cvar_settemp("g_projectiles_newton_style_2_minfactor", "1"); + cvar_settemp("g_pickup_healthmega_anyway", "0"); + cvar_settemp("g_pickup_healthmega_max", "200"); + cvar_settemp("g_pickup_armorsmall_anyway", "0"); + cvar_settemp("g_pickup_armorsmall_max", "20"); } void ok_Initialize() @@ -306,19 +347,17 @@ void ok_Initialize() precache_all_playermodels("models/ok_player/*.dpm"); - precache_model("models/weapons/h_ok_mg.iqm"); - precache_model("models/weapons/v_ok_mg.md3"); - precache_model("models/weapons/g_ok_mg.md3"); - - precache_model("models/weapons/h_ok_shotgun.iqm"); - precache_model("models/weapons/v_ok_shotgun.md3"); - precache_model("models/weapons/g_ok_shotgun.md3"); - - precache_model("models/weapons/h_ok_sniper.iqm"); - precache_model("models/weapons/v_ok_sniper.md3"); - precache_model("models/weapons/g_ok_sniper.md3"); - - precache_sound("weapons/dryfire.wav"); + precache_model(W_Model("g_ok_mg.md3")); + precache_model(W_Model("v_ok_mg.md3")); + precache_model(W_Model("h_ok_mg.iqm")); + precache_model(W_Model("g_ok_shotgun.md3")); + precache_model(W_Model("v_ok_shotgun.md3")); + precache_model(W_Model("h_ok_shotgun.iqm")); + precache_model(W_Model("g_ok_sniper.md3")); + precache_model(W_Model("v_ok_sniper.md3")); + precache_model(W_Model("h_ok_sniper.iqm")); + + precache_sound(W_Sound("dryfire")); addstat(STAT_OK_AMMO_CHARGE, AS_FLOAT, ok_use_ammocharge); addstat(STAT_OK_AMMO_CHARGEPOOL, AS_FLOAT, ok_ammo_charge); diff --git a/qcsrc/server/mutators/mutator_physical_items.qc b/qcsrc/server/mutators/mutator_physical_items.qc index c99228673..1b7815716 100644 --- a/qcsrc/server/mutators/mutator_physical_items.qc +++ b/qcsrc/server/mutators/mutator_physical_items.qc @@ -16,15 +16,17 @@ void physical_item_think() // if the item is not spawned, make sure the invisible / ghost item returns to its origin and stays there if(autocvar_g_physical_items_reset) { - if(self.owner.nextthink > time) // awaiting respawn + if(self.owner.wait > time) // awaiting respawn { setorigin(self, self.spawn_origin); self.angles = self.spawn_angles; self.solid = SOLID_NOT; + self.alpha = -1; self.movetype = MOVETYPE_NONE; } else { + self.alpha = 1; self.solid = SOLID_CORPSE; self.movetype = MOVETYPE_PHYSICS; } @@ -88,12 +90,24 @@ MUTATOR_HOOKFUNCTION(item_spawning) wep.touch = physical_item_touch; wep.event_damage = physical_item_damage; - wep.spawn_origin = self.origin; + if(!wep.cnt) + { + // fix the spawn origin + setorigin(wep, wep.origin + '0 0 1'); + entity oldself; + oldself = self; + self = wep; + builtin_droptofloor(); + self = oldself; + } + + wep.spawn_origin = wep.origin; wep.spawn_angles = self.angles; self.effects |= EF_NODRAW; // hide the original weapon self.movetype = MOVETYPE_FOLLOW; self.aiment = wep; // attach the original weapon + self.SendEntity = func_null; return FALSE; } diff --git a/qcsrc/server/mutators/mutator_piggyback.qc b/qcsrc/server/mutators/mutator_piggyback.qc new file mode 100644 index 000000000..668766767 --- /dev/null +++ b/qcsrc/server/mutators/mutator_piggyback.qc @@ -0,0 +1,295 @@ +.float cvar_cl_nocarry; + +entity pb_TailOf(entity p) +{ + while(p.piggybacker) + p = p.piggybacker; + return p; +} + +entity pb_RootOf(entity p) +{ + while(p.pbhost) + p = p.pbhost; + return p; +} + +.float pb_oldmoveflags; +.float pb_oldverticalfly; +void pb_Attach(entity host, entity slave) +{ + entity root = pb_RootOf(host); + host = pb_TailOf(host); + + if(host == slave || root == slave || host == slave.piggybacker || slave == host.piggybacker) + return; + + host.piggybacker = slave; + slave.movetype = MOVETYPE_FOLLOW; + slave.aiment = host.pbent; + slave.pbhost = host; + slave.pb_oldsolid = slave.solid; + slave.solid = SOLID_CORPSE; + slave.pb_canattach = 0; + + if(IS_MONSTER(host)) + { + host.pb_oldmoveflags = host.monster_moveflags; + host.pb_oldverticalfly = (host.spawnflags & MONSTERFLAG_FLY_VERTICAL); + host.spawnflags |= MONSTERFLAG_FLY_VERTICAL; + host.monster_moveflags = MONSTER_MOVE_NOMOVE; + } + + RemoveGrapplingHook(slave); + + Send_Notification(NOTIF_ONE, slave, MSG_CENTER, CENTER_PIGGYBACK_RIDING, (IS_MONSTER(host)) ? host.monster_name : host.netname); + if(IS_PLAYER(host)) + Send_Notification(NOTIF_ONE, host, MSG_CENTER, CENTER_PIGGYBACK_CARRYING, slave.netname); +} + +void pb_Detach(entity host) +{ + entity slave = host.piggybacker; + + if(!slave) + return; + + slave.aiment = world; + slave.pbhost = world; + + if(IS_MONSTER(host)) + { + host.monster_moveto = '0 0 0'; + host.monster_moveflags = host.pb_oldmoveflags; + if(!host.pb_oldverticalfly) { host.spawnflags &= ~MONSTERFLAG_FLY_VERTICAL; } + } + + if(IS_PLAYER(slave)) + { + // this doesn't happen when we're fixing a broken reference + + if(slave.movetype == MOVETYPE_FOLLOW) // don't reset if player was killed + slave.movetype = MOVETYPE_WALK; + slave.velocity = '0 0 0'; + slave.solid = slave.pb_oldsolid; + + tracebox(host.origin, slave.mins, slave.maxs, slave.origin, MOVE_NOMONSTERS, slave); + + if(trace_fraction < 1) + setorigin(slave, trace_endpos); // fix player glitching out of the world + + Kill_Notification(NOTIF_ONE, slave, MSG_CENTER_CPID, CPID_PIGGYBACK); + if(IS_PLAYER(host)) + Kill_Notification(NOTIF_ONE, host, MSG_CENTER_CPID, CPID_PIGGYBACK); + } + + host.piggybacker = world; +} + +void pb_PBEntThink() +{ + setorigin(self, self.owner.origin + '0 0 0.82' * self.owner.maxs_z); + self.nextthink = time; +} + +void pb_FixPBEnt(entity p) +{ + entity e = spawn(); + e.owner = p; + e.classname = "pb_ent"; + e.think = pb_PBEntThink; + e.nextthink = time; + p.pbent = e; +} + +#define BROKEN_PBREF(e) ((e).piggybacker && ((e).piggybacker.pbhost != (e) || (e).piggybacker.movetype != MOVETYPE_FOLLOW)) + +MUTATOR_HOOKFUNCTION(pb_MatchEnd) +{ + entity head; + FOR_EACH_PLAYER(head) { pb_Detach(head); } + FOR_EACH_MONSTER(head) { pb_Detach(head); } + return FALSE; +} + +MUTATOR_HOOKFUNCTION(pb_PlayerUseKey) +{ + if(MUTATOR_RETURNVALUE || gameover) { return FALSE; } + + if(self.pbhost) + { + pb_Detach(self.pbhost); + return TRUE; + } + if(self.piggybacker) + { + pb_Detach(self); + return TRUE; + } + + if(!self.vehicle) + if(!Player_Trapped(self)) + { + entity head, closest_target = world; + + head = WarpZone_FindRadius(self.origin, autocvar_g_vehicles_enter_radius, TRUE); + while(head) + { + if(IS_PLAYER(head) || (IS_MONSTER(head) && ((get_monsterinfo(head.monsterid)).spawnflags & MON_FLAG_RIDE))) + if(SAME_TEAM(head, self) || autocvar_g_piggyback_ride_enemies || (IS_MONSTER(head) && head.realowner == self)) + if(head != self) + if(!head.cvar_cl_nocarry) + if(head.deadflag == DEAD_NO) // we check for trapped status here, but better to be safe than sorry, considering the madness happening + if(!head.vehicle) + if((!Player_Trapped(head) && !Player_Trapped(self)) || (Player_Trapped(head) || Player_Trapped(self))) + { + if(closest_target) + { + if(vlen(self.origin - head.origin) < vlen(self.origin - closest_target.origin)) + { closest_target = head; } + } + else { closest_target = head; } + } + + head = head.chain; + } + + if(closest_target) + if(IS_BOT_CLIENT(closest_target)) { pb_Attach(self, closest_target); return TRUE; } + else { pb_Attach(closest_target, self); return TRUE; } + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(pb_PlayerThink) +{ + if(BROKEN_PBREF(self)) { pb_Detach(self); } + + if(!self.pbent) + pb_FixPBEnt(self); + + if(IS_MONSTER(self.pbhost)) + if(self.movement || (self.BUTTON_JUMP || self.BUTTON_CROUCH)) + if(!self.pbhost.enemy) + { + float forw, rit, updown = 0; + vector wishvel = '0 0 0'; + + makevectors(self.angles); + if(self.BUTTON_JUMP) + updown = 500; + else if(self.BUTTON_CROUCH) + updown = -500; + + if(self.movement) + { + forw = self.movement_x * 500; + rit = self.movement_y * 500; + //updown = self.movement_z * 100; + + wishvel = v_forward * forw + v_right * rit; + } + + //vector wishvel = normalize(('10 0 0' + v_forward * self.movement_x) + ('0 10 0' + v_right * self.movement_y) + ('0 0 1' * self.movement_z)); + //print(vtos(self.origin), vtos(self.origin + wishvel), "\n"); + self.pbhost.monster_moveto = self.origin + wishvel; + self.pbhost.monster_moveto_z = v_up_z * updown; + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(pb_MonsterThink) +{ + if(BROKEN_PBREF(self)) { pb_Detach(self); } + + if(!self.pbent) + pb_FixPBEnt(self); + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(pb_ResetMap) +{ + FOR_EACH_PLAYER(self) { pb_Detach(self); } + return FALSE; +} + +MUTATOR_HOOKFUNCTION(pb_RemovePlayer) +{ + if(self.piggybacker) + { + self.piggybacker.pb_canattach = 1; + pb_Detach(self); + } + + if(self.pbhost) + pb_Detach(self.pbhost); + self.pb_canattach = 0; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(pb_PlayerDies) +{ + if(!self.pbhost && self.pb_canattach) + self.pb_canattach = 0; + + if(self.piggybacker) + { + self.piggybacker.pb_canattach = 1; + pb_Detach(self); + } + + if(self.pbhost) + pb_Detach(self.pbhost); + self.pb_canattach = 0; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(pb_ItemTouch) +{ + if(self.weapon) + if(other.piggybacker) + if(other.weapons & self.weapons) + { + entity p = other; + while(p.piggybacker) + { + p = p.piggybacker; + if(!(p.weapons & self.weapons)) + { + other = p; + break; + } + } + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(pb_GetCvars) +{ + GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_nocarry, "cl_nocarry"); + return FALSE; +} + +MUTATOR_DEFINITION(mutator_piggyback) +{ + MUTATOR_HOOK(MatchEnd, pb_MatchEnd, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerUseKey, pb_PlayerUseKey, CBC_ORDER_LAST); + MUTATOR_HOOK(PlayerPreThink, pb_PlayerThink, CBC_ORDER_ANY); + MUTATOR_HOOK(MonsterMove, pb_MonsterThink, CBC_ORDER_ANY); + MUTATOR_HOOK(reset_map_players, pb_ResetMap, CBC_ORDER_FIRST); + MUTATOR_HOOK(MakePlayerObserver, pb_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(ClientDisconnect, pb_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDies, pb_PlayerDies, CBC_ORDER_ANY); + MUTATOR_HOOK(MonsterDies, pb_PlayerDies, CBC_ORDER_ANY); + MUTATOR_HOOK(MonsterRemove, pb_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(ItemTouch, pb_ItemTouch, CBC_ORDER_ANY); + MUTATOR_HOOK(GetCvars, pb_GetCvars, CBC_ORDER_ANY); + + return FALSE; +} diff --git a/qcsrc/server/mutators/mutator_piggyback.qh b/qcsrc/server/mutators/mutator_piggyback.qh new file mode 100644 index 000000000..d9cd8e883 --- /dev/null +++ b/qcsrc/server/mutators/mutator_piggyback.qh @@ -0,0 +1,12 @@ +.entity piggybacker; +.entity pbent; +.entity pbhost; +.float pb_canattach; +//.float pb_attachblock; +.float pb_oldsolid; + +void pb_Attach(entity host, entity slave); +void pb_Detach(entity host); + +entity pb_TailOf(entity p); +entity pb_RootOf(entity p); diff --git a/qcsrc/server/mutators/mutator_random_vehicles.qc b/qcsrc/server/mutators/mutator_random_vehicles.qc new file mode 100644 index 000000000..14af7589f --- /dev/null +++ b/qcsrc/server/mutators/mutator_random_vehicles.qc @@ -0,0 +1,43 @@ +MUTATOR_HOOKFUNCTION(rvehicles_OnEntityPreSpawn) +{ + if(startsWith(self.classname, "vehicle_")) + { + entity e = spawn(), oldself = self; + float i; + e.classname = self.classname; + setorigin(e, self.origin); + e.angles = self.angles; + e.team = self.team; + e.target = self.target; + e.targetname = self.targetname; + self = e; + RandomSelection_Init(); + for(i = VEH_FIRST; i <= VEH_LAST; ++i) + { + tracebox(self.origin, (get_vehicleinfo(i)).mins * 1.1, (get_vehicleinfo(i)).maxs * 1.1, self.origin, MOVE_NORMAL, self); + if(trace_fraction == 1.0 && !trace_startsolid) + { + if(i == VEH_RAPTOR) // temp hack + { + traceline(self.origin, self.origin + '0 0 700', MOVE_NOMONSTERS, self); + if(!(trace_fraction == 1.0 && !trace_startsolid)) + continue; + } + RandomSelection_Add(world, i, string_null, 1, 1); + } + } + + if(!vehicle_initialize(RandomSelection_chosen_float, FALSE)) { self = oldself; remove(e); return FALSE; } + self = oldself; + return TRUE; + } + + return FALSE; +} + +MUTATOR_DEFINITION(mutator_random_vehicles) +{ + MUTATOR_HOOK(OnEntityPreSpawn, rvehicles_OnEntityPreSpawn, CBC_ORDER_ANY); + + return FALSE; +} diff --git a/qcsrc/server/mutators/mutator_riflearena.qc b/qcsrc/server/mutators/mutator_riflearena.qc new file mode 100644 index 000000000..9e70d7e65 --- /dev/null +++ b/qcsrc/server/mutators/mutator_riflearena.qc @@ -0,0 +1,108 @@ +void ra_SetCvars() +{ + cvar_settemp("g_balance_rifle_secondary_spread", ftos(cvar("g_riflearena_rifle_secondary_spread"))); + cvar_settemp("g_balance_rifle_secondary_shots", ftos(cvar("g_riflearena_rifle_secondary_shots"))); + cvar_settemp("g_balance_rifle_secondary_animtime", ftos(cvar("g_riflearena_rifle_secondary_animtime"))); + cvar_settemp("g_balance_rifle_secondary_refire", ftos(cvar("g_riflearena_rifle_secondary_refire"))); + cvar_settemp("g_balance_rifle_secondary_damage", ftos(cvar("g_riflearena_rifle_secondary_damage"))); +} + +MUTATOR_HOOKFUNCTION(ra_PlayerDamage) +{ + if(IS_PLAYER(frag_attacker)) + if(IS_PLAYER(frag_target)) + { + if (DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER)) + { + if(frag_attacker == frag_target) + frag_damage = 5; + else + frag_damage = 0; + frag_mirrordamage = 0; + if (frag_target != frag_attacker) + { + if (frag_target.health >= 1 && IS_PLAYER(frag_target) && !frag_target.frozen && frag_target.deadflag == DEAD_NO) + Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); + frag_force = '0 0 0'; + } + } + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(ra_FilterItem) +{ + switch (self.items) + { + case IT_5HP: + case IT_ARMOR_SHARD: + return FALSE; + } + + return TRUE; +} + +MUTATOR_HOOKFUNCTION(ra_StartItems) +{ + start_items |= IT_UNLIMITED_AMMO; + start_ammo_nails = warmup_start_ammo_nails = 100; + warmup_start_weapons = start_weapons = WEPSET_RIFLE; + + if(autocvar_g_riflearena_withlaser) + { + start_weapons |= WEPSET_BLASTER; + warmup_start_weapons |= WEPSET_BLASTER; + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(ra_ForbidThrowCurrentWeapon) +{ + return TRUE; +} + +MUTATOR_HOOKFUNCTION(ra_BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":RA"); + return FALSE; +} + +MUTATOR_HOOKFUNCTION(ra_BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Rifle Arena"); + return FALSE; +} + +MUTATOR_HOOKFUNCTION(ra_SetModname) +{ + modname = "Rifle Arena"; + return TRUE; +} + +MUTATOR_DEFINITION(mutator_riflearena) +{ + MUTATOR_HOOK(PlayerDamage_Calculate, ra_PlayerDamage, CBC_ORDER_ANY); + MUTATOR_HOOK(FilterItem, ra_FilterItem, CBC_ORDER_ANY); + MUTATOR_HOOK(SetStartItems, ra_StartItems, CBC_ORDER_ANY); + MUTATOR_HOOK(ForbidThrowCurrentWeapon, ra_ForbidThrowCurrentWeapon, CBC_ORDER_ANY); + MUTATOR_HOOK(BuildMutatorsString, ra_BuildMutatorsString, CBC_ORDER_ANY); + MUTATOR_HOOK(BuildMutatorsPrettyString, ra_BuildMutatorsPrettyString, CBC_ORDER_ANY); + MUTATOR_HOOK(SetModname, ra_SetModname, CBC_ORDER_ANY); + + MUTATOR_ONADD + { + ra_SetCvars(); + + WEP_ACTION(WEP_BLASTER, WR_INIT); + WEP_ACTION(WEP_RIFLE, WR_INIT); + } + MUTATOR_ONREMOVE + { + print("This cannot be removed at runtime\n"); + return -1; + } + + return FALSE; +} diff --git a/qcsrc/server/mutators/mutator_spawn_near_teammate.qc b/qcsrc/server/mutators/mutator_spawn_near_teammate.qc index ffae9543b..9e69ebd13 100644 --- a/qcsrc/server/mutators/mutator_spawn_near_teammate.qc +++ b/qcsrc/server/mutators/mutator_spawn_near_teammate.qc @@ -3,9 +3,11 @@ .float msnt_timer; .vector msnt_deathloc; +.float cvar_cl_spawn_near_teammate; + MUTATOR_HOOKFUNCTION(msnt_Spawn_Score) { - if(autocvar_g_spawn_near_teammate_ignore_spawnpoint) + if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && self.cvar_cl_spawn_near_teammate)) return 0; entity p; @@ -42,7 +44,8 @@ MUTATOR_HOOKFUNCTION(msnt_Spawn_Score) MUTATOR_HOOKFUNCTION(msnt_PlayerSpawn) { // Note: when entering this, fixangle is already set. - if(autocvar_g_spawn_near_teammate_ignore_spawnpoint) + if(autocvar_g_spawn_near_teammate_ignore_spawnpoint || self.cvar_cl_spawn_near_teammate) + if(!Player_Trapped(self)) { if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death) self.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death; @@ -56,8 +59,8 @@ MUTATOR_HOOKFUNCTION(msnt_PlayerSpawn) if(team_mate.deadflag == DEAD_NO) if(team_mate.msnt_timer < time) if(SAME_TEAM(self, team_mate)) + if(!Player_Trapped(team_mate)) if(time > team_mate.spawnshieldtime) // spawn shielding - if(team_mate.frozen == 0) if(team_mate != self) { tracebox(team_mate.origin, PL_MIN, PL_MAX, team_mate.origin - '0 0 100', MOVE_WORLDONLY, team_mate); @@ -154,11 +157,18 @@ MUTATOR_HOOKFUNCTION(msnt_PlayerDies) return 0; } +MUTATOR_HOOKFUNCTION(msnt_GetCvars) +{ + GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_spawn_near_teammate, "cl_spawn_near_teammate"); + return FALSE; +} + MUTATOR_DEFINITION(mutator_spawn_near_teammate) { MUTATOR_HOOK(Spawn_Score, msnt_Spawn_Score, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerSpawn, msnt_PlayerSpawn, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerDies, msnt_PlayerDies, CBC_ORDER_ANY); + MUTATOR_HOOK(GetCvars, msnt_GetCvars, CBC_ORDER_ANY); return 0; } diff --git a/qcsrc/server/mutators/mutator_superspec.qc b/qcsrc/server/mutators/mutator_superspec.qc index 74e17a6a1..e6ff99419 100644 --- a/qcsrc/server/mutators/mutator_superspec.qc +++ b/qcsrc/server/mutators/mutator_superspec.qc @@ -1,8 +1,6 @@ #define _SSMAGIX "SUPERSPEC_OPTIONSFILE_V1" #define _ISLOCAL ((edict_num(1) == self) ? TRUE : FALSE) -#define ASF_STRENGTH 1 -#define ASF_SHIELD 2 #define ASF_MEGA_AR 4 #define ASF_MEGA_HP 8 #define ASF_FLAG_GRAB 16 @@ -117,9 +115,7 @@ MUTATOR_HOOKFUNCTION(superspec_ItemTouch) } } - if((self.autospec_flags & ASF_SHIELD && _item.invincible_finished) || - (self.autospec_flags & ASF_STRENGTH && _item.strength_finished) || - (self.autospec_flags & ASF_MEGA_AR && _item.classname == "item_armor_large") || + if( (self.autospec_flags & ASF_MEGA_AR && _item.classname == "item_armor_large") || (self.autospec_flags & ASF_MEGA_HP && _item.classname == "item_health_mega") || (self.autospec_flags & ASF_FLAG_GRAB && _item.classname == "item_flag_team")) { @@ -272,8 +268,6 @@ MUTATOR_HOOKFUNCTION(superspec_SV_ParseClientCommand) if(argv(1) == "help") { _aspeco = "use cmd autospec [option] [on|off] to set options\n\n"; - _aspeco = strcat(_aspeco, "^3 strength ^7(short^5 st^7) for automatic spectate on strength powerup\n"); - _aspeco = strcat(_aspeco, "^3 shield ^7(short^5 sh^7) for automatic spectate on shield powerup\n"); _aspeco = strcat(_aspeco, "^3 mega_health ^7(short^5 mh^7) for automatic spectate on mega health\n"); _aspeco = strcat(_aspeco, "^3 mega_armor ^7(short^5 ma^7) for automatic spectate on mega armor\n"); _aspeco = strcat(_aspeco, "^3 flag_grab ^7(short^5 fg^7) for automatic spectate on CTF flag grab\n"); @@ -309,8 +303,6 @@ MUTATOR_HOOKFUNCTION(superspec_SV_ParseClientCommand) } else { - if((argv(i) == "strength") || (argv(i) == "st")) _bits |= ASF_STRENGTH; - if((argv(i) == "shield") || (argv(i) == "sh")) _bits |= ASF_SHIELD; if((argv(i) == "mega_health") || (argv(i) == "mh")) _bits |= ASF_MEGA_HP; if((argv(i) == "mega_armor") || (argv(i) == "ma")) _bits |= ASF_MEGA_AR; if((argv(i) == "flag_grab") || (argv(i) == "fg")) _bits |= ASF_FLAG_GRAB; @@ -324,8 +316,6 @@ MUTATOR_HOOKFUNCTION(superspec_SV_ParseClientCommand) } _aspeco = ""; - OPTIONINFO(self.autospec_flags, _aspeco, ASF_STRENGTH, "Strength", "strength", "st"); - OPTIONINFO(self.autospec_flags, _aspeco, ASF_SHIELD, "Shield", "shield", "sh"); OPTIONINFO(self.autospec_flags, _aspeco, ASF_MEGA_HP, "Mega Health", "mega_health", "mh"); OPTIONINFO(self.autospec_flags, _aspeco, ASF_MEGA_AR, "Mega Armor", "mega_armor", "ma"); OPTIONINFO(self.autospec_flags, _aspeco, ASF_FLAG_GRAB, "Flag grab", "flag_grab","fg"); @@ -343,7 +333,7 @@ MUTATOR_HOOKFUNCTION(superspec_SV_ParseClientCommand) entity _player; FOR_EACH_PLAYER(_player) { - if(_player.strength_finished > time || _player.invincible_finished > time) + if(_player.buffs) return _spectate(_player); } @@ -351,32 +341,6 @@ MUTATOR_HOOKFUNCTION(superspec_SV_ParseClientCommand) return TRUE; } - if(cmd_name == "followstrength") - { - entity _player; - FOR_EACH_PLAYER(_player) - { - if(_player.strength_finished > time) - return _spectate(_player); - } - - superspec_msg("", "", self, "No active Strength\n", 1); - return TRUE; - } - - if(cmd_name == "followshield") - { - entity _player; - FOR_EACH_PLAYER(_player) - { - if(_player.invincible_finished > time) - return _spectate(_player); - } - - superspec_msg("", "", self, "No active Shield\n", 1); - return TRUE; - } - if(cmd_name == "followfc") { if(!g_ctf) @@ -387,11 +351,15 @@ MUTATOR_HOOKFUNCTION(superspec_SV_ParseClientCommand) float found = FALSE; if(cmd_argc == 2) + if(!ctf_oneflag) // no team flags in 1 flag CTF { - if(argv(1) == "red") - _team = NUM_TEAM_1; - else - _team = NUM_TEAM_2; + switch(argv(1)) + { + case "red": _team = NUM_TEAM_1; break; + case "blue": _team = NUM_TEAM_2; break; + case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break; + case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break; + } } FOR_EACH_PLAYER(_player) diff --git a/qcsrc/server/mutators/mutator_touchexplode.qc b/qcsrc/server/mutators/mutator_touchexplode.qc index 5f02a8aba..d50536cc5 100644 --- a/qcsrc/server/mutators/mutator_touchexplode.qc +++ b/qcsrc/server/mutators/mutator_touchexplode.qc @@ -6,8 +6,8 @@ void PlayerTouchExplode(entity p1, entity p2) org = (p1.origin + p2.origin) * 0.5; org_z += (p1.mins_z + p2.mins_z) * 0.5; - sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM); - pointparticles(particleeffectnum("explosion_small"), org, '0 0 0', 1); + sound(self, CH_TRIGGER, W_Sound("grenade_impact"), VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_EXPLOSION_SMALL, org, '0 0 0', 1); entity e; e = spawn(); @@ -23,13 +23,13 @@ MUTATOR_HOOKFUNCTION(touchexplode_PlayerThink) if(!self.frozen) if(IS_PLAYER(self)) if(self.deadflag == DEAD_NO) - if (!IS_INDEPENDENT_PLAYER(self)) + if(!IS_INDEPENDENT_PLAYER(self)) FOR_EACH_PLAYER(other) if(self != other) { if(time > other.touchexplode_time) if(!other.frozen) if(other.deadflag == DEAD_NO) - if (!IS_INDEPENDENT_PLAYER(other)) + if(!IS_INDEPENDENT_PLAYER(other)) if(boxesoverlap(self.absmin, self.absmax, other.absmin, other.absmax)) { PlayerTouchExplode(self, other); diff --git a/qcsrc/server/mutators/mutator_walljump.qc b/qcsrc/server/mutators/mutator_walljump.qc new file mode 100644 index 000000000..489e55b0f --- /dev/null +++ b/qcsrc/server/mutators/mutator_walljump.qc @@ -0,0 +1,75 @@ +.float lastwj; + +vector PlayerTouchWall () +{ + local float dist, max_normal; + local vector start, end; + dist = 10; + max_normal = 0.2; + start = self.origin; + end = start + v_forward * 100; + tracebox (start, self.mins, self.maxs, end, TRUE, self); + if (trace_fraction < 1 && vlen (self.origin - trace_endpos) < dist && trace_plane_normal_z < max_normal) + if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)) + return trace_plane_normal; + end = start - v_forward * 100; + tracebox (start, self.mins, self.maxs, end, TRUE, self); + if (trace_fraction < 1 && vlen (self.origin - trace_endpos) < dist && trace_plane_normal_z < max_normal) + if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)) + return trace_plane_normal; + end = start + v_right * 100; + tracebox (start, self.mins, self.maxs, end, TRUE, self); + if (trace_fraction < 1 && vlen (self.origin - trace_endpos) < dist && trace_plane_normal_z < max_normal) + if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)) + return trace_plane_normal; + end = start - v_right * 100; + tracebox (start, self.mins, self.maxs, end, TRUE, self); + if (trace_fraction < 1 && vlen (self.origin - trace_endpos) < dist && trace_plane_normal_z < max_normal) + if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)) + return trace_plane_normal; + return '0 0 0'; +} + +MUTATOR_HOOKFUNCTION(walljump_PlayerJump) +{ + if(autocvar_g_walljump) + if(time - self.lastwj > autocvar_g_walljump_delay) + if(!(self.flags & FL_ONGROUND)) + if(self.movetype != MOVETYPE_NONE && self.movetype != MOVETYPE_FOLLOW && self.movetype != MOVETYPE_FLY && self.movetype != MOVETYPE_NOCLIP) + if(self.flags & FL_JUMPRELEASED) + if(!self.frozen) + if(self.deadflag == DEAD_NO) + { + vector plane_normal = PlayerTouchWall(); + + if(plane_normal != '0 0 0') + { + self.lastwj = time; + float wj_force = autocvar_g_walljump_force; + float wj_xy_factor = autocvar_g_walljump_velocity_xy_factor; + float wj_z_factor = autocvar_g_walljump_velocity_z_factor; + self.velocity_x += plane_normal_x * wj_force; + self.velocity_x /= wj_xy_factor; + self.velocity_y += plane_normal_y * wj_force; + self.velocity_y /= wj_xy_factor; + self.velocity_z = autocvar_sv_jumpvelocity * wj_z_factor; + if (self.crouch) self.velocity_z *= -1; + self.oldvelocity = self.velocity; + + Send_Effect(EFFECT_SMOKE_RING, trace_endpos, plane_normal, 5); + PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND); + animdecide_setaction(self, ANIMACTION_JUMP, TRUE); + + player_multijump = TRUE; + } + } + + return FALSE; +} + +MUTATOR_DEFINITION(mutator_walljump) +{ + MUTATOR_HOOK(PlayerJump, walljump_PlayerJump, CBC_ORDER_ANY); + + return FALSE; +} diff --git a/qcsrc/server/mutators/mutator_zombie_apocalypse.qc b/qcsrc/server/mutators/mutator_zombie_apocalypse.qc new file mode 100644 index 000000000..ce2f11962 --- /dev/null +++ b/qcsrc/server/mutators/mutator_zombie_apocalypse.qc @@ -0,0 +1,48 @@ +float za_spawn_delay; + +void za_SpawnMonster() +{ + if(gameover) { return; } + + entity e = spawn(), mon; + + if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256)) + mon = spawnmonster(autocvar_g_za_spawnmonster, 0, self, self, e.origin, FALSE, FALSE, 2); + + e.think = SUB_Remove; + e.nextthink = time + 0.1; +} + +MUTATOR_HOOKFUNCTION(za_StartFrame) +{ + if(time < za_spawn_delay || autocvar_g_za_max_monsters <= 0 || !autocvar_g_za) + return FALSE; + + float n_monsters = 0, maxmon = autocvar_g_za_max_monsters; + entity head; + + // count dead monsters too (zombies) + FOR_EACH_MONSTER(head) ++n_monsters; + + while(n_monsters < maxmon) + { + ++n_monsters; + za_SpawnMonster(); + } + + za_spawn_delay = time + autocvar_g_za_spawn_delay; + + return FALSE; +} + +MUTATOR_DEFINITION(mutator_zombie_apocalypse) +{ + MUTATOR_HOOK(SV_StartFrame, za_StartFrame, CBC_ORDER_ANY); + + MUTATOR_ONADD + { + za_spawn_delay = time + game_starttime; + } + + return FALSE; +} diff --git a/qcsrc/server/mutators/mutators.qc b/qcsrc/server/mutators/mutators.qc index 0fa2caab2..7ab7fe812 100644 --- a/qcsrc/server/mutators/mutators.qc +++ b/qcsrc/server/mutators/mutators.qc @@ -4,27 +4,35 @@ void mutators_add() { if(cvar(mut_cvar) && dependence) { MUTATOR_ADD(mut_name); } } CHECK_MUTATOR_ADD("g_dodging", mutator_dodging, 1); - CHECK_MUTATOR_ADD("g_spawn_near_teammate", mutator_spawn_near_teammate, teamplay); + CHECK_MUTATOR_ADD("g_spawn_near_teammate", mutator_spawn_near_teammate, teamplay && !g_assault); CHECK_MUTATOR_ADD("g_physical_items", mutator_physical_items, 1); CHECK_MUTATOR_ADD("g_touchexplode", mutator_touchexplode, 1); CHECK_MUTATOR_ADD("g_instagib", mutator_instagib, !g_nexball); CHECK_MUTATOR_ADD("g_invincible_projectiles", mutator_invincibleprojectiles, 1); - CHECK_MUTATOR_ADD("g_new_toys", mutator_new_toys, !cvar("g_instagib") && !cvar("g_overkill")); - CHECK_MUTATOR_ADD("g_nix", mutator_nix, !cvar("g_instagib") && !cvar("g_overkill")); + CHECK_MUTATOR_ADD("g_new_toys", mutator_new_toys, !cvar("g_instagib") && !cvar("g_overkill") && !cvar("g_riflearena") && !cvar("g_melee_only")); + CHECK_MUTATOR_ADD("g_nix", mutator_nix, !cvar("g_instagib") && !cvar("g_overkill") && !cvar("g_riflearena") && !cvar("g_melee_only")); CHECK_MUTATOR_ADD("g_rocket_flying", mutator_rocketflying, 1); CHECK_MUTATOR_ADD("g_vampire", mutator_vampire, !cvar("g_instagib")); CHECK_MUTATOR_ADD("g_superspectate", mutator_superspec, 1); - CHECK_MUTATOR_ADD("g_pinata", mutator_pinata, !cvar("g_instagib") && !cvar("g_overkill")); + CHECK_MUTATOR_ADD("g_freeze", mutator_freeze, !g_freezetag && !g_ca && !g_jailbreak && !g_cts && !g_race && !g_infection); + CHECK_MUTATOR_ADD("g_pinata", mutator_pinata, !cvar("g_instagib") && !cvar("g_overkill") && !cvar("g_riflearena") && !cvar("g_melee_only")); CHECK_MUTATOR_ADD("g_midair", mutator_midair, 1); CHECK_MUTATOR_ADD("g_bloodloss", mutator_bloodloss, 1); CHECK_MUTATOR_ADD("g_random_gravity", mutator_random_gravity, 1); CHECK_MUTATOR_ADD("g_multijump", mutator_multijump, 1); - CHECK_MUTATOR_ADD("g_melee_only", mutator_melee_only, !cvar("g_instagib") && !g_nexball); + CHECK_MUTATOR_ADD("g_melee_only", mutator_melee_only, !cvar("g_instagib") && !g_nexball && !cvar("g_overkill") && !cvar("g_riflearena")); CHECK_MUTATOR_ADD("g_nades", mutator_nades, 1); CHECK_MUTATOR_ADD("g_sandbox", sandbox, 1); + CHECK_MUTATOR_ADD("g_riflearena", mutator_riflearena, !cvar("g_instagib") && !g_nexball && !cvar("g_overkill")); CHECK_MUTATOR_ADD("g_campcheck", mutator_campcheck, 1); - CHECK_MUTATOR_ADD("g_overkill", mutator_overkill, !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill"); - CHECK_MUTATOR_ADD("g_buffs", mutator_buffs, 1); + CHECK_MUTATOR_ADD("g_overkill", mutator_overkill, !cvar("g_instagib") && !g_nexball); + CHECK_MUTATOR_ADD("g_za", mutator_zombie_apocalypse, cvar("g_monsters") && !g_nexball && !g_invasion && !g_cts && !g_race); + CHECK_MUTATOR_ADD("g_walljump", mutator_walljump, 1); + CHECK_MUTATOR_ADD("g_hats", mutator_hats, !cvar("g_overkill")); + CHECK_MUTATOR_ADD("g_random_vehicles", mutator_random_vehicles, cvar("g_vehicles")); + CHECK_MUTATOR_ADD("g_piggyback", mutator_piggyback, 1); + CHECK_MUTATOR_ADD("g_buffs", mutator_buffs, !g_nexball && !g_race && !g_cts); + CHECK_MUTATOR_ADD("g_itemeditor", mutator_itemeditor, cvar("g_pickup_items") && !g_ca); #undef CHECK_MUTATOR_ADD } diff --git a/qcsrc/server/mutators/mutators.qh b/qcsrc/server/mutators/mutators.qh index 4dcc9df2f..9e78011d1 100644 --- a/qcsrc/server/mutators/mutators.qh +++ b/qcsrc/server/mutators/mutators.qh @@ -6,12 +6,17 @@ MUTATOR_DECLARATION(gamemode_keepaway); MUTATOR_DECLARATION(gamemode_ctf); MUTATOR_DECLARATION(gamemode_nexball); MUTATOR_DECLARATION(gamemode_onslaught); +MUTATOR_DECLARATION(gamemode_conquest); MUTATOR_DECLARATION(gamemode_domination); MUTATOR_DECLARATION(gamemode_lms); MUTATOR_DECLARATION(gamemode_invasion); +MUTATOR_DECLARATION(gamemode_vip); +MUTATOR_DECLARATION(gamemode_infection); +MUTATOR_DECLARATION(gamemode_jailbreak); MUTATOR_DECLARATION(gamemode_cts); MUTATOR_DECLARATION(gamemode_race); MUTATOR_DECLARATION(gamemode_tdm); +MUTATOR_DECLARATION(gamemode_deathmatch); MUTATOR_DECLARATION(mutator_dodging); MUTATOR_DECLARATION(mutator_invincibleprojectiles); @@ -23,6 +28,8 @@ MUTATOR_DECLARATION(mutator_physical_items); MUTATOR_DECLARATION(mutator_vampire); MUTATOR_DECLARATION(mutator_superspec); MUTATOR_DECLARATION(mutator_instagib); +MUTATOR_DECLARATION(mutator_freeze); +MUTATOR_DECLARATION(mutator_overkill); MUTATOR_DECLARATION(mutator_touchexplode); MUTATOR_DECLARATION(mutator_pinata); MUTATOR_DECLARATION(mutator_midair); @@ -31,9 +38,14 @@ MUTATOR_DECLARATION(mutator_random_gravity); MUTATOR_DECLARATION(mutator_multijump); MUTATOR_DECLARATION(mutator_melee_only); MUTATOR_DECLARATION(mutator_nades); +MUTATOR_DECLARATION(mutator_riflearena); MUTATOR_DECLARATION(mutator_campcheck); +MUTATOR_DECLARATION(mutator_zombie_apocalypse); +MUTATOR_DECLARATION(mutator_walljump); +MUTATOR_DECLARATION(mutator_hats); +MUTATOR_DECLARATION(mutator_random_vehicles); +MUTATOR_DECLARATION(mutator_piggyback); MUTATOR_DECLARATION(mutator_buffs); +MUTATOR_DECLARATION(mutator_itemeditor); MUTATOR_DECLARATION(sandbox); -MUTATOR_DECLARATION(mutator_overkill); - diff --git a/qcsrc/server/mutators/mutators_include.qc b/qcsrc/server/mutators/mutators_include.qc index 0f52e34f0..3727924c6 100644 --- a/qcsrc/server/mutators/mutators_include.qc +++ b/qcsrc/server/mutators/mutators_include.qc @@ -9,10 +9,15 @@ #include "gamemode_nexball.qc" #include "gamemode_onslaught.qc" #include "gamemode_lms.qc" +#include "gamemode_vip.qc" #include "gamemode_invasion.qc" +#include "gamemode_infection.qc" +#include "gamemode_jailbreak.qc" #include "gamemode_race.qc" #include "gamemode_cts.qc" #include "gamemode_tdm.qc" +#include "gamemode_conquest.qc" +#include "gamemode_deathmatch.qc" #include "mutator_invincibleproj.qc" #include "mutator_new_toys.qc" @@ -24,6 +29,7 @@ #include "mutator_physical_items.qc" #include "sandbox.qc" #include "mutator_superspec.qc" +#include "mutator_freeze.qc" #include "mutator_overkill.qc" #include "mutator_instagib.qc" #include "mutator_touchexplode.qc" @@ -34,5 +40,12 @@ #include "mutator_multijump.qc" #include "mutator_melee_only.qc" #include "mutator_nades.qc" +#include "mutator_riflearena.qc" #include "mutator_campcheck.qc" +#include "mutator_zombie_apocalypse.qc" +#include "mutator_walljump.qc" +#include "mutator_hats.qc" +#include "mutator_random_vehicles.qc" +#include "mutator_piggyback.qc" #include "mutator_buffs.qc" +#include "mutator_itemeditor.qc" diff --git a/qcsrc/server/mutators/mutators_include.qh b/qcsrc/server/mutators/mutators_include.qh index c869ab696..4c27cbe86 100644 --- a/qcsrc/server/mutators/mutators_include.qh +++ b/qcsrc/server/mutators/mutators_include.qh @@ -4,15 +4,24 @@ #include "gamemode_ca.qh" #include "gamemode_ctf.qh" #include "gamemode_domination.qh" +#include "gamemode_freezetag.qh" #include "gamemode_keyhunt.qh" #include "gamemode_keepaway.qh" #include "gamemode_nexball.qh" #include "gamemode_lms.qh" +#include "gamemode_vip.qh" #include "gamemode_invasion.qh" -#include "gamemode_race.qh" +#include "gamemode_infection.qh" +#include "gamemode_jailbreak.qh" #include "gamemode_cts.qh" +#include "gamemode_race.qh" +#include "gamemode_onslaught.qh" +#include "gamemode_conquest.qh" #include "mutator_dodging.qh" #include "mutator_overkill.qh" +#include "mutator_freeze.qh" +#include "mutator_instagib.qh" #include "mutator_nades.qh" +#include "mutator_piggyback.qh" #include "mutator_buffs.qh" diff --git a/qcsrc/server/mutators/sandbox.qc b/qcsrc/server/mutators/sandbox.qc index e84c6d696..e8db22137 100644 --- a/qcsrc/server/mutators/sandbox.qc +++ b/qcsrc/server/mutators/sandbox.qc @@ -4,6 +4,16 @@ float object_count; .entity object_attach; .string material; +void sandbox_SnapToGrid(entity e, float amnt) +{ + vector orig = e.origin; + orig_x = rint(orig_x / amnt) * amnt; + orig_y = rint(orig_y / amnt) * amnt; + orig_z = rint(orig_z / amnt) * amnt; + setorigin(e, orig); + e.prevorigin = orig; +} + .float touch_timer; void sandbox_ObjectFunction_Touch() { @@ -34,6 +44,18 @@ void sandbox_ObjectFunction_Think() { entity e; + if(autocvar_g_sandbox_snaptogrid && self.origin != self.prevorigin) + { + sandbox_SnapToGrid(self, autocvar_g_sandbox_snaptogrid); + + if(self.angles_y > 45) + self.angles_y = 90; + else if(self.angles_y < -45) + self.angles_y = -90; + else if(self.angles_y < 45 || self.angles_y > -45) + self.angles_y = 0; + } + // decide if and how this object can be grabbed if(autocvar_g_sandbox_readonly) self.grab = 0; // no grabbing @@ -56,7 +78,7 @@ void sandbox_ObjectFunction_Think() } self.nextthink = time; - + CSQCMODEL_AUTOUPDATE(); } @@ -84,11 +106,14 @@ entity sandbox_ObjectEdit_Get(float permissions) void sandbox_ObjectEdit_Scale(entity e, float f) { e.scale = f; + e.modelscale = f; if(e.scale) { e.scale = bound(autocvar_g_sandbox_object_scale_min, e.scale, autocvar_g_sandbox_object_scale_max); + e.modelscale = f; setmodel(e, e.model); // reset mins and maxs based on mesh setsize(e, e.mins * e.scale, e.maxs * e.scale); // adapt bounding box size to model size + FixSize(e); } } @@ -174,7 +199,7 @@ entity sandbox_ObjectSpawn(float database) setorigin(e, trace_endpos); e.angles_y = self.v_angle_y; } - + oldself = self; self = e; CSQCMODEL_AUTOINIT(); @@ -323,7 +348,7 @@ entity sandbox_ObjectPort_Load(string s, float database) parent = e; // mark parent objects as such } // properties stored for all objects - setmodel(e, argv(argv_num)); ++argv_num; + setmodel_fixsize(e, argv(argv_num)); ++argv_num; e.skin = stof(argv(argv_num)); ++argv_num; e.alpha = stof(argv(argv_num)); ++argv_num; e.colormod = stov(argv(argv_num)); ++argv_num; @@ -480,6 +505,11 @@ MUTATOR_HOOKFUNCTION(sandbox_PlayerCommand) print_to(self, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(self.object_flood - time), " ^7seconds beofore spawning another object")); return TRUE; } + if(IS_SPEC(self) || IS_OBSERVER(self)) + { + print_to(self, "^1SANDBOX - WARNING: ^7Cannot spawn objects while spectating"); + return TRUE; + } self.object_flood = time + autocvar_g_sandbox_editor_flood; if(object_count >= autocvar_g_sandbox_editor_maxobjects) { @@ -498,7 +528,7 @@ MUTATOR_HOOKFUNCTION(sandbox_PlayerCommand) } e = sandbox_ObjectSpawn(FALSE); - setmodel(e, argv(2)); + setmodel_fixsize(e, argv(2)); if(autocvar_g_sandbox_info > 0) print(strcat("^3SANDBOX - SERVER: ^7", self.netname, " spawned an object at origin ^3", vtos(e.origin), "\n")); @@ -644,6 +674,11 @@ MUTATOR_HOOKFUNCTION(sandbox_PlayerCommand) e.frame = stof(argv(3)); break; case "scale": + if(e.solid == SOLID_BSP) + { + print_to(self, "^1SANDBOX - WARNING: ^7BSP solid objects cannot be resized"); + return TRUE; + } sandbox_ObjectEdit_Scale(e, stof(argv(3))); break; case "solidity": @@ -655,6 +690,12 @@ MUTATOR_HOOKFUNCTION(sandbox_PlayerCommand) case "1": // solid e.solid = SOLID_BBOX; break; + case "2": // bsp solid + if(autocvar_g_sandbox_allow_bspsolid) + { + e.solid = SOLID_BSP; + break; + } default: break; } @@ -805,33 +846,21 @@ MUTATOR_HOOKFUNCTION(sandbox_StartFrame) return TRUE; } -MUTATOR_HOOKFUNCTION(sandbox_SetModname) +void sandbox_DelayedInit() { - modname = "Sandbox"; - return TRUE; + sandbox_Database_Load(); } MUTATOR_DEFINITION(sandbox) { MUTATOR_HOOK(SV_ParseClientCommand, sandbox_PlayerCommand, CBC_ORDER_ANY); MUTATOR_HOOK(SV_StartFrame, sandbox_StartFrame, CBC_ORDER_ANY); - MUTATOR_HOOK(SetModname, sandbox_SetModname, CBC_ORDER_ANY); MUTATOR_ONADD { autosave_time = time + autocvar_g_sandbox_storage_autosave; // don't save the first server frame if(autocvar_g_sandbox_storage_autoload) - sandbox_Database_Load(); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // nothing to roll back - } - - MUTATOR_ONREMOVE - { - // nothing to remove + InitializeEntity(world, sandbox_DelayedInit, INITPRIO_LAST); } return FALSE; diff --git a/qcsrc/server/portals.qc b/qcsrc/server/portals.qc index aff0652d4..540ae1a36 100644 --- a/qcsrc/server/portals.qc +++ b/qcsrc/server/portals.qc @@ -254,6 +254,10 @@ void Portal_Touch() if(other.classname == "grapplinghook") return; // handled by think + if(!autocvar_g_vehicles_teleportable) + if(IS_VEHICLE(other)) + return; // no teleporting vehicles? + if(!self.enemy) error("Portal_Touch called for a broken portal\n"); @@ -399,7 +403,7 @@ void Portal_Remove(entity portal, float killed) { fixedmakevectors(portal.mangle); sound(portal, CH_SHOTS, "porto/explode.wav", VOL_BASE, ATTEN_NORM); - pointparticles(particleeffectnum("rocket_explode"), portal.origin + v_forward * 16, v_forward * 1024, 4); + Send_Effect(EFFECT_ROCKET_EXPLODE, portal.origin + v_forward * 16, v_forward * 1024, 4); remove(portal); } else diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src index 78c0b091e..7c1374d9c 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -24,11 +24,9 @@ sys-post.qh ../common/command/rpn.qh ../common/command/generic.qh ../common/command/shared_defs.qh +../common/command/script.qh ../common/net_notice.qh ../common/animdecide.qh -../common/monsters/monsters.qh -../common/monsters/sv_monsters.qh -../common/monsters/spawn.qh ../common/weapons/config.qh ../common/weapons/weapons.qh // TODO @@ -49,14 +47,32 @@ autocvars.qh constants.qh defs.qh // Should rename this, it has fields and globals +../common/jeff.qh + ../common/notifications.qh // must be after autocvars ../common/deathtypes.qh // must be after notifications +../common/effects.qh + +../common/monsters/monsters.qh +../common/monsters/sv_monsters.qh +../common/monsters/spawn.qh + +../common/minigames/sv_minigames.qh +../common/minigames/minigames.qh + +../common/turrets/turrets.qh +../common/turrets/sv_turrets.qh +../common/turrets/util.qc + +../common/vehicles/vehicles_include.qh + mutators/mutators_include.qh -//// tZork Turrets //// -tturrets/include/turrets_early.qh -vehicles/vehicles_def.qh +generator.qh +controlpoint.qh + +teamplay.qh campaign.qh ../common/campaign_common.qh @@ -116,9 +132,7 @@ bot/bot.qc g_subs.qc -g_tetris.qc - -//runematch.qc +jeff.qc g_violence.qc g_damage.qc @@ -130,7 +144,6 @@ cl_physics.qc // tZork's libs movelib.qc steerlib.qc -pathlib/pathlib.qh g_world.qc g_casings.qc @@ -172,10 +185,6 @@ cl_client.qc t_plats.qc antilag.qc -//ctf.qc -//domination.qc -//mode_onslaught.qc -//nexball.qc g_hook.qc t_swamp.qc @@ -188,6 +197,7 @@ campaign.qc ../common/command/markup.qc ../common/command/rpn.qc ../common/command/generic.qc +../common/command/script.qc ../common/net_notice.qc command/common.qc @@ -198,8 +208,6 @@ command/getreplies.qc command/cmd.qc command/sv_cmd.qc -//assault.qc - ipban.qc ../common/mapinfo.qc @@ -210,22 +218,23 @@ t_quake.qc race.qc - -//// tZork Turrets //// -tturrets/include/turrets.qh -vehicles/vehicles.qh - scores.qc spawnpoints.qc portals.qc +generator.qc +controlpoint.qc + target_spawn.qc func_breakable.qc target_music.qc +../common/vehicles/vehicles_include.qc + ../common/nades.qc + ../common/buffs.qc ../csqcmodellib/sv_model.qc @@ -243,6 +252,11 @@ round_handler.qc ../common/monsters/spawn.qc +../common/turrets/sv_turrets.qc +../common/turrets/turrets.qc +../common/turrets/checkpoint.qc +../common/turrets/targettrigger.qc + mutators/mutators_include.qc ../warpzonelib/anglestransform.qc @@ -255,3 +269,8 @@ mutators/mutators_include.qc ../common/test.qc ../common/util.qc ../common/notifications.qc +../common/effects.qc + + +../common/minigames/minigames.qc +../common/minigames/sv_minigames.qc diff --git a/qcsrc/server/race.qc b/qcsrc/server/race.qc index 1c12058d0..f3cee85ef 100644 --- a/qcsrc/server/race.qc +++ b/qcsrc/server/race.qc @@ -1182,3 +1182,23 @@ float race_GetFractionalLapCount(entity e) return l + c / nc; } + +void race_send_speedaward(float msg) +{ + // send the best speed of the round + WriteByte(msg, SVC_TEMPENTITY); + WriteByte(msg, TE_CSQC_RACE); + WriteByte(msg, RACE_NET_SPEED_AWARD); + WriteInt24_t(msg, floor(speedaward_speed+0.5)); + WriteString(msg, speedaward_holder); +} + +void race_send_speedaward_alltimebest(float msg) +{ + // send the best speed + WriteByte(msg, SVC_TEMPENTITY); + WriteByte(msg, TE_CSQC_RACE); + WriteByte(msg, RACE_NET_SPEED_AWARD_BEST); + WriteInt24_t(msg, floor(speedaward_alltimebest+0.5)); + WriteString(msg, speedaward_alltimebest_holder); +} diff --git a/qcsrc/server/race.qh b/qcsrc/server/race.qh index 09b4b36ce..a18e9773e 100644 --- a/qcsrc/server/race.qh +++ b/qcsrc/server/race.qh @@ -9,6 +9,19 @@ float race_timelimit; .float race_completed; float race_completing; +.float race_penalty; + +float speedaward_lastupdate; +float speedaward_lastsent; + +float speedaward_speed; +string speedaward_holder; +string speedaward_uid; + +float speedaward_alltimebest; +string speedaward_alltimebest_holder; +string speedaward_alltimebest_uid; + .float race_movetime; // for reading .float race_movetime_frac; // fractional accumulator for higher accuracy (helper for writing) .float race_movetime_count; // integer accumulator @@ -26,3 +39,6 @@ float race_GetFractionalLapCount(entity e); float race_readTime(string map, float pos); string race_readUID(string map, float pos); string race_readName(string map, float pos); + +void race_send_speedaward(float msg); +void race_send_speedaward_alltimebest(float msg); diff --git a/qcsrc/server/scores.qc b/qcsrc/server/scores.qc index 620ce8ce5..08497eb8e 100644 --- a/qcsrc/server/scores.qc +++ b/qcsrc/server/scores.qc @@ -1,4 +1,3 @@ -.entity scorekeeper; entity teamscorekeepers[16]; string scores_label[MAX_SCORE]; float scores_flags[MAX_SCORE]; @@ -208,14 +207,13 @@ void ScoreInfo_Init(float teams) scores_initialized.classname = "ent_client_scoreinfo"; Net_LinkEntity(scores_initialized, FALSE, 0, ScoreInfo_SendEntity); } - if(teams >= 1) - TeamScore_Spawn(NUM_TEAM_1, "Red"); - if(teams >= 2) - TeamScore_Spawn(NUM_TEAM_2, "Blue"); - if(teams >= 3) - TeamScore_Spawn(NUM_TEAM_3, "Yellow"); - if(teams >= 4) - TeamScore_Spawn(NUM_TEAM_4, "Pink"); + if(teams) + { + teams = min(4, teams); + float i; + for(i = 1; i <= teams; ++i) + TeamScore_Spawn(Team_NumberToTeam(i), Team_ColorName(Team_NumberToTeam(i))); + } } /* @@ -351,6 +349,29 @@ float PlayerScore_Add(entity player, float scorefield, float score) return (s.(scores[scorefield]) += score); } +float PlayerScore_Set(entity player, float scorefield, float score) +{ + if(!scores_initialized) return 0; + + entity s = player.scorekeeper; + + if(!s) { + if(gameover) + return 0; + error("Setting score of unknown player!"); + } + + float old = s.(scores[scorefield]); + + if(old == score) + return old; + + s.SendFlags |= pow(2, scorefield); + s.(scores[scorefield]) = score; + + return score; +} + float PlayerTeamScore_Add(entity player, float pscorefield, float tscorefield, float score) { float r; diff --git a/qcsrc/server/scores.qh b/qcsrc/server/scores.qh index c26a4d295..a62475a66 100644 --- a/qcsrc/server/scores.qh +++ b/qcsrc/server/scores.qh @@ -1,6 +1,7 @@ entity scores_initialized; // non-world when scores labels/rules have been set .float scores[MAX_SCORE]; .float teamscores[MAX_TEAMSCORE]; +.entity scorekeeper; .float scoreboard_pos; /** @@ -22,6 +23,14 @@ void PlayerScore_Detach(entity player); */ float PlayerScore_Add(entity player, float scorefield, float score); +/** + * Sets the player's scorefield to an absolute value. + * NEVER call this if PlayerScore_Attach has not been called yet! + * Means: FIXME make players unable to join the game when not called ClientConnect yet. + * Returns the new score. + */ +float PlayerScore_Set(entity player, float scorefield, float score); + /** * Initialize the score of this player if needed. * Does nothing in teamplay. diff --git a/qcsrc/server/scores_rules.qc b/qcsrc/server/scores_rules.qc index 6343625c0..2fb6740dc 100644 --- a/qcsrc/server/scores_rules.qc +++ b/qcsrc/server/scores_rules.qc @@ -1,6 +1,3 @@ -float c1, c2, c3, c4; -void CheckAllowedTeams (entity for_whom); - // NOTE: SP_ constants may not be >= MAX_SCORE; ST_constants may not be >= MAX_TEAMSCORE // scores that should be in all modes: float ScoreRules_teams; diff --git a/qcsrc/server/spawnpoints.qc b/qcsrc/server/spawnpoints.qc index 3f4e72c3b..ec8807e1b 100644 --- a/qcsrc/server/spawnpoints.qc +++ b/qcsrc/server/spawnpoints.qc @@ -42,7 +42,7 @@ void spawnpoint_use() self.team = activator.team; some_spawn_has_been_used = 1; } - print("spawnpoint was used!\n"); + //print("spawnpoint was used!\n"); } void relocate_spawnpoint() @@ -146,6 +146,44 @@ void spawnfunc_info_player_deathmatch (void) relocate_spawnpoint(); } +/*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24) +Starting point for a player in team one (Red). +Keys: "angle" viewing angle when spawning. */ +void spawnfunc_info_player_team1() +{ + self.team = NUM_TEAM_1; // red + spawnfunc_info_player_deathmatch(); +} + + +/*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24) +Starting point for a player in team two (Blue). +Keys: "angle" viewing angle when spawning. */ +void spawnfunc_info_player_team2() +{ + self.team = NUM_TEAM_2; // blue + spawnfunc_info_player_deathmatch(); +} + +/*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24) +Starting point for a player in team three (Yellow). +Keys: "angle" viewing angle when spawning. */ +void spawnfunc_info_player_team3() +{ + self.team = NUM_TEAM_3; // yellow + spawnfunc_info_player_deathmatch(); +} + + +/*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24) +Starting point for a player in team four (Purple). +Keys: "angle" viewing angle when spawning. */ +void spawnfunc_info_player_team4() +{ + self.team = NUM_TEAM_4; // purple + spawnfunc_info_player_deathmatch(); +} + // Returns: // _x: prio (-1 if unusable) // _y: weight diff --git a/qcsrc/server/spawnpoints.qh b/qcsrc/server/spawnpoints.qh index 607629e42..eb9b186bd 100644 --- a/qcsrc/server/spawnpoints.qh +++ b/qcsrc/server/spawnpoints.qh @@ -2,4 +2,6 @@ float spawnpoint_nag; float SpawnEvent_Send(entity to, float sf); entity Spawn_FilterOutBadSpots(entity firstspot, float mindist, float teamcheck); +void spawnfunc_info_player_deathmatch(); +void spawnpoint_use(); entity SelectSpawnPoint (float anypoint); diff --git a/qcsrc/server/steerlib.qc b/qcsrc/server/steerlib.qc index 2f59924df..77e97fa2f 100644 --- a/qcsrc/server/steerlib.qc +++ b/qcsrc/server/steerlib.qc @@ -493,7 +493,7 @@ vector steerlib_beamsteer(vector dir, float length, float step, float step_up, f #ifdef TLIBS_TETSLIBS void flocker_die() { - pointparticles(particleeffectnum("rocket_explode"), self.origin, '0 0 0', 1); + Send_Effect(EFFECT_ROCKET_EXPLODE, self.origin, '0 0 0', 1); self.owner.cnt += 1; self.owner = world; diff --git a/qcsrc/server/sv_main.qc b/qcsrc/server/sv_main.qc index b25528e16..13aa61902 100644 --- a/qcsrc/server/sv_main.qc +++ b/qcsrc/server/sv_main.qc @@ -8,9 +8,9 @@ void CreatureFrame (void) { if (self.movetype == MOVETYPE_NOCLIP) { continue; } - float vehic = (self.vehicle_flags & VHF_ISVEHICLE); + float vehic = IS_VEHICLE(self); float projectile = (self.flags & FL_PROJECTILE); - float monster = (self.flags & FL_MONSTER); + float monster = IS_MONSTER(self); if (self.watertype <= CONTENT_WATER && self.waterlevel > 0) // workaround a retarded bug made by id software :P (yes, it's that old of a bug) { @@ -207,7 +207,7 @@ void StartFrame (void) if (timeout_status == TIMEOUT_LEADTIME) // just before the timeout (when timeout_status will be TIMEOUT_ACTIVE) orig_slowmo = autocvar_slowmo; // slowmo will be restored after the timeout - skill = autocvar_skill; + bot_skill = autocvar_skill; // detect when the pre-game countdown (if any) has ended and the game has started game_delay = (time < game_starttime) ? TRUE : FALSE; diff --git a/qcsrc/server/t_halflife.qc b/qcsrc/server/t_halflife.qc index bb2254c97..de208120c 100644 --- a/qcsrc/server/t_halflife.qc +++ b/qcsrc/server/t_halflife.qc @@ -39,7 +39,7 @@ void func_ladder_touch() { if (!other.iscreature) return; - if (other.vehicle_flags & VHF_ISVEHICLE) + if(IS_VEHICLE(other)) return; EXACTTRIGGER_TOUCH; diff --git a/qcsrc/server/t_items.qc b/qcsrc/server/t_items.qc index d4af7e3fd..75281cc50 100644 --- a/qcsrc/server/t_items.qc +++ b/qcsrc/server/t_items.qc @@ -61,21 +61,16 @@ void ItemRead(float _IsNew) if(sf & ISF_ANGLES) { - self.angles_x = ReadCoord(); - self.angles_y = ReadCoord(); - self.angles_z = ReadCoord(); + self.angles_x = ReadAngle(); + self.angles_y = ReadAngle(); + self.angles_z = ReadAngle(); self.move_angles = self.angles; } if(sf & ISF_SIZE) { - self.mins_x = ReadCoord(); - self.mins_y = ReadCoord(); - self.mins_z = ReadCoord(); - self.maxs_x = ReadCoord(); - self.maxs_y = ReadCoord(); - self.maxs_z = ReadCoord(); - setsize(self, self.mins, self.maxs); + float use_bigsize = ReadByte(); + setsize(self, '-16 -16 0', (use_bigsize) ? '16 16 48' : '16 16 32'); } if(sf & ISF_STATUS) // need to read/write status frist so model can handle simple, fb etc. @@ -121,7 +116,8 @@ void ItemRead(float _IsNew) if(sf & ISF_MODEL) { self.drawmask = MASK_NORMAL; - self.movetype = MOVETYPE_TOSS; + self.move_movetype = self.movetype = MOVETYPE_TOSS; + //self.renderflags |= RF_DEPTHHACK; self.draw = ItemDraw; if(self.mdl) @@ -135,20 +131,18 @@ void ItemRead(float _IsNew) string _fn2 = substring(_fn, 0 , strlen(_fn) -4); self.draw = ItemDrawSimple; - - if(fexists(sprintf("%s%s.md3", _fn2, autocvar_cl_simpleitems_postfix))) self.mdl = strzone(sprintf("%s%s.md3", _fn2, autocvar_cl_simpleitems_postfix)); else if(fexists(sprintf("%s%s.dpm", _fn2, autocvar_cl_simpleitems_postfix))) self.mdl = strzone(sprintf("%s%s.dpm", _fn2, autocvar_cl_simpleitems_postfix)); else if(fexists(sprintf("%s%s.iqm", _fn2, autocvar_cl_simpleitems_postfix))) self.mdl = strzone(sprintf("%s%s.iqm", _fn2, autocvar_cl_simpleitems_postfix)); - else if(fexists(sprintf("%s%s.obj", _fn2, autocvar_cl_simpleitems_postfix))) - self.mdl = strzone(sprintf("%s%s.obj", _fn2, autocvar_cl_simpleitems_postfix)); + else if(fexists(sprintf("%s%s.mdl", _fn2, autocvar_cl_simpleitems_postfix))) + self.mdl = strzone(sprintf("%s%s.mdl", _fn2, autocvar_cl_simpleitems_postfix)); else { self.draw = ItemDraw; - dprint("Simple item requested for ", _fn, " but no model exsist for it\n"); + dprint("Simple item requested for ", _fn, " but no model exists for it\n"); } } @@ -219,19 +213,14 @@ float ItemSend(entity to, float sf) if(sf & ISF_ANGLES) { - WriteCoord(MSG_ENTITY, self.angles_x); - WriteCoord(MSG_ENTITY, self.angles_y); - WriteCoord(MSG_ENTITY, self.angles_z); + WriteAngle(MSG_ENTITY, self.angles_x); + WriteAngle(MSG_ENTITY, self.angles_y); + WriteAngle(MSG_ENTITY, self.angles_z); } if(sf & ISF_SIZE) { - WriteCoord(MSG_ENTITY, self.mins_x); - WriteCoord(MSG_ENTITY, self.mins_y); - WriteCoord(MSG_ENTITY, self.mins_z); - WriteCoord(MSG_ENTITY, self.maxs_x); - WriteCoord(MSG_ENTITY, self.maxs_y); - WriteCoord(MSG_ENTITY, self.maxs_z); + WriteByte(MSG_ENTITY, ((self.flags & FL_POWERUP) || self.health || self.armorvalue)); } if(sf & ISF_STATUS) @@ -267,23 +256,13 @@ void ItemUpdate(entity item) float have_pickup_item(void) { - if(self.flags & FL_POWERUP) - { - if(autocvar_g_powerups > 0) - return TRUE; - if(autocvar_g_powerups == 0) - return FALSE; - } - else - { - if(autocvar_g_pickup_items > 0) - return TRUE; - if(autocvar_g_pickup_items == 0) - return FALSE; - if(g_weaponarena) - if(self.weapons || (self.items & IT_AMMO)) // no item or ammo pickups in weaponarena - return FALSE; - } + if(autocvar_g_pickup_items > 0) + return TRUE; + if(autocvar_g_pickup_items == 0) + return FALSE; + if(g_weaponarena) + if(self.weapons || (self.items & IT_AMMO)) + return FALSE; return TRUE; } @@ -353,9 +332,6 @@ void Item_Show (entity e, float mode) e.ItemStatus &= ~ITS_AVAILABLE; } - if (e.items & IT_STRENGTH || e.items & IT_INVINCIBLE) - e.ItemStatus |= ITS_POWERUP; - if (autocvar_g_nodepthtestitems) e.effects |= EF_NODEPTHTEST; @@ -384,20 +360,13 @@ void Item_Think() void Item_Respawn (void) { Item_Show(self, 1); - // this is ugly... - if(self.items == IT_STRENGTH) - sound (self, CH_TRIGGER, "misc/strength_respawn.wav", VOL_BASE, ATTEN_NORM); // play respawn sound - else if(self.items == IT_INVINCIBLE) - sound (self, CH_TRIGGER, "misc/shield_respawn.wav", VOL_BASE, ATTEN_NORM); // play respawn sound - else - sound (self, CH_TRIGGER, "misc/itemrespawn.wav", VOL_BASE, ATTEN_NORM); // play respawn sound + sound (self, CH_TRIGGER, "misc/itemrespawn.wav", VOL_BASE, ATTEN_NORM); // play respawn sound setorigin (self, self.origin); self.think = Item_Think; self.nextthink = time; - - //pointparticles(particleeffectnum("item_respawn"), self.origin + self.mins_z * '0 0 1' + '0 0 48', '0 0 0', 1); - pointparticles(particleeffectnum("item_respawn"), self.origin + 0.5 * (self.mins + self.maxs), '0 0 0', 1); + + Send_Effect(EFFECT_ITEM_RESPAWN, self.origin + 0.5 * (self.mins + self.maxs), '0 0 0', 1); } void Item_RespawnCountdown (void) @@ -421,8 +390,6 @@ void Item_RespawnCountdown (void) { case IT_FUEL_REGEN: name = "item-fuelregen"; rgb = '1 0.5 0'; break; case IT_JETPACK: name = "item-jetpack"; rgb = '0.5 0.5 0.5'; break; - case IT_STRENGTH: name = "item-strength"; rgb = '0 0 1'; break; - case IT_INVINCIBLE: name = "item-shield"; rgb = '1 0 1'; break; } item_name = name; item_color = rgb; @@ -574,6 +541,7 @@ float Item_GiveTo(entity item, entity player) pickedup |= Item_GiveAmmoTo(item, player, ammo_rockets, g_pickup_rockets_max, ITEM_MODE_NONE); pickedup |= Item_GiveAmmoTo(item, player, ammo_cells, g_pickup_cells_max, ITEM_MODE_NONE); pickedup |= Item_GiveAmmoTo(item, player, ammo_plasma, g_pickup_plasma_max, ITEM_MODE_NONE); + pickedup |= Item_GiveAmmoTo(item, player, ammo_supercells, g_pickup_cells_max, ITEM_MODE_NONE); pickedup |= Item_GiveAmmoTo(item, player, health, item.max_health, ITEM_MODE_HEALTH); pickedup |= Item_GiveAmmoTo(item, player, armorvalue, item.max_armorvalue, ITEM_MODE_ARMOR); @@ -602,16 +570,6 @@ float Item_GiveTo(entity item, entity player) Send_Notification(NOTIF_ONE, player, MSG_INFO, INFO_ITEM_WEAPON_GOT, item.netname); } - if (item.strength_finished) - { - pickedup = TRUE; - player.strength_finished = max(player.strength_finished, time) + item.strength_finished; - } - if (item.invincible_finished) - { - pickedup = TRUE; - player.invincible_finished = max(player.invincible_finished, time) + item.invincible_finished; - } if (item.superweapons_finished) { pickedup = TRUE; @@ -668,19 +626,13 @@ void Item_Touch (void) } if (self.classname == "droppedweapon") - { - self.strength_finished = max(0, self.strength_finished - time); - self.invincible_finished = max(0, self.invincible_finished - time); self.superweapons_finished = max(0, self.superweapons_finished - time); - } if(!Item_GiveTo(self, other)) { if (self.classname == "droppedweapon") { // undo what we did above - self.strength_finished += time; - self.invincible_finished += time; self.superweapons_finished += time; } return; @@ -688,9 +640,11 @@ void Item_Touch (void) :pickup + jeff_Announcer_ItemTouch(other, self); + other.last_pickup = time; - pointparticles(particleeffectnum("item_pickup"), self.origin, '0 0 0', 1); + Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(self), '0 0 0', 1); sound (other, CH_TRIGGER, self.item_pickupsound, VOL_BASE, ATTEN_NORM); if (self.classname == "droppedweapon") @@ -767,13 +721,15 @@ void Item_FindTeam() } } -// Savage: used for item garbage-collection -// TODO: perhaps nice special effect? -void RemoveItem(void) +void RemoveItem(entity e) { - remove(self); + if(wasfreed(e) || !e) { return; } + Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(e), '0 0 0', 1); + remove(e); } +void RemoveItem_think() { RemoveItem(self); } + // pickup evaluation functions // these functions decide how desirable an item is to the bots @@ -792,7 +748,7 @@ float weapon_pickupevalfunc(entity player, entity item) else if(player.ammo_cells || player.ammo_shells || player.ammo_plasma || player.ammo_nails || player.ammo_rockets) { // Skilled bots will grab more - c = bound(0, skill / 10, 1) * 0.5; + c = bound(0, bot_skill / 10, 1) * 0.5; } else c = 0; @@ -832,7 +788,7 @@ float weapon_pickupevalfunc(entity player, entity item) float commodity_pickupevalfunc(entity player, entity item) { float c, i; - float need_shells = FALSE, need_nails = FALSE, need_rockets = FALSE, need_cells = FALSE, need_plasma = FALSE, need_fuel = FALSE; + float need_shells = FALSE, need_nails = FALSE, need_rockets = FALSE, need_cells = FALSE, need_plasma = FALSE, need_fuel = FALSE, need_supercells = FALSE; entity wi; c = 0; @@ -855,7 +811,9 @@ float commodity_pickupevalfunc(entity player, entity item) else if(wi.items & IT_PLASMA) need_plasma = TRUE; else if(wi.items & IT_FUEL) - need_fuel = TRUE; + need_cells = TRUE; + else if(wi.items & IT_SUPERCELLS) + need_supercells = TRUE; } // TODO: figure out if the player even has the weapon this ammo is for? @@ -881,6 +839,10 @@ float commodity_pickupevalfunc(entity player, entity item) if (item.ammo_plasma) if (player.ammo_plasma < g_pickup_plasma_max) c = c + max(0, 1 - player.ammo_plasma / g_pickup_plasma_max); + if (need_supercells) + if (item.ammo_supercells) + if (player.ammo_supercells < g_pickup_cells_max) + c = c + max(0, 1 - player.ammo_supercells / g_pickup_cells_max); if (need_fuel) if (item.ammo_fuel) if (player.ammo_fuel < g_pickup_fuel_max) @@ -898,7 +860,7 @@ float commodity_pickupevalfunc(entity player, entity item) void Item_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) { if(ITEM_DAMAGE_NEEDKILL(deathtype)) - RemoveItem(); + RemoveItem(self); } 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) @@ -946,27 +908,16 @@ void StartItem (string itemmodel, string pickupsound, float defaultrespawntime, self.movetype = MOVETYPE_TOSS; // Savage: remove thrown items after a certain period of time ("garbage collection") - self.think = RemoveItem; + self.think = RemoveItem_think; self.nextthink = time + 20; self.takedamage = DAMAGE_YES; self.event_damage = Item_Damage; - if(self.strength_finished || self.invincible_finished || self.superweapons_finished) - /* - if(self.items == 0) - if(!(self.weapons & ~WEPSET_SUPERWEAPONS)) // only superweapons - if(self.ammo_nails == 0) - if(self.ammo_cells == 0) - if(self.ammo_rockets == 0) - if(self.ammo_shells == 0) - if(self.ammo_fuel == 0) - if(self.health == 0) - if(self.armorvalue == 0) - */ + if(self.superweapons_finished) { // if item is worthless after a timer, have it expire then - self.nextthink = max(self.strength_finished, self.invincible_finished, self.superweapons_finished); + self.nextthink = self.superweapons_finished; } // don't drop if in a NODROP zone (such as lava) @@ -987,6 +938,9 @@ void StartItem (string itemmodel, string pickupsound, float defaultrespawntime, return; } + if(self.angles != '0 0 0') + self.SendFlags |= ISF_ANGLES; + self.reset = Item_Reset; // it's a level item if(self.spawnflags & 1) @@ -1005,7 +959,7 @@ void StartItem (string itemmodel, string pickupsound, float defaultrespawntime, setsize (self, '-16 -16 0', '16 16 48'); else setsize (self, '-16 -16 0', '16 16 32'); - + self.SendFlags |= ISF_SIZE; // note droptofloor returns FALSE if stuck/or would fall too far droptofloor(); waypoint_spawnforitem(self); @@ -1047,12 +1001,7 @@ void StartItem (string itemmodel, string pickupsound, float defaultrespawntime, precache_sound (self.item_pickupsound); precache_sound ("misc/itemrespawncountdown.wav"); - if(itemid == IT_STRENGTH) - precache_sound ("misc/strength_respawn.wav"); - else if(itemid == IT_INVINCIBLE) - precache_sound ("misc/shield_respawn.wav"); - else - precache_sound ("misc/itemrespawn.wav"); + precache_sound ("misc/itemrespawn.wav"); if((itemflags & (FL_POWERUP | FL_WEAPON)) || (itemid & (IT_HEALTH | IT_ARMOR | IT_KEY1 | IT_KEY2))) self.target = "###item###"; // for finding the nearest item using find() @@ -1061,7 +1010,9 @@ void StartItem (string itemmodel, string pickupsound, float defaultrespawntime, self.bot_pickup = TRUE; self.bot_pickupevalfunc = pickupevalfunc; self.bot_pickupbasevalue = pickupbasevalue; - self.mdl = self.model; + //if(self.mdl) { strunzone(self.mdl); } + self.mdl = ""; + self.mdl = strzone(self.model); self.netname = itemname; self.touch = Item_Touch; setmodel(self, "null"); // precision set below @@ -1079,6 +1030,8 @@ void StartItem (string itemmodel, string pickupsound, float defaultrespawntime, } setsize (self, self.pos1, self.pos2); + self.SendFlags |= ISF_SIZE; + if(itemflags & FL_POWERUP) self.ItemStatus |= ITS_ANIMATE1; @@ -1108,11 +1061,7 @@ void StartItem (string itemmodel, string pickupsound, float defaultrespawntime, else Item_Reset(); - Net_LinkEntity(self, FALSE, 0, ItemSend); - - self.SendFlags |= ISF_SIZE; - if(self.angles) - self.SendFlags |= ISF_ANGLES; + Net_LinkEntity(self, !((itemflags & FL_POWERUP) || self.health || self.armorvalue), 0, ItemSend); // call this hook after everything else has been done if(MUTATOR_CALLHOOK(Item_Spawn)) @@ -1122,12 +1071,24 @@ void StartItem (string itemmodel, string pickupsound, float defaultrespawntime, return; } } + +string Item_Model(string item_mdl) +{ + if(autocvar_sv_items_modeloverride != "" && autocvar_sv_items_modeloverride != "default") + { + string themdl = sprintf("models/items_%s/%s", autocvar_sv_weapons_modeloverride, item_mdl); + if(fexists(themdl)) + return themdl; + } + return strcat("models/items/", item_mdl); +} + void spawnfunc_item_rockets (void) { if(!self.ammo_rockets) self.ammo_rockets = g_pickup_rockets; if(!self.pickup_anyway) self.pickup_anyway = g_pickup_ammo_anyway; - StartItem ("models/items/a_rockets.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "rockets", IT_ROCKETS, 0, 0, commodity_pickupevalfunc, 3000); + StartItem (Item_Model("a_rockets.md3"), "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "rockets", IT_ROCKETS, 0, 0, commodity_pickupevalfunc, 3000); } void spawnfunc_item_shells (void); @@ -1146,7 +1107,7 @@ void spawnfunc_item_bullets (void) { self.ammo_nails = g_pickup_nails; if(!self.pickup_anyway) self.pickup_anyway = g_pickup_ammo_anyway; - StartItem ("models/items/a_bullets.mdl", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "bullets", IT_NAILS, 0, 0, commodity_pickupevalfunc, 2000); + StartItem (Item_Model("a_bullets.mdl"), "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "bullets", IT_NAILS, 0, 0, commodity_pickupevalfunc, 2000); } void spawnfunc_item_cells (void) { @@ -1154,7 +1115,7 @@ void spawnfunc_item_cells (void) { self.ammo_cells = g_pickup_cells; if(!self.pickup_anyway) self.pickup_anyway = g_pickup_ammo_anyway; - StartItem ("models/items/a_cells.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "cells", IT_CELLS, 0, 0, commodity_pickupevalfunc, 2000); + StartItem (Item_Model("a_cells.md3"), "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "cells", IT_CELLS, 0, 0, commodity_pickupevalfunc, 2000); } void spawnfunc_item_plasma() @@ -1163,7 +1124,7 @@ void spawnfunc_item_plasma() self.ammo_plasma = g_pickup_plasma; if(!self.pickup_anyway) self.pickup_anyway = g_pickup_ammo_anyway; - StartItem ("models/items/a_cells.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "plasma", IT_PLASMA, 0, 0, commodity_pickupevalfunc, 2000); + StartItem (Item_Model("a_cells.md3"), "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "plasma", IT_PLASMA, 0, 0, commodity_pickupevalfunc, 2000); } void spawnfunc_item_shells (void) { @@ -1181,7 +1142,7 @@ void spawnfunc_item_shells (void) { self.ammo_shells = g_pickup_shells; if(!self.pickup_anyway) self.pickup_anyway = g_pickup_ammo_anyway; - StartItem ("models/items/a_shells.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "shells", IT_SHELLS, 0, 0, commodity_pickupevalfunc, 500); + StartItem (Item_Model("a_shells.md3"), "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "shells", IT_SHELLS, 0, 0, commodity_pickupevalfunc, 500); } void spawnfunc_item_armor_small (void) { @@ -1191,7 +1152,7 @@ void spawnfunc_item_armor_small (void) { self.max_armorvalue = g_pickup_armorsmall_max; if(!self.pickup_anyway) self.pickup_anyway = g_pickup_armorsmall_anyway; - StartItem ("models/items/item_armor_small.md3", "misc/armor1.wav", g_pickup_respawntime_short, g_pickup_respawntimejitter_short, "5 Armor", IT_ARMOR_SHARD, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW); + StartItem (Item_Model("item_armor_small.md3"), "misc/armor1.wav", g_pickup_respawntime_short, g_pickup_respawntimejitter_short, "5 Armor", IT_ARMOR_SHARD, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW); } void spawnfunc_item_armor_medium (void) { @@ -1201,67 +1162,73 @@ void spawnfunc_item_armor_medium (void) { self.max_armorvalue = g_pickup_armormedium_max; if(!self.pickup_anyway) self.pickup_anyway = g_pickup_armormedium_anyway; - StartItem ("models/items/item_armor_medium.md3", "misc/armor10.wav", g_pickup_respawntime_medium, g_pickup_respawntimejitter_medium, "25 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_MID); + StartItem (Item_Model("item_armor_medium.md3"), "misc/armor10.wav", g_pickup_respawntime_medium, g_pickup_respawntimejitter_medium, "25 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_MID); } void spawnfunc_item_armor_big (void) { + self.classname = "item_armor_big"; if(!self.armorvalue) self.armorvalue = g_pickup_armorbig; if(!self.max_armorvalue) self.max_armorvalue = g_pickup_armorbig_max; if(!self.pickup_anyway) self.pickup_anyway = g_pickup_armorbig_anyway; - StartItem ("models/items/item_armor_big.md3", "misc/armor17_5.wav", g_pickup_respawntime_long, g_pickup_respawntimejitter_long, "50 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, 20000); + StartItem (Item_Model("item_armor_big.md3"), "misc/armor17_5.wav", g_pickup_respawntime_long, g_pickup_respawntimejitter_long, "50 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, 20000); } void spawnfunc_item_armor_large (void) { + self.classname = "item_armor_large"; if(!self.armorvalue) self.armorvalue = g_pickup_armorlarge; if(!self.max_armorvalue) self.max_armorvalue = g_pickup_armorlarge_max; if(!self.pickup_anyway) self.pickup_anyway = g_pickup_armorlarge_anyway; - StartItem ("models/items/item_armor_large.md3", "misc/armor25.wav", g_pickup_respawntime_long, g_pickup_respawntimejitter_long, "100 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_HIGH); + StartItem (Item_Model("item_armor_large.md3"), "misc/armor25.wav", g_pickup_respawntime_long, g_pickup_respawntimejitter_long, "100 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_HIGH); } void spawnfunc_item_health_small (void) { + self.classname = "item_health_small"; if(!self.max_health) self.max_health = g_pickup_healthsmall_max; if(!self.health) self.health = g_pickup_healthsmall; if(!self.pickup_anyway) self.pickup_anyway = g_pickup_healthsmall_anyway; - StartItem ("models/items/g_h1.md3", "misc/minihealth.wav", g_pickup_respawntime_short, g_pickup_respawntimejitter_short, "5 Health", IT_5HP, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW); + StartItem (Item_Model("g_h1.md3"), "misc/minihealth.wav", g_pickup_respawntime_short, g_pickup_respawntimejitter_short, "5 Health", IT_5HP, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW); } void spawnfunc_item_health_medium (void) { + self.classname = "item_health_medium"; if(!self.max_health) self.max_health = g_pickup_healthmedium_max; if(!self.health) self.health = g_pickup_healthmedium; if(!self.pickup_anyway) self.pickup_anyway = g_pickup_healthmedium_anyway; - StartItem ("models/items/g_h25.md3", "misc/mediumhealth.wav", g_pickup_respawntime_short, g_pickup_respawntimejitter_short, "25 Health", IT_25HP, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_MID); + StartItem (Item_Model("g_h25.md3"), "misc/mediumhealth.wav", g_pickup_respawntime_short, g_pickup_respawntimejitter_short, "25 Health", IT_25HP, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_MID); } void spawnfunc_item_health_large (void) { + self.classname = "item_health_large"; if(!self.max_health) self.max_health = g_pickup_healthlarge_max; if(!self.health) self.health = g_pickup_healthlarge; if(!self.pickup_anyway) self.pickup_anyway = g_pickup_healthlarge_anyway; - StartItem ("models/items/g_h50.md3", "misc/mediumhealth.wav", g_pickup_respawntime_medium, g_pickup_respawntimejitter_medium, "50 Health", IT_25HP, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_MID); + StartItem (Item_Model("g_h50.md3"), "misc/mediumhealth.wav", g_pickup_respawntime_medium, g_pickup_respawntimejitter_medium, "50 Health", IT_25HP, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_MID); } void spawnfunc_item_health_mega (void) { + self.classname = "item_health_mega"; if(!self.max_health) self.max_health = g_pickup_healthmega_max; if(!self.health) self.health = g_pickup_healthmega; if(!self.pickup_anyway) self.pickup_anyway = g_pickup_healthmega_anyway; - StartItem ("models/items/g_h100.md3", "misc/megahealth.wav", g_pickup_respawntime_long, g_pickup_respawntimejitter_long, "100 Health", IT_HEALTH, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_HIGH); + StartItem (Item_Model("g_h100.md3"), "misc/megahealth.wav", g_pickup_respawntime_long, g_pickup_respawntimejitter_long, "100 Health", IT_HEALTH, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_HIGH); } // support old misnamed entities @@ -1271,18 +1238,9 @@ void spawnfunc_item_health1() { spawnfunc_item_health_small(); } void spawnfunc_item_health25() { spawnfunc_item_health_medium(); } void spawnfunc_item_health100() { spawnfunc_item_health_mega(); } -void spawnfunc_item_strength (void) { - precache_sound("weapons/strength_fire.wav"); - if(!self.strength_finished) - self.strength_finished = autocvar_g_balance_powerup_strength_time; - StartItem ("models/items/g_strength.md3", "misc/powerup.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup, "Strength Powerup", IT_STRENGTH, 0, FL_POWERUP, generic_pickupevalfunc, 100000); -} - -void spawnfunc_item_invincible (void) { - if(!self.invincible_finished) - self.invincible_finished = autocvar_g_balance_powerup_invincible_time; - StartItem ("models/items/g_invincible.md3", "misc/powerup_shield.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup, "Shield", IT_INVINCIBLE, 0, FL_POWERUP, generic_pickupevalfunc, 100000); -} +// mutator hookable items +void spawnfunc_item_strength() { /* dummy item */ } +void spawnfunc_item_invincible() { /* dummy item */ } // compatibility: void spawnfunc_item_quad (void) {self.classname = "item_strength";spawnfunc_item_strength();} @@ -1318,10 +1276,6 @@ void spawnfunc_target_items (void) string s; self.use = target_items_use; - if(!self.strength_finished) - self.strength_finished = autocvar_g_balance_powerup_strength_time; - if(!self.invincible_finished) - self.invincible_finished = autocvar_g_balance_powerup_invincible_time; if(!self.superweapons_finished) self.superweapons_finished = autocvar_g_balance_superweapons_time; @@ -1330,7 +1284,7 @@ void spawnfunc_target_items (void) precache_sound("misc/armor25.wav"); precache_sound("misc/powerup.wav"); precache_sound("misc/poweroff.wav"); - precache_sound("weapons/weaponpickup.wav"); + precache_sound(W_Sound("weaponpickup")); n = tokenize_console(self.netname); if(argv(0) == "give") @@ -1344,8 +1298,6 @@ void spawnfunc_target_items (void) if (argv(i) == "unlimited_ammo") self.items |= IT_UNLIMITED_AMMO; else if(argv(i) == "unlimited_weapon_ammo") self.items |= IT_UNLIMITED_WEAPON_AMMO; else if(argv(i) == "unlimited_superweapons") self.items |= IT_UNLIMITED_SUPERWEAPONS; - else if(argv(i) == "strength") self.items |= IT_STRENGTH; - else if(argv(i) == "invincible") self.items |= IT_INVINCIBLE; else if(argv(i) == "superweapons") self.items |= IT_SUPERWEAPON; else if(argv(i) == "jetpack") self.items |= IT_JETPACK; else if(argv(i) == "fuel_regen") self.items |= IT_FUEL_REGEN; @@ -1401,8 +1353,6 @@ void spawnfunc_target_items (void) self.netname = ""; self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_UNLIMITED_WEAPON_AMMO), "unlimited_weapon_ammo"); self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_UNLIMITED_SUPERWEAPONS), "unlimited_superweapons"); - self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, self.strength_finished * !!(self.items & IT_STRENGTH), "strength"); - self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, self.invincible_finished * !!(self.items & IT_INVINCIBLE), "invincible"); self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, self.superweapons_finished * !!(self.items & IT_SUPERWEAPON), "superweapons"); self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_JETPACK), "jetpack"); self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_FUEL_REGEN), "fuel_regen"); @@ -1411,6 +1361,7 @@ void spawnfunc_target_items (void) if(self.ammo_rockets != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_rockets), "rockets"); if(self.ammo_cells != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_cells), "cells"); if(self.ammo_plasma != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_plasma), "plasma"); + if(self.ammo_supercells != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_supercells), "supercells"); if(self.ammo_fuel != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_fuel), "fuel"); if(self.health != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.health), "health"); if(self.armorvalue != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.health), "armor"); @@ -1445,7 +1396,7 @@ void spawnfunc_item_fuel(void) self.ammo_fuel = g_pickup_fuel; if(!self.pickup_anyway) self.pickup_anyway = g_pickup_ammo_anyway; - StartItem ("models/items/g_fuel.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "Fuel", IT_FUEL, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW); + StartItem (Item_Model("g_fuel.md3"), "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "Fuel", IT_FUEL, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW); } void spawnfunc_item_fuel_regen(void) @@ -1455,7 +1406,7 @@ void spawnfunc_item_fuel_regen(void) spawnfunc_item_fuel(); return; } - StartItem ("models/items/g_fuelregen.md3", "misc/itempickup.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup, "Fuel regenerator", IT_FUEL_REGEN, 0, FL_POWERUP, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW); + StartItem (Item_Model("g_fuelregen.md3"), "misc/itempickup.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup, "Fuel regenerator", IT_FUEL_REGEN, 0, FL_POWERUP, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW); } void spawnfunc_item_jetpack(void) @@ -1467,7 +1418,7 @@ void spawnfunc_item_jetpack(void) spawnfunc_item_fuel(); return; } - 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); + StartItem (Item_Model("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); } float GiveWeapon(entity e, float wpn, float op, float val) @@ -1596,18 +1547,15 @@ float GiveItems(entity e, float beginarg, float endarg) if (e.switchweapon == w_getbestweapon(e)) _switchweapon = TRUE; - e.strength_finished = max(0, e.strength_finished - time); - e.invincible_finished = max(0, e.invincible_finished - time); e.superweapons_finished = max(0, e.superweapons_finished - time); PREGIVE(e, items); PREGIVE_WEAPONS(e); - PREGIVE(e, strength_finished); - PREGIVE(e, invincible_finished); PREGIVE(e, superweapons_finished); PREGIVE(e, ammo_nails); PREGIVE(e, ammo_cells); PREGIVE(e, ammo_plasma); + PREGIVE(e, ammo_supercells); PREGIVE(e, ammo_shells); PREGIVE(e, ammo_rockets); PREGIVE(e, ammo_fuel); @@ -1643,8 +1591,6 @@ float GiveItems(entity e, float beginarg, float endarg) continue; case "ALL": got += GiveBit(e, items, IT_FUEL_REGEN, op, val); - got += GiveValue(e, strength_finished, op, val); - got += GiveValue(e, invincible_finished, op, val); got += GiveValue(e, superweapons_finished, op, val); got += GiveBit(e, items, IT_UNLIMITED_AMMO, op, val); case "all": @@ -1662,6 +1608,7 @@ float GiveItems(entity e, float beginarg, float endarg) case "allammo": got += GiveValue(e, ammo_cells, op, val); got += GiveValue(e, ammo_plasma, op, val); + got += GiveValue(e, ammo_supercells, op, val); got += GiveValue(e, ammo_shells, op, val); got += GiveValue(e, ammo_nails, op, val); got += GiveValue(e, ammo_rockets, op, val); @@ -1682,12 +1629,6 @@ float GiveItems(entity e, float beginarg, float endarg) case "fuel_regen": got += GiveBit(e, items, IT_FUEL_REGEN, op, val); break; - case "strength": - got += GiveValue(e, strength_finished, op, val); - break; - case "invincible": - got += GiveValue(e, invincible_finished, op, val); - break; case "superweapons": got += GiveValue(e, superweapons_finished, op, val); break; @@ -1697,6 +1638,9 @@ float GiveItems(entity e, float beginarg, float endarg) case "plasma": got += GiveValue(e, ammo_plasma, op, val); break; + case "supercells": + got += GiveValue(e, ammo_supercells, op, val); + break; case "shells": got += GiveValue(e, ammo_shells, op, val); break; @@ -1743,17 +1687,16 @@ float GiveItems(entity e, float beginarg, float endarg) wi = get_weaponinfo(j); if(wi.weapon) { - POSTGIVE_WEAPON(e, j, "weapons/weaponpickup.wav", string_null); + POSTGIVE_WEAPON(e, j, W_Sound("weaponpickup"), string_null); if (!(save_weapons & WepSet_FromWeapon(j))) if(e.weapons & WepSet_FromWeapon(j)) WEP_ACTION(wi.weapon, WR_INIT); } } - POSTGIVE_VALUE(e, strength_finished, 1, "misc/powerup.wav", "misc/poweroff.wav"); - POSTGIVE_VALUE(e, invincible_finished, 1, "misc/powerup_shield.wav", "misc/poweroff.wav"); POSTGIVE_VALUE(e, ammo_nails, 0, "misc/itempickup.wav", string_null); POSTGIVE_VALUE(e, ammo_cells, 0, "misc/itempickup.wav", string_null); POSTGIVE_VALUE(e, ammo_plasma, 0, "misc/itempickup.wav", string_null); + POSTGIVE_VALUE(e, ammo_supercells, 0, "misc/itempickup.wav", string_null); POSTGIVE_VALUE(e, ammo_shells, 0, "misc/itempickup.wav", string_null); POSTGIVE_VALUE(e, ammo_rockets, 0, "misc/itempickup.wav", string_null); POSTGIVE_VALUE_ROT(e, ammo_fuel, 1, pauserotfuel_finished, autocvar_g_balance_pause_fuel_rot, pauseregen_finished, autocvar_g_balance_pause_fuel_regen, "misc/itempickup.wav", string_null); @@ -1764,14 +1707,6 @@ float GiveItems(entity e, float beginarg, float endarg) if(self.weapons & WEPSET_SUPERWEAPONS) e.superweapons_finished = autocvar_g_balance_superweapons_time; - if(e.strength_finished <= 0) - e.strength_finished = 0; - else - e.strength_finished += time; - if(e.invincible_finished <= 0) - e.invincible_finished = 0; - else - e.invincible_finished += time; if(e.superweapons_finished <= 0) e.superweapons_finished = 0; else diff --git a/qcsrc/server/t_items.qh b/qcsrc/server/t_items.qh index 13cc3796f..7f6caefd0 100644 --- a/qcsrc/server/t_items.qh +++ b/qcsrc/server/t_items.qh @@ -16,19 +16,13 @@ const float IT_SUPERWEAPON = 4096; const float IT_STRENGTH = 8192; const float IT_INVINCIBLE = 16384; const float IT_HEALTH = 32768; -const float IT_PLASMA = 65536; +const float IT_SUPERCELLS = 8388608; +const float IT_PLASMA = 16777216; // shared value space (union): // for items: WANT_CONST float IT_KEY1 = 131072; WANT_CONST float IT_KEY2 = 262144; - // for players: - const float IT_RED_FLAG_TAKEN = 32768; - const float IT_RED_FLAG_LOST = 65536; - const float IT_RED_FLAG_CARRYING = 98304; - const float IT_BLUE_FLAG_TAKEN = 131072; - const float IT_BLUE_FLAG_LOST = 262144; - const float IT_BLUE_FLAG_CARRYING = 393216; // end const float IT_5HP = 524288; diff --git a/qcsrc/server/t_jumppads.qc b/qcsrc/server/t_jumppads.qc index f52b492a7..76f90e958 100644 --- a/qcsrc/server/t_jumppads.qc +++ b/qcsrc/server/t_jumppads.qc @@ -174,7 +174,7 @@ void trigger_push_touch() if(self.pushltime < time) // prevent "snorring" sound when a player hits the jumppad more than once { // flash when activated - pointparticles(particleeffectnum("jumppad_activate"), other.origin, other.velocity, 1); + Send_Effect(EFFECT_JUMPPAD, other.origin, other.velocity, 1); sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM); self.pushltime = time + 0.2; } diff --git a/qcsrc/server/t_quake3.qc b/qcsrc/server/t_quake3.qc index 7c1a58287..6c64df5ef 100644 --- a/qcsrc/server/t_quake3.qc +++ b/qcsrc/server/t_quake3.qc @@ -174,6 +174,7 @@ float DoesQ3ARemoveThisEntity() if(maxclients == 1) gametypename = "single"; // we do not have the other types (oneflag, obelisk, harvester, teamtournament) + // actually, we have oneflag, but it can't be detected this early (map based...) if(strstrofs(self.gametype, gametypename, 0) < 0) return 1; } diff --git a/qcsrc/server/t_teleporters.qc b/qcsrc/server/t_teleporters.qc index 543c1cf0b..78d79c708 100644 --- a/qcsrc/server/t_teleporters.qc +++ b/qcsrc/server/t_teleporters.qc @@ -101,8 +101,8 @@ void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angle sound (player, CH_TRIGGER, "misc/teleport.wav", VOL_BASE, ATTEN_NORM); if(tflags & TELEPORT_FLAG_PARTICLES) { - pointparticles(particleeffectnum("teleport"), player.origin, '0 0 0', 1); - pointparticles(particleeffectnum("teleport"), to + v_forward * 32, '0 0 0', 1); + Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1); + Send_Effect(EFFECT_TELEPORT, to + v_forward * 32, '0 0 0', 1); } self.pushltime = time + 0.2; } @@ -212,7 +212,7 @@ void Teleport_Touch (void) if(!other.vehicle.teleportable) return; - if(other.turrcaps_flags & TFL_TURRCAPS_ISTURRET) + if(IS_TURRET(other)) return; if(other.deadflag != DEAD_NO) diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc index 2f6963bf7..dd7d4c125 100644 --- a/qcsrc/server/teamplay.qc +++ b/qcsrc/server/teamplay.qc @@ -1,13 +1,6 @@ string cache_mutatormsg; string cache_lastmutatormsg; -// client counts for each team -float c1, c2, c3, c4; -// # of bots on those teams -float cb1, cb2, cb3, cb4; - -//float audit_teams_time; - void TeamchangeFrags(entity e) { PlayerScore_Clear(e); @@ -77,6 +70,8 @@ void InitGameplayMode() if(g_dm) { + // no custom limits needed here + MUTATOR_ADD(gamemode_deathmatch); } if(g_tdm) @@ -133,6 +128,8 @@ void InitGameplayMode() ActivateTeamplay(); fraglimit_override = autocvar_g_keyhunt_point_limit; leadlimit_override = autocvar_g_keyhunt_point_leadlimit; + if(autocvar_g_keyhunt_team_spawns) + have_team_spawns = -1; // request team spawns MUTATOR_ADD(gamemode_keyhunt); } @@ -156,6 +153,7 @@ void InitGameplayMode() if(g_onslaught) { ActivateTeamplay(); + fraglimit_override = autocvar_g_onslaught_point_limit; have_team_spawns = -1; // request team spawns MUTATOR_ADD(gamemode_onslaught); } @@ -166,38 +164,16 @@ void InitGameplayMode() { ActivateTeamplay(); race_teams = bound(2, autocvar_g_race_teams, 4); - have_team_spawns = -1; // request team spawns + if(autocvar_g_race_team_spawns) + have_team_spawns = -1; // request team spawns } else race_teams = 0; + qualifying_override = autocvar_g_race_qualifying_timelimit_override; fraglimit_override = autocvar_g_race_laps_limit; leadlimit_override = 0; // currently not supported by race - // we need to find out the correct value for g_race_qualifying - float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0; - - if(autocvar_g_campaign) - { - g_race_qualifying = 1; - independent_players = 1; - } - else if(!autocvar_g_campaign && want_qualifying) - { - g_race_qualifying = 2; - independent_players = 1; - race_fraglimit = (race_fraglimit >= 0) ? fraglimit_override : autocvar_fraglimit; - race_leadlimit = (race_leadlimit >= 0) ? leadlimit_override : autocvar_leadlimit; - race_timelimit = (race_timelimit >= 0) ? timelimit_override : autocvar_timelimit; - fraglimit_override = 0; - leadlimit_override = 0; - timelimit_override = autocvar_g_race_qualifying_timelimit; - } - else - { - g_race_qualifying = 0; - } - MUTATOR_ADD(gamemode_race); } @@ -206,24 +182,40 @@ void InitGameplayMode() g_race_qualifying = 1; fraglimit_override = 0; leadlimit_override = 0; - independent_players = 1; MUTATOR_ADD(gamemode_cts); } if(g_nexball) { - fraglimit_override = autocvar_g_nexball_goallimit; - leadlimit_override = autocvar_g_nexball_goalleadlimit; - ActivateTeamplay(); - have_team_spawns = -1; // request team spawns - MUTATOR_ADD(gamemode_nexball); + fraglimit_override = autocvar_g_nexball_goallimit; + leadlimit_override = autocvar_g_nexball_goalleadlimit; + ActivateTeamplay(); + have_team_spawns = -1; // request team spawns + MUTATOR_ADD(gamemode_nexball); } if(g_keepaway) { + fraglimit_override = autocvar_g_keepaway_point_limit; MUTATOR_ADD(gamemode_keepaway); } + if(g_conquest) + { + fraglimit_override = autocvar_g_conquest_point_limit; + ActivateTeamplay(); + MUTATOR_ADD(gamemode_conquest); + } + + if(g_vip) + { + fraglimit_override = autocvar_g_vip_point_limit; + leadlimit_override = autocvar_g_vip_point_leadlimit; + ActivateTeamplay(); + have_team_spawns = -1; // request team spawns + MUTATOR_ADD(gamemode_vip); + } + if(g_invasion) { fraglimit_override = autocvar_g_invasion_point_limit; @@ -236,6 +228,22 @@ void InitGameplayMode() MUTATOR_ADD(gamemode_invasion); } + if(g_infection) + { + fraglimit_override = autocvar_g_infection_point_limit; + leadlimit_override = autocvar_g_infection_point_leadlimit; + MUTATOR_ADD(gamemode_infection); + } + + if(g_jailbreak) + { + fraglimit_override = autocvar_g_jailbreak_point_limit; + leadlimit_override = autocvar_g_jailbreak_point_leadlimit; + ActivateTeamplay(); + have_team_spawns = -1; // request team spawns + MUTATOR_ADD(gamemode_jailbreak); + } + if(teamplay) entcs_init(); @@ -255,6 +263,31 @@ void InitGameplayMode() cvar_set("g_race_qualifying_timelimit", ftos(qualifying_override)); } + if(g_race) + { + // we need to find out the correct value for g_race_qualifying + if(autocvar_g_campaign) + { + g_race_qualifying = 1; + } + else if(!autocvar_g_campaign && autocvar_g_race_qualifying_timelimit > 0) + { + g_race_qualifying = 2; + race_fraglimit = autocvar_fraglimit; + race_leadlimit = autocvar_leadlimit; + race_timelimit = autocvar_timelimit; + cvar_set("fraglimit", "0"); + cvar_set("leadlimit", "0"); + cvar_set("timelimit", ftos(autocvar_g_race_qualifying_timelimit)); + } + else + g_race_qualifying = 0; + } + + if(g_race || g_cts) + if(g_race_qualifying) + independent_players = 1; + InitializeEntity(world, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK); } @@ -287,7 +320,7 @@ string getwelcomemessage(void) else modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena"); } - if(cvar("g_balance_blaster_weaponstart") == 0) + else if(cvar("g_balance_blaster_weaponstart") == 0) modifications = strcat(modifications, ", No start weapons"); if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity"))) modifications = strcat(modifications, ", Low gravity"); @@ -299,10 +332,6 @@ string getwelcomemessage(void) modifications = strcat(modifications, ", Weapons stay"); if(g_jetpack) modifications = strcat(modifications, ", Jet pack"); - if(autocvar_g_powerups == 0) - modifications = strcat(modifications, ", No powerups"); - if(autocvar_g_powerups > 0) - modifications = strcat(modifications, ", Powerups"); modifications = substring(modifications, 2, strlen(modifications) - 2); string versionmessage; @@ -338,6 +367,10 @@ string getwelcomemessage(void) if (motd != "") { s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd)); } + + // branding for the win + s = strcat(s, "\n\n Running the SMB mod pack\nhttps://github.com/MarioSMB/modpack"); + return s; } @@ -389,46 +422,21 @@ void SetPlayerTeam(entity pl, float t, float s, float noprint) // set c1...c4 to show what teams are allowed void CheckAllowedTeams (entity for_whom) { - float dm; + float dm = 2; entity head; - string teament_name; + string teament_name = string_null; c1 = c2 = c3 = c4 = -1; cb1 = cb2 = cb3 = cb4 = 0; - teament_name = string_null; - if(g_onslaught) - { - // onslaught is special - head = findchain(classname, "onslaught_generator"); - while (head) - { - if (head.team == NUM_TEAM_1) c1 = 0; - if (head.team == NUM_TEAM_2) c2 = 0; - if (head.team == NUM_TEAM_3) c3 = 0; - if (head.team == NUM_TEAM_4) c4 = 0; - head = head.chain; - } - } - else if(g_domination) - teament_name = "dom_team"; - else if(g_ctf) - teament_name = "ctf_team"; - else if(g_tdm) - teament_name = "tdm_team"; - else if(g_nexball) - teament_name = "nexball_team"; - else if(g_assault) - c1 = c2 = 0; // Assault always has 2 teams - else - { - // cover anything else by treating it like tdm with no teams spawned - dm = 2; - - ret_float = dm; - MUTATOR_CALLHOOK(GetTeamCount); - dm = ret_float; + ret_string = teament_name; + ret_float = dm; + float mutator_value = MUTATOR_CALLHOOK(GetTeamCount); + teament_name = ret_string; + dm = ret_float; + if(!mutator_value) + { if(dm >= 4) c1 = c2 = c3 = c4 = 0; else if(dm >= 3) @@ -445,14 +453,13 @@ void CheckAllowedTeams (entity for_whom) { if(!(g_domination && head.netname == "")) { - if(head.team == NUM_TEAM_1) - c1 = 0; - else if(head.team == NUM_TEAM_2) - c2 = 0; - else if(head.team == NUM_TEAM_3) - c3 = 0; - else if(head.team == NUM_TEAM_4) - c4 = 0; + switch(head.team) + { + case NUM_TEAM_1: c1 = 0; break; + case NUM_TEAM_2: c2 = 0; break; + case NUM_TEAM_3: c3 = 0; break; + case NUM_TEAM_4: c4 = 0; break; + } } head = find(head, classname, teament_name); } @@ -520,41 +527,14 @@ void GetTeamCounts(entity ignore) if(head != ignore)// && head.netname != "") { value = PlayerValue(head); - if(IS_BOT_CLIENT(head)) - bvalue = value; - else - bvalue = 0; - if(t == NUM_TEAM_1) - { - if(c1 >= 0) - { - c1 = c1 + value; - cb1 = cb1 + bvalue; - } - } - if(t == NUM_TEAM_2) - { - if(c2 >= 0) - { - c2 = c2 + value; - cb2 = cb2 + bvalue; - } - } - if(t == NUM_TEAM_3) - { - if(c3 >= 0) - { - c3 = c3 + value; - cb3 = cb3 + bvalue; - } - } - if(t == NUM_TEAM_4) + bvalue = (IS_BOT_CLIENT(head)) ? value : 0; + + switch(t) { - if(c4 >= 0) - { - c4 = c4 + value; - cb4 = cb4 + bvalue; - } + case NUM_TEAM_1: if(c1 >= 0) { c1 += value; cb1 += bvalue; } break; + case NUM_TEAM_2: if(c2 >= 0) { c2 += value; cb2 += bvalue; } break; + case NUM_TEAM_3: if(c3 >= 0) { c3 += value; cb3 += bvalue; } break; + case NUM_TEAM_4: if(c4 >= 0) { c4 += value; cb4 += bvalue; } break; } } } diff --git a/qcsrc/server/teamplay.qh b/qcsrc/server/teamplay.qh new file mode 100644 index 000000000..5cf836532 --- /dev/null +++ b/qcsrc/server/teamplay.qh @@ -0,0 +1,8 @@ +// team definitions +float c1, c2, c3, c4; // client counts for each team +float cb1, cb2, cb3, cb4; // # of bots on those teams + +// definitions for functions used outside teamplay.qc +void CheckAllowedTeams(entity for_whom); +void GetTeamCounts(entity other); +float JoinBestTeam(entity pl, float only_return_best, float forcebestteam); diff --git a/qcsrc/server/tturrets/include/turrets.qh b/qcsrc/server/tturrets/include/turrets.qh deleted file mode 100644 index 60936410d..000000000 --- a/qcsrc/server/tturrets/include/turrets.qh +++ /dev/null @@ -1,29 +0,0 @@ -#ifdef TTURRETS_ENABLED - -// Include section. -#include "../system/system_misc.qc" /// Assorted junk & jewls -#include "../system/system_main.qc" /// And routines -#include "../system/system_aimprocs.qc" /// Aiming realted stuff -#include "../system/system_scoreprocs.qc" /// Target calssification -#include "../system/system_damage.qc" /// Outch, they are hurting me! what should i do? - -// Non combat units -#include "../units/unit_fusionreactor.qc" /// Supply unites that need it with power -#include "../units/unit_targettrigger.qc" /// Hit me! -#include "../units/unit_checkpoint.qc" /// Halfsmart pathing. - -// Combat units -#include "../units/unit_plasma.qc" /// Basic energy cannon -#include "../units/unit_mlrs.qc" /// Basic multibay RL -#include "../units/unit_hellion.qc" /// Seeking missiles MLRS -#include "../units/unit_flac.qc" /// anti missile turret -#include "../units/unit_phaser.qc" /// ZzzapT -#include "../units/unit_hk.qc" /// Hunter killers -#include "../units/unit_machinegun.qc" /// whacka -#include "../units/unit_tessla.qc" /// Chain lightning capabale turret -#include "../units/unit_walker.qc" /// Moving minigun-rocket-meele err thing -#include "../units/unit_ewheel.qc" /// A evil wheel. with guns on. -//#include "../units/unit_repulsor.qc" /// Fires a wave that knocks foes back -//#include "../units/unit_hive.qc" /// Swarm AI - -#endif // TTURRETS_ENABLED diff --git a/qcsrc/server/tturrets/include/turrets_early.qh b/qcsrc/server/tturrets/include/turrets_early.qh deleted file mode 100644 index 4ce95fc39..000000000 --- a/qcsrc/server/tturrets/include/turrets_early.qh +++ /dev/null @@ -1,473 +0,0 @@ -// Comment out below to skip turrets -#define TTURRETS_ENABLED - -#ifdef TTURRETS_ENABLED -#ifdef SVQC -//#message "with tZork turrets" - -float turret_count; - -vector real_origin(entity ent); - -/// Map time control over pain inflicted -.float turret_scale_damage; -/// Map time control targetting range -.float turret_scale_range; -/// Map time control refire -.float turret_scale_refire; -/// Map time control ammo held and recharged -.float turret_scale_ammo; -/// Map time control aim speed -.float turret_scale_aim; -/// Map time control health -.float turret_scale_health; -/// Map time control respawn time -.float turret_scale_respawn; - -/// Used for cvar reloading -.string cvar_basename; - -//.float spawnflags -#define TSF_SUSPENDED 1 -/// Spawn a pillar model under the turret to make it look ok on uneven ground surfaces -#define TSF_TERRAINBASE 2 -/// Disable builtin ammo regeneration -#define TSF_NO_AMMO_REGEN 4 -/// Dont break path to chase enemys. will still fire at them if possible. -#define TSF_NO_PATHBREAK 8 -/// Dont respawn -#define TSL_NO_RESPAWN 16 -/// Let this turret roam when idle. -#define TSL_ROAM 32 - -/// target selection flags -.float target_select_flags; -/// target validatoin flags -.float target_validate_flags; -/// Dont select a target on its own. -#define TFL_TARGETSELECT_NO 2 -/// Need line of sight -#define TFL_TARGETSELECT_LOS 4 -/// Players are valid targets -#define TFL_TARGETSELECT_PLAYERS 8 -/// Missiles are valid targets -#define TFL_TARGETSELECT_MISSILES 16 -/// Responds to turret_trigger_target events -#define TFL_TARGETSELECT_TRIGGERTARGET 32 -/// Angular limitations of turret head limits target selection -#define TFL_TARGETSELECT_ANGLELIMITS 64 -/// Range limits apply in targetselection -#define TFL_TARGETSELECT_RANGELIMTS 128 -/// DOnt select targets with a .team matching its own -#define TFL_TARGETSELECT_TEAMCHECK 256 -/// Cant select targets on its own. needs to be triggerd or slaved. -#define TFL_TARGETSELECT_NOBUILTIN 512 -/// TFL_TARGETSELECT_TEAMCHECK is inverted (selects only mebers of own .team) -#define TFL_TARGETSELECT_OWNTEAM 1024 -/// Turrets aren't valid targets -#define TFL_TARGETSELECT_NOTURRETS 2048 -/// Use feild of view -#define TFL_TARGETSELECT_FOV 4096 - -#define TFL_TARGETSELECT_MISSILESONLY 8192 - -/// aim flags -.float aim_flags; -/// Dont aim. -#define TFL_AIM_NO 1 -/// Go for ground, not direct hit, but only if target is on ground. -#define TFL_AIM_GROUNDGROUND 2 -/// Try to predict target movement (does not account for gravity) -#define TFL_AIM_LEAD 4 -/// Compensate for shot traveltime when lead -#define TFL_AIM_SHOTTIMECOMPENSATE 8 -/// Try to do real prediction of targets z pos at impact. -#define TFL_AIM_ZPREDICT 16 -/// Simply aim at target's current location -#define TFL_AIM_SIMPLE 32 - -/// track (turn and pitch head) flags -.float track_flags; -/// Dont move head -#define TFL_TRACK_NO 2 -/// Pitch the head -#define TFL_TRACK_PITCH 4 -/// Rotate the head -#define TFL_TRACK_ROT 8 - -/// How tracking is preformed -.float track_type; -/// Hard angle increments. Ugly for fast turning, best accuracy. -#define TFL_TRACKTYPE_STEPMOTOR 1 -/// Smoth absolute movement. Looks ok, fair accuracy. -#define TFL_TRACKTYPE_FLUIDPRECISE 2 -/// Simulated inertia. "Wobbly mode" Looks kool, can mean really bad accuracy depending on how the fields below are set -#define TFL_TRACKTYPE_FLUIDINERTIA 3 -/// TFL_TRACKTYPE_FLUIDINERTIA: pitch multiplier -.float track_accel_pitch; -/// TFL_TRACKTYPE_FLUIDINERTIA: rotation multiplier -.float track_accel_rot; -/// TFL_TRACKTYPE_FLUIDINERTIA: Blendrate with old rotation (inertia simulation) 1 = only old, 0 = only new -.float track_blendrate; - -/// How prefire check is preformed -.float firecheck_flags; -/// Dont kill the dead -#define TFL_FIRECHECK_DEAD 4 -/// Range limits apply -#define TFL_FIRECHECK_DISTANCES 8 -/// Line Of Sight needs to be clear -#define TFL_FIRECHECK_LOS 16 -/// Consider distance inpactpoint<->aimspot -#define TFL_FIRECHECK_AIMDIST 32 -/// Consider enemy origin<->impactpoint -#define TFL_FIRECHECK_REALDIST 64 -/// Consider angular diff head<->aimspot -#define TFL_FIRECHECK_ANGLEDIST 128 -/// (re)consider target.team<->self.team -#define TFL_FIRECHECK_TEAMCECK 256 -/// Try to avoid friendly fire -#define TFL_FIRECHECK_AFF 512 -/// Own .ammo needs to be >= then own .shot_dmg -#define TFL_FIRECHECK_OWM_AMMO 1024 -/// Others ammo need to be < others .ammo_max -#define TFL_FIRECHECK_OTHER_AMMO 2048 -/// Check own .attack_finished_single vs time -#define TFL_FIRECHECK_REFIRE 4096 -/// Move the acctual target to aimspot before tracing impact (and back after) -//#define TFL_FIRECHECK_VERIFIED 8192 -/// Dont do any chekcs -#define TFL_FIRECHECK_NO 16384 - -/// How shooting is done -.float shoot_flags; -/// Dont shoot -#define TFL_SHOOT_NO 64 -/// Fire in vollys (partial implementation through .shot_volly) -#define TFL_SHOOT_VOLLY 2 -/// Always do a full volly, even if target is lost or dead. (not implemented) -#define TFL_SHOOT_VOLLYALWAYS 4 -/// Loop though all valid tarters, and hit them. -#define TFL_SHOOT_HITALLVALID 8 -/// Fiering makes unit loose target (after volly is done, if in volly mode) -#define TFL_SHOOT_CLEARTARGET 16 -///Custom shooting; -#define TFL_SHOOT_CUSTOM 32 - -/// Information aboute the units capabilities -.float turrcaps_flags; -/// No kown capabilities -#define TFL_TURRCAPS_NONE 0 -/// Capable of sniping -#define TFL_TURRCAPS_SNIPER 2 -/// Capable of splasdamage -#define TFL_TURRCAPS_RADIUSDMG 4 -/// Has one or more cannons with zero shot traveltime -#define TFL_TURRCAPS_HITSCAN 8 -/// More then one (type of) gun -#define TFL_TURRCAPS_MULTIGUN 16 -/// Carries at least one guided weapon -#define TFL_TURRCAPS_GUIDED 32 -/// At least one gun fiers slow projectiles -#define TFL_TURRCAPS_SLOWPROJ 64 -/// At least one gun fiers medium speed projectiles -#define TFL_TURRCAPS_MEDPROJ 128 -/// At least one gun fiers fast projectiles -#define TFL_TURRCAPS_FASTPROJ 256 -/// At least one gun capable of damaging players -#define TFL_TURRCAPS_PLAYERKILL 512 -/// At least one gun that can shoot town missiles -#define TFL_TURRCAPS_MISSILEKILL 1024 -/// Has support capabilities. powerplants and sutch. -#define TFL_TURRCAPS_SUPPORT 2048 -/// Proveides at least one type of ammmo -#define TFL_TURRCAPS_AMMOSOURCE 4096 -/// Can recive targets from external sources -#define TFL_TURRCAPS_RECIVETARGETS 8192 -/// Capable of self-transport -#define TFL_TURRCAPS_MOVE 16384 -/// Will roam arround even if not chasing anyting -#define TFL_TURRCAPS_ROAM 32768 -#define TFL_TURRCAPS_ISTURRET 65536 - -/// Ammo types needed and/or provided -//.float ammo_flags; -#define ammo_flags currentammo -/// Has and needs no ammo -#define TFL_AMMO_NONE 64 -/// Uses power -#define TFL_AMMO_ENERGY 2 -/// Uses bullets -#define TFL_AMMO_BULLETS 4 -/// Uses explosives -#define TFL_AMMO_ROCKETS 8 -/// Regenerates ammo on its own -#define TFL_AMMO_RECHARGE 16 -/// Can recive ammo from others -#define TFL_AMMO_RECIVE 32 - -/// How incomming damage is handeld -.float damage_flags; -/// Cant be hurt -#define TFL_DMG_NO 256 -/// Can be damaged -#define TFL_DMG_YES 2 -/// Can be damaged by teammates -#define TFL_DMG_TAKEFROMTEAM 4 -/// Traget attackers -#define TFL_DMG_RETALIATE 8 -/// Target attackers, even is on own team -#define TFL_DMG_RETALIATEONTEAM 16 -/// Loses target when damaged -#define TFL_DMG_TARGETLOSS 32 -/// Reciving damage trows off aim (pointless atm, aim gets recalculated to fast). not implemented. -#define TFL_DMG_AIMSHAKE 64 -/// Reciving damage slaps the head arround -#define TFL_DMG_HEADSHAKE 128 -/// Die and stay dead. -#define TFL_DMG_DEATH_NORESPAWN 256 - -// Spawnflags -/// Spawn in teambased modes -#define TFL_SPAWN_TEAM 2 -/// Spawn in FFA modes -#define TFL_SPAWN_FFA 4 - - -/* -* Fields used by turrets -*/ -/// Turrets internal ai speed -.float ticrate; - -/// Where to point the when no target -.vector idle_aim; - -/// Top part of turret -.entity tur_head; - -/// Start/respawn health -.float tur_health; - -/// Defend this entity (or ratehr this entitys position) -.entity tur_defend; - -/// and shoot from here. (can be non constant, think MLRS) -.vector tur_shotorg; - -/// Aim at this spot -.vector tur_aimpos; - -/// Predicted time the round will impact -.float tur_impacttime; - -// Predicted place the round will impact -//.vector tur_impactpoint; // unused - -/// What entity the aimtrace hit, if any. -.entity tur_impactent; - -/// Distance to enemy -.float tur_dist_enemy; - -/// Distance to aimspot -.float tur_dist_aimpos; - -/// Distance impact<->aim -.float tur_dist_impact_to_aimpos; - -/// Decresment counter form .shot_volly to 0. -.float volly_counter; - -/* -* Projectile/missile. its up to the individual turret implementation to -** deal the damage, blow upp the missile or whatever. -*/ -/// Track then refireing is possible -//.float attack_finished; = attack_finished_single -/// Shoot this often -.float shot_refire; -/// Shots travel this fast, when appliable -.float shot_speed; -/// Inaccuracy -.float shot_spread; -/// Estimated (core) damage of projectiles. also reduce on ammo with this amount when fiering -.float shot_dmg; -/// If radius dmg, this is how big that radius is. -.float shot_radius; -/// Max force exserted by round impact -.float shot_force; -/// < 1 = shoot # times at target (if possible) -.float shot_volly; -/// Refire after a compleated volly. -.float shot_volly_refire; - -/// Consider targets within this range -.float target_range; -/// Dont consider targets closer then -.float target_range_min; -/// Targets closer to this are prefered -.float target_range_optimal; - -/* -* The standard targetselection tries to select a target based on -* range, angle offset, target type, "is old target" -* Thise biases will allow score scaling to (dis)favor diffrent targets -*/ -/// (dis)Favor best range this mutch -.float target_select_rangebias; -/// (dis)Favor targeting my old enemy this mutch -.float target_select_samebias; -/// (dis)Favor targeting the enemy closest to my guns current angle this mutch -.float target_select_anglebias; -/// (dis)Favor Missiles? (-1 to diable targeting compleatly) -.float target_select_missilebias; -/// (dis)Favot living players (-1 to diable targeting compleatly) -.float target_select_playerbias; -/// Field of view -//.float target_select_fov; -/// Last timestamp this turret aquierd a valid target -.float target_select_time; -/// Throttle re-validation of current target -.float target_validate_time; -/* -* Aim refers to real aiming, not gun pos (thats done by track) -*/ -/// Maximum offset between impact and aim spot to fire -.float aim_firetolerance_dist; -/// How fast can i rotate/pitch (per second in stepmotor mode, base force in smooth modes) -.float aim_speed; -/// cant aim higher/lower then this -.float aim_maxpitch; -/// I cant rotate more then this -.float aim_maxrot; - -// Ammo/power. keeping dmg and ammo on a one to one ratio is preferable (for rating) -/// Staring & current ammo -.float ammo; -/// Regenerate this mutch ammo (per second) -.float ammo_recharge; -/// Max amount of ammo i can hold -.float ammo_max; - - -// Uncomment below to enable various debug output. -//#define TURRET_DEBUG -//#define TURRET_DEBUG_TARGETVALIDATE -//#define TURRET_DEBUG_TARGETSELECT - -#ifdef TURRET_DEBUG -.float tur_dbg_dmg_t_h; // Total dmg that hit something (can be more then tur_dbg_dmg_t_f since it should count radius dmg. -.float tur_dbg_dmg_t_f; // Total damage spent -.float tur_dbg_start; // When did i go online? -.float tur_dbg_tmr1; // timer for random use -.float tur_dbg_tmr2; // timer for random use -.float tur_dbg_tmr3; // timer for random use -.vector tur_dbg_rvec; // Random vector, mainly for coloruing stuff' -#endif - -// System main's -/// Main AI loop -void turret_think(); -/// Prefire checks and sutch -void turret_fire(); - -// Callbacks -/// implements the actual fiering -.void() turret_firefunc; -/// prefire checks go here. return 1 to go bang, 0 not to. -.float() turret_firecheckfunc; -/// Execure AFTER main AI loop -.void() turret_postthink; - -/// Add a target -.float(entity e_target,entity e_sender) turret_addtarget; - -.void() turret_diehook; -.void() turret_respawnhook; - -/* -* Target selection, preferably but not nessesarely -* return a normalized result. -*/ -/// Function to use for target evaluation. usualy turret_stdproc_targetscore_generic -.float(entity _turret, entity _target) turret_score_target; - -/* -* Target selection -*/ -/// Generic, fairly smart, bias-aware target selection. -float turret_stdproc_targetscore_generic(entity _turret, entity _target); -/// Experimental supportunits targetselector -float turret_stdproc_targetscore_support(entity _turret,entity _target); - -/* -* Aim functions -*/ -/// Generic aimer guided by self.aim_flags -vector turret_stdproc_aim_generic(); - -/* -* Turret turning & pitch -*/ -/// Tries to line up the turret head with the aimpos -void turret_stdproc_track(); - -/// Generic damage handeling. blows up the turret when health <= 0 -void turret_stdproc_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce); -/// Spawns a explotion, does some damage & trows bits arround. -void turret_stdproc_die(); -/// reassembles the turret. -void turret_stdproc_respawn(); - -/// Evaluate target validity -float turret_validate_target(entity e_turret,entity e_target,float validate_flags); -/// Turret Head Angle Diff Vector. updated by a sucsessfull call to turret_validate_target -vector tvt_thadv; -/// Turret Angle Diff Vector. updated by a sucsessfull call to turret_validate_target -vector tvt_tadv; -/// Turret Head Angle Diff Float. updated by a sucsessfull call to turret_validate_target -float tvt_thadf; -/// Turret Angle Diff Float. updated by a sucsessfull call to turret_validate_target -float tvt_tadf; -/// Distance. updated by a sucsessfull call to turret_validate_target -float tvt_dist; - -/// updates aim org, shot org, shot dir and enemy org for selected turret -void turret_do_updates(entity e_turret); -.vector tur_shotdir_updated; - -void turrets_precash(); -#endif // SVQC - -// common -.float turret_type; -const float TID_COMMON = 1; -const float TID_EWHEEL = 2; -const float TID_FLAC = 3; -const float TID_FUSION = 4; -const float TID_HELLION = 5; -const float TID_HK = 6; -const float TID_MACHINEGUN = 7; -const float TID_MLRS = 8; -const float TID_PHASER = 9; -const float TID_PLASMA = 10; -const float TID_PLASMA_DUAL = 11; -const float TID_TESLA = 12; -const float TID_WALKER = 13; -const float TID_LAST = 13; - -const float TNSF_UPDATE = 2; -const float TNSF_STATUS = 4; -const float TNSF_SETUP = 8; -const float TNSF_ANG = 16; -const float TNSF_AVEL = 32; -const float TNSF_MOVE = 64; -.float anim_start_time; -const float TNSF_ANIM = 128; - -const float TNSF_FULL_UPDATE = 16777215; - -#endif // TTURRETS_ENABLED - - diff --git a/qcsrc/server/tturrets/system/system_aimprocs.qc b/qcsrc/server/tturrets/system/system_aimprocs.qc deleted file mode 100644 index c3dbe55a4..000000000 --- a/qcsrc/server/tturrets/system/system_aimprocs.qc +++ /dev/null @@ -1,73 +0,0 @@ -/* -* Generic aim - -supports: -TFL_AIM_NO -TFL_AIM_GROUNDGROUND -TFL_AIM_LEAD -TFL_AIM_SHOTTIMECOMPENSATE -*/ -vector turret_stdproc_aim_generic() -{ - - vector pre_pos, prep; - float distance, impact_time, i, mintime; - - turret_tag_fire_update(); - - if(self.aim_flags & TFL_AIM_SIMPLE) - return real_origin(self.enemy); - - mintime = max(self.attack_finished_single - time,0) + sys_frametime; - - // Baseline - pre_pos = real_origin(self.enemy); - - // Lead? - if (self.aim_flags & TFL_AIM_LEAD) - { - if (self.aim_flags & TFL_AIM_SHOTTIMECOMPENSATE) // Need to conpensate for shot traveltime - { - // FIXME: this cant be the best way to do this.. - prep = pre_pos; -#ifdef GMQCC - impact_time = 0; -#endif - for(i = 0; i < 4; ++i) - { - distance = vlen(prep - self.tur_shotorg); - impact_time = distance / self.shot_speed; - prep = pre_pos + self.enemy.velocity * impact_time; - } - - prep = pre_pos + (self.enemy.velocity * (impact_time + mintime)); - - if(self.aim_flags & TFL_AIM_ZPREDICT) - if (!(self.enemy.flags & FL_ONGROUND)) - if(self.enemy.movetype == MOVETYPE_WALK || self.enemy.movetype == MOVETYPE_TOSS || self.enemy.movetype == MOVETYPE_BOUNCE) - { - float vz; - prep_z = pre_pos_z; - vz = self.enemy.velocity_z; - for(i = 0; i < impact_time; i += sys_frametime) - { - vz = vz - (autocvar_sv_gravity * sys_frametime); - prep_z = prep_z + vz * sys_frametime; - } - } - pre_pos = prep; - } - else - pre_pos = pre_pos + self.enemy.velocity * mintime; - } - - if(self.aim_flags & TFL_AIM_GROUNDGROUND) - { - //tracebox(pre_pos + '0 0 32',self.enemy.mins,self.enemy.maxs,pre_pos -'0 0 64',MOVE_WORLDONLY,self.enemy); - traceline(pre_pos + '0 0 32',pre_pos -'0 0 64',MOVE_WORLDONLY,self.enemy); - if(trace_fraction != 1.0) - pre_pos = trace_endpos; - } - - return pre_pos; -} diff --git a/qcsrc/server/tturrets/system/system_damage.qc b/qcsrc/server/tturrets/system/system_damage.qc deleted file mode 100644 index a8feaebc3..000000000 --- a/qcsrc/server/tturrets/system/system_damage.qc +++ /dev/null @@ -1,131 +0,0 @@ -/* -* Spawn a boom, trow fake bits arround -* and hide the real ones. -*/ -void turret_hide() -{ - self.effects |= EF_NODRAW; - self.nextthink = time + self.respawntime - 0.2; - self.think = turret_stdproc_respawn; -} - -void turret_stdproc_die() -{ - self.deadflag = DEAD_DEAD; - self.tur_head.deadflag = self.deadflag; - -// Unsolidify and hide real parts - self.solid = SOLID_NOT; - self.tur_head.solid = self.solid; - - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - self.health = 0; - -// Go boom - //RadiusDamage (self,self, min(self.ammo,50),min(self.ammo,50) * 0.25,250,world,min(self.ammo,50)*5,DEATH_TURRET,world); - - if(self.damage_flags & TFL_DMG_DEATH_NORESPAWN) - { - if (self.turret_diehook) - self.turret_diehook(); - - remove(self.tur_head); - remove(self); - } - else - { - // Setup respawn - self.SendFlags |= TNSF_STATUS; - self.nextthink = time + 0.2; - self.think = turret_hide; - - if (self.turret_diehook) - self.turret_diehook(); - } -} - -void turret_stdproc_respawn() -{ - // Make sure all parts belong to the same team since - // this function doubles as "teamchange" function. - self.tur_head.team = self.team; - - self.effects &= ~EF_NODRAW; - self.deadflag = DEAD_NO; - self.effects = EF_LOWPRECISION; - self.solid = SOLID_BBOX; - - self.takedamage = DAMAGE_AIM; - self.event_damage = turret_stdproc_damage; - - self.avelocity = '0 0 0'; - self.tur_head.avelocity = self.avelocity; - self.tur_head.angles = self.idle_aim; - self.health = self.tur_health; - - self.enemy = world; - self.volly_counter = self.shot_volly; - self.ammo = self.ammo_max; - - self.nextthink = time + self.ticrate; - self.think = turret_think; - - self.SendFlags = TNSF_FULL_UPDATE; - - if (self.turret_respawnhook) - self.turret_respawnhook(); -} - -/* -* Standard damage proc. -*/ -void turret_stdproc_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce) -{ - // Enougth allready! - if(self.deadflag == DEAD_DEAD) - return; - - // Inactive turrets take no damage. (hm..) - if (!self.active) - return; - - if (teamplay) - if (self.team == attacker.team) - { - // This does not happen anymore. Re-enable if you fix that. - if(IS_REAL_CLIENT(attacker)) - sprint(attacker, "\{1}Turret tells you: I'm on your team!\n"); - - if(autocvar_g_friendlyfire) - damage = damage * autocvar_g_friendlyfire; - else - return; - } - - self.health = self.health - damage; - - // thorw head slightly off aim when hit? - if (self.damage_flags & TFL_DMG_HEADSHAKE) - { - self.tur_head.angles_x = self.tur_head.angles_x + (-0.5 + random()) * damage; - self.tur_head.angles_y = self.tur_head.angles_y + (-0.5 + random()) * damage; - - self.SendFlags |= TNSF_ANG; - } - - if (self.turrcaps_flags & TFL_TURRCAPS_MOVE) - self.velocity = self.velocity + vforce; - - if (self.health <= 0) - { - self.event_damage = func_null; - self.tur_head.event_damage = func_null; - self.takedamage = DAMAGE_NO; - self.nextthink = time; - self.think = turret_stdproc_die; - } - - self.SendFlags |= TNSF_STATUS; -} diff --git a/qcsrc/server/tturrets/system/system_main.qc b/qcsrc/server/tturrets/system/system_main.qc deleted file mode 100644 index d56a81bbf..000000000 --- a/qcsrc/server/tturrets/system/system_main.qc +++ /dev/null @@ -1,1378 +0,0 @@ -#define cvar_base "g_turrets_unit_" -.float clientframe; -void turrets_setframe(float _frame, float client_only) -{ - if((client_only ? self.clientframe : self.frame ) != _frame) - { - self.SendFlags |= TNSF_ANIM; - self.anim_start_time = time; - } - - if(client_only) - self.clientframe = _frame; - else - self.frame = _frame; - -} - -float turret_send(entity to, float sf) -{ - - WriteByte(MSG_ENTITY, ENT_CLIENT_TURRET); - WriteByte(MSG_ENTITY, sf); - if(sf & TNSF_SETUP) - { - WriteByte(MSG_ENTITY, self.turret_type); - - WriteCoord(MSG_ENTITY, self.origin_x); - WriteCoord(MSG_ENTITY, self.origin_y); - WriteCoord(MSG_ENTITY, self.origin_z); - - WriteAngle(MSG_ENTITY, self.angles_x); - WriteAngle(MSG_ENTITY, self.angles_y); - } - - if(sf & TNSF_ANG) - { - WriteShort(MSG_ENTITY, rint(self.tur_head.angles_x)); - WriteShort(MSG_ENTITY, rint(self.tur_head.angles_y)); - } - - if(sf & TNSF_AVEL) - { - WriteShort(MSG_ENTITY, rint(self.tur_head.avelocity_x)); - WriteShort(MSG_ENTITY, rint(self.tur_head.avelocity_y)); - } - - if(sf & TNSF_MOVE) - { - WriteShort(MSG_ENTITY, rint(self.origin_x)); - WriteShort(MSG_ENTITY, rint(self.origin_y)); - WriteShort(MSG_ENTITY, rint(self.origin_z)); - - WriteShort(MSG_ENTITY, rint(self.velocity_x)); - WriteShort(MSG_ENTITY, rint(self.velocity_y)); - WriteShort(MSG_ENTITY, rint(self.velocity_z)); - - WriteShort(MSG_ENTITY, rint(self.angles_y)); - } - - if(sf & TNSF_ANIM) - { - WriteCoord(MSG_ENTITY, self.anim_start_time); - WriteByte(MSG_ENTITY, self.frame); - } - - if(sf & TNSF_STATUS) - { - WriteByte(MSG_ENTITY, self.team); - - if(self.health <= 0) - WriteByte(MSG_ENTITY, 0); - else - WriteByte(MSG_ENTITY, ceil((self.health / self.tur_health) * 255)); - } - - return TRUE; -} - -void load_unit_settings(entity ent, string unitname, float is_reload) -{ - string sbase; - - if (ent == world) - return; - - if (!ent.turret_scale_damage) ent.turret_scale_damage = 1; - if (!ent.turret_scale_range) ent.turret_scale_range = 1; - if (!ent.turret_scale_refire) ent.turret_scale_refire = 1; - if (!ent.turret_scale_ammo) ent.turret_scale_ammo = 1; - if (!ent.turret_scale_aim) ent.turret_scale_aim = 1; - if (!ent.turret_scale_health) ent.turret_scale_health = 1; - if (!ent.turret_scale_respawn) ent.turret_scale_respawn = 1; - - sbase = strcat(cvar_base,unitname); - if (is_reload) - { - ent.enemy = world; - ent.tur_head.avelocity = '0 0 0'; - - ent.tur_head.angles = '0 0 0'; - } - - ent.health = cvar(strcat(sbase,"_health")) * ent.turret_scale_health; - ent.respawntime = cvar(strcat(sbase,"_respawntime")) * ent.turret_scale_respawn; - - ent.shot_dmg = cvar(strcat(sbase,"_shot_dmg")) * ent.turret_scale_damage; - ent.shot_refire = cvar(strcat(sbase,"_shot_refire")) * ent.turret_scale_refire; - ent.shot_radius = cvar(strcat(sbase,"_shot_radius")) * ent.turret_scale_damage; - ent.shot_speed = cvar(strcat(sbase,"_shot_speed")); - ent.shot_spread = cvar(strcat(sbase,"_shot_spread")); - ent.shot_force = cvar(strcat(sbase,"_shot_force")) * ent.turret_scale_damage; - ent.shot_volly = cvar(strcat(sbase,"_shot_volly")); - ent.shot_volly_refire = cvar(strcat(sbase,"_shot_volly_refire")) * ent.turret_scale_refire; - - ent.target_range = cvar(strcat(sbase,"_target_range")) * ent.turret_scale_range; - ent.target_range_min = cvar(strcat(sbase,"_target_range_min")) * ent.turret_scale_range; - ent.target_range_optimal = cvar(strcat(sbase,"_target_range_optimal")) * ent.turret_scale_range; - //ent.target_range_fire = cvar(strcat(sbase,"_target_range_fire")) * ent.turret_scale_range; - - ent.target_select_rangebias = cvar(strcat(sbase,"_target_select_rangebias")); - ent.target_select_samebias = cvar(strcat(sbase,"_target_select_samebias")); - ent.target_select_anglebias = cvar(strcat(sbase,"_target_select_anglebias")); - ent.target_select_playerbias = cvar(strcat(sbase,"_target_select_playerbias")); - //ent.target_select_fov = cvar(cvar_gets(sbase,"_target_select_fov")); - - ent.ammo_max = cvar(strcat(sbase,"_ammo_max")) * ent.turret_scale_ammo; - ent.ammo_recharge = cvar(strcat(sbase,"_ammo_recharge")) * ent.turret_scale_ammo; - - ent.aim_firetolerance_dist = cvar(strcat(sbase,"_aim_firetolerance_dist")); - ent.aim_speed = cvar(strcat(sbase,"_aim_speed")) * ent.turret_scale_aim; - ent.aim_maxrot = cvar(strcat(sbase,"_aim_maxrot")); - ent.aim_maxpitch = cvar(strcat(sbase,"_aim_maxpitch")); - - ent.track_type = cvar(strcat(sbase,"_track_type")); - ent.track_accel_pitch = cvar(strcat(sbase,"_track_accel_pitch")); - ent.track_accel_rot = cvar(strcat(sbase,"_track_accel_rot")); - ent.track_blendrate = cvar(strcat(sbase,"_track_blendrate")); - - if(is_reload) - if(ent.turret_respawnhook) - ent.turret_respawnhook(); -} - -void turret_projectile_explode() -{ - - self.takedamage = DAMAGE_NO; - self.event_damage = func_null; -#ifdef TURRET_DEBUG - float d; - d = RadiusDamage (self, self.owner, self.owner.shot_dmg, 0, self.owner.shot_radius, self, world, self.owner.shot_force, self.totalfrags, world); - self.owner.tur_dbg_dmg_t_h = self.owner.tur_dbg_dmg_t_h + d; - self.owner.tur_dbg_dmg_t_f = self.owner.tur_dbg_dmg_t_f + self.owner.shot_dmg; -#else - RadiusDamage (self, self.realowner, self.owner.shot_dmg, 0, self.owner.shot_radius, self, world, self.owner.shot_force, self.totalfrags, world); -#endif - remove(self); -} - -void turret_projectile_touch() -{ - PROJECTILE_TOUCH; - turret_projectile_explode(); -} - -void turret_projectile_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce) -{ - self.velocity += vforce; - self.health -= damage; - //self.realowner = attacker; // Dont change realowner, it does not make much sense for turrets - if(self.health <= 0) - W_PrepareExplosionByDamage(self.owner, turret_projectile_explode); -} - -entity turret_projectile(string _snd, float _size, float _health, float _death, float _proj_type, float _cull, float _cli_anim) -{ - entity proj; - - sound (self, CH_WEAPON_A, _snd, VOL_BASE, ATTEN_NORM); - proj = spawn (); - setorigin(proj, self.tur_shotorg); - setsize(proj, '-0.5 -0.5 -0.5' * _size, '0.5 0.5 0.5' * _size); - proj.owner = self; - proj.realowner = self; - proj.bot_dodge = TRUE; - proj.bot_dodgerating = self.shot_dmg; - proj.think = turret_projectile_explode; - proj.touch = turret_projectile_touch; - proj.nextthink = time + 9; - proj.movetype = MOVETYPE_FLYMISSILE; - proj.velocity = normalize(self.tur_shotdir_updated + randomvec() * self.shot_spread) * self.shot_speed; - proj.flags = FL_PROJECTILE; - proj.enemy = self.enemy; - proj.totalfrags = _death; - PROJECTILE_MAKETRIGGER(proj); - if(_health) - { - proj.health = _health; - proj.takedamage = DAMAGE_YES; - proj.event_damage = turret_projectile_damage; - } - else - proj.flags |= FL_NOTARGET; - - CSQCProjectile(proj, _cli_anim, _proj_type, _cull); - - return proj; -} - -/** -** updates enemy distances, predicted impact point/time -** and updated aim<->predict impact distance. -**/ -void turret_do_updates(entity t_turret) -{ - vector enemy_pos; - entity oldself; - - oldself = self; - self = t_turret; - - enemy_pos = real_origin(self.enemy); - - turret_tag_fire_update(); - - self.tur_shotdir_updated = v_forward; - self.tur_dist_enemy = vlen(self.tur_shotorg - enemy_pos); - self.tur_dist_aimpos = vlen(self.tur_shotorg - self.tur_aimpos); - - /*if((self.firecheck_flags & TFL_FIRECHECK_VERIFIED) && (self.enemy)) - { - oldpos = self.enemy.origin; - setorigin(self.enemy, self.tur_aimpos); - tracebox(self.tur_shotorg, '-1 -1 -1', '1 1 1', self.tur_shotorg + (self.tur_shotdir_updated * self.tur_dist_aimpos), MOVE_NORMAL,self); - setorigin(self.enemy, oldpos); - - if(trace_ent == self.enemy) - self.tur_dist_impact_to_aimpos = 0; - else - self.tur_dist_impact_to_aimpos = vlen(trace_endpos - self.tur_aimpos); - } - else*/ - tracebox(self.tur_shotorg, '-1 -1 -1','1 1 1', self.tur_shotorg + (self.tur_shotdir_updated * self.tur_dist_aimpos), MOVE_NORMAL,self); - - self.tur_dist_impact_to_aimpos = vlen(trace_endpos - self.tur_aimpos) - (vlen(self.enemy.maxs - self.enemy.mins) * 0.5); - self.tur_impactent = trace_ent; - self.tur_impacttime = vlen(self.tur_shotorg - trace_endpos) / self.shot_speed; - - self = oldself; -} - -/* -vector turret_fovsearch_pingpong() -{ - vector wish_angle; - if(self.phase < time) - { - if( self.tur_head.phase ) - self.tur_head.phase = 0; - else - self.tur_head.phase = 1; - self.phase = time + 5; - } - - if( self.tur_head.phase) - wish_angle = self.idle_aim + '0 1 0' * (self.aim_maxrot * (self.target_select_fov / 360)); - else - wish_angle = self.idle_aim - '0 1 0' * (self.aim_maxrot * (self.target_select_fov / 360)); - - return wish_angle; -} - -vector turret_fovsearch_steprot() -{ - vector wish_angle; - //float rot_add; - - wish_angle = self.tur_head.angles; - wish_angle_x = self.idle_aim_x; - - if (self.phase < time) - { - //rot_add = self.aim_maxrot / self.target_select_fov; - wish_angle_y += (self.target_select_fov * 2); - - if(wish_angle_y > 360) - wish_angle_y = wish_angle_y - 360; - - self.phase = time + 1.5; - } - - return wish_angle; -} - -vector turret_fovsearch_random() -{ - vector wish_angle; - - if (self.phase < time) - { - wish_angle_y = random() * self.aim_maxrot; - if(random() < 0.5) - wish_angle_y *= -1; - - wish_angle_x = random() * self.aim_maxpitch; - if(random() < 0.5) - wish_angle_x *= -1; - - self.phase = time + 5; - - self.tur_aimpos = wish_angle; - } - - return self.idle_aim + self.tur_aimpos; -} -*/ - -/** -** Handles head rotation according to -** the units .track_type and .track_flags -**/ -.float turret_framecounter; -void turret_stdproc_track() -{ - vector target_angle; // This is where we want to aim - vector move_angle; // This is where we can aim - float f_tmp; - vector v1, v2; - v1 = self.tur_head.angles; - v2 = self.tur_head.avelocity; - - if (self.track_flags == TFL_TRACK_NO) - return; - - if (!self.active) - target_angle = self.idle_aim - ('1 0 0' * self.aim_maxpitch); - else if (self.enemy == world) - { - if(time > self.lip) - target_angle = self.idle_aim + self.angles; - else - target_angle = vectoangles(normalize(self.tur_aimpos - self.tur_shotorg)); - } - else - { - target_angle = vectoangles(normalize(self.tur_aimpos - self.tur_shotorg)); - } - - self.tur_head.angles_x = anglemods(self.tur_head.angles_x); - self.tur_head.angles_y = anglemods(self.tur_head.angles_y); - - // Find the diffrence between where we currently aim and where we want to aim - //move_angle = target_angle - (self.angles + self.tur_head.angles); - //move_angle = shortangle_vxy(move_angle,(self.angles + self.tur_head.angles)); - - move_angle = AnglesTransform_ToAngles(AnglesTransform_LeftDivide(AnglesTransform_FromAngles(self.angles), AnglesTransform_FromAngles(target_angle))) - self.tur_head.angles; - move_angle = shortangle_vxy(move_angle, self.tur_head.angles); - - switch(self.track_type) - { - case TFL_TRACKTYPE_STEPMOTOR: - f_tmp = self.aim_speed * self.ticrate; // dgr/sec -> dgr/tic - if (self.track_flags & TFL_TRACK_PITCH) - { - self.tur_head.angles_x += bound(-f_tmp,move_angle_x, f_tmp); - if(self.tur_head.angles_x > self.aim_maxpitch) - self.tur_head.angles_x = self.aim_maxpitch; - - if(self.tur_head.angles_x < -self.aim_maxpitch) - self.tur_head.angles_x = self.aim_maxpitch; - } - - if (self.track_flags & TFL_TRACK_ROT) - { - self.tur_head.angles_y += bound(-f_tmp, move_angle_y, f_tmp); - if(self.tur_head.angles_y > self.aim_maxrot) - self.tur_head.angles_y = self.aim_maxrot; - - if(self.tur_head.angles_y < -self.aim_maxrot) - self.tur_head.angles_y = self.aim_maxrot; - } - - // CSQC - self.SendFlags |= TNSF_ANG; - - return; - - case TFL_TRACKTYPE_FLUIDINERTIA: - f_tmp = self.aim_speed * self.ticrate; // dgr/sec -> dgr/tic - move_angle_x = bound(-self.aim_speed, move_angle_x * self.track_accel_pitch * f_tmp, self.aim_speed); - move_angle_y = bound(-self.aim_speed, move_angle_y * self.track_accel_rot * f_tmp, self.aim_speed); - move_angle = (self.tur_head.avelocity * self.track_blendrate) + (move_angle * (1 - self.track_blendrate)); - break; - - case TFL_TRACKTYPE_FLUIDPRECISE: - - move_angle_y = bound(-self.aim_speed, move_angle_y, self.aim_speed); - move_angle_x = bound(-self.aim_speed, move_angle_x, self.aim_speed); - - break; - } - - // pitch - if (self.track_flags & TFL_TRACK_PITCH) - { - self.tur_head.avelocity_x = move_angle_x; - if((self.tur_head.angles_x + self.tur_head.avelocity_x * self.ticrate) > self.aim_maxpitch) - { - self.tur_head.avelocity_x = 0; - self.tur_head.angles_x = self.aim_maxpitch; - - self.SendFlags |= TNSF_ANG; - } - - if((self.tur_head.angles_x + self.tur_head.avelocity_x * self.ticrate) < -self.aim_maxpitch) - { - self.tur_head.avelocity_x = 0; - self.tur_head.angles_x = -self.aim_maxpitch; - - self.SendFlags |= TNSF_ANG; - } - } - - // rot - if (self.track_flags & TFL_TRACK_ROT) - { - self.tur_head.avelocity_y = move_angle_y; - - if((self.tur_head.angles_y + self.tur_head.avelocity_y * self.ticrate) > self.aim_maxrot) - { - self.tur_head.avelocity_y = 0; - self.tur_head.angles_y = self.aim_maxrot; - - self.SendFlags |= TNSF_ANG; - } - - if((self.tur_head.angles_y + self.tur_head.avelocity_y * self.ticrate) < -self.aim_maxrot) - { - self.tur_head.avelocity_y = 0; - self.tur_head.angles_y = -self.aim_maxrot; - - self.SendFlags |= TNSF_ANG; - } - } - - self.SendFlags |= TNSF_AVEL; - - // Force a angle update every 10'th frame - self.turret_framecounter += 1; - if(self.turret_framecounter >= 10) - { - self.SendFlags |= TNSF_ANG; - self.turret_framecounter = 0; - } -} - - -/* - + = implemented - - = not implemented - - + TFL_FIRECHECK_NO - + TFL_FIRECHECK_WORLD - + TFL_FIRECHECK_DEAD - + TFL_FIRECHECK_DISTANCES - - TFL_FIRECHECK_LOS - + TFL_FIRECHECK_AIMDIST - + TFL_FIRECHECK_REALDIST - - TFL_FIRECHECK_ANGLEDIST - - TFL_FIRECHECK_TEAMCECK - + TFL_FIRECHECK_AFF - + TFL_FIRECHECK_OWM_AMMO - + TFL_FIRECHECK_OTHER_AMMO - + TFL_FIRECHECK_REFIRE -*/ - -/** -** Preforms pre-fire checks based on the uints firecheck_flags -**/ -float turret_stdproc_firecheck() -{ - // This one just dont care =) - if (self.firecheck_flags & TFL_FIRECHECK_NO) - return 1; - - if (self.enemy == world) - return 0; - - // Ready? - if (self.firecheck_flags & TFL_FIRECHECK_REFIRE) - if (self.attack_finished_single > time) return 0; - - // Special case: volly fire turret that has to fire a full volly if a shot was fired. - if (self.shoot_flags & TFL_SHOOT_VOLLYALWAYS) - if (self.volly_counter != self.shot_volly) - if(self.ammo >= self.shot_dmg) - return 1; - - // Lack of zombies makes shooting dead things unnecessary :P - if (self.firecheck_flags & TFL_FIRECHECK_DEAD) - if (self.enemy.deadflag != DEAD_NO) - return 0; - - // Own ammo? - if (self.firecheck_flags & TFL_FIRECHECK_OWM_AMMO) - if (self.ammo < self.shot_dmg) - return 0; - - // Other's ammo? (support-supply units) - if (self.firecheck_flags & TFL_FIRECHECK_OTHER_AMMO) - if (self.enemy.ammo >= self.enemy.ammo_max) - return 0; - - // Target of opertunity? - if(turret_validate_target(self, self.tur_impactent, self.target_validate_flags) > 0) - { - self.enemy = self.tur_impactent; - return 1; - } - - if (self.firecheck_flags & TFL_FIRECHECK_DISTANCES) - { - // To close? - if (self.tur_dist_aimpos < self.target_range_min) - if(turret_validate_target(self, self.tur_impactent, self.target_validate_flags) > 0) - return 1; // Target of opertunity? - else - return 0; - } - - // Try to avoid FF? - if (self.firecheck_flags & TFL_FIRECHECK_AFF) - if (self.tur_impactent.team == self.team) - return 0; - - // aim<->predicted impact - if (self.firecheck_flags & TFL_FIRECHECK_AIMDIST) - if (self.tur_dist_impact_to_aimpos > self.aim_firetolerance_dist) - return 0; - - // Volly status - if (self.shot_volly > 1) - if (self.volly_counter == self.shot_volly) - if (self.ammo < (self.shot_dmg * self.shot_volly)) - return 0; - - /*if(self.firecheck_flags & TFL_FIRECHECK_VERIFIED) - if(self.tur_impactent != self.enemy) - return 0;*/ - - return 1; -} - -/* - + TFL_TARGETSELECT_NO - + TFL_TARGETSELECT_LOS - + TFL_TARGETSELECT_PLAYERS - + TFL_TARGETSELECT_MISSILES - - TFL_TARGETSELECT_TRIGGERTARGET - + TFL_TARGETSELECT_ANGLELIMITS - + TFL_TARGETSELECT_RANGELIMTS - + TFL_TARGETSELECT_TEAMCHECK - - TFL_TARGETSELECT_NOBUILTIN - + TFL_TARGETSELECT_OWNTEAM -*/ - -/** -** Evaluate a entity for target valitity based on validate_flags -** NOTE: the caller must check takedamage before calling this, to inline this check. -**/ -float turret_validate_target(entity e_turret, entity e_target, float validate_flags) -{ - vector v_tmp; - - //if(!validate_flags & TFL_TARGETSELECT_NOBUILTIN) - // return -0.5; - - if(e_target.owner == e_turret) - return -0.5; - - if (!checkpvs(e_target.origin, e_turret)) - return -1; - - if (!e_target) - return -2; - - if(g_onslaught) - if (substring(e_target.classname, 0, 10) == "onslaught_") // don't attack onslaught targets, that's the player's job! - return - 3; - - if (validate_flags & TFL_TARGETSELECT_NO) - return -4; - - // If only this was used more.. - if (e_target.flags & FL_NOTARGET) - return -5; - - // Cant touch this - if(e_target.vehicle_flags & VHF_ISVEHICLE) - { - if (e_target.vehicle_health <= 0) - return -6; - } - else if (e_target.health <= 0) - return -6; - - // player - if (IS_CLIENT(e_target)) - { - if (!(validate_flags & TFL_TARGETSELECT_PLAYERS)) - return -7; - - if (e_target.deadflag != DEAD_NO) - return -8; - } - - // enemy turrets - if (validate_flags & TFL_TARGETSELECT_NOTURRETS) - if (e_target.turret_firefunc || e_target.owner.tur_head == e_target) - if(e_target.team != e_turret.team) // Dont break support units. - return -9; - - // Missile - if (e_target.flags & FL_PROJECTILE) - if (!(validate_flags & TFL_TARGETSELECT_MISSILES)) - return -10; - - if (validate_flags & TFL_TARGETSELECT_MISSILESONLY) - if (!(e_target.flags & FL_PROJECTILE)) - return -10.5; - - // Team check - if (validate_flags & TFL_TARGETSELECT_TEAMCHECK) - { - if (validate_flags & TFL_TARGETSELECT_OWNTEAM) - { - if (e_target.team != e_turret.team) - return -11; - - if (e_turret.team != e_target.owner.team) - return -12; - } - else - { - if (e_target.team == e_turret.team) - return -13; - - if (e_turret.team == e_target.owner.team) - return -14; - } - } - - // Range limits? - tvt_dist = vlen(e_turret.origin - real_origin(e_target)); - if (validate_flags & TFL_TARGETSELECT_RANGELIMTS) - { - if (tvt_dist < e_turret.target_range_min) - return -15; - - if (tvt_dist > e_turret.target_range) - return -16; - } - - // Can we even aim this thing? - tvt_thadv = angleofs3(e_turret.tur_head.origin, e_turret.angles + e_turret.tur_head.angles, e_target); - tvt_tadv = shortangle_vxy(angleofs(e_turret, e_target), e_turret.angles); - tvt_thadf = vlen(tvt_thadv); - tvt_tadf = vlen(tvt_tadv); - - /* - if(validate_flags & TFL_TARGETSELECT_FOV) - { - if(e_turret.target_select_fov < tvt_thadf) - return -21; - } - */ - - if (validate_flags & TFL_TARGETSELECT_ANGLELIMITS) - { - if (fabs(tvt_tadv_x) > e_turret.aim_maxpitch) - return -17; - - if (fabs(tvt_tadv_y) > e_turret.aim_maxrot) - return -18; - } - - // Line of sight? - if (validate_flags & TFL_TARGETSELECT_LOS) - { - v_tmp = real_origin(e_target) + ((e_target.mins + e_target.maxs) * 0.5); - - traceline(e_turret.origin + '0 0 16', v_tmp, 0, e_turret); - - if (e_turret.aim_firetolerance_dist < vlen(v_tmp - trace_endpos)) - return -19; - } - - if (e_target.classname == "grapplinghook") - return -20; - - /* - if (e_target.classname == "func_button") - return -21; - */ - -#ifdef TURRET_DEBUG_TARGETSELECT - dprint("Target:",e_target.netname," is a valid target for ",e_turret.netname,"\n"); -#endif - - return 1; -} - -entity turret_select_target() -{ - entity e; // target looper entity - float score; // target looper entity score - entity e_enemy; // currently best scoreing target - float m_score; // currently best scoreing target's score - - m_score = 0; - if(self.enemy && self.enemy.takedamage && turret_validate_target(self,self.enemy,self.target_validate_flags) > 0) - { - e_enemy = self.enemy; - m_score = self.turret_score_target(self,e_enemy) * self.target_select_samebias; - } - else - e_enemy = self.enemy = world; - - e = findradius(self.origin, self.target_range); - - // Nothing to aim at? - if (!e) - return world; - - while (e) - { - if(e.takedamage) - { - float f = turret_validate_target(self, e, self.target_select_flags); - //dprint("F is: ", ftos(f), "\n"); - if ( f > 0) - { - score = self.turret_score_target(self,e); - if ((score > m_score) && (score > 0)) - { - e_enemy = e; - m_score = score; - } - } - } - e = e.chain; - } - - return e_enemy; -} - -void turret_think() -{ - entity e; - - self.nextthink = time + self.ticrate; - - // ONS uses somewhat backwards linking. - if (teamplay) - { - if (g_onslaught) - if (self.target) - { - e = find(world, targetname,self.target); - if (e != world) - self.team = e.team; - } - - if (self.team != self.tur_head.team) - turret_stdproc_respawn(); - } - -#ifdef TURRET_DEBUG - if (self.tur_dbg_tmr1 < time) - { - if (self.enemy) paint_target (self.enemy,128,self.tur_dbg_rvec,0.9); - paint_target(self,256,self.tur_dbg_rvec,0.9); - self.tur_dbg_tmr1 = time + 1; - } -#endif - - // Handle ammo - if (!(self.spawnflags & TSF_NO_AMMO_REGEN)) - if (self.ammo < self.ammo_max) - self.ammo = min(self.ammo + self.ammo_recharge, self.ammo_max); - - // Inactive turrets needs to run the think loop, - // So they can handle animation and wake up if need be. - if (!self.active) - { - turret_stdproc_track(); - return; - } - - // This is typicaly used for zaping every target in range - // turret_fusionreactor uses this to recharge friendlys. - if (self.shoot_flags & TFL_SHOOT_HITALLVALID) - { - // Do a self.turret_fire for every valid target. - e = findradius(self.origin,self.target_range); - while (e) - { - if(e.takedamage) - { - if (turret_validate_target(self,e,self.target_validate_flags)) - { - self.enemy = e; - - turret_do_updates(self); - - if (self.turret_firecheckfunc()) - turret_fire(); - } - } - - e = e.chain; - } - self.enemy = world; - } - else if(self.shoot_flags & TFL_SHOOT_CUSTOM) - { - // This one is doing something.. oddball. assume its handles what needs to be handled. - - // Predict? - if (!(self.aim_flags & TFL_AIM_NO)) - self.tur_aimpos = turret_stdproc_aim_generic(); - - // Turn & pitch? - if (!(self.track_flags & TFL_TRACK_NO)) - turret_stdproc_track(); - - turret_do_updates(self); - - // Fire? - if (self.turret_firecheckfunc()) - turret_fire(); - } - else - { - // Special case for volly always. if it fired once it must compleate the volly. - if(self.shoot_flags & TFL_SHOOT_VOLLYALWAYS) - if(self.volly_counter != self.shot_volly) - { - // Predict or whatnot - if (!(self.aim_flags & TFL_AIM_NO)) - self.tur_aimpos = turret_stdproc_aim_generic(); - - // Turn & pitch - if (!(self.track_flags & TFL_TRACK_NO)) - turret_stdproc_track(); - - turret_do_updates(self); - - // Fire! - if (self.turret_firecheckfunc() != 0) - turret_fire(); - - if(self.turret_postthink) - self.turret_postthink(); - - return; - } - - // Check if we have a vailid enemy, and try to find one if we dont. - - // g_turrets_targetscan_maxdelay forces a target re-scan at least this often - float do_target_scan = 0; - if((self.target_select_time + autocvar_g_turrets_targetscan_maxdelay) < time) - do_target_scan = 1; - - // Old target (if any) invalid? - if(self.target_validate_time < time) - if (turret_validate_target(self, self.enemy, self.target_validate_flags) <= 0) - { - self.enemy = world; - self.target_validate_time = time + 0.5; - do_target_scan = 1; - } - - // But never more often then g_turrets_targetscan_mindelay! - if (self.target_select_time + autocvar_g_turrets_targetscan_mindelay > time) - do_target_scan = 0; - - if(do_target_scan) - { - self.enemy = turret_select_target(); - self.target_select_time = time; - } - - // No target, just go to idle, do any custom stuff and bail. - if (self.enemy == world) - { - // Turn & pitch - if (!(self.track_flags & TFL_TRACK_NO)) - turret_stdproc_track(); - - // do any per-turret stuff - if(self.turret_postthink) - self.turret_postthink(); - - // And bail. - return; - } - else - self.lip = time + autocvar_g_turrets_aimidle_delay; // Keep track of the last time we had a target. - - // Predict? - if (!(self.aim_flags & TFL_AIM_NO)) - self.tur_aimpos = turret_stdproc_aim_generic(); - - // Turn & pitch? - if (!(self.track_flags & TFL_TRACK_NO)) - turret_stdproc_track(); - - turret_do_updates(self); - - // Fire? - if (self.turret_firecheckfunc()) - turret_fire(); - } - - // do any custom per-turret stuff - if(self.turret_postthink) - self.turret_postthink(); -} - -void turret_fire() -{ - if (autocvar_g_turrets_nofire != 0) - return; - - self.turret_firefunc(); - - self.attack_finished_single = time + self.shot_refire; - self.ammo -= self.shot_dmg; - self.volly_counter = self.volly_counter - 1; - - if (self.volly_counter <= 0) - { - self.volly_counter = self.shot_volly; - - if (self.shoot_flags & TFL_SHOOT_CLEARTARGET) - self.enemy = world; - - if (self.shot_volly > 1) - self.attack_finished_single = time + self.shot_volly_refire; - } - -#ifdef TURRET_DEBUG - if (self.enemy) paint_target3(self.tur_aimpos, 64, self.tur_dbg_rvec, self.tur_impacttime + 0.25); -#endif -} - -void turret_stdproc_fire() -{ - dprint("^1Bang, ^3your dead^7 ",self.enemy.netname,"! ^1(turret with no real firefunc)\n"); -} - -/* - When .used a turret switch team to activator.team. - If activator is world, the turret go inactive. -*/ -void turret_stdproc_use() -{ - dprint("Turret ",self.netname, " used by ", activator.classname, "\n"); - - self.team = activator.team; - - if(self.team == 0) - self.active = ACTIVE_NOT; - else - self.active = ACTIVE_ACTIVE; - -} - -void turret_link() -{ - Net_LinkEntity(self, TRUE, 0, turret_send); - self.think = turret_think; - self.nextthink = time; - self.tur_head.effects = EF_NODRAW; -} - -void turrets_manager_think() -{ - self.nextthink = time + 1; - - entity e; - if (autocvar_g_turrets_reloadcvars == 1) - { - e = nextent(world); - while (e) - { - if (e.turrcaps_flags & TFL_TURRCAPS_ISTURRET) - { - load_unit_settings(e,e.cvar_basename,1); - if(e.turret_postthink) - e.turret_postthink(); - } - - e = nextent(e); - } - cvar_set("g_turrets_reloadcvars","0"); - } -} - -/* -* Standard turret initialization. use this! -* (unless you have a very good reason not to) -* if the return value is 0, the turret should be removed. -*/ -float turret_stdproc_init (string cvar_base_name, string base, string head, float _turret_type) -{ - entity e, ee = world; - - // Are turrets allowed? - if (autocvar_g_turrets == 0) - return 0; - - if(_turret_type < 1 || _turret_type > TID_LAST) - { - dprint("Invalid / Unkown turret type\"", ftos(_turret_type), "\", aborting!\n"); - return 0; - } - self.turret_type = _turret_type; - - e = find(world, classname, "turret_manager"); - if (!e) - { - e = spawn(); - e.classname = "turret_manager"; - e.think = turrets_manager_think; - e.nextthink = time + 2; - } - - if (!(self.spawnflags & TSF_SUSPENDED)) - builtin_droptofloor(); // why can't we use regular droptofloor here? - - // Terrainbase spawnflag. This puts a enlongated model - // under the turret, so it looks ok on uneaven surfaces. - /* TODO: Handle this with CSQC - if (self.spawnflags & TSF_TERRAINBASE) - { - entity tb; - tb = spawn(); - setmodel(tb,"models/turrets/terrainbase.md3"); - setorigin(tb,self.origin); - tb.solid = SOLID_BBOX; - } - */ - - self.cvar_basename = cvar_base_name; - load_unit_settings(self, self.cvar_basename, 0); - - self.effects = EF_NODRAW; - - // Handle turret teams. - if (!teamplay) - self.team = MAX_SHOT_DISTANCE; // Group all turrets into the same team, so they dont kill eachother. - else if(g_onslaught && self.targetname) - { - e = find(world,target,self.targetname); - if(e != world) - { - self.team = e.team; - ee = e; - } - } - else if(!self.team) - self.team = MAX_SHOT_DISTANCE; // Group all turrets into the same team, so they dont kill eachother. - - /* - * Try to guess some reasonaly defaults - * for missing params and do sanety checks - * thise checks could produce some "interesting" results - * if it hits a glitch in my logic :P so try to set as mutch - * as possible beforehand. - */ - if (!self.ticrate) - { - if (self.turrcaps_flags & TFL_TURRCAPS_SUPPORT) - self.ticrate = 0.2; // Support units generaly dont need to have a high speed ai-loop - else - self.ticrate = 0.1; // 10 fps for normal turrets - } - - self.ticrate = bound(sys_frametime, self.ticrate, 60); // keep it sane - -// General stuff - if (self.netname == "") - self.netname = self.classname; - - if (!self.respawntime) - self.respawntime = 60; - self.respawntime = max(-1, self.respawntime); - - if (!self.health) - self.health = 1000; - self.tur_health = max(1, self.health); - self.bot_attack = TRUE; - self.monster_attack = TRUE; - - if (!self.turrcaps_flags) - self.turrcaps_flags = TFL_TURRCAPS_RADIUSDMG | TFL_TURRCAPS_MEDPROJ | TFL_TURRCAPS_PLAYERKILL; - - if (!self.damage_flags) - self.damage_flags = TFL_DMG_YES | TFL_DMG_RETALIATE | TFL_DMG_AIMSHAKE; - -// Shot stuff. - if (!self.shot_refire) - self.shot_refire = 1; - self.shot_refire = bound(0.01, self.shot_refire, 9999); - - if (!self.shot_dmg) - self.shot_dmg = self.shot_refire * 50; - self.shot_dmg = max(1, self.shot_dmg); - - if (!self.shot_radius) - self.shot_radius = self.shot_dmg * 0.5; - self.shot_radius = max(1, self.shot_radius); - - if (!self.shot_speed) - self.shot_speed = 2500; - self.shot_speed = max(1, self.shot_speed); - - if (!self.shot_spread) - self.shot_spread = 0.0125; - self.shot_spread = bound(0.0001, self.shot_spread, 500); - - if (!self.shot_force) - self.shot_force = self.shot_dmg * 0.5 + self.shot_radius * 0.5; - self.shot_force = bound(0.001, self.shot_force, 5000); - - if (!self.shot_volly) - self.shot_volly = 1; - self.shot_volly = bound(1, self.shot_volly, floor(self.ammo_max / self.shot_dmg)); - - if (!self.shot_volly_refire) - self.shot_volly_refire = self.shot_refire * self.shot_volly; - self.shot_volly_refire = bound(self.shot_refire, self.shot_volly_refire, 60); - - if (!self.firecheck_flags) - self.firecheck_flags = TFL_FIRECHECK_DEAD | TFL_FIRECHECK_DISTANCES | - TFL_FIRECHECK_LOS | TFL_FIRECHECK_AIMDIST | TFL_FIRECHECK_TEAMCECK | - TFL_FIRECHECK_OWM_AMMO | TFL_FIRECHECK_REFIRE; - -// Range stuff. - if (!self.target_range) - self.target_range = self.shot_speed * 0.5; - self.target_range = bound(0, self.target_range, MAX_SHOT_DISTANCE); - - if (!self.target_range_min) - self.target_range_min = self.shot_radius * 2; - self.target_range_min = bound(0, self.target_range_min, MAX_SHOT_DISTANCE); - - if (!self.target_range_optimal) - self.target_range_optimal = self.target_range * 0.5; - self.target_range_optimal = bound(0, self.target_range_optimal, MAX_SHOT_DISTANCE); - - -// Aim stuff. - if (!self.aim_maxrot) - self.aim_maxrot = 90; - self.aim_maxrot = bound(0, self.aim_maxrot, 360); - - if (!self.aim_maxpitch) - self.aim_maxpitch = 20; - self.aim_maxpitch = bound(0, self.aim_maxpitch, 90); - - if (!self.aim_speed) - self.aim_speed = 36; - self.aim_speed = bound(0.1, self.aim_speed, 1000); - - if (!self.aim_firetolerance_dist) - self.aim_firetolerance_dist = 5 + (self.shot_radius * 2); - self.aim_firetolerance_dist = bound(0.1, self.aim_firetolerance_dist, MAX_SHOT_DISTANCE); - - if (!self.aim_flags) - { - self.aim_flags = TFL_AIM_LEAD | TFL_AIM_SHOTTIMECOMPENSATE; - if(self.turrcaps_flags & TFL_TURRCAPS_RADIUSDMG) - self.aim_flags |= TFL_AIM_GROUNDGROUND; - } - - if (!self.track_type) - self.track_type = TFL_TRACKTYPE_STEPMOTOR; - - if (self.track_type != TFL_TRACKTYPE_STEPMOTOR) - { - // Fluid / Ineria mode. Looks mutch nicer. - // Can reduce aim preformance alot, needs a bit diffrent aimspeed - - if (!self.aim_speed) - self.aim_speed = 180; - self.aim_speed = bound(0.1, self.aim_speed, 1000); - - if (!self.track_accel_pitch) - self.track_accel_pitch = 0.5; - - if (!self.track_accel_rot) - self.track_accel_rot = 0.5; - - if (!self.track_blendrate) - self.track_blendrate = 0.35; - } - - if (!self.track_flags) - self.track_flags = TFL_TRACK_PITCH | TFL_TRACK_ROT; - - -// Target selection stuff. - if (!self.target_select_rangebias) - self.target_select_rangebias = 1; - self.target_select_rangebias = bound(-10, self.target_select_rangebias, 10); - - if (!self.target_select_samebias) - self.target_select_samebias = 1; - self.target_select_samebias = bound(-10, self.target_select_samebias, 10); - - if (!self.target_select_anglebias) - self.target_select_anglebias = 1; - self.target_select_anglebias = bound(-10, self.target_select_anglebias, 10); - - if (!self.target_select_missilebias) - self.target_select_missilebias = -10; - - self.target_select_missilebias = bound(-10, self.target_select_missilebias, 10); - self.target_select_playerbias = bound(-10, self.target_select_playerbias, 10); - - if (!self.target_select_flags) - { - self.target_select_flags = TFL_TARGETSELECT_LOS | TFL_TARGETSELECT_TEAMCHECK - | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_ANGLELIMITS; - - if (self.turrcaps_flags & TFL_TURRCAPS_MISSILEKILL) - self.target_select_flags |= TFL_TARGETSELECT_MISSILES; - - if (self.turrcaps_flags & TFL_TURRCAPS_PLAYERKILL) - self.target_select_flags |= TFL_TARGETSELECT_PLAYERS; - //else - // self.target_select_flags = TFL_TARGETSELECT_NO; - } - - self.target_validate_flags = self.target_select_flags; - -// Ammo stuff - if (!self.ammo_max) - self.ammo_max = self.shot_dmg * 10; - self.ammo_max = max(self.shot_dmg, self.ammo_max); - - if (!self.ammo) - self.ammo = self.shot_dmg * 5; - self.ammo = bound(0,self.ammo, self.ammo_max); - - if (!self.ammo_recharge) - self.ammo_recharge = self.shot_dmg * 0.5; - self.ammo_recharge = max(0 ,self.ammo_recharge); - - // Convert the recharge from X per sec to X per ticrate - self.ammo_recharge = self.ammo_recharge * self.ticrate; - - if (!self.ammo_flags) - self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE; - -// Damage stuff - if(self.spawnflags & TSL_NO_RESPAWN) - if (!(self.damage_flags & TFL_DMG_DEATH_NORESPAWN)) - self.damage_flags |= TFL_DMG_DEATH_NORESPAWN; - -// Offsets & origins - if (!self.tur_shotorg) self.tur_shotorg = '50 0 50'; - - if (!self.health) - self.health = 150; - -// Game hooks - if(MUTATOR_CALLHOOK(TurretSpawn)) - return 0; - -// End of default & sanety checks, start building the turret. - -// Spawn extra bits - self.tur_head = spawn(); - self.tur_head.netname = self.tur_head.classname = "turret_head"; - self.tur_head.team = self.team; - self.tur_head.owner = self; - - setmodel(self, base); - setmodel(self.tur_head, head); - - setsize(self, '-32 -32 0', '32 32 64'); - setsize(self.tur_head, '0 0 0', '0 0 0'); - - setorigin(self.tur_head, '0 0 0'); - setattachment(self.tur_head, self, "tag_head"); - - self.tur_health = self.health; - self.solid = SOLID_BBOX; - self.tur_head.solid = SOLID_NOT; - self.takedamage = DAMAGE_AIM; - self.tur_head.takedamage = DAMAGE_NO; - self.movetype = MOVETYPE_NOCLIP; - self.tur_head.movetype = MOVETYPE_NOCLIP; - - // Defend mode? - if (!self.tur_defend) - if (self.target != "") - { - self.tur_defend = find(world, targetname, self.target); - if (self.tur_defend == world) - { - self.target = ""; - dprint("Turret has invalid defendpoint!\n"); - } - } - - // In target defend mode, aim on the spot to defend when idle. - if (self.tur_defend) - self.idle_aim = self.tur_head.angles + angleofs(self.tur_head, self.tur_defend); - else - self.idle_aim = '0 0 0'; - - // Attach stdprocs. override when and what needed - self.turret_firecheckfunc = turret_stdproc_firecheck; - self.turret_firefunc = turret_stdproc_fire; - self.event_damage = turret_stdproc_damage; - - if (self.turrcaps_flags & TFL_TURRCAPS_SUPPORT) - self.turret_score_target = turret_stdproc_targetscore_support; - else - self.turret_score_target = turret_stdproc_targetscore_generic; - - self.use = turret_stdproc_use; - - ++turret_count; - self.nextthink = time + 1; - self.nextthink += turret_count * sys_frametime; - - self.tur_head.team = self.team; - self.view_ofs = '0 0 0'; - -#ifdef TURRET_DEBUG - self.tur_dbg_start = self.nextthink; - while (vlen(self.tur_dbg_rvec) < 2) - self.tur_dbg_rvec = randomvec() * 4; - - self.tur_dbg_rvec_x = fabs(self.tur_dbg_rvec_x); - self.tur_dbg_rvec_y = fabs(self.tur_dbg_rvec_y); - self.tur_dbg_rvec_z = fabs(self.tur_dbg_rvec_z); -#endif - - // Its all good. - self.turrcaps_flags |= TFL_TURRCAPS_ISTURRET; - - self.classname = "turret_main"; - - self.active = ACTIVE_ACTIVE; - - // In ONS mode, and linked to a ONS ent. need to call the use to set team. - if (g_onslaught && ee) - { - activator = ee; - self.use(); - } - - turret_link(); - turret_stdproc_respawn(); - turret_tag_fire_update(); - - return 1; -} - - diff --git a/qcsrc/server/tturrets/system/system_misc.qc b/qcsrc/server/tturrets/system/system_misc.qc deleted file mode 100644 index 3fdd5eb1e..000000000 --- a/qcsrc/server/tturrets/system/system_misc.qc +++ /dev/null @@ -1,355 +0,0 @@ -/* -* Return a angle within +/- 360. -*/ -float anglemods(float v) -{ - v = v - 360 * floor(v / 360); - - if(v >= 180) - return v - 360; - else if(v <= -180) - return v + 360; - else - return v; -} - -/* -* Return the short angle -*/ -float shortangle_f(float ang1, float ang2) -{ - if(ang1 > ang2) - { - if(ang1 > 180) - return ang1 - 360; - } - else - { - if(ang1 < -180) - return ang1 + 360; - } - - return ang1; -} - -vector shortangle_v(vector ang1, vector ang2) -{ - vector vtmp; - - vtmp_x = shortangle_f(ang1_x,ang2_x); - vtmp_y = shortangle_f(ang1_y,ang2_y); - vtmp_z = shortangle_f(ang1_z,ang2_z); - - return vtmp; -} - -vector shortangle_vxy(vector ang1, vector ang2) -{ - vector vtmp = '0 0 0'; - - vtmp_x = shortangle_f(ang1_x,ang2_x); - vtmp_y = shortangle_f(ang1_y,ang2_y); - - return vtmp; -} - - -/* -* Get "real" origin, in worldspace, even if ent is attached to something else. -*/ -vector real_origin(entity ent) -{ - entity e; - vector v = ((ent.absmin + ent.absmax) * 0.5); - - e = ent.tag_entity; - while(e) - { - v = v + ((e.absmin + e.absmax) * 0.5); - e = e.tag_entity; - } - - return v; -} - -/* -* Return the angle between two enteties -*/ -vector angleofs(entity from, entity to) -{ - vector v_res; - - v_res = normalize(to.origin - from.origin); - v_res = vectoangles(v_res); - v_res = v_res - from.angles; - - if (v_res_x < 0) v_res_x += 360; - if (v_res_x > 180) v_res_x -= 360; - - if (v_res_y < 0) v_res_y += 360; - if (v_res_y > 180) v_res_y -= 360; - - return v_res; -} - -vector angleofs3(vector from, vector from_a, entity to) -{ - vector v_res; - - v_res = normalize(to.origin - from); - v_res = vectoangles(v_res); - v_res = v_res - from_a; - - if (v_res_x < 0) v_res_x += 360; - if (v_res_x > 180) v_res_x -= 360; - - if (v_res_y < 0) v_res_y += 360; - if (v_res_y > 180) v_res_y -= 360; - - return v_res; -} - -/* -* Update self.tur_shotorg by getting up2date bone info -* NOTICE this func overwrites the global v_forward, v_right and v_up vectors. -*/ -#define turret_tag_fire_update() self.tur_shotorg = gettaginfo(self.tur_head, gettagindex(self.tur_head, "tag_fire"));v_forward = normalize(v_forward) -float turret_tag_fire_update_s() -{ - if(!self.tur_head) - { - error("Call to turret_tag_fire_update with self.tur_head missing!\n"); - self.tur_shotorg = '0 0 0'; - return FALSE; - } - - self.tur_shotorg = gettaginfo(self.tur_head, gettagindex(self.tur_head, "tag_fire")); - v_forward = normalize(v_forward); - - return TRUE; -} - -/* -* Railgun-like beam, but has thickness and suppots slowing of target -*/ -void FireImoBeam (vector start, vector end, vector smin, vector smax, - float bforce, float f_dmg, float f_velfactor, float deathtype) - -{ - vector hitloc, force, endpoint, dir; - entity ent; - - dir = normalize(end - start); - force = dir * bforce; - - // go a little bit into the wall because we need to hit this wall later - end = end + dir; - - // trace multiple times until we hit a wall, each obstacle will be made unsolid. - // note down which entities were hit so we can damage them later - while (1) - { - tracebox(start, smin, smax, end, FALSE, self); - - // if it is world we can't hurt it so stop now - if (trace_ent == world || trace_fraction == 1) - break; - - if (trace_ent.solid == SOLID_BSP) - 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; - - // stop if this is a wall - - // make the entity non-solid - trace_ent.solid = SOLID_NOT; - } - - endpoint = trace_endpos; - - // 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); - } - - // 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; - ent.railgunhitloc = '0 0 0'; - ent.railgunhitsolidbackup = SOLID_NOT; - ent.railgunhit = FALSE; - - // apply the damage - if (ent.takedamage) - { - Damage (ent, self, self, f_dmg, deathtype, hitloc, force); - ent.velocity = ent.velocity * f_velfactor; - //ent.alpha = 0.25 + random() * 0.75; - } - - // advance to the next entity - ent = findfloat(ent, railgunhit, TRUE); - } - trace_endpos = endpoint; -} - -// Plug this into wherever precache is done. -void g_turrets_common_precash() -{ - precache_model ("models/turrets/c512.md3"); - precache_model ("models/marker.md3"); -} - -void turrets_precache_debug_models() -{ - precache_model ("models/turrets/c512.md3"); - precache_model ("models/pathlib/goodsquare.md3"); - precache_model ("models/pathlib/badsquare.md3"); - precache_model ("models/pathlib/square.md3"); - precache_model ("models/pathlib/edge.md3"); -} - -void turrets_precash() -{ - #ifdef TURRET_DEBUG - turrets_precache_debug_models(); - #endif -} - - -#ifdef TURRET_DEBUG -void SUB_Remove(); -void marker_think() -{ - if(self.cnt) - if(self.cnt < time) - { - self.think = SUB_Remove; - self.nextthink = time; - return; - } - - self.frame += 1; - if(self.frame > 29) - self.frame = 0; - - self.nextthink = time; -} - -void mark_error(vector where,float lifetime) -{ - entity err; - - err = spawn(); - err.classname = "error_marker"; - setmodel(err,"models/marker.md3"); - setorigin(err,where); - err.movetype = MOVETYPE_NONE; - err.think = marker_think; - err.nextthink = time; - err.skin = 0; - if(lifetime) - err.cnt = lifetime + time; -} - -void mark_info(vector where,float lifetime) -{ - entity err; - - err = spawn(); - err.classname = "info_marker"; - setmodel(err,"models/marker.md3"); - setorigin(err,where); - err.movetype = MOVETYPE_NONE; - err.think = marker_think; - err.nextthink = time; - err.skin = 1; - if(lifetime) - err.cnt = lifetime + time; -} - -entity mark_misc(vector where,float lifetime) -{ - entity err; - - err = spawn(); - err.classname = "mark_misc"; - setmodel(err,"models/marker.md3"); - setorigin(err,where); - err.movetype = MOVETYPE_NONE; - err.think = marker_think; - err.nextthink = time; - err.skin = 3; - if(lifetime) - err.cnt = lifetime + time; - return err; -} - -/* -* Paint a v_color colord circle on target onwho -* that fades away over f_time -*/ -void paint_target(entity onwho, float f_size, vector v_color, float f_time) -{ - entity e; - - e = spawn(); - setmodel(e, "models/turrets/c512.md3"); // precision set above - e.scale = (f_size/512); - //setsize(e, '0 0 0', '0 0 0'); - //setattachment(e,onwho,""); - setorigin(e,onwho.origin + '0 0 1'); - e.alpha = 0.15; - e.movetype = MOVETYPE_FLY; - - e.velocity = (v_color * 32); // + '0 0 1' * 64; - - e.colormod = v_color; - SUB_SetFade(e,time,f_time); -} - -void paint_target2(entity onwho, float f_size, vector v_color, float f_time) -{ - entity e; - - e = spawn(); - setmodel(e, "models/turrets/c512.md3"); // precision set above - e.scale = (f_size/512); - setsize(e, '0 0 0', '0 0 0'); - - setorigin(e,onwho.origin + '0 0 1'); - e.alpha = 0.15; - e.movetype = MOVETYPE_FLY; - - e.velocity = (v_color * 32); // + '0 0 1' * 64; - e.avelocity_x = -128; - - e.colormod = v_color; - SUB_SetFade(e,time,f_time); -} - -void paint_target3(vector where, float f_size, vector v_color, float f_time) -{ - entity e; - e = spawn(); - setmodel(e, "models/turrets/c512.md3"); // precision set above - e.scale = (f_size/512); - setsize(e, '0 0 0', '0 0 0'); - setorigin(e,where+ '0 0 1'); - e.movetype = MOVETYPE_NONE; - e.velocity = '0 0 0'; - e.colormod = v_color; - SUB_SetFade(e,time,f_time); -} -#endif diff --git a/qcsrc/server/tturrets/system/system_scoreprocs.qc b/qcsrc/server/tturrets/system/system_scoreprocs.qc deleted file mode 100644 index 539be2ad9..000000000 --- a/qcsrc/server/tturrets/system/system_scoreprocs.qc +++ /dev/null @@ -1,130 +0,0 @@ -float turret_stdproc_targetscore_support(entity _turret,entity _target) -{ - float score; // Total score - float s_score = 0, d_score; - - if (_turret.enemy == _target) s_score = 1; - - d_score = min(_turret.target_range_optimal,tvt_dist) / max(_turret.target_range_optimal,tvt_dist); - - score = (d_score * _turret.target_select_rangebias) + - (s_score * _turret.target_select_samebias); - - return score; -} - -/* -* Generic bias aware score system. -*/ -float turret_stdproc_targetscore_generic(entity _turret, entity _target) -{ - float d_dist; // Defendmode Distance - float score; // Total score - float d_score; // Distance score - float a_score; // Angular score - float m_score = 0; // missile score - float p_score = 0; // player score - float ikr; // ideal kill range - - if (_turret.tur_defend) - { - d_dist = vlen(real_origin(_target) - _turret.tur_defend.origin); - ikr = vlen(_turret.origin - _turret.tur_defend.origin); - d_score = 1 - d_dist / _turret.target_range; - } - else - { - // Make a normlized value base on the targets distance from our optimal killzone - ikr = _turret.target_range_optimal; - d_score = min(ikr, tvt_dist) / max(ikr, tvt_dist); - } - - a_score = 1 - tvt_thadf / _turret.aim_maxrot; - - if ((_turret.target_select_missilebias > 0) && (_target.flags & FL_PROJECTILE)) - m_score = 1; - - if ((_turret.target_select_playerbias > 0) && IS_CLIENT(_target)) - p_score = 1; - - d_score = max(d_score, 0); - a_score = max(a_score, 0); - m_score = max(m_score, 0); - p_score = max(p_score, 0); - - score = (d_score * _turret.target_select_rangebias) + - (a_score * _turret.target_select_anglebias) + - (m_score * _turret.target_select_missilebias) + - (p_score * _turret.target_select_playerbias); - - if(_turret.target_range < vlen(_turret.tur_shotorg - real_origin(_target))) - { - //dprint("Wtf?\n"); - score *= 0.001; - } - -#ifdef TURRET_DEBUG - string sd,sa,sm,sp,ss; - string sdt,sat,smt,spt; - - sd = ftos(d_score); - d_score *= _turret.target_select_rangebias; - sdt = ftos(d_score); - - //sv = ftos(v_score); - //v_score *= _turret.target_select_samebias; - //svt = ftos(v_score); - - sa = ftos(a_score); - a_score *= _turret.target_select_anglebias; - sat = ftos(a_score); - - sm = ftos(m_score); - m_score *= _turret.target_select_missilebias; - smt = ftos(m_score); - - sp = ftos(p_score); - p_score *= _turret.target_select_playerbias; - spt = ftos(p_score); - - - ss = ftos(score); - bprint("^3Target scores^7 \[ ",_turret.netname, " \] ^3for^7 \[ ", _target.netname," \]\n"); - bprint("^5Range:\[ ",sd, " \]^2+bias:\[ ",sdt," \]\n"); - bprint("^5Angle:\[ ",sa, " \]^2+bias:\[ ",sat," \]\n"); - bprint("^5Missile:\[ ",sm," \]^2+bias:\[ ",smt," \]\n"); - bprint("^5Player:\[ ",sp, " \]^2+bias:\[ ",spt," \]\n"); - bprint("^3Total (w/bias):\[^1",ss,"\]\n"); - -#endif - - return score; -} - -/* -float turret_stdproc_targetscore_close(entity _turret,entity _target) -{ - return 1 - (tvt_dist / _turret.target_range); -} - -float turret_stdproc_targetscore_far (entity _turret,entity _target) -{ - return tvt_dist / _turret.target_range; -} - -float turret_stdproc_targetscore_optimal(entity _turret,entity _target) -{ - return min(_turret.target_range_optimal,tvt_dist) / max(_turret.target_range_optimal,tvt_dist); -} - -float turret_stdproc_score_angular(entity _turret,entity _target) -{ - return 1 - (tvt_thadf / _turret.aim_maxrot); -} - -float turret_stdproc_targetscore_defend(entity _turret,entity _target) -{ - return 0; - //min(_target.origin,_turret.tur_defend.origin) / max(_target.origin,_turret.tur_defend.origin); -} -*/ diff --git a/qcsrc/server/tturrets/units/unit_ewheel.qc b/qcsrc/server/tturrets/units/unit_ewheel.qc deleted file mode 100644 index e8e677ac8..000000000 --- a/qcsrc/server/tturrets/units/unit_ewheel.qc +++ /dev/null @@ -1,310 +0,0 @@ -#define ewheel_amin_stop 0 -#define ewheel_amin_fwd_slow 1 -#define ewheel_amin_fwd_fast 2 -#define ewheel_amin_bck_slow 3 -#define ewheel_amin_bck_fast 4 - -void ewheel_attack() -{ - float i; - entity _mis; - - for (i = 0; i < 1; ++i) - { - turret_do_updates(self); - - _mis = turret_projectile("weapons/lasergun_fire.wav", 1, 0, DEATH_TURRET_EWHEEL, PROJECTILE_BLASTER, TRUE, TRUE); // WEAPONTODO: this is not a projectile made by the blaster, add separate effect for it - _mis.missile_flags = MIF_SPLASH; - - pointparticles(particleeffectnum("laser_muzzleflash"), self.tur_shotorg, self.tur_shotdir_updated * 1000, 1); - - self.tur_head.frame += 2; - - if (self.tur_head.frame > 3) - self.tur_head.frame = 0; - } - -} -//#define EWHEEL_FANCYPATH -void ewheel_move_path() -{ -#ifdef EWHEEL_FANCYPATH - // Are we close enougth to a path node to switch to the next? - if (vlen(self.origin - self.pathcurrent.origin) < 64) - if (self.pathcurrent.path_next == world) - { - // Path endpoint reached - pathlib_deletepath(self.pathcurrent.owner); - self.pathcurrent = world; - - if (self.pathgoal) - { - if (self.pathgoal.use) - self.pathgoal.use(); - - if (self.pathgoal.enemy) - { - self.pathcurrent = pathlib_astar(self.pathgoal.origin,self.pathgoal.enemy.origin); - self.pathgoal = self.pathgoal.enemy; - } - } - else - self.pathgoal = world; - } - else - self.pathcurrent = self.pathcurrent.path_next; - -#else - if (vlen(self.origin - self.pathcurrent.origin) < 64) - self.pathcurrent = self.pathcurrent.enemy; -#endif - - if (self.pathcurrent) - { - - self.moveto = self.pathcurrent.origin; - self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); - - movelib_move_simple(v_forward, autocvar_g_turrets_unit_ewheel_speed_fast, 0.4); - } -} - -void ewheel_move_enemy() -{ - - float newframe; - - self.steerto = steerlib_arrive(self.enemy.origin,self.target_range_optimal); - - //self.steerto = steerlib_standoff(self.enemy.origin,self.target_range_optimal); - //self.steerto = steerlib_beamsteer(self.steerto,1024,64,68,256); - self.moveto = self.origin + self.steerto * 128; - - if (self.tur_dist_enemy > self.target_range_optimal) - { - if ( self.tur_head.spawnshieldtime < 1 ) - { - newframe = ewheel_amin_fwd_fast; - movelib_move_simple(v_forward, autocvar_g_turrets_unit_ewheel_speed_fast, 0.4); - } - else if (self.tur_head.spawnshieldtime < 2) - { - - newframe = ewheel_amin_fwd_slow; - movelib_move_simple(v_forward, autocvar_g_turrets_unit_ewheel_speed_slow, 0.4); - } - else - { - newframe = ewheel_amin_fwd_slow; - movelib_move_simple(v_forward, autocvar_g_turrets_unit_ewheel_speed_slower, 0.4); - } - } - else if (self.tur_dist_enemy < self.target_range_optimal * 0.5) - { - newframe = ewheel_amin_bck_slow; - movelib_move_simple(v_forward * -1, autocvar_g_turrets_unit_ewheel_speed_slow, 0.4); - } - else - { - newframe = ewheel_amin_stop; - movelib_beak_simple(autocvar_g_turrets_unit_ewheel_speed_stop); - } - - turrets_setframe(newframe , FALSE); - - /*if(self.frame != newframe) - { - self.frame = newframe; - self.SendFlags |= TNSF_ANIM; - self.anim_start_time = time; - }*/ -} - - -void ewheel_move_idle() -{ - if(self.frame != 0) - { - self.SendFlags |= TNSF_ANIM; - self.anim_start_time = time; - } - - self.frame = 0; - if (vlen(self.velocity)) - movelib_beak_simple(autocvar_g_turrets_unit_ewheel_speed_stop); -} - -void ewheel_postthink() -{ - float vz; - vector wish_angle, real_angle; - - vz = self.velocity_z; - - self.angles_x = anglemods(self.angles_x); - self.angles_y = anglemods(self.angles_y); - - fixedmakevectors(self.angles); - - wish_angle = normalize(self.steerto); - wish_angle = vectoangles(wish_angle); - real_angle = wish_angle - self.angles; - real_angle = shortangle_vxy(real_angle, self.tur_head.angles); - - self.tur_head.spawnshieldtime = fabs(real_angle_y); - real_angle_y = bound(-self.tur_head.aim_speed, real_angle_y, self.tur_head.aim_speed); - self.angles_y = (self.angles_y + real_angle_y); - - if(self.enemy) - ewheel_move_enemy(); - else if(self.pathcurrent) - ewheel_move_path(); - else - ewheel_move_idle(); - - - self.velocity_z = vz; - - if(vlen(self.velocity)) - self.SendFlags |= TNSF_MOVE; -} - -void ewheel_respawnhook() -{ - entity e; - - // Respawn is called & first spawn to, to set team. need to make sure we do not move the initial spawn. - if(self.movetype != MOVETYPE_WALK) - return; - - self.velocity = '0 0 0'; - self.enemy = world; - - setorigin(self, self.pos1); - - if (self.target != "") - { - e = find(world,targetname,self.target); - if (!e) - { - dprint("Initital waypoint for ewheel does NOT exsist, fix your map!\n"); - self.target = ""; - } - - if (e.classname != "turret_checkpoint") - dprint("Warning: not a turrret path\n"); - else - { - -#ifdef EWHEEL_FANCYPATH - self.pathcurrent = WALKER_PATH(self.origin,e.origin); - self.pathgoal = e; -#else - self.pathcurrent = e; -#endif - } - } -} - -void ewheel_diehook() -{ - self.velocity = '0 0 0'; - -#ifdef EWHEEL_FANCYPATH - if (self.pathcurrent) - pathlib_deletepath(self.pathcurrent.owner); -#endif - self.pathcurrent = world; -} - -void turret_ewheel_dinit() -{ - entity e; - - if (self.netname == "") - self.netname = "eWheel Turret"; - - if (self.target != "") - { - e = find(world,targetname,self.target); - if (!e) - { - bprint("Warning! initital waypoint for ewheel does NOT exsist!\n"); - self.target = ""; - } - - if (e.classname != "turret_checkpoint") - dprint("Warning: not a turrret path\n"); - else - self.goalcurrent = e; - } - - self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE | TFL_AMMO_RECIVE; - self.turrcaps_flags = TFL_TURRCAPS_PLAYERKILL | TFL_TURRCAPS_MOVE | TFL_TURRCAPS_ROAM ; - self.turret_respawnhook = ewheel_respawnhook; - - self.turret_diehook = ewheel_diehook; - - if (turret_stdproc_init("ewheel_std", "models/turrets/ewheel-base2.md3", "models/turrets/ewheel-gun1.md3", TID_EWHEEL) == 0) - { - remove(self); - return; - } - - self.frame = 1; - self.target_select_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_LOS; - self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_LOS; - self.iscreature = TRUE; - self.teleportable = TELEPORT_NORMAL; - self.damagedbycontents = TRUE; - self.movetype = MOVETYPE_WALK; - self.solid = SOLID_SLIDEBOX; - self.takedamage = DAMAGE_AIM; - self.idle_aim = '0 0 0'; - self.pos1 = self.origin; - - setsize(self, '-32 -32 0', '32 32 48'); - - // Our fire routine - self.turret_firefunc = ewheel_attack; - self.turret_postthink = ewheel_postthink; - self.tur_head.frame = 1; - - // Convert from dgr / sec to dgr / tic - self.tur_head.aim_speed = autocvar_g_turrets_unit_ewheel_turnrate; - self.tur_head.aim_speed = self.tur_head.aim_speed / (1 / self.ticrate); - - //setorigin(self,self.origin + '0 0 128'); - if (self.target != "") - { - e = find(world,targetname,self.target); - if (!e) - { - dprint("Initital waypoint for ewheel does NOT exsist, fix your map!\n"); - self.target = ""; - } - - if (e.classname != "turret_checkpoint") - dprint("Warning: not a turrret path\n"); - else - { -#ifdef EWHEEL_FANCYPATH - self.pathcurrent = WALKER_PATH(self.origin, e.origin); - self.pathgoal = e; -#else - self.pathcurrent = e; -#endif - } - } -} - -void spawnfunc_turret_ewheel() -{ - g_turrets_common_precash(); - - precache_model ("models/turrets/ewheel-base2.md3"); - precache_model ("models/turrets/ewheel-gun1.md3"); - - self.think = turret_ewheel_dinit; - self.nextthink = time + 0.5; -} diff --git a/qcsrc/server/tturrets/units/unit_flac.qc b/qcsrc/server/tturrets/units/unit_flac.qc deleted file mode 100644 index 3c9e55863..000000000 --- a/qcsrc/server/tturrets/units/unit_flac.qc +++ /dev/null @@ -1,74 +0,0 @@ -void spawnfunc_turret_flac(); -void turret_flac_dinit(); -void turret_flac_attack(); - -void turret_flac_projectile_think_explode() -{ - if(self.enemy != world) - if(vlen(self.origin - self.enemy.origin) < self.owner.shot_radius * 3) - setorigin(self,self.enemy.origin + randomvec() * self.owner.shot_radius); - -#ifdef TURRET_DEBUG - float d; - d = RadiusDamage (self, self.owner, self.owner.shot_dmg, self.owner.shot_dmg, self.owner.shot_radius, self, world, self.owner.shot_force, self.totalfrags, world); - self.owner.tur_dbg_dmg_t_h = self.owner.tur_dbg_dmg_t_h + d; - self.owner.tur_dbg_dmg_t_f = self.owner.tur_dbg_dmg_t_f + self.owner.shot_dmg; -#else - RadiusDamage (self, self.realowner, self.owner.shot_dmg, self.owner.shot_dmg, self.owner.shot_radius, self, world, self.owner.shot_force, self.totalfrags, world); -#endif - remove(self); -} - -void turret_flac_attack() -{ - entity proj; - - turret_tag_fire_update(); - - proj = turret_projectile("weapons/hagar_fire.wav", 5, 0, DEATH_TURRET_FLAC, PROJECTILE_HAGAR, TRUE, TRUE); - pointparticles(particleeffectnum("laser_muzzleflash"), self.tur_shotorg, self.tur_shotdir_updated * 1000, 1); - proj.think = turret_flac_projectile_think_explode; - proj.nextthink = time + self.tur_impacttime + (random() * 0.01 - random() * 0.01); - proj.missile_flags = MIF_SPLASH | MIF_PROXY; - - self.tur_head.frame = self.tur_head.frame + 1; - if (self.tur_head.frame >= 4) - self.tur_head.frame = 0; - -} - -void turret_flac_dinit() -{ - if (self.netname == "") - self.netname = "FLAC Cannon"; - - self.turrcaps_flags = TFL_TURRCAPS_RADIUSDMG | TFL_TURRCAPS_FASTPROJ | TFL_TURRCAPS_MISSILEKILL; - self.ammo_flags = TFL_AMMO_ROCKETS | TFL_AMMO_RECHARGE; - self.aim_flags = TFL_AIM_LEAD | TFL_AIM_SHOTTIMECOMPENSATE; - - if (turret_stdproc_init("flac_std", "models/turrets/base.md3", "models/turrets/flac.md3", TID_FLAC) == 0) - { - remove(self); - return; - } - setsize(self.tur_head,'-32 -32 0','32 32 64'); - - self.damage_flags |= TFL_DMG_HEADSHAKE; - self.target_select_flags |= TFL_TARGETSELECT_NOTURRETS | TFL_TARGETSELECT_MISSILESONLY; - - // Our fire routine - self.turret_firefunc = turret_flac_attack; - -} -/*QUAKED turret_flac (0 .5 .8) ? -*/ - -void spawnfunc_turret_flac() -{ - precache_model ("models/turrets/base.md3"); - precache_model ("models/turrets/flac.md3"); - - self.think = turret_flac_dinit; - self.nextthink = time + 0.5; -} - diff --git a/qcsrc/server/tturrets/units/unit_fusionreactor.qc b/qcsrc/server/tturrets/units/unit_fusionreactor.qc deleted file mode 100644 index 014fa25f6..000000000 --- a/qcsrc/server/tturrets/units/unit_fusionreactor.qc +++ /dev/null @@ -1,94 +0,0 @@ -void spawnfunc_turret_fusionreactor(); -void turret_fusionreactor_dinit(); -void turret_fusionreactor_fire(); - -void turret_fusionreactor_fire() -{ - vector fl_org; - - self.enemy.ammo = min(self.enemy.ammo + self.shot_dmg,self.enemy.ammo_max); - fl_org = 0.5 * (self.enemy.absmin + self.enemy.absmax); - te_smallflash(fl_org); -} - -void turret_fusionreactor_postthink() -{ - self.tur_head.avelocity = '0 250 0' * (self.ammo / self.ammo_max); -} - -/* -void turret_fusionreactor_respawnhook() -{ - self.tur_head.avelocity = '0 50 0'; -} -*/ - -/** -** Preforms pre-fire checks for fusionreactor -**/ -float turret_fusionreactor_firecheck() -{ - if (self.attack_finished_single > time) - return 0; - - if (self.enemy.deadflag != DEAD_NO) - return 0; - - if (self.enemy == world) - return 0; - - if (self.ammo < self.shot_dmg) - return 0; - - if (self.enemy.ammo >= self.enemy.ammo_max) - return 0; - - if (vlen(self.enemy.origin - self.origin) > self.target_range) - return 0; - - if(self.team != self.enemy.team) - return 0; - - if (!(self.enemy.ammo_flags & TFL_AMMO_ENERGY)) - return 0; - - return 1; -} - -void turret_fusionreactor_dinit() -{ - if (self.netname == "") self.netname = "Fusionreactor"; - - self.turrcaps_flags = TFL_TURRCAPS_SUPPORT | TFL_TURRCAPS_AMMOSOURCE; - self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE; - self.target_select_flags = TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_OWNTEAM | TFL_TARGETSELECT_RANGELIMTS; - self.firecheck_flags = TFL_FIRECHECK_OWM_AMMO | TFL_FIRECHECK_OTHER_AMMO | TFL_FIRECHECK_DISTANCES | TFL_FIRECHECK_DEAD; - self.shoot_flags = TFL_SHOOT_HITALLVALID; - self.aim_flags = TFL_AIM_NO; - self.track_flags = TFL_TRACK_NO; - // self.turret_respawnhook = turret_fusionreactor_respawnhook; - - if (turret_stdproc_init("fusreac_std", "models/turrets/base.md3", "models/turrets/reactor.md3", TID_FUSION) == 0) - { - remove(self); - return; - } - self.tur_head.scale = 0.75; - self.tur_head.avelocity = '0 50 0'; - setsize(self,'-34 -34 0','34 34 90'); - - self.turret_firecheckfunc = turret_fusionreactor_firecheck; - self.turret_firefunc = turret_fusionreactor_fire; - self.turret_postthink = turret_fusionreactor_postthink; -} - -/*QUAKED turret_fusionreactor (0 .5 .8) ? -*/ -void spawnfunc_turret_fusionreactor() -{ - precache_model ("models/turrets/reactor.md3"); - precache_model ("models/turrets/base.md3"); - - self.think = turret_fusionreactor_dinit; - self.nextthink = time + 0.5; -} diff --git a/qcsrc/server/tturrets/units/unit_hellion.qc b/qcsrc/server/tturrets/units/unit_hellion.qc deleted file mode 100644 index e1b88b06a..000000000 --- a/qcsrc/server/tturrets/units/unit_hellion.qc +++ /dev/null @@ -1,126 +0,0 @@ -void spawnfunc_turret_hellion(); -void turret_hellion_dinit(); -void turret_hellion_attack(); - -void turret_hellion_missile_think() -{ - vector olddir,newdir; - vector pre_pos; - float itime; - - self.nextthink = time + 0.05; - - olddir = normalize(self.velocity); - - if(self.tur_health < time) - turret_projectile_explode(); - - // Enemy dead? just keep on the current heading then. - if ((self.enemy == world) || (self.enemy.deadflag != DEAD_NO)) - { - - // Make sure we dont return to tracking a respawned player - self.enemy = world; - - // Turn model - self.angles = vectoangles(self.velocity); - - if ( (vlen(self.origin - self.owner.origin)) > (self.owner.shot_radius * 5) ) - turret_projectile_explode(); - - // Accelerate - self.velocity = olddir * min(vlen(self.velocity) * autocvar_g_turrets_unit_hellion_std_shot_speed_gain, autocvar_g_turrets_unit_hellion_std_shot_speed_max); - - UpdateCSQCProjectile(self); - - return; - } - - // Enemy in range? - if (vlen(self.origin - self.enemy.origin) < self.owner.shot_radius * 0.2) - turret_projectile_explode(); - - // Predict enemy position - itime = vlen(self.enemy.origin - self.origin) / vlen(self.velocity); - pre_pos = self.enemy.origin + self.enemy.velocity * itime; - - pre_pos = (pre_pos + self.enemy.origin) * 0.5; - - // Find out the direction to that place - newdir = normalize(pre_pos - self.origin); - - // Turn - newdir = normalize(olddir + newdir * 0.35); - - // Turn model - self.angles = vectoangles(self.velocity); - - // Accelerate - self.velocity = newdir * min(vlen(self.velocity) * autocvar_g_turrets_unit_hellion_std_shot_speed_gain, autocvar_g_turrets_unit_hellion_std_shot_speed_max); - - if (itime < 0.05) - self.think = turret_projectile_explode; - - UpdateCSQCProjectile(self); -} -void turret_hellion_attack() -{ - entity missile; - - if(self.tur_head.frame != 0) - self.tur_shotorg = gettaginfo(self.tur_head, gettagindex(self.tur_head, "tag_fire")); - else - self.tur_shotorg = gettaginfo(self.tur_head, gettagindex(self.tur_head, "tag_fire2")); - - missile = turret_projectile("weapons/rocket_fire.wav", 6, 10, DEATH_TURRET_HELLION, PROJECTILE_ROCKET, FALSE, FALSE); - te_explosion (missile.origin); - missile.think = turret_hellion_missile_think; - missile.nextthink = time; - missile.flags = FL_PROJECTILE; - missile.tur_health = time + 9; - missile.tur_aimpos = randomvec() * 128; - missile.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_GUIDED_HEAT; - self.tur_head.frame += 1; -} - -void turret_hellion_postthink() -{ - if (self.tur_head.frame != 0) - self.tur_head.frame += 1; - - if (self.tur_head.frame >= 7) - self.tur_head.frame = 0; -} - -void turret_hellion_dinit() -{ - if (self.netname == "") self.netname = "Hellion Missile Turret"; - - self.turrcaps_flags = TFL_TURRCAPS_RADIUSDMG | TFL_TURRCAPS_FASTPROJ | TFL_TURRCAPS_PLAYERKILL | TFL_TURRCAPS_MISSILEKILL; - self.aim_flags = TFL_AIM_SIMPLE; - self.target_select_flags = TFL_TARGETSELECT_LOS | TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK ; - self.firecheck_flags = TFL_FIRECHECK_DEAD | TFL_FIRECHECK_DISTANCES | TFL_FIRECHECK_TEAMCECK | TFL_FIRECHECK_REFIRE | TFL_FIRECHECK_AFF | TFL_FIRECHECK_OWM_AMMO; - self.ammo_flags = TFL_AMMO_ROCKETS | TFL_AMMO_RECHARGE; - - if (turret_stdproc_init("hellion_std", "models/turrets/base.md3", "models/turrets/hellion.md3", TID_HELLION) == 0) - { - remove(self); - return; - } - - self.turret_firefunc = turret_hellion_attack; - self.turret_postthink = turret_hellion_postthink; -} - -/*QUAKED turret_hellion (0 .5 .8) ? -*/ -void spawnfunc_turret_hellion() -{ - precache_model ("models/turrets/hellion.md3"); - precache_model ("models/turrets/base.md3"); - - self.think = turret_hellion_dinit; - self.nextthink = time + 0.5; -} - - diff --git a/qcsrc/server/tturrets/units/unit_hk.qc b/qcsrc/server/tturrets/units/unit_hk.qc deleted file mode 100644 index 6ce6c72e2..000000000 --- a/qcsrc/server/tturrets/units/unit_hk.qc +++ /dev/null @@ -1,336 +0,0 @@ -//#define TURRET_DEBUG_HK - -#ifdef TURRET_DEBUG_HK -.float atime; -#endif - -void spawnfunc_turret_hk(); -void turret_hk_dinit(); -void turret_hk_attack(); - - -float hk_is_valid_target(entity e_target) -{ - if (e_target == world) - return 0; - - // If only this was used more.. - if (e_target.flags & FL_NOTARGET) - return 0; - - // Cant touch this - if ((e_target.takedamage == DAMAGE_NO) || (e_target.health < 0)) - return 0; - - // player - if (IS_CLIENT(e_target)) - { - if (self.owner.target_select_playerbias < 0) - return 0; - - if (e_target.deadflag != DEAD_NO) - return 0; - } - - // Missile - if ((e_target.flags & FL_PROJECTILE) && (self.owner.target_select_missilebias < 0)) - return 0; - - // Team check - if ((e_target.team == self.owner.team) || (self.owner.team == e_target.owner.team)) - return 0; - - return 1; -} -void turret_hk_missile_think() -{ - vector vu, vd, vf, vl, vr, ve; // Vector (direction) - float fu, fd, ff, fl, fr, fe; // Fraction to solid - vector olddir,wishdir,newdir; // Final direction - float lt_for; // Length of Trace FORwrad - float lt_seek; // Length of Trace SEEK (left, right, up down) - float pt_seek; // Pitch of Trace SEEK (How mutch to angele left, right up, down trace towards v_forward) - vector pre_pos; - float myspeed; - entity e; - float ad,edist; - - self.nextthink = time + self.ticrate; - - //if (self.cnt < time) - // turret_hk_missile_explode(); - - if (self.enemy.deadflag != DEAD_NO) - self.enemy = world; - - // Pick the closest valid target. - if (!self.enemy) - { - e = findradius(self.origin, 5000); - while (e) - { - if (hk_is_valid_target(e)) - { - if (!self.enemy) - self.enemy = e; - else - if (vlen(self.origin - e.origin) < vlen(self.origin - self.enemy.origin)) - self.enemy = e; - } - e = e.chain; - } - } - - self.angles = vectoangles(self.velocity); - self.angles_x = self.angles_x * -1; - makevectors(self.angles); - self.angles_x = self.angles_x * -1; - - if (self.enemy) - { - edist = vlen(self.origin - self.enemy.origin); - // Close enougth to do decent damage? - if ( edist <= (self.owner.shot_radius * 0.25) ) - { - turret_projectile_explode(); - return; - } - - // Get data on enemy position - pre_pos = self.enemy.origin + - self.enemy.velocity * - min((vlen(self.enemy.origin - self.origin) / vlen(self.velocity)),0.5); - - traceline(self.origin, pre_pos,TRUE,self.enemy); - ve = normalize(pre_pos - self.origin); - fe = trace_fraction; - - } - else - { - edist = 0; - ve = '0 0 0'; - fe = 0; - } - - if ((fe != 1) || (self.enemy == world) || (edist > 1000)) - { - myspeed = vlen(self.velocity); - - lt_for = myspeed * 3; - lt_seek = myspeed * 2.95; - - // Trace forward - traceline(self.origin, self.origin + v_forward * lt_for,FALSE,self); - vf = trace_endpos; - ff = trace_fraction; - - // Find angular offset - ad = vlen(vectoangles(normalize(self.enemy.origin - self.origin)) - self.angles); - - // To close to something, Slow down! - if ( ((ff < 0.7) || (ad > 4)) && (myspeed > autocvar_g_turrets_unit_hk_std_shot_speed) ) - myspeed = max(myspeed * autocvar_g_turrets_unit_hk_std_shot_speed_decel, autocvar_g_turrets_unit_hk_std_shot_speed); - - // Failry clear, accelerate. - if ( (ff > 0.7) && (myspeed < autocvar_g_turrets_unit_hk_std_shot_speed_max) ) - myspeed = min(myspeed * autocvar_g_turrets_unit_hk_std_shot_speed_accel, autocvar_g_turrets_unit_hk_std_shot_speed_max); - - // Setup trace pitch - pt_seek = 1 - ff; - pt_seek = bound(0.15,pt_seek,0.8); - if (ff < 0.5) pt_seek = 1; - - // Trace left - traceline(self.origin, self.origin + (-1 * (v_right * pt_seek) + (v_forward * ff)) * lt_seek,FALSE,self); - vl = trace_endpos; - fl = trace_fraction; - - // Trace right - traceline(self.origin, self.origin + ((v_right * pt_seek) + (v_forward * ff)) * lt_seek ,FALSE,self); - vr = trace_endpos; - fr = trace_fraction; - - // Trace up - traceline(self.origin, self.origin + ((v_up * pt_seek) + (v_forward * ff)) * lt_seek ,FALSE,self); - vu = trace_endpos; - fu = trace_fraction; - - // Trace down - traceline(self.origin, self.origin + (-1 * (v_up * pt_seek) + (v_forward * ff)) * lt_seek ,FALSE,self); - vd = trace_endpos; - fd = trace_fraction; - - vl = normalize(vl - self.origin); - vr = normalize(vr - self.origin); - vu = normalize(vu - self.origin); - vd = normalize(vd - self.origin); - - // Panic tresh passed, find a single direction and turn as hard as we can - if (pt_seek == 1) - { - wishdir = v_right; - if (fl > fr) wishdir = -1 * v_right; - if (fu > fl) wishdir = v_up; - if (fd > fu) wishdir = -1 * v_up; - } - else - { - // Normalize our trace vectors to make a smooth path - wishdir = normalize( (vl * fl) + (vr * fr) + (vu * fu) + (vd * fd) ); - } - - if (self.enemy) - { - if (fe < 0.1) fe = 0.1; // Make sure we always try to move sligtly towards our target - wishdir = (wishdir * (1 - fe)) + (ve * fe); - } - } - else - { - // Got a clear path to target, speed up fast (if not at full speed) and go straight for it. - myspeed = vlen(self.velocity); - if (myspeed < autocvar_g_turrets_unit_hk_std_shot_speed_max) - myspeed = min(myspeed * autocvar_g_turrets_unit_hk_std_shot_speed_accel2,autocvar_g_turrets_unit_hk_std_shot_speed_max); - - wishdir = ve; - } - - if ((myspeed > autocvar_g_turrets_unit_hk_std_shot_speed) && (self.cnt > time)) - myspeed = min(myspeed * autocvar_g_turrets_unit_hk_std_shot_speed_accel2,autocvar_g_turrets_unit_hk_std_shot_speed_max); - - // Ranoutagazfish? - if (self.cnt < time) - { - self.cnt = time + 0.25; - self.nextthink = 0; - self.movetype = MOVETYPE_BOUNCE; - return; - } - - // Calculate new heading - olddir = normalize(self.velocity); - newdir = normalize(olddir + wishdir * autocvar_g_turrets_unit_hk_std_shot_speed_turnrate); - - // Set heading & speed - self.velocity = newdir * myspeed; - - // Align model with new heading - self.angles = vectoangles(self.velocity); - - -#ifdef TURRET_DEBUG_HK - //if(self.atime < time) { - if ((fe <= 0.99)||(edist > 1000)) - { - te_lightning2(world,self.origin, self.origin + vr * lt_seek); - te_lightning2(world,self.origin, self.origin + vl * lt_seek); - te_lightning2(world,self.origin, self.origin + vu * lt_seek); - te_lightning2(world,self.origin, self.origin + vd * lt_seek); - te_lightning2(world,self.origin, vf); - } - else - { - te_lightning2(world,self.origin, self.enemy.origin); - } - bprint("Speed: ", ftos(rint(myspeed)), "\n"); - bprint("Trace to solid: ", ftos(rint(ff * 100)), "%\n"); - bprint("Trace to target:", ftos(rint(fe * 100)), "%\n"); - self.atime = time + 0.2; - //} -#endif - - UpdateCSQCProjectile(self); -} - -void turret_hk_attack() -{ - entity missile; - - missile = turret_projectile("weapons/rocket_fire.wav", 6, 10, DEATH_TURRET_HK, PROJECTILE_ROCKET, FALSE, FALSE); - te_explosion (missile.origin); - - missile.think = turret_hk_missile_think; - missile.nextthink = time + 0.25; - missile.movetype = MOVETYPE_BOUNCEMISSILE; - missile.velocity = self.tur_shotdir_updated * (self.shot_speed * 0.75); - missile.angles = vectoangles(missile.velocity); - missile.cnt = time + 30; - missile.ticrate = max(autocvar_sys_ticrate, 0.05); - missile.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_GUIDED_AI; - - if (self.tur_head.frame == 0) - self.tur_head.frame = self.tur_head.frame + 1; - -} - -void turret_hk_postthink() -{ - if (self.tur_head.frame != 0) - self.tur_head.frame = self.tur_head.frame + 1; - - if (self.tur_head.frame > 5) - self.tur_head.frame = 0; -} - -float turret_hk_addtarget(entity e_target,entity e_sender) -{ - if (e_target) - { - if (turret_validate_target(self,e_target,self.target_validate_flags) > 0) - { - self.enemy = e_target; - return 1; - } - } - - return 0; -} - -void turret_hk_dinit() -{ - if (self.netname == "") - self.netname = "Hunter-killer turret"; - - self.turrcaps_flags = TFL_TURRCAPS_RADIUSDMG | TFL_TURRCAPS_MEDPROJ | TFL_TURRCAPS_PLAYERKILL | TFL_TURRCAPS_RECIVETARGETS; - self.ammo_flags = TFL_AMMO_ROCKETS | TFL_AMMO_RECHARGE; - self.aim_flags = TFL_AIM_SIMPLE; - self.target_select_flags = TFL_TARGETSELECT_LOS | TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_TRIGGERTARGET | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK; - self.firecheck_flags = TFL_FIRECHECK_DEAD | TFL_FIRECHECK_TEAMCECK | TFL_FIRECHECK_REFIRE | TFL_FIRECHECK_AFF; - self.shoot_flags = TFL_SHOOT_CLEARTARGET; - - if (turret_stdproc_init("hk_std", "models/turrets/base.md3", "models/turrets/hk.md3", TID_HK) == 0) - { - remove(self); - return; - } - - self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_TEAMCHECK; - - // Our fire routine - self.turret_firefunc = turret_hk_attack; - - // re-color badge & handle recoil effect - self.turret_postthink = turret_hk_postthink; - - // What to do when reciveing foreign target data - self.turret_addtarget = turret_hk_addtarget; -} - - -/*QUAKED turret_hk (0 .5 .8) ? -* Turret that fires Hunter-killer missiles. -* Missiles seek their target and try to avoid obstacles. If target dies early, they -* pick a new one on their own. -*/ - -void spawnfunc_turret_hk() -{ - precache_model ("models/turrets/base.md3"); - precache_model ("models/turrets/hk.md3"); - - self.think = turret_hk_dinit; - self.nextthink = time + 0.5; -} - - diff --git a/qcsrc/server/tturrets/units/unit_machinegun.qc b/qcsrc/server/tturrets/units/unit_machinegun.qc deleted file mode 100644 index d235dfb32..000000000 --- a/qcsrc/server/tturrets/units/unit_machinegun.qc +++ /dev/null @@ -1,52 +0,0 @@ -void spawnfunc_turret_machinegun(); -void turret_machinegun_std_init(); -void turret_machinegun_attack(); - -//.float bulletcounter; -void turret_machinegun_attack() -{ - fireBullet (self.tur_shotorg, self.tur_shotdir_updated,self.shot_spread, 0, self.shot_dmg, self.shot_force, DEATH_TURRET_MACHINEGUN, 0); - - W_MachineGun_MuzzleFlash(); // WEAPONTODO - setattachment(self.muzzle_flash, self.tur_head, "tag_fire"); -} - - -void turret_machinegun_std_init() -{ - if (self.netname == "") self.netname = "Machinegun Turret"; - - self.ammo_flags = TFL_AMMO_BULLETS | TFL_AMMO_RECHARGE | TFL_AMMO_RECIVE; - self.turrcaps_flags = TFL_TURRCAPS_PLAYERKILL; - self.aim_flags = TFL_AIM_LEAD | TFL_AIM_SHOTTIMECOMPENSATE; - - self.turrcaps_flags |= TFL_TURRCAPS_HITSCAN; - - if (turret_stdproc_init("machinegun_std", "models/turrets/base.md3", "models/turrets/machinegun.md3", TID_MACHINEGUN) == 0) - { - remove(self); - return; - } - - self.damage_flags |= TFL_DMG_HEADSHAKE; - self.target_select_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK; - - // Our fire routine - self.turret_firefunc = turret_machinegun_attack; - -} - - -/*QUAKED turret_machinegun (0 .5 .8) ? -* machinegun turret. does what you'd expect -*/ -void spawnfunc_turret_machinegun() -{ - precache_model ("models/turrets/machinegun.md3"); - precache_model ("models/turrets/base.md3"); - precache_sound ("weapons/uzi_fire.wav"); - - self.think = turret_machinegun_std_init; - self.nextthink = time + 0.5; -} - diff --git a/qcsrc/server/tturrets/units/unit_mlrs.qc b/qcsrc/server/tturrets/units/unit_mlrs.qc deleted file mode 100644 index 783966032..000000000 --- a/qcsrc/server/tturrets/units/unit_mlrs.qc +++ /dev/null @@ -1,63 +0,0 @@ -void spawnfunc_turret_mlrs(); -void turret_mlrs_dinit(); -void turret_mlrs_attack(); - -void turret_mlrs_postthink() -{ - // 0 = full, 6 = empty - self.tur_head.frame = bound(0, 6 - floor(0.1 + self.ammo / self.shot_dmg), 6); - if(self.tur_head.frame < 0) - { - dprint("ammo:",ftos(self.ammo),"\n"); - dprint("shot_dmg:",ftos(self.shot_dmg),"\n"); - } -} - -void turret_mlrs_attack() -{ - entity missile; - - turret_tag_fire_update(); - missile = turret_projectile("weapons/rocket_fire.wav", 6, 10, DEATH_TURRET_MLRS, PROJECTILE_ROCKET, TRUE, TRUE); - missile.nextthink = time + max(self.tur_impacttime,(self.shot_radius * 2) / self.shot_speed); - missile.missile_flags = MIF_SPLASH; - te_explosion (missile.origin); -} - -void turret_mlrs_dinit() -{ - if (self.netname == "") self.netname = "MLRS turret"; - - self.turrcaps_flags = TFL_TURRCAPS_RADIUSDMG | TFL_TURRCAPS_MEDPROJ | TFL_TURRCAPS_PLAYERKILL; - self.ammo_flags = TFL_AMMO_ROCKETS | TFL_AMMO_RECHARGE; - self.aim_flags = TFL_AIM_LEAD | TFL_AIM_SHOTTIMECOMPENSATE; - - if (turret_stdproc_init("mlrs_std", "models/turrets/base.md3", "models/turrets/mlrs.md3", TID_MLRS) == 0) - { - remove(self); - return; - } - - self.damage_flags |= TFL_DMG_HEADSHAKE; - self.shoot_flags |= TFL_SHOOT_VOLLYALWAYS; - self.volly_counter = self.shot_volly; - - // Our fire routine - self.turret_firefunc = turret_mlrs_attack; - self.turret_postthink = turret_mlrs_postthink; - -} - -/*QUAKED turret_mlrs (0 .5 .8) ? -*/ - -void spawnfunc_turret_mlrs() -{ - precache_model ("models/turrets/mlrs.md3"); - precache_model ("models/turrets/base.md3"); - - self.think = turret_mlrs_dinit; - self.nextthink = time + 0.5; -} - - diff --git a/qcsrc/server/tturrets/units/unit_phaser.qc b/qcsrc/server/tturrets/units/unit_phaser.qc deleted file mode 100644 index c704aa111..000000000 --- a/qcsrc/server/tturrets/units/unit_phaser.qc +++ /dev/null @@ -1,142 +0,0 @@ -void spawnfunc_turret_phaser(); -void turret_phaser_dinit(); -void turret_phaser_attack(); - -.float fireflag; - -float turret_phaser_firecheck() -{ - if (self.fireflag != 0) return 0; - return turret_stdproc_firecheck(); -} - -void turret_phaser_postthink() -{ - if (self.tur_head.frame == 0) - return; - - if (self.fireflag == 1) - { - if (self.tur_head.frame == 10) - self.tur_head.frame = 1; - else - self.tur_head.frame = self.tur_head.frame +1; - } - else if (self.fireflag == 2 ) - { - self.tur_head.frame = self.tur_head.frame +1; - if (self.tur_head.frame == 15) - { - self.tur_head.frame = 0; - self.fireflag = 0; - } - } -} - -void beam_think() -{ - if ((time > self.cnt) || (self.owner.deadflag != DEAD_NO)) - { - self.owner.attack_finished_single = time + self.owner.shot_refire; - self.owner.fireflag = 2; - self.owner.tur_head.frame = 10; - sound (self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM); - remove(self); - return; - } - - turret_do_updates(self.owner); - - if (time - self.shot_spread > 0) - { - self.shot_spread = time + 2; - sound (self, CH_SHOTS_SINGLE, "turrets/phaser.wav", VOL_BASE, ATTEN_NORM); - } - - - self.nextthink = time + self.ticrate; - - self.owner.attack_finished_single = time + frametime; - entity oldself; - oldself = self; - self = self.owner; - FireImoBeam ( self.tur_shotorg, - self.tur_shotorg + self.tur_shotdir_updated * self.target_range, - '-1 -1 -1' * self.shot_radius, - '1 1 1' * self.shot_radius, - self.shot_force, - oldself.shot_dmg, - 0.75, - DEATH_TURRET_PHASER); - self = oldself; - self.scale = vlen(self.owner.tur_shotorg - trace_endpos) / 256; - -} - -void turret_phaser_attack() -{ - entity beam; - - beam = spawn(); - beam.ticrate = 0.1; //autocvar_sys_ticrate; - setmodel(beam,"models/turrets/phaser_beam.md3"); - beam.effects = EF_LOWPRECISION; - beam.solid = SOLID_NOT; - beam.think = beam_think; - beam.cnt = time + self.shot_speed; - beam.shot_spread = time + 2; - beam.nextthink = time; - beam.owner = self; - beam.shot_dmg = self.shot_dmg / (self.shot_speed / beam.ticrate); - beam.scale = self.target_range / 256; - beam.movetype = MOVETYPE_NONE; - beam.enemy = self.enemy; - beam.bot_dodge = TRUE; - beam.bot_dodgerating = beam.shot_dmg; - sound (beam, CH_SHOTS_SINGLE, "turrets/phaser.wav", VOL_BASE, ATTEN_NORM); - self.fireflag = 1; - - beam.attack_finished_single = self.attack_finished_single; - self.attack_finished_single = time; // + autocvar_sys_ticrate; - - setattachment(beam,self.tur_head,"tag_fire"); - - soundat (self, trace_endpos, CH_SHOTS, "weapons/neximpact.wav", VOL_BASE, ATTEN_NORM); - - if (self.tur_head.frame == 0) - self.tur_head.frame = 1; -} - -void turret_phaser_dinit() -{ - if (self.netname == "") self.netname = "Phaser Cannon"; - - self.turrcaps_flags = TFL_TURRCAPS_SNIPER|TFL_TURRCAPS_HITSCAN|TFL_TURRCAPS_PLAYERKILL; - self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE | TFL_AMMO_RECIVE; - self.aim_flags = TFL_AIM_LEAD; - - if (turret_stdproc_init("phaser_std", "models/turrets/base.md3","models/turrets/phaser.md3", TID_PHASER) == 0) - { - remove(self); - return; - } - - self.turret_firecheckfunc = turret_phaser_firecheck; - self.turret_firefunc = turret_phaser_attack; - self.turret_postthink = turret_phaser_postthink; - -} - -/*QUAKED turret_phaser(0 .5 .8) ? -*/ -void spawnfunc_turret_phaser() -{ - precache_sound ("turrets/phaser.wav"); - precache_model ("models/turrets/phaser.md3"); - precache_model ("models/turrets/phaser_beam.md3"); - precache_model ("models/turrets/base.md3"); - - self.think = turret_phaser_dinit; - self.nextthink = time + 0.5; -} - diff --git a/qcsrc/server/tturrets/units/unit_plasma.qc b/qcsrc/server/tturrets/units/unit_plasma.qc deleted file mode 100644 index 26a3dc04e..000000000 --- a/qcsrc/server/tturrets/units/unit_plasma.qc +++ /dev/null @@ -1,173 +0,0 @@ -void spawnfunc_turret_plasma(); -void spawnfunc_turret_plasma_dual(); - -void turret_plasma_std_init(); -void turret_plasma_dual_init(); - -void turret_plasma_attack(); - - -void turret_plasma_postthink() -{ - if (self.tur_head.frame != 0) - self.tur_head.frame = self.tur_head.frame + 1; - - if (self.tur_head.frame > 5) - self.tur_head.frame = 0; -} - -void turret_plasma_dual_postthink() -{ - if ((self.tur_head.frame != 0) && (self.tur_head.frame != 3)) - self.tur_head.frame = self.tur_head.frame + 1; - - if (self.tur_head.frame > 6) - self.tur_head.frame = 0; -} - -void turret_plasma_minsta_attack (void) -{ - float flying; - flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last - - FireRailgunBullet (self.tur_shotorg, self.tur_shotorg + self.tur_shotdir_updated * MAX_SHOT_DISTANCE, 10000000000, - 800, 0, 0, 0, 0, DEATH_TURRET_PLASMA); - - - pointparticles(particleeffectnum("nex_muzzleflash"), self.tur_shotorg, self.tur_shotdir_updated * 1000, 1); - - // teamcolor / hit beam effect - vector v; - v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); - if(teamplay) - { - switch(self.team) - { - case NUM_TEAM_1: // Red - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3RED"), self.tur_shotorg, v); - break; - case NUM_TEAM_2: // Blue - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3BLUE"), self.tur_shotorg, v); - break; - case NUM_TEAM_3: // Yellow - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3YELLOW"), self.tur_shotorg, v); - break; - case NUM_TEAM_4: // Pink - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3PINK"), self.tur_shotorg, v); - break; - } - } - else - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3"), self.tur_shotorg, v); - if (self.tur_head.frame == 0) - self.tur_head.frame = 1; -} - -void turret_plasma_attack() -{ - entity missile = turret_projectile("weapons/hagar_fire.wav", 1, 0, DEATH_TURRET_PLASMA, PROJECTILE_ELECTRO_BEAM, TRUE, TRUE); - missile.missile_flags = MIF_SPLASH; - - pointparticles(particleeffectnum("laser_muzzleflash"), self.tur_shotorg, self.tur_shotdir_updated * 1000, 1); - if (self.tur_head.frame == 0) - self.tur_head.frame = 1; -} - -void turret_plasma_dual_attack() -{ - entity missile = turret_projectile("weapons/hagar_fire.wav", 1, 0, DEATH_TURRET_PLASMA, PROJECTILE_ELECTRO_BEAM, TRUE, TRUE); - missile.missile_flags = MIF_SPLASH; - pointparticles(particleeffectnum("laser_muzzleflash"), self.tur_shotorg, self.tur_shotdir_updated * 1000, 1); - self.tur_head.frame += 1; -} - -void turret_plasma_std_init() -{ - if (self.netname == "") self.netname = "Plasma Cannon"; - - // What ammo to use - self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE | TFL_AMMO_RECIVE; - - // How to aim - self.aim_flags = TFL_AIM_LEAD | TFL_AIM_SHOTTIMECOMPENSATE | TFL_AIM_GROUNDGROUND; - self.turrcaps_flags = TFL_TURRCAPS_RADIUSDMG | TFL_TURRCAPS_MEDPROJ | TFL_TURRCAPS_PLAYERKILL; - - if (turret_stdproc_init("plasma_std", "models/turrets/base.md3", "models/turrets/plasma.md3", TID_PLASMA) == 0) - { - remove(self); - return; - } - - self.damage_flags |= TFL_DMG_HEADSHAKE; - self.firecheck_flags |= TFL_FIRECHECK_AFF; - - // Our fireing routine - if(g_instagib) - self.turret_firefunc = turret_plasma_minsta_attack; - else - self.turret_firefunc = turret_plasma_attack; - - // Custom per turret frame stuff. usualy animation. - self.turret_postthink = turret_plasma_postthink; - turret_do_updates(self); -} - - -void turret_plasma_dual_init() -{ - if (self.netname == "") self.netname = "Dual Plasma Cannon"; - - // What ammo to use - self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE | TFL_AMMO_RECIVE; - - // How to aim at targets - self.aim_flags = TFL_AIM_LEAD | TFL_AIM_SHOTTIMECOMPENSATE | TFL_AIM_GROUNDGROUND ; - self.turrcaps_flags = TFL_TURRCAPS_RADIUSDMG | TFL_TURRCAPS_MEDPROJ | TFL_TURRCAPS_PLAYERKILL; - - if (turret_stdproc_init("plasma_dual", "models/turrets/base.md3", "models/turrets/plasmad.md3", TID_PLASMA_DUAL) == 0) - { - remove(self); - return; - } - - self.damage_flags |= TFL_DMG_HEADSHAKE; - self.firecheck_flags |= TFL_FIRECHECK_AFF; - - // Our fireing routine - self.turret_firefunc = turret_plasma_dual_attack; - - // Custom per turret frame stuff. usualy animation. - self.turret_postthink = turret_plasma_dual_postthink; -} - - -/* -* Basic moderate (std) or fast (dual) fireing, short-mid range energy cannon. -* Not too mutch of a therat on its own, but can be rather dangerous in groups. -* Regenerates ammo slowly, support with a fusionreactor(s) to do some real damage. -*/ - -/*QUAKED turret_plasma (0 .5 .8) ? -*/ -void spawnfunc_turret_plasma() -{ - g_turrets_common_precash(); - precache_model ("models/turrets/plasma.md3"); - precache_model ("models/turrets/base.md3"); - - self.think = turret_plasma_std_init; - self.nextthink = time + 0.5; -} - -/*QUAKED turret_plasma_dual (0 .5 .8) ? -*/ -void spawnfunc_turret_plasma_dual() -{ - - precache_model ("models/turrets/plasmad.md3"); - precache_model ("models/turrets/base.md3"); - - self.think = turret_plasma_dual_init; - self.nextthink = time + 0.5; -} - diff --git a/qcsrc/server/tturrets/units/unit_tessla.qc b/qcsrc/server/tturrets/units/unit_tessla.qc deleted file mode 100644 index 4989b2445..000000000 --- a/qcsrc/server/tturrets/units/unit_tessla.qc +++ /dev/null @@ -1,191 +0,0 @@ -void spawnfunc_turret_tesla(); -void turret_tesla_dinit(); -void turret_tesla_fire(); - -entity toast(entity from, float range, float damage) -{ - entity e; - entity etarget = world; - float d,dd; - float r; - - dd = range + 1; - - e = findradius(from.origin,range); - while (e) - { - if ((e.railgunhit != 1) && (e != from)) - { - r = turret_validate_target(self,e,self.target_validate_flags); - if (r > 0) - { - traceline(from.origin,0.5 * (e.absmin + e.absmax),MOVE_WORLDONLY,from); - if (trace_fraction == 1.0) - { - d = vlen(e.origin - from.origin); - if (d < dd) - { - dd = d; - etarget = e; - } - } - } - } - e = e.chain; - } - - if (etarget) - { - te_csqc_lightningarc(from.origin,etarget.origin); - Damage(etarget, self, self, damage, DEATH_TURRET_TESLA, etarget.origin, '0 0 0'); - etarget.railgunhit = 1; - } - - return etarget; -} - -float turret_tesla_firecheck() -{ - // g_turrets_targetscan_maxdelay forces a target re-scan at least this often - float do_target_scan = 0; - - if((self.target_select_time + autocvar_g_turrets_targetscan_maxdelay) < time) - do_target_scan = 1; - - // Old target (if any) invalid? - if(self.target_validate_time < time) - if (turret_validate_target(self, self.enemy, self.target_validate_flags) <= 0) - { - self.enemy = world; - self.target_validate_time = time + 0.5; - do_target_scan = 1; - } - - // But never more often then g_turrets_targetscan_mindelay! - if (self.target_select_time + autocvar_g_turrets_targetscan_mindelay > time) - do_target_scan = 0; - - if(do_target_scan) - { - self.enemy = turret_select_target(); - self.target_select_time = time; - } - - if (!turret_stdproc_firecheck()) - return 0; - - if(self.enemy) - return 1; - - return 0; -} - - -void turret_tesla_fire() -{ - entity e, t; - float d, r, i; - - d = self.shot_dmg; - r = self.target_range; - e = spawn(); - setorigin(e,self.tur_shotorg); - - self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_MISSILES | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK; - - t = toast(e,r,d); - remove(e); - - if (t == world) return; - - self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_MISSILES | TFL_TARGETSELECT_TEAMCHECK; - - self.attack_finished_single = time + self.shot_refire; - for (i = 0; i < 10; ++i) - { - d *= 0.75; - r *= 0.85; - t = toast(t, r, d); - if (t == world) break; - - } - - e = findchainfloat(railgunhit, 1); - while (e) - { - e.railgunhit = 0; - e = e.chain; - } -} - -void turret_tesla_postthink() -{ - if (!self.active) - { - self.tur_head.avelocity = '0 0 0'; - return; - } - - if(self.ammo < self.shot_dmg) - { - self.tur_head.avelocity = '0 45 0' * (self.ammo / self.shot_dmg); - } - else - { - self.tur_head.avelocity = '0 180 0' * (self.ammo / self.shot_dmg); - - if(self.attack_finished_single > time) - return; - - float f; - f = (self.ammo / self.ammo_max); - f = f * f; - if(f > random()) - if(random() < 0.1) - te_csqc_lightningarc(self.tur_shotorg,self.tur_shotorg + (randomvec() * 350)); - } -} - - -void turret_tesla_dinit() -{ - if (self.netname == "") self.netname = "Tesla Coil"; - - self.turrcaps_flags = TFL_TURRCAPS_HITSCAN | TFL_TURRCAPS_PLAYERKILL | TFL_TURRCAPS_MISSILEKILL; - - self.target_select_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_MISSILES | - TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK; - - self.firecheck_flags = TFL_FIRECHECK_REFIRE | TFL_FIRECHECK_OWM_AMMO; - self.shoot_flags = TFL_SHOOT_CUSTOM; - self.ammo_flags = TFL_AMMO_ENERGY | TFL_AMMO_RECHARGE | TFL_AMMO_RECIVE; - self.aim_flags = TFL_AIM_NO; - self.track_flags = TFL_TRACK_NO; - - if (turret_stdproc_init("tesla_std", "models/turrets/tesla_base.md3", "models/turrets/tesla_head.md3", TID_TESLA) == 0) - { - remove(self); - return; - } - setsize(self,'-60 -60 0','60 60 128'); - - self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_MISSILES | - TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK; - - self.turret_firefunc = turret_tesla_fire; - self.turret_postthink = turret_tesla_postthink; - self.turret_firecheckfunc = turret_tesla_firecheck; -} - -/*QUAKED turret_tesla (0 .5 .8) ? -*/ -void spawnfunc_turret_tesla() -{ - precache_model ("models/turrets/tesla_head.md3"); - precache_model ("models/turrets/tesla_base.md3"); - - - self.think = turret_tesla_dinit; - self.nextthink = time + 0.5; -} - diff --git a/qcsrc/server/tturrets/units/unit_walker.qc b/qcsrc/server/tturrets/units/unit_walker.qc deleted file mode 100644 index a91daa190..000000000 --- a/qcsrc/server/tturrets/units/unit_walker.qc +++ /dev/null @@ -1,643 +0,0 @@ -#define ANIM_NO 0 -#define ANIM_TURN 1 -#define ANIM_WALK 2 -#define ANIM_RUN 3 -#define ANIM_STRAFE_L 4 -#define ANIM_STRAFE_R 5 -#define ANIM_JUMP 6 -#define ANIM_LAND 7 -#define ANIM_PAIN 8 -#define ANIM_MEELE 9 -#define ANIM_SWIM 10 -#define ANIM_ROAM 11 -.float animflag; - -#define WALKER_MIN '-70 -70 0' -#define WALKER_MAX '70 70 95' - -#define WALKER_PATH(s,e) pathlib_astar(s,e) - -float walker_firecheck() -{ - if (self.animflag == ANIM_MEELE) - return 0; - - return turret_stdproc_firecheck(); -} - -void walker_meele_do_dmg() -{ - vector where; - entity e; - - makevectors(self.angles); - where = self.origin + v_forward * 128; - - e = findradius(where,32); - while (e) - { - if (turret_validate_target(self, e, self.target_validate_flags)) - if (e != self && e.owner != self) - Damage(e, self, self, autocvar_g_turrets_unit_walker_std_meele_dmg, DEATH_TURRET_WALK_MEELE, '0 0 0', v_forward * autocvar_g_turrets_unit_walker_std_meele_force); - - e = e.chain; - } -} - -void walker_setnoanim() -{ - turrets_setframe(ANIM_NO, FALSE); - self.animflag = self.frame; -} -void walker_rocket_explode() -{ - RadiusDamage (self, self.owner, autocvar_g_turrets_unit_walker_std_rocket_dmg, 0, autocvar_g_turrets_unit_walker_std_rocket_radius, self, world, autocvar_g_turrets_unit_walker_std_rocket_force, DEATH_TURRET_WALK_ROCKET, world); - - remove (self); -} - -void walker_rocket_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce) -{ - self.health = self.health - damage; - self.velocity = self.velocity + vforce; - - if (self.health <= 0) - W_PrepareExplosionByDamage(self.owner, walker_rocket_explode); -} - -#define WALKER_ROCKET_MOVE movelib_move_simple(newdir, autocvar_g_turrets_unit_walker_std_rocket_speed, autocvar_g_turrets_unit_walker_std_rocket_turnrate); UpdateCSQCProjectile(self) -void walker_rocket_loop(); -void walker_rocket_think() -{ - vector newdir; - float edist; - float itime; - float m_speed; - - self.nextthink = time; - - edist = vlen(self.enemy.origin - self.origin); - - // Simulate crude guidance - if (self.cnt < time) - { - if (edist < 1000) - self.tur_shotorg = randomvec() * min(edist, 64); - else - self.tur_shotorg = randomvec() * min(edist, 256); - - self.cnt = time + 0.5; - } - - if (edist < 128) - self.tur_shotorg = '0 0 0'; - - if (self.tur_health < time) - { - self.think = walker_rocket_explode; - self.nextthink = time; - return; - } - - if (self.shot_dmg != 1337 && random() < 0.01) - { - walker_rocket_loop(); - return; - } - - m_speed = vlen(self.velocity); - - // Enemy dead? just keep on the current heading then. - if (self.enemy == world || self.enemy.deadflag != DEAD_NO) - self.enemy = world; - - if (self.enemy) - { - itime = max(edist / m_speed, 1); - newdir = steerlib_pull(self.enemy.origin + self.tur_shotorg); - } - else - newdir = normalize(self.velocity); - - WALKER_ROCKET_MOVE; -} - -void walker_rocket_loop3() -{ - vector newdir; - self.nextthink = time; - - if (self.tur_health < time) - { - self.think = walker_rocket_explode; - return; - } - - if (vlen(self.origin - self.tur_shotorg) < 100 ) - { - self.think = walker_rocket_think; - return; - } - - newdir = steerlib_pull(self.tur_shotorg); - WALKER_ROCKET_MOVE; - - self.angles = vectoangles(self.velocity); -} - -void walker_rocket_loop2() -{ - vector newdir; - - self.nextthink = time; - - if (self.tur_health < time) - { - self.think = walker_rocket_explode; - return; - } - - if (vlen(self.origin - self.tur_shotorg) < 100 ) - { - self.tur_shotorg = self.origin - '0 0 200'; - self.think = walker_rocket_loop3; - return; - } - - newdir = steerlib_pull(self.tur_shotorg); - WALKER_ROCKET_MOVE; -} - -void walker_rocket_loop() -{ - self.nextthink = time; - self.tur_shotorg = self.origin + '0 0 300'; - self.think = walker_rocket_loop2; - self.shot_dmg = 1337; -} - -void walker_fire_rocket(vector org) -{ - entity rocket; - - fixedmakevectors(self.angles); - - te_explosion (org); - - rocket = spawn (); - setorigin(rocket, org); - - sound (self, CH_WEAPON_A, "weapons/hagar_fire.wav", VOL_BASE, ATTEN_NORM); - setsize (rocket, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot - - rocket.classname = "walker_rocket"; - rocket.owner = self; - rocket.bot_dodge = TRUE; - rocket.bot_dodgerating = 50; - rocket.takedamage = DAMAGE_YES; - rocket.damageforcescale = 2; - rocket.health = 25; - rocket.tur_shotorg = randomvec() * 512; - rocket.cnt = time + 1; - rocket.enemy = self.enemy; - - if (random() < 0.01) - rocket.think = walker_rocket_loop; - else - rocket.think = walker_rocket_think; - - rocket.event_damage = walker_rocket_damage; - - rocket.nextthink = time; - rocket.movetype = MOVETYPE_FLY; - rocket.velocity = normalize((v_forward + v_up * 0.5) + (randomvec() * 0.2)) * autocvar_g_turrets_unit_walker_std_rocket_speed; - rocket.angles = vectoangles(rocket.velocity); - rocket.touch = walker_rocket_explode; - rocket.flags = FL_PROJECTILE; - rocket.solid = SOLID_BBOX; - rocket.tur_health = time + 9; - rocket.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_GUIDED_HEAT; - - CSQCProjectile(rocket, FALSE, PROJECTILE_ROCKET, FALSE); // no culling, has fly sound -} - -.vector enemy_last_loc; -.float enemy_last_time; -void walker_move_to(vector _target, float _dist) -{ - switch (self.waterlevel) - { - case WATERLEVEL_NONE: - if (_dist > 500) - self.animflag = ANIM_RUN; - else - self.animflag = ANIM_WALK; - case WATERLEVEL_WETFEET: - case WATERLEVEL_SWIMMING: - if (self.animflag != ANIM_SWIM) - self.animflag = ANIM_WALK; - else - self.animflag = ANIM_SWIM; - break; - case WATERLEVEL_SUBMERGED: - self.animflag = ANIM_SWIM; - } - - self.moveto = _target; - self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); - - if(self.enemy) - { - self.enemy_last_loc = _target; - self.enemy_last_time = time; - } -} - -//#define WALKER_FANCYPATHING - -void walker_move_path() -{ -#ifdef WALKER_FANCYPATHING - // Are we close enougth to a path node to switch to the next? - if (vlen(self.origin - self.pathcurrent.origin) < 64) - if (self.pathcurrent.path_next == world) - { - // Path endpoint reached - pathlib_deletepath(self.pathcurrent.owner); - self.pathcurrent = world; - - if (self.pathgoal) - { - if (self.pathgoal.use) - self.pathgoal.use(); - - if (self.pathgoal.enemy) - { - self.pathcurrent = WALKER_PATH(self.pathgoal.origin,self.pathgoal.enemy.origin); - self.pathgoal = self.pathgoal.enemy; - } - } - else - self.pathgoal = world; - } - else - self.pathcurrent = self.pathcurrent.path_next; - - self.moveto = self.pathcurrent.origin; - self.steerto = steerlib_attract2(self.moveto,0.5,500,0.95); - walker_move_to(self.moveto, 0); - -#else - if (vlen(self.origin - self.pathcurrent.origin) < 64) - self.pathcurrent = self.pathcurrent.enemy; - - if(!self.pathcurrent) - return; - - self.moveto = self.pathcurrent.origin; - self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); - walker_move_to(self.moveto, 0); -#endif -} - -.float idletime; -void walker_postthink() -{ - fixedmakevectors(self.angles); - - if (self.spawnflags & TSF_NO_PATHBREAK && self.pathcurrent) - walker_move_path(); - else if (self.enemy == world) - { - if(self.pathcurrent) - walker_move_path(); - else - { - if(self.enemy_last_time != 0) - { - if(vlen(self.origin - self.enemy_last_loc) < 128 || time - self.enemy_last_time > 10) - self.enemy_last_time = 0; - else - walker_move_to(self.enemy_last_loc, 0); - } - else - { - if(self.animflag != ANIM_NO) - { - traceline(self.origin + '0 0 64', self.origin + '0 0 64' + v_forward * 128, MOVE_NORMAL, self); - - if(trace_fraction != 1.0) - self.tur_head.idletime = -1337; - else - { - traceline(trace_endpos, trace_endpos - '0 0 256', MOVE_NORMAL, self); - if(trace_fraction == 1.0) - self.tur_head.idletime = -1337; - } - - if(self.tur_head.idletime == -1337) - { - self.moveto = self.origin + randomvec() * 256; - self.tur_head.idletime = 0; - } - - self.moveto = self.moveto * 0.9 + ((self.origin + v_forward * 500) + randomvec() * 400) * 0.1; - self.moveto_z = self.origin_z + 64; - walker_move_to(self.moveto, 0); - } - - if(self.idletime < time) - { - if(random() < 0.5 || !(self.spawnflags & TSL_ROAM)) - { - self.idletime = time + 1 + random() * 5; - self.moveto = self.origin; - self.animflag = ANIM_NO; - } - else - { - self.animflag = ANIM_WALK; - self.idletime = time + 4 + random() * 2; - self.moveto = self.origin + randomvec() * 256; - self.tur_head.moveto = self.moveto; - self.tur_head.idletime = 0; - } - } - } - } - } - else - { - if (self.tur_dist_enemy < autocvar_g_turrets_unit_walker_std_meele_range && self.animflag != ANIM_MEELE) - { - vector wish_angle; - - wish_angle = angleofs(self, self.enemy); - if (self.animflag != ANIM_SWIM) - if (fabs(wish_angle_y) < 15) - { - self.moveto = self.enemy.origin; - self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); - self.animflag = ANIM_MEELE; - } - } - else if (self.tur_head.attack_finished_single < time) - { - if(self.tur_head.shot_volly) - { - self.animflag = ANIM_NO; - - self.tur_head.shot_volly = self.tur_head.shot_volly -1; - if(self.tur_head.shot_volly == 0) - self.tur_head.attack_finished_single = time + autocvar_g_turrets_unit_walker_std_rocket_refire; - else - self.tur_head.attack_finished_single = time + 0.2; - - if(self.tur_head.shot_volly > 1) - walker_fire_rocket(gettaginfo(self, gettagindex(self, "tag_rocket01"))); - else - walker_fire_rocket(gettaginfo(self, gettagindex(self, "tag_rocket02"))); - } - else - { - if (self.tur_dist_enemy > autocvar_g_turrets_unit_walker_std_rockets_range_min) - if (self.tur_dist_enemy < autocvar_g_turrets_unit_walker_std_rockets_range) - self.tur_head.shot_volly = 4; - } - } - else - { - if (self.animflag != ANIM_MEELE) - walker_move_to(self.enemy.origin, self.tur_dist_enemy); - } - } - - //if(self.animflag != ANIM_NO) - { - vector real_angle; - float turny = 0, turnx = 0; - float vz; - - real_angle = vectoangles(self.steerto) - self.angles; - vz = self.velocity_z; - - switch (self.animflag) - { - case ANIM_NO: - movelib_beak_simple(autocvar_g_turrets_unit_walker_speed_stop); - break; - - case ANIM_TURN: - turny = autocvar_g_turrets_unit_walker_turn; - movelib_beak_simple(autocvar_g_turrets_unit_walker_speed_stop); - break; - - case ANIM_WALK: - turny = autocvar_g_turrets_unit_walker_turn_walk; - movelib_move_simple(v_forward, autocvar_g_turrets_unit_walker_speed_walk, 0.6); - break; - - case ANIM_RUN: - turny = autocvar_g_turrets_unit_walker_turn_run; - movelib_move_simple(v_forward, autocvar_g_turrets_unit_walker_speed_run, 0.6); - break; - - case ANIM_STRAFE_L: - turny = autocvar_g_turrets_unit_walker_turn_strafe; - movelib_move_simple(v_right * -1, autocvar_g_turrets_unit_walker_speed_walk, 0.8); - break; - - case ANIM_STRAFE_R: - turny = autocvar_g_turrets_unit_walker_turn_strafe; - movelib_move_simple(v_right, autocvar_g_turrets_unit_walker_speed_walk, 0.8); - break; - - case ANIM_JUMP: - self.velocity += '0 0 1' * autocvar_g_turrets_unit_walker_speed_jump; - break; - - case ANIM_LAND: - break; - - case ANIM_PAIN: - if(self.frame != ANIM_PAIN) - defer(0.25, walker_setnoanim); - - break; - - case ANIM_MEELE: - if(self.frame != ANIM_MEELE) - { - defer(0.41, walker_setnoanim); - defer(0.21, walker_meele_do_dmg); - } - - movelib_beak_simple(autocvar_g_turrets_unit_walker_speed_stop); - break; - - case ANIM_SWIM: - turny = autocvar_g_turrets_unit_walker_turn_swim; - turnx = autocvar_g_turrets_unit_walker_turn_swim; - - self.angles_x += bound(-10, shortangle_f(real_angle_x, self.angles_x), 10); - movelib_move_simple(v_forward, autocvar_g_turrets_unit_walker_speed_swim, 0.3); - vz = self.velocity_z + sin(time * 4) * 8; - break; - - case ANIM_ROAM: - turny = autocvar_g_turrets_unit_walker_turn_walk; - movelib_move_simple(v_forward ,autocvar_g_turrets_unit_walker_speed_roam, 0.5); - break; - } - - if(turny) - { - turny = bound( turny * -1, shortangle_f(real_angle_y, self.angles_y), turny ); - self.angles_y += turny; - } - - if(turnx) - { - turnx = bound( turnx * -1, shortangle_f(real_angle_x, self.angles_x), turnx ); - self.angles_x += turnx; - } - - self.velocity_z = vz; - } - - - if(self.origin != self.oldorigin) - self.SendFlags |= TNSF_MOVE; - - self.oldorigin = self.origin; - turrets_setframe(self.animflag, FALSE); -} - -void walker_attack() -{ - sound (self, CH_WEAPON_A, "weapons/uzi_fire.wav", VOL_BASE, ATTEN_NORM); - fireBullet (self.tur_shotorg, self.tur_shotdir_updated, self.shot_spread, 0, self.shot_dmg, self.shot_force, DEATH_TURRET_WALK_GUN, 0); - pointparticles(particleeffectnum("laser_muzzleflash"), self.tur_shotorg, self.tur_shotdir_updated * 1000, 1); -} - - -void walker_respawnhook() -{ - entity e; - - // Respawn is called & first spawn to, to set team. need to make sure we do not move the initial spawn. - if(self.movetype != MOVETYPE_WALK) - return; - - setorigin(self, self.pos1); - self.angles = self.pos2; - - if (self.target != "") - { - e = find(world, targetname, self.target); - if (!e) - { - dprint("Warning! initital waypoint for Walker does NOT exsist!\n"); - self.target = ""; - } - - if (e.classname != "turret_checkpoint") - dprint("Warning: not a turrret path\n"); - else - { - #ifdef WALKER_FANCYPATHING - self.pathcurrent = WALKER_PATH(self.origin, e.origin); - self.pathgoal = e; -#else - self.pathcurrent = e; -#endif - } - } -} - -void walker_diehook() -{ -#ifdef WALKER_FANCYPATHING - if (self.pathcurrent) - pathlib_deletepath(self.pathcurrent.owner); -#endif - self.pathcurrent = world; -} - -void turret_walker_dinit() -{ - entity e; - - if (self.netname == "") self.netname = "Walker Turret"; - - self.ammo_flags = TFL_AMMO_BULLETS | TFL_AMMO_RECHARGE | TFL_AMMO_RECIVE; - self.turrcaps_flags = TFL_TURRCAPS_PLAYERKILL | TFL_TURRCAPS_MOVE ; - self.aim_flags = TFL_AIM_LEAD; - - self.turrcaps_flags |= TFL_TURRCAPS_HITSCAN; - - - self.turret_respawnhook = walker_respawnhook; - self.turret_diehook = walker_diehook; - - self.ticrate = 0.05; - if (turret_stdproc_init("walker_std", "models/turrets/walker_body.md3", "models/turrets/walker_head_minigun.md3", TID_WALKER) == 0) - { - remove(self); - return; - } - setsize(self, WALKER_MIN, WALKER_MAX); - self.target_select_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_LOS; - self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_LOS; - self.iscreature = TRUE; - self.teleportable = TELEPORT_NORMAL; - self.damagedbycontents = TRUE; - self.movetype = MOVETYPE_WALK; - self.solid = SOLID_SLIDEBOX; - self.takedamage = DAMAGE_AIM; - setorigin(self, self.origin); - tracebox(self.origin + '0 0 128', self.mins, self.maxs, self.origin - '0 0 10000', MOVE_NORMAL, self); - setorigin(self, trace_endpos + '0 0 4'); - self.pos1 = self.origin; - self.pos2 = self.angles; - self.idle_aim = '0 0 0'; - self.turret_firecheckfunc = walker_firecheck; - self.turret_firefunc = walker_attack; - self.turret_postthink = walker_postthink; - - if (self.target != "") - { - e = find(world, targetname, self.target); - if (!e) - { - dprint("Initital waypoint for walker does NOT exsist, fix your map!\n"); - self.target = ""; - } - - if (e.classname != "turret_checkpoint") - dprint("Warning: not a turrret path\n"); - else - { -#ifdef WALKER_FANCYPATHING - self.pathcurrent = WALKER_PATH(self.origin, e.origin); - self.pathgoal = e; -#else - self.pathcurrent = e; -#endif - } - } -} - - -void spawnfunc_turret_walker() -{ - g_turrets_common_precash(); - - precache_model ("models/turrets/walker_head_minigun.md3"); - precache_model ("models/turrets/walker_body.md3"); - precache_model ( "models/turrets/rocket.md3"); - precache_sound ( "weapons/rocket_impact.wav" ); - - self.think = turret_walker_dinit; - self.nextthink = time + 0.5; -} diff --git a/qcsrc/server/vehicles/racer.qc b/qcsrc/server/vehicles/racer.qc deleted file mode 100644 index fc9cee865..000000000 --- a/qcsrc/server/vehicles/racer.qc +++ /dev/null @@ -1,683 +0,0 @@ -#define RACER_MIN '-120 -120 -40' -#define RACER_MAX '120 120 40' - -#ifdef SVQC -void racer_exit(float eject); -void racer_enter(); - -// Auto cvars -float autocvar_g_vehicle_racer; - -float autocvar_g_vehicle_racer_speed_afterburn; -float autocvar_g_vehicle_racer_afterburn_cost; - -float autocvar_g_vehicle_racer_anglestabilizer; -float autocvar_g_vehicle_racer_downforce; - -float autocvar_g_vehicle_racer_speed_forward; -float autocvar_g_vehicle_racer_speed_strafe; -float autocvar_g_vehicle_racer_springlength; -float autocvar_g_vehicle_racer_upforcedamper; -float autocvar_g_vehicle_racer_friction; - -float autocvar_g_vehicle_racer_hovertype; -float autocvar_g_vehicle_racer_hoverpower; - -float autocvar_g_vehicle_racer_turnroll; -float autocvar_g_vehicle_racer_turnspeed; -float autocvar_g_vehicle_racer_pitchspeed; - -float autocvar_g_vehicle_racer_energy; -float autocvar_g_vehicle_racer_energy_regen; -float autocvar_g_vehicle_racer_energy_regen_pause; - -float autocvar_g_vehicle_racer_health; -float autocvar_g_vehicle_racer_health_regen; -float autocvar_g_vehicle_racer_health_regen_pause; - -float autocvar_g_vehicle_racer_shield; -float autocvar_g_vehicle_racer_shield_regen; -float autocvar_g_vehicle_racer_shield_regen_pause; - -float autocvar_g_vehicle_racer_cannon_cost; -float autocvar_g_vehicle_racer_cannon_damage; -float autocvar_g_vehicle_racer_cannon_radius; -float autocvar_g_vehicle_racer_cannon_refire; -float autocvar_g_vehicle_racer_cannon_speed; -float autocvar_g_vehicle_racer_cannon_spread; -float autocvar_g_vehicle_racer_cannon_force; - -float autocvar_g_vehicle_racer_rocket_accel; -float autocvar_g_vehicle_racer_rocket_damage; -float autocvar_g_vehicle_racer_rocket_radius; -float autocvar_g_vehicle_racer_rocket_force; -float autocvar_g_vehicle_racer_rocket_refire; -float autocvar_g_vehicle_racer_rocket_speed; -float autocvar_g_vehicle_racer_rocket_turnrate; - -float autocvar_g_vehicle_racer_rocket_locktarget; -float autocvar_g_vehicle_racer_rocket_locking_time; -float autocvar_g_vehicle_racer_rocket_locking_releasetime; -float autocvar_g_vehicle_racer_rocket_locked_time; -float autocvar_g_vehicle_racer_rocket_locked_maxangle; -float autocvar_g_vehicle_racer_rocket_climbspeed; - -float autocvar_g_vehicle_racer_respawntime; - -float autocvar_g_vehicle_racer_blowup_radius; -float autocvar_g_vehicle_racer_blowup_coredamage; -float autocvar_g_vehicle_racer_blowup_edgedamage; -float autocvar_g_vehicle_racer_blowup_forceintensity; - -float autocvar_g_vehicle_racer_bouncefactor; -float autocvar_g_vehicle_racer_bouncestop; -vector autocvar_g_vehicle_racer_bouncepain; - -var vector racer_force_from_tag(string tag_name, float spring_length, float max_power); - -void racer_align4point(float _delta) -{ - vector push_vector; - float fl_push, fr_push, bl_push, br_push; - - push_vector = racer_force_from_tag("tag_engine_fr", autocvar_g_vehicle_racer_springlength, autocvar_g_vehicle_racer_hoverpower); - fr_push = force_fromtag_normpower; - //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_racer_collision_multiplier); - - push_vector += racer_force_from_tag("tag_engine_fl", autocvar_g_vehicle_racer_springlength, autocvar_g_vehicle_racer_hoverpower); - fl_push = force_fromtag_normpower; - //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_racer_collision_multiplier); - - push_vector += racer_force_from_tag("tag_engine_br", autocvar_g_vehicle_racer_springlength, autocvar_g_vehicle_racer_hoverpower); - br_push = force_fromtag_normpower; - //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_racer_collision_multiplier); - - push_vector += racer_force_from_tag("tag_engine_bl", autocvar_g_vehicle_racer_springlength, autocvar_g_vehicle_racer_hoverpower); - bl_push = force_fromtag_normpower; - //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_racer_collision_multiplier); - - self.velocity += push_vector * _delta; - - // Anti ocilation - if(self.velocity_z > 0) - self.velocity_z *= 1 - autocvar_g_vehicle_racer_upforcedamper * _delta; - - push_vector_x = (fl_push - bl_push); - push_vector_x += (fr_push - br_push); - push_vector_x *= 360; - - push_vector_z = (fr_push - fl_push); - push_vector_z += (br_push - bl_push); - push_vector_z *= 360; - - // Apply angle diffrance - self.angles_z += push_vector_z * _delta; - self.angles_x += push_vector_x * _delta; - - // Apply stabilizer - self.angles_x *= 1 - (autocvar_g_vehicle_racer_anglestabilizer * _delta); - self.angles_z *= 1 - (autocvar_g_vehicle_racer_anglestabilizer * _delta); -} - -void racer_fire_cannon(string tagname) -{ - vector v; - entity bolt; - - v = gettaginfo(self, gettagindex(self, tagname)); - bolt = vehicles_projectile("wakizashi_gun_muzzleflash", "weapons/lasergun_fire.wav", - v, normalize(v_forward + randomvec() * autocvar_g_vehicle_racer_cannon_spread) * autocvar_g_vehicle_racer_cannon_speed, - autocvar_g_vehicle_racer_cannon_damage, autocvar_g_vehicle_racer_cannon_radius, autocvar_g_vehicle_racer_cannon_force, 0, - DEATH_VH_WAKI_GUN, PROJECTILE_WAKICANNON, 0, TRUE, TRUE, self.owner); - - // Fix z-aim (for chase mode) - v = normalize(trace_endpos - bolt.origin); - v_forward_z = v_z * 0.5; - bolt.velocity = v_forward * autocvar_g_vehicle_racer_cannon_speed; -} - -void racer_rocket_groundhugger() -{ - vector olddir, newdir; - float oldvel, newvel; - - self.nextthink = time; - - if(self.owner.deadflag != DEAD_NO || self.cnt < time) - { - self.use(); - return; - } - - if (!self.realowner.vehicle) - { - UpdateCSQCProjectile(self); - return; - } - - olddir = normalize(self.velocity); - oldvel = vlen(self.velocity); - newvel = oldvel + self.lip; - - tracebox(self.origin, self.mins, self.maxs, self.origin + olddir * 64, MOVE_WORLDONLY,self); - if(trace_fraction <= 0.5) - { - // Hitting somethign soon, just speed ahead - self.velocity = olddir * newvel; - UpdateCSQCProjectile(self); - return; - } - - traceline(trace_endpos, trace_endpos - '0 0 64', MOVE_NORMAL, self); - if(trace_fraction != 1.0) - { - newdir = normalize(trace_endpos + '0 0 64' - self.origin) * autocvar_g_vehicle_racer_rocket_turnrate; - self.velocity = normalize(olddir + newdir) * newvel; - } - else - { - self.velocity = olddir * newvel; - self.velocity_z -= 1600 * sys_frametime; // 2x grav looks better for this one - } - - UpdateCSQCProjectile(self); - return; -} - -void racer_rocket_tracker() -{ - vector olddir, newdir; - float oldvel, newvel; - - self.nextthink = time; - - if (self.owner.deadflag != DEAD_NO || self.cnt < time) - { - self.use(); - return; - } - - if (!self.realowner.vehicle) - { - UpdateCSQCProjectile(self); - return; - } - - olddir = normalize(self.velocity); - oldvel = vlen(self.velocity); - newvel = oldvel + self.lip; - makevectors(vectoangles(olddir)); - - float time_to_impact = min(vlen(self.enemy.origin - self.origin) / vlen(self.velocity), 1); - vector predicted_origin = self.enemy.origin + self.enemy.velocity * time_to_impact; - - traceline(self.origin, self.origin + v_forward * 64 - '0 0 32', MOVE_NORMAL, self); - newdir = normalize(predicted_origin - self.origin); - - //vector - float height_diff = predicted_origin_z - self.origin_z; - - if(vlen(newdir - v_forward) > autocvar_g_vehicle_racer_rocket_locked_maxangle) - { - //bprint("Target lost!\n"); - //dprint("OF:", ftos(vlen(newdir - v_forward)), "\n"); - self.think = racer_rocket_groundhugger; - return; - } - - if(trace_fraction != 1.0 && trace_ent != self.enemy) - newdir_z += 16 * sys_frametime; - - self.velocity = normalize(olddir + newdir * autocvar_g_vehicle_racer_rocket_turnrate) * newvel; - self.velocity_z -= 800 * sys_frametime; - self.velocity_z += max(height_diff, autocvar_g_vehicle_racer_rocket_climbspeed) * sys_frametime ; - - UpdateCSQCProjectile(self); - return; -} - -void racer_fire_rocket(string tagname, entity trg) -{ - vector v = gettaginfo(self, gettagindex(self, tagname)); - entity rocket = rocket = vehicles_projectile("wakizashi_rocket_launch", "weapons/rocket_fire.wav", - v, v_forward * autocvar_g_vehicle_racer_rocket_speed, - autocvar_g_vehicle_racer_rocket_damage, autocvar_g_vehicle_racer_rocket_radius, autocvar_g_vehicle_racer_rocket_force, 3, - DEATH_VH_WAKI_ROCKET, PROJECTILE_WAKIROCKET, 20, FALSE, FALSE, self.owner); - - rocket.lip = autocvar_g_vehicle_racer_rocket_accel * sys_frametime; - rocket.wait = autocvar_g_vehicle_racer_rocket_turnrate; - rocket.nextthink = time; - rocket.enemy = trg; - rocket.cnt = time + 15; - - if(trg) - rocket.think = racer_rocket_tracker; - else - rocket.think = racer_rocket_groundhugger; -} - -float racer_frame() -{ - entity player, racer; - vector df; - float ftmp; - - if(intermission_running) - return 1; - - player = self; - racer = self.vehicle; - self = racer; - - player.BUTTON_ZOOM = player.BUTTON_CROUCH = 0; - - vehicles_painframe(); - - if(racer.deadflag != DEAD_NO) - { - self = player; - player.BUTTON_ATCK = player.BUTTON_ATCK2 = 0; - return 1; - } - - racer_align4point(frametime); - - crosshair_trace(player); - - racer.angles_x *= -1; - - // Yaw - ftmp = autocvar_g_vehicle_racer_turnspeed * frametime; - ftmp = bound(-ftmp, shortangle_f(player.v_angle_y - racer.angles_y, racer.angles_y), ftmp); - racer.angles_y = anglemods(racer.angles_y + ftmp); - - // Roll - racer.angles_z += -ftmp * autocvar_g_vehicle_racer_turnroll * frametime; - - // Pitch - ftmp = autocvar_g_vehicle_racer_pitchspeed * frametime; - ftmp = bound(-ftmp, shortangle_f(player.v_angle_x - racer.angles_x, racer.angles_x), ftmp); - racer.angles_x = bound(-30, anglemods(racer.angles_x + ftmp), 30); - - makevectors(racer.angles); - racer.angles_x *= -1; - - //ftmp = racer.velocity_z; - df = racer.velocity * -autocvar_g_vehicle_racer_friction; - //racer.velocity_z = ftmp; - - if(vlen(player.movement) != 0) - { - if(player.movement_x) - df += v_forward * ((player.movement_x > 0) ? autocvar_g_vehicle_racer_speed_forward : -autocvar_g_vehicle_racer_speed_forward); - - if(player.movement_y) - df += v_right * ((player.movement_y > 0) ? autocvar_g_vehicle_racer_speed_strafe : -autocvar_g_vehicle_racer_speed_strafe); - - if(self.sound_nexttime < time || self.sounds != 1) - { - self.sounds = 1; - self.sound_nexttime = time + 10.922667; //soundlength("vehicles/racer_move.wav"); - sound (self, CH_TRIGGER_SINGLE, "vehicles/racer_move.wav", VOL_VEHICLEENGINE, ATTEN_NORM); - } - } - else - { - if(self.sound_nexttime < time || self.sounds != 0) - { - self.sounds = 0; - self.sound_nexttime = time + 11.888604; //soundlength("vehicles/racer_idle.wav"); - sound (self, CH_TRIGGER_SINGLE, "vehicles/racer_idle.wav", VOL_VEHICLEENGINE, ATTEN_NORM); - } - } - - // Afterburn - if (player.BUTTON_JUMP && racer.vehicle_energy >= (autocvar_g_vehicle_racer_afterburn_cost * frametime)) - { - if(time - racer.wait > 0.2) - pointparticles(particleeffectnum("wakizashi_booster_smoke"), self.origin - v_forward * 32, v_forward * vlen(self.velocity), 1); - - racer.wait = time; - racer.vehicle_energy -= autocvar_g_vehicle_racer_afterburn_cost * frametime; - df += (v_forward * autocvar_g_vehicle_racer_speed_afterburn); - - if(racer.invincible_finished < time) - { - traceline(racer.origin, racer.origin - '0 0 256', MOVE_NORMAL, self); - if(trace_fraction != 1.0) - pointparticles(particleeffectnum("smoke_small"), trace_endpos, '0 0 0', 1); - - racer.invincible_finished = time + 0.1 + (random() * 0.1); - } - - if(racer.strength_finished < time) - { - racer.strength_finished = time + 10.922667; //soundlength("vehicles/racer_boost.wav"); - sound (racer.tur_head, CH_TRIGGER_SINGLE, "vehicles/racer_boost.wav", VOL_VEHICLEENGINE, ATTEN_NORM); - } - } - else - { - racer.strength_finished = 0; - sound (racer.tur_head, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_VEHICLEENGINE, ATTEN_NORM); - } - - df -= v_up * (vlen(racer.velocity) * autocvar_g_vehicle_racer_downforce); - player.movement = racer.velocity += df * frametime; - - if(player.BUTTON_ATCK) - if(time > racer.attack_finished_single) - if(racer.vehicle_energy >= autocvar_g_vehicle_racer_cannon_cost) - { - racer.vehicle_energy -= autocvar_g_vehicle_racer_cannon_cost; - racer.wait = time; - - crosshair_trace(player); - if(racer.cnt) - { - racer_fire_cannon("tag_fire1"); - racer.cnt = 0; - } - else - { - racer_fire_cannon("tag_fire2"); - racer.cnt = 1; - } - racer.attack_finished_single = time + autocvar_g_vehicle_racer_cannon_refire; - } - - if(autocvar_g_vehicle_racer_rocket_locktarget) - { - vehicles_locktarget((1 / autocvar_g_vehicle_racer_rocket_locking_time) * frametime, - (1 / autocvar_g_vehicle_racer_rocket_locking_releasetime) * frametime, - autocvar_g_vehicle_racer_rocket_locked_time); - - if(self.lock_target) - { - if(racer.lock_strength == 1) - UpdateAuxiliaryXhair(player, real_origin(self.lock_target), '1 0 0', 0); - else if(self.lock_strength > 0.5) - UpdateAuxiliaryXhair(player, real_origin(self.lock_target), '0 1 0', 0); - else if(self.lock_strength < 0.5) - UpdateAuxiliaryXhair(player, real_origin(self.lock_target), '0 0 1', 0); - } - } - - if(time > racer.delay) - if(player.BUTTON_ATCK2) - { - racer.misc_bulletcounter += 1; - racer.delay = time + 0.3; - - if(racer.misc_bulletcounter == 1) - racer_fire_rocket("tag_rocket_r", (racer.lock_strength == 1 && racer.lock_target) ? racer.lock_target : world); - else if(racer.misc_bulletcounter == 2) - { - racer_fire_rocket("tag_rocket_l", (racer.lock_strength == 1 && racer.lock_target) ? racer.lock_target : world); - racer.lock_strength = 0; - racer.lock_target = world; - racer.misc_bulletcounter = 0; - - racer.delay = time + autocvar_g_vehicle_racer_rocket_refire; - racer.lip = time; - } - } - player.vehicle_reload1 = bound(0, 100 * ((time - racer.lip) / (racer.delay - racer.lip)), 100); - - if(racer.vehicle_flags & VHF_SHIELDREGEN) - vehicles_regen(racer.dmg_time, vehicle_shield, autocvar_g_vehicle_racer_shield, autocvar_g_vehicle_racer_shield_regen_pause, autocvar_g_vehicle_racer_shield_regen, frametime, TRUE); - - if(racer.vehicle_flags & VHF_HEALTHREGEN) - vehicles_regen(racer.dmg_time, vehicle_health, autocvar_g_vehicle_racer_health, autocvar_g_vehicle_racer_health_regen_pause, autocvar_g_vehicle_racer_health_regen, frametime, FALSE); - - if(racer.vehicle_flags & VHF_ENERGYREGEN) - vehicles_regen(racer.wait, vehicle_energy, autocvar_g_vehicle_racer_energy, autocvar_g_vehicle_racer_energy_regen_pause, autocvar_g_vehicle_racer_energy_regen, frametime, FALSE); - - - VEHICLE_UPDATE_PLAYER(player, health, racer); - VEHICLE_UPDATE_PLAYER(player, energy, racer); - - if(racer.vehicle_flags & VHF_HASSHIELD) - VEHICLE_UPDATE_PLAYER(player, shield, racer); - - player.BUTTON_ATCK = player.BUTTON_ATCK2 = 0; - setorigin(player,racer.origin + '0 0 32'); - player.velocity = racer.velocity; - - self = player; - return 1; -} - -void racer_think() -{ - self.nextthink = time; - - float pushdeltatime = time - self.lastpushtime; - if (pushdeltatime > 0.15) pushdeltatime = 0; - self.lastpushtime = time; - if(!pushdeltatime) return; - - tracebox(self.origin, self.mins, self.maxs, self.origin - ('0 0 1' * autocvar_g_vehicle_racer_springlength), MOVE_NORMAL, self); - - vector df = self.velocity * -autocvar_g_vehicle_racer_friction; - df_z += (1 - trace_fraction) * autocvar_g_vehicle_racer_hoverpower + sin(time * 2) * (autocvar_g_vehicle_racer_springlength * 2); - - self.velocity += df * pushdeltatime; - if(self.velocity_z > 0) - self.velocity_z *= 1 - autocvar_g_vehicle_racer_upforcedamper * pushdeltatime; - - self.angles_x *= 1 - (autocvar_g_vehicle_racer_anglestabilizer * pushdeltatime); - self.angles_z *= 1 - (autocvar_g_vehicle_racer_anglestabilizer * pushdeltatime); -} - -void racer_enter() -{ - self.movetype = MOVETYPE_BOUNCE; - self.owner.vehicle_health = (self.vehicle_health / autocvar_g_vehicle_racer_health) * 100; - self.owner.vehicle_shield = (self.vehicle_shield / autocvar_g_vehicle_racer_shield) * 100; - - if(self.owner.flagcarried) - setorigin(self.owner.flagcarried, '-190 0 96'); - - //targetdrone_spawn(self.origin + '0 0 512' + randomvec() * 256, 1); -} - -void racer_exit(float eject) -{ - vector spot; - - self.think = racer_think; - self.nextthink = time; - self.movetype = MOVETYPE_BOUNCE; - sound (self.tur_head, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_VEHICLEENGINE, ATTEN_NORM); - - if (!self.owner) - return; - - makevectors(self.angles); - if(eject) - { - spot = self.origin + v_forward * 100 + '0 0 64'; - spot = vehicles_findgoodexit(spot); - setorigin(self.owner , spot); - self.owner.velocity = (v_up + v_forward * 0.25) * 750; - self.owner.oldvelocity = self.owner.velocity; - } - else - { - if(vlen(self.velocity) > 2 * autocvar_sv_maxairspeed) - { - self.owner.velocity = normalize(self.velocity) * autocvar_sv_maxairspeed * 2; - self.owner.velocity_z += 200; - spot = self.origin + v_forward * 32 + '0 0 32'; - spot = vehicles_findgoodexit(spot); - } - else - { - self.owner.velocity = self.velocity * 0.5; - self.owner.velocity_z += 10; - spot = self.origin - v_forward * 200 + '0 0 32'; - spot = vehicles_findgoodexit(spot); - } - self.owner.oldvelocity = self.owner.velocity; - setorigin(self.owner , spot); - } - antilag_clear(self.owner); - self.owner = world; -} - -void racer_impact() -{ - if(autocvar_g_vehicle_racer_bouncepain_x) - vehicles_impact(autocvar_g_vehicle_racer_bouncepain_x, autocvar_g_vehicle_racer_bouncepain_y, autocvar_g_vehicle_racer_bouncepain_z); -} - -void racer_blowup() -{ - self.deadflag = DEAD_DEAD; - self.vehicle_exit(VHEF_NORMAL); - - RadiusDamage (self, self.enemy, autocvar_g_vehicle_racer_blowup_coredamage, - autocvar_g_vehicle_racer_blowup_edgedamage, - autocvar_g_vehicle_racer_blowup_radius, world, world, - autocvar_g_vehicle_racer_blowup_forceintensity, - DEATH_VH_WAKI_DEATH, world); - - self.alpha = -1; - self.movetype = MOVETYPE_NONE; - self.effects = EF_NODRAW; - self.colormod = '0 0 0'; - self.avelocity = '0 0 0'; - self.velocity = '0 0 0'; - - setorigin(self, self.pos1); - self.touch = func_null; - self.nextthink = 0; -} - -void racer_deadtouch() -{ - self.avelocity_x *= 0.7; - self.cnt -= 1; - if(self.cnt <= 0) - racer_blowup(); -} - -void racer_die() -{ - self.health = 0; - self.event_damage = func_null; - self.solid = SOLID_CORPSE; - self.takedamage = DAMAGE_NO; - self.deadflag = DEAD_DYING; - self.movetype = MOVETYPE_BOUNCE; - self.wait = time; - self.cnt = 1 + random() * 2; - self.touch = racer_deadtouch; - - pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1); - - if(random() < 0.5) - self.avelocity_z = 32; - else - self.avelocity_z = -32; - - self.avelocity_x = -vlen(self.velocity) * 0.2; - self.velocity += '0 0 700'; - self.colormod = '-0.5 -0.5 -0.5'; - - self.think = racer_blowup; - self.nextthink = 2 + time + random() * 3; -} -void racer_spawn(float _spawnflag) -{ - if(self.scale != 0.5) - { - if(autocvar_g_vehicle_racer_hovertype != 0) - racer_force_from_tag = vehicles_force_fromtag_maglev; - else - racer_force_from_tag = vehicles_force_fromtag_hover; - - // FIXME: this be hakkz, fix the models insted (scale body, add tag_viewport to the hudmodel). - self.scale = 0.5; - setattachment(self.vehicle_hudmodel, self, ""); - setattachment(self.vehicle_viewport, self, "tag_viewport"); - - self.mass = 900; - } - - self.think = racer_think; - self.nextthink = time; - self.vehicle_health = autocvar_g_vehicle_racer_health; - self.vehicle_shield = autocvar_g_vehicle_racer_shield; - - self.movetype = MOVETYPE_TOSS; - self.solid = SOLID_SLIDEBOX; - self.delay = time; - self.scale = 0.5; - - setsize(self, RACER_MIN * 0.5, RACER_MAX * 0.5); - self.bouncefactor = autocvar_g_vehicle_racer_bouncefactor; - self.bouncestop = autocvar_g_vehicle_racer_bouncestop; - self.vehicle_impact = racer_impact; - self.damageforcescale = 0.5; - //self.destvec = autocvar_g_vehicle_racer_bouncepain; -} - -void spawnfunc_vehicle_racer() -{ - if(!autocvar_g_vehicle_racer) - { - remove(self); - return; - } - - self.vehicle_flags |= VHF_DMGSHAKE; - self.vehicle_flags |= VHF_DMGROLL; - - precache_sound ("weapons/lasergun_fire.wav"); - precache_sound ("weapons/rocket_fire.wav"); - - precache_sound ("vehicles/racer_idle.wav"); - precache_sound ("vehicles/racer_move.wav"); - precache_sound ("vehicles/racer_boost.wav"); - - precache_model ("models/vhshield.md3"); - precache_model ("models/vehicles/wakizashi.dpm"); - precache_model ("models/vehicles/wakizashi_cockpit.dpm"); - - if(autocvar_g_vehicle_racer_energy) - if(autocvar_g_vehicle_racer_energy_regen) - self.vehicle_flags |= VHF_ENERGYREGEN; - - if(autocvar_g_vehicle_racer_shield) - self.vehicle_flags |= VHF_HASSHIELD; - - if(autocvar_g_vehicle_racer_shield_regen) - self.vehicle_flags |= VHF_SHIELDREGEN; - - if(autocvar_g_vehicle_racer_health_regen) - self.vehicle_flags |= VHF_HEALTHREGEN; - - if(!vehicle_initialize( - "Wakizashi", - "models/vehicles/wakizashi.dpm", - "null", // we need this so tur_head is networked and usable for sounds - "models/vehicles/wakizashi_cockpit.dpm", - "", "", "tag_viewport", - HUD_WAKIZASHI, - 0.5 * RACER_MIN, 0.5 * RACER_MAX, - FALSE, - racer_spawn, autocvar_g_vehicle_racer_respawntime, - racer_frame, - racer_enter, racer_exit, - racer_die, racer_think, - TRUE, - autocvar_g_vehicle_racer_health, - autocvar_g_vehicle_racer_shield)) - { - remove(self); - return; - } -} -#endif // SVQC diff --git a/qcsrc/server/vehicles/raptor.qc b/qcsrc/server/vehicles/raptor.qc deleted file mode 100644 index 53e4d35f7..000000000 --- a/qcsrc/server/vehicles/raptor.qc +++ /dev/null @@ -1,949 +0,0 @@ -#define RSM_FIRST 0 -#define RSM_BOMB 0 -#define RSM_FLARE 1 -#define RSM_LAST 1 - -#define RAPTOR_MIN '-80 -80 0' -#define RAPTOR_MAX '80 80 70' - -#ifdef SVQC -float autocvar_g_vehicle_raptor; - -float autocvar_g_vehicle_raptor_respawntime; -float autocvar_g_vehicle_raptor_takeofftime; - -float autocvar_g_vehicle_raptor_movestyle; -float autocvar_g_vehicle_raptor_turnspeed; -float autocvar_g_vehicle_raptor_pitchspeed; -float autocvar_g_vehicle_raptor_pitchlimit; - -float autocvar_g_vehicle_raptor_speed_forward; -float autocvar_g_vehicle_raptor_speed_strafe; -float autocvar_g_vehicle_raptor_speed_up; -float autocvar_g_vehicle_raptor_speed_down; -float autocvar_g_vehicle_raptor_friction; - -float autocvar_g_vehicle_raptor_bomblets; -float autocvar_g_vehicle_raptor_bomblet_alt; -float autocvar_g_vehicle_raptor_bomblet_time; -float autocvar_g_vehicle_raptor_bomblet_damage; -float autocvar_g_vehicle_raptor_bomblet_spread; -float autocvar_g_vehicle_raptor_bomblet_edgedamage; -float autocvar_g_vehicle_raptor_bomblet_radius; -float autocvar_g_vehicle_raptor_bomblet_force; -float autocvar_g_vehicle_raptor_bomblet_explode_delay; -float autocvar_g_vehicle_raptor_bombs_refire; - -float autocvar_g_vehicle_raptor_flare_refire; -float autocvar_g_vehicle_raptor_flare_lifetime; -float autocvar_g_vehicle_raptor_flare_chase; -float autocvar_g_vehicle_raptor_flare_range; - -float autocvar_g_vehicle_raptor_cannon_turnspeed; -float autocvar_g_vehicle_raptor_cannon_turnlimit; -float autocvar_g_vehicle_raptor_cannon_pitchlimit_up; -float autocvar_g_vehicle_raptor_cannon_pitchlimit_down; - -float autocvar_g_vehicle_raptor_cannon_locktarget; -float autocvar_g_vehicle_raptor_cannon_locking_time; -float autocvar_g_vehicle_raptor_cannon_locking_releasetime; -float autocvar_g_vehicle_raptor_cannon_locked_time; -float autocvar_g_vehicle_raptor_cannon_predicttarget; - -float autocvar_g_vehicle_raptor_cannon_cost; -float autocvar_g_vehicle_raptor_cannon_damage; -float autocvar_g_vehicle_raptor_cannon_radius; -float autocvar_g_vehicle_raptor_cannon_refire; -float autocvar_g_vehicle_raptor_cannon_speed; -float autocvar_g_vehicle_raptor_cannon_spread; -float autocvar_g_vehicle_raptor_cannon_force; - -float autocvar_g_vehicle_raptor_energy; -float autocvar_g_vehicle_raptor_energy_regen; -float autocvar_g_vehicle_raptor_energy_regen_pause; - -float autocvar_g_vehicle_raptor_health; -float autocvar_g_vehicle_raptor_health_regen; -float autocvar_g_vehicle_raptor_health_regen_pause; - -float autocvar_g_vehicle_raptor_shield; -float autocvar_g_vehicle_raptor_shield_regen; -float autocvar_g_vehicle_raptor_shield_regen_pause; - -float autocvar_g_vehicle_raptor_blowup_radius; -float autocvar_g_vehicle_raptor_blowup_coredamage; -float autocvar_g_vehicle_raptor_blowup_edgedamage; -float autocvar_g_vehicle_raptor_blowup_forceintensity; - -float autocvar_g_vehicle_raptor_bouncefactor; -float autocvar_g_vehicle_raptor_bouncestop; -vector autocvar_g_vehicle_raptor_bouncepain; - -void raptor_spawn(float); -float raptor_frame(); -float raptor_takeoff(); - -.entity bomb1; -.entity bomb2; - -float raptor_altitude(float amax) -{ - tracebox(self.origin, self.mins, self.maxs, self.origin - ('0 0 1' * amax), MOVE_WORLDONLY, self); - return vlen(self.origin - trace_endpos); -} - - -void raptor_bomblet_boom() -{ - RadiusDamage (self, self.realowner, autocvar_g_vehicle_raptor_bomblet_damage, - autocvar_g_vehicle_raptor_bomblet_edgedamage, - autocvar_g_vehicle_raptor_bomblet_radius, world, world, - autocvar_g_vehicle_raptor_bomblet_force, DEATH_VH_RAPT_BOMB, world); - remove(self); -} - -void raptor_bomblet_touch() -{ - if(other == self.owner) - return; - - PROJECTILE_TOUCH; - self.think = raptor_bomblet_boom; - self.nextthink = time + random() * autocvar_g_vehicle_raptor_bomblet_explode_delay; -} - -void raptor_bomb_burst() -{ - if(self.cnt > time) - if(autocvar_g_vehicle_raptor_bomblet_alt) - { - self.nextthink = time; - traceline(self.origin, self.origin + (normalize(self.velocity) * autocvar_g_vehicle_raptor_bomblet_alt), MOVE_NORMAL, self); - if((trace_fraction == 1.0) || (vlen(self.origin - self.owner.origin) < autocvar_g_vehicle_raptor_bomblet_radius)) - { - UpdateCSQCProjectile(self); - return; - } - } - - entity bomblet; - float i; - - Damage_DamageInfo(self.origin, 0, 0, 0, '0 0 0', DEATH_VH_RAPT_FRAGMENT, 0, self); - - for(i = 0; i < autocvar_g_vehicle_raptor_bomblets; ++i) - { - bomblet = spawn(); - setorigin(bomblet, self.origin); - - bomblet.movetype = MOVETYPE_TOSS; - bomblet.touch = raptor_bomblet_touch; - bomblet.think = raptor_bomblet_boom; - bomblet.nextthink = time + 5; - bomblet.owner = self.owner; - bomblet.realowner = self.realowner; - bomblet.velocity = normalize(normalize(self.velocity) + (randomvec() * autocvar_g_vehicle_raptor_bomblet_spread)) * vlen(self.velocity); - - PROJECTILE_MAKETRIGGER(bomblet); - CSQCProjectile(bomblet, TRUE, PROJECTILE_RAPTORBOMBLET, TRUE); - } - - remove(self); -} - -void raptor_bombdrop() -{ - entity bomb_1, bomb_2; - - bomb_1 = spawn(); - bomb_2 = spawn(); - - setorigin(bomb_1, gettaginfo(self, gettagindex(self, "bombmount_left"))); - setorigin(bomb_2, gettaginfo(self, gettagindex(self, "bombmount_right"))); - - bomb_1.movetype = bomb_2.movetype = MOVETYPE_BOUNCE; - bomb_1.velocity = bomb_2.velocity = self.velocity; - bomb_1.touch = bomb_2.touch = raptor_bomb_burst; - bomb_1.think = bomb_2.think = raptor_bomb_burst; - bomb_1.cnt = bomb_2.cnt = time + 10; - - if(autocvar_g_vehicle_raptor_bomblet_alt) - bomb_1.nextthink = bomb_2.nextthink = time; - else - bomb_1.nextthink = bomb_2.nextthink = time + autocvar_g_vehicle_raptor_bomblet_time; - - bomb_1.owner = bomb_2.owner = self; - bomb_1.realowner = bomb_2.realowner = self.owner; - bomb_1.solid = bomb_2.solid = SOLID_BBOX; - bomb_1.gravity = bomb_2.gravity = 1; - - PROJECTILE_MAKETRIGGER(bomb_1); - PROJECTILE_MAKETRIGGER(bomb_2); - - CSQCProjectile(bomb_1, TRUE, PROJECTILE_RAPTORBOMB, TRUE); - CSQCProjectile(bomb_2, TRUE, PROJECTILE_RAPTORBOMB, TRUE); -} - - -void raptor_fire_cannon(entity gun, string tagname) -{ - vehicles_projectile("raptor_cannon_muzzleflash", "weapons/lasergun_fire.wav", - gettaginfo(gun, gettagindex(gun, tagname)), normalize(v_forward + randomvec() * autocvar_g_vehicle_raptor_cannon_spread) * autocvar_g_vehicle_raptor_cannon_speed, - autocvar_g_vehicle_raptor_cannon_damage, autocvar_g_vehicle_raptor_cannon_radius, autocvar_g_vehicle_raptor_cannon_force, 0, - DEATH_VH_RAPT_CANNON, PROJECTILE_RAPTORCANNON, 0, TRUE, TRUE, self.owner); -} - -void raptor_think() -{ -} - -void raptor_enter() -{ - self.vehicle_weapon2mode = RSM_BOMB; - self.owner.PlayerPhysplug = raptor_takeoff; - self.movetype = MOVETYPE_BOUNCEMISSILE; - self.solid = SOLID_SLIDEBOX; - self.owner.vehicle_health = (self.vehicle_health / autocvar_g_vehicle_raptor_health) * 100; - self.owner.vehicle_shield = (self.vehicle_shield / autocvar_g_vehicle_raptor_shield) * 100; - self.velocity_z = 1; // Nudge upwards to takeoff sequense can work. - self.tur_head.exteriormodeltoclient = self.owner; - - self.delay = time + autocvar_g_vehicle_raptor_bombs_refire; - self.lip = time; - - if(self.owner.flagcarried) - setorigin(self.owner.flagcarried, '-20 0 96'); - - CSQCVehicleSetup(self.owner, 0); -} - -void raptor_land() -{ - float hgt; - - hgt = raptor_altitude(512); - self.velocity = (self.velocity * 0.9) + ('0 0 -1800' * (hgt / 256) * sys_frametime); - self.angles_x *= 0.95; - self.angles_z *= 0.95; - - if(hgt < 128) - if(hgt > 0) - self.frame = (hgt / 128) * 25; - - self.bomb1.gun1.avelocity_y = 90 + ((self.frame / 25) * 2000); - self.bomb1.gun2.avelocity_y = -self.bomb1.gun1.avelocity_y; - - if(hgt < 16) - { - self.movetype = MOVETYPE_TOSS; - self.think = raptor_think; - self.frame = 0; - } - - self.nextthink = time; -} - -void raptor_exit(float eject) -{ - vector spot; - self.tur_head.exteriormodeltoclient = world; - - if(self.deadflag == DEAD_NO) - { - self.think = raptor_land; - self.nextthink = time; - } - - if (!self.owner) - return; - - makevectors(self.angles); - if(eject) - { - spot = self.origin + v_forward * 100 + '0 0 64'; - spot = vehicles_findgoodexit(spot); - setorigin(self.owner , spot); - self.owner.velocity = (v_up + v_forward * 0.25) * 750; - self.owner.oldvelocity = self.owner.velocity; - } - else - { - if(vlen(self.velocity) > 2 * autocvar_sv_maxairspeed) - { - self.owner.velocity = normalize(self.velocity) * autocvar_sv_maxairspeed * 2; - self.owner.velocity_z += 200; - spot = self.origin + v_forward * 32 + '0 0 64'; - spot = vehicles_findgoodexit(spot); - } - else - { - self.owner.velocity = self.velocity * 0.5; - self.owner.velocity_z += 10; - spot = self.origin - v_forward * 200 + '0 0 64'; - spot = vehicles_findgoodexit(spot); - } - self.owner.oldvelocity = self.owner.velocity; - setorigin(self.owner , spot); - } - - antilag_clear(self.owner); - self.owner = world; -} - -float raptor_takeoff() -{ - entity player, raptor; - - player = self; - raptor = self.vehicle; - self = raptor; - if(self.sound_nexttime < time) - { - self.sound_nexttime = time + 7.955812; //soundlength("vehicles/raptor_fly.wav"); - sound (self, CH_TRIGGER_SINGLE, "vehicles/raptor_speed.wav", VOL_VEHICLEENGINE, ATTEN_NORM); - } - - // Takeoff sequense - if(raptor.frame < 25) - { - raptor.frame += 25 / (autocvar_g_vehicle_raptor_takeofftime / sys_frametime); - raptor.velocity_z = min(raptor.velocity_z * 1.5, 256); - self.bomb1.gun1.avelocity_y = 90 + ((raptor.frame / 25) * 25000); - self.bomb1.gun2.avelocity_y = -self.bomb1.gun1.avelocity_y; - player.BUTTON_ATCK = player.BUTTON_ATCK2 = player.BUTTON_CROUCH = 0; - - setorigin(player, raptor.origin + '0 0 32'); - } - else - player.PlayerPhysplug = raptor_frame; - - if(self.vehicle_flags & VHF_SHIELDREGEN) - vehicles_regen(raptor.dmg_time, vehicle_shield, autocvar_g_vehicle_raptor_shield, autocvar_g_vehicle_raptor_shield_regen_pause, autocvar_g_vehicle_raptor_shield_regen, frametime, TRUE); - - if(self.vehicle_flags & VHF_HEALTHREGEN) - vehicles_regen(raptor.dmg_time, vehicle_health, autocvar_g_vehicle_raptor_health, autocvar_g_vehicle_raptor_health_regen_pause, autocvar_g_vehicle_raptor_health_regen, frametime, FALSE); - - if(self.vehicle_flags & VHF_ENERGYREGEN) - vehicles_regen(raptor.cnt, vehicle_energy, autocvar_g_vehicle_raptor_energy, autocvar_g_vehicle_raptor_energy_regen_pause, autocvar_g_vehicle_raptor_energy_regen, frametime, FALSE); - - - raptor.bomb1.alpha = raptor.bomb2.alpha = (time - raptor.lip) / (raptor.delay - raptor.lip); - player.vehicle_reload2 = bound(0, raptor.bomb1.alpha * 100, 100); - - VEHICLE_UPDATE_PLAYER(player, health, raptor); - VEHICLE_UPDATE_PLAYER(player, energy, raptor); - if(self.vehicle_flags & VHF_HASSHIELD) - VEHICLE_UPDATE_PLAYER(player, shield, raptor); - - player.BUTTON_ATCK = player.BUTTON_ATCK2 = player.BUTTON_CROUCH = 0; - self = player; - return 1; -} - -void raptor_flare_touch() -{ - remove(self); -} - -void raptor_flare_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - self.health -= damage; - if(self.health <= 0) - remove(self); -} - -void raptor_flare_think() -{ - self.nextthink = time + 0.1; - entity _missile = findchainentity(enemy, self.owner); - while(_missile) - { - if(_missile.flags & FL_PROJECTILE) - if(vlen(self.origin - _missile.origin) < autocvar_g_vehicle_raptor_flare_range) - if(random() > autocvar_g_vehicle_raptor_flare_chase) - _missile.enemy = self; - _missile = _missile.chain; - } - - if(self.tur_impacttime < time) - remove(self); -} - -float raptor_frame() -{ - entity player, raptor; - float ftmp = 0; - vector df; - - if(intermission_running) - return 1; - - player = self; - raptor = self.vehicle; - self = raptor; - vehicles_painframe(); - /* - ftmp = vlen(self.velocity); - if(ftmp > autocvar_g_vehicle_raptor_speed_forward) - ftmp = 1; - else - ftmp = ftmp / autocvar_g_vehicle_raptor_speed_forward; - */ - - if(self.sound_nexttime < time) - { - self.sound_nexttime = time + 7.955812; - //sound (self.tur_head, CH_TRIGGER_SINGLE, "vehicles/raptor_fly.wav", 1 - ftmp, ATTEN_NORM ); - sound (self, CH_TRIGGER_SINGLE, "vehicles/raptor_speed.wav", 1, ATTEN_NORM); - self.wait = ftmp; - } - /* - else if(fabs(ftmp - self.wait) > 0.2) - { - sound (self.tur_head, CH_TRIGGER_SINGLE, "", 1 - ftmp, ATTEN_NORM ); - sound (self, CH_TRIGGER_SINGLE, "", ftmp, ATTEN_NORM); - self.wait = ftmp; - } - */ - - if(raptor.deadflag != DEAD_NO) - { - self = player; - player.BUTTON_ATCK = player.BUTTON_ATCK2 = 0; - return 1; - } - crosshair_trace(player); - - vector vang; - vang = raptor.angles; - df = vectoangles(normalize(trace_endpos - self.origin + '0 0 32')); - vang_x *= -1; - df_x *= -1; - if(df_x > 180) df_x -= 360; - if(df_x < -180) df_x += 360; - if(df_y > 180) df_y -= 360; - if(df_y < -180) df_y += 360; - - ftmp = shortangle_f(player.v_angle_y - vang_y, vang_y); - if(ftmp > 180) ftmp -= 360; if(ftmp < -180) ftmp += 360; - raptor.avelocity_y = bound(-autocvar_g_vehicle_raptor_turnspeed, ftmp + raptor.avelocity_y * 0.9, autocvar_g_vehicle_raptor_turnspeed); - - // Pitch - ftmp = 0; - if(player.movement_x > 0 && vang_x < autocvar_g_vehicle_raptor_pitchlimit) ftmp = 5; - else if(player.movement_x < 0 && vang_x > -autocvar_g_vehicle_raptor_pitchlimit) ftmp = -20; - - df_x = bound(-autocvar_g_vehicle_raptor_pitchlimit, df_x , autocvar_g_vehicle_raptor_pitchlimit); - ftmp = vang_x - bound(-autocvar_g_vehicle_raptor_pitchlimit, df_x + ftmp, autocvar_g_vehicle_raptor_pitchlimit); - raptor.avelocity_x = bound(-autocvar_g_vehicle_raptor_pitchspeed, ftmp + raptor.avelocity_x * 0.9, autocvar_g_vehicle_raptor_pitchspeed); - - raptor.angles_x = anglemods(raptor.angles_x); - raptor.angles_y = anglemods(raptor.angles_y); - raptor.angles_z = anglemods(raptor.angles_z); - - if(autocvar_g_vehicle_raptor_movestyle == 1) - makevectors('0 1 0' * raptor.angles_y); - else - makevectors(player.v_angle); - - df = raptor.velocity * -autocvar_g_vehicle_raptor_friction; - - if(player.movement_x != 0) - { - if(player.movement_x > 0) - df += v_forward * autocvar_g_vehicle_raptor_speed_forward; - else if(player.movement_x < 0) - df -= v_forward * autocvar_g_vehicle_raptor_speed_forward; - } - - if(player.movement_y != 0) - { - if(player.movement_y < 0) - df -= v_right * autocvar_g_vehicle_raptor_speed_strafe; - else if(player.movement_y > 0) - df += v_right * autocvar_g_vehicle_raptor_speed_strafe; - - raptor.angles_z = bound(-30,raptor.angles_z + (player.movement_y / autocvar_g_vehicle_raptor_speed_strafe),30); - } - else - { - raptor.angles_z *= 0.95; - if(raptor.angles_z >= -1 && raptor.angles_z <= -1) - raptor.angles_z = 0; - } - - if(player.BUTTON_CROUCH) - df -= v_up * autocvar_g_vehicle_raptor_speed_down; - else if (player.BUTTON_JUMP) - df += v_up * autocvar_g_vehicle_raptor_speed_up; - - raptor.velocity += df * frametime; - player.velocity = player.movement = raptor.velocity; - setorigin(player, raptor.origin + '0 0 32'); - - vector vf, ad; - // Target lock & predict - if(autocvar_g_vehicle_raptor_cannon_locktarget == 2) - { - if(raptor.gun1.lock_time < time || raptor.gun1.enemy.deadflag) - raptor.gun1.enemy = world; - - if(trace_ent) - if(trace_ent.movetype) - if(trace_ent.takedamage) - if(!trace_ent.deadflag) - { - if(teamplay) - { - if(trace_ent.team != player.team) - { - raptor.gun1.enemy = trace_ent; - raptor.gun1.lock_time = time + 5; - } - } - else - { - raptor.gun1.enemy = trace_ent; - raptor.gun1.lock_time = time + 0.5; - } - } - - if(raptor.gun1.enemy) - { - float i, distance, impact_time; - - vf = real_origin(raptor.gun1.enemy); - UpdateAuxiliaryXhair(player, vf, '1 0 0', 1); - vector _vel = raptor.gun1.enemy.velocity; - if(raptor.gun1.enemy.movetype == MOVETYPE_WALK) - _vel_z *= 0.1; - - if(autocvar_g_vehicle_raptor_cannon_predicttarget) - { - ad = vf; - for(i = 0; i < 4; ++i) - { - distance = vlen(ad - player.origin); - impact_time = distance / autocvar_g_vehicle_raptor_cannon_speed; - ad = vf + _vel * impact_time; - } - trace_endpos = ad; - } - else - trace_endpos = vf; - } - } - else if(autocvar_g_vehicle_raptor_cannon_locktarget == 1) - { - - vehicles_locktarget((1 / autocvar_g_vehicle_raptor_cannon_locking_time) * frametime, - (1 / autocvar_g_vehicle_raptor_cannon_locking_releasetime) * frametime, - autocvar_g_vehicle_raptor_cannon_locked_time); - - if(self.lock_target != world) - if(autocvar_g_vehicle_raptor_cannon_predicttarget) - if(self.lock_strength == 1) - { - float i, distance, impact_time; - - vf = real_origin(raptor.lock_target); - ad = vf; - for(i = 0; i < 4; ++i) - { - distance = vlen(ad - raptor.origin); - impact_time = distance / autocvar_g_vehicle_raptor_cannon_speed; - ad = vf + raptor.lock_target.velocity * impact_time; - } - trace_endpos = ad; - } - - if(self.lock_target) - { - if(raptor.lock_strength == 1) - UpdateAuxiliaryXhair(player, real_origin(raptor.lock_target), '1 0 0', 1); - else if(self.lock_strength > 0.5) - UpdateAuxiliaryXhair(player, real_origin(raptor.lock_target), '0 1 0', 1); - else if(self.lock_strength < 0.5) - UpdateAuxiliaryXhair(player, real_origin(raptor.lock_target), '0 0 1', 1); - } - } - - - vehicle_aimturret(raptor, trace_endpos, raptor.gun1, "fire1", - autocvar_g_vehicle_raptor_cannon_pitchlimit_down * -1, autocvar_g_vehicle_raptor_cannon_pitchlimit_up, - autocvar_g_vehicle_raptor_cannon_turnlimit * -1, autocvar_g_vehicle_raptor_cannon_turnlimit, autocvar_g_vehicle_raptor_cannon_turnspeed); - - vehicle_aimturret(raptor, trace_endpos, raptor.gun2, "fire1", - autocvar_g_vehicle_raptor_cannon_pitchlimit_down * -1, autocvar_g_vehicle_raptor_cannon_pitchlimit_up, - autocvar_g_vehicle_raptor_cannon_turnlimit * -1, autocvar_g_vehicle_raptor_cannon_turnlimit, autocvar_g_vehicle_raptor_cannon_turnspeed); - - /* - ad = ad * 0.5; - v_forward = vf * 0.5; - traceline(ad, ad + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, raptor); - UpdateAuxiliaryXhair(player, trace_endpos, '0 1 0', 0); - */ - - if(player.BUTTON_ATCK) - if(raptor.attack_finished_single <= time) - if(raptor.vehicle_energy > autocvar_g_vehicle_raptor_cannon_cost) - { - raptor.misc_bulletcounter += 1; - raptor.attack_finished_single = time + autocvar_g_vehicle_raptor_cannon_refire; - if(raptor.misc_bulletcounter <= 2) - raptor_fire_cannon(self.gun1, "fire1"); - else if(raptor.misc_bulletcounter == 3) - raptor_fire_cannon(self.gun2, "fire1"); - else - { - raptor.attack_finished_single = time + autocvar_g_vehicle_raptor_cannon_refire * 2; - raptor_fire_cannon(self.gun2, "fire1"); - raptor.misc_bulletcounter = 0; - } - raptor.vehicle_energy -= autocvar_g_vehicle_raptor_cannon_cost; - self.cnt = time; - } - - if(self.vehicle_flags & VHF_SHIELDREGEN) - vehicles_regen(raptor.dmg_time, vehicle_shield, autocvar_g_vehicle_raptor_shield, autocvar_g_vehicle_raptor_shield_regen_pause, autocvar_g_vehicle_raptor_shield_regen, frametime, TRUE); - - if(self.vehicle_flags & VHF_HEALTHREGEN) - vehicles_regen(raptor.dmg_time, vehicle_health, autocvar_g_vehicle_raptor_health, autocvar_g_vehicle_raptor_health_regen_pause, autocvar_g_vehicle_raptor_health_regen, frametime, FALSE); - - if(self.vehicle_flags & VHF_ENERGYREGEN) - vehicles_regen(raptor.cnt, vehicle_energy, autocvar_g_vehicle_raptor_energy, autocvar_g_vehicle_raptor_energy_regen_pause, autocvar_g_vehicle_raptor_energy_regen, frametime, FALSE); - - if(raptor.vehicle_weapon2mode == RSM_BOMB) - { - if(time > raptor.lip + autocvar_g_vehicle_raptor_bombs_refire) - if(player.BUTTON_ATCK2) - { - raptor_bombdrop(); - raptor.delay = time + autocvar_g_vehicle_raptor_bombs_refire; - raptor.lip = time; - } - } - else - { - if(time > raptor.lip + autocvar_g_vehicle_raptor_flare_refire) - if(player.BUTTON_ATCK2) - { - float i; - entity _flare; - - for(i = 0; i < 3; ++i) - { - _flare = spawn(); - setmodel(_flare, "models/runematch/rune.mdl"); - _flare.effects = EF_LOWPRECISION | EF_FLAME; - _flare.scale = 0.5; - setorigin(_flare, self.origin - '0 0 16'); - _flare.movetype = MOVETYPE_TOSS; - _flare.gravity = 0.15; - _flare.velocity = 0.25 * raptor.velocity + (v_forward + randomvec() * 0.25)* -500; - _flare.think = raptor_flare_think; - _flare.nextthink = time; - _flare.owner = raptor; - _flare.solid = SOLID_CORPSE; - _flare.takedamage = DAMAGE_YES; - _flare.event_damage = raptor_flare_damage; - _flare.health = 20; - _flare.tur_impacttime = time + autocvar_g_vehicle_raptor_flare_lifetime; - _flare.touch = raptor_flare_touch; - } - raptor.delay = time + autocvar_g_vehicle_raptor_flare_refire; - raptor.lip = time; - } - } - - raptor.bomb1.alpha = raptor.bomb2.alpha = (time - raptor.lip) / (raptor.delay - raptor.lip); - player.vehicle_reload2 = bound(0, raptor.bomb1.alpha * 100, 100); - - if(self.bomb1.cnt < time) - { - entity _missile = findchainentity(enemy, raptor); - float _incomming = 0; - while(_missile) - { - if(_missile.flags & FL_PROJECTILE) - if(MISSILE_IS_TRACKING(_missile)) - if(vlen(self.origin - _missile.origin) < 2 * autocvar_g_vehicle_raptor_flare_range) - ++_incomming; - - _missile = _missile.chain; - } - - if(_incomming) - sound(self, CH_PAIN_SINGLE, "vehicles/missile_alarm.wav", VOL_BASE, ATTEN_NONE); - - self.bomb1.cnt = time + 1; - } - - - VEHICLE_UPDATE_PLAYER(player, health, raptor); - VEHICLE_UPDATE_PLAYER(player, energy, raptor); - if(self.vehicle_flags & VHF_HASSHIELD) - VEHICLE_UPDATE_PLAYER(player, shield, raptor); - - player.BUTTON_ATCK = player.BUTTON_ATCK2 = player.BUTTON_CROUCH = 0; - - self = player; - return 1; -} - -void raptor_blowup() -{ - self.deadflag = DEAD_DEAD; - self.vehicle_exit(VHEF_NORMAL); - - RadiusDamage(self, self.enemy, autocvar_g_vehicle_raptor_blowup_coredamage, - autocvar_g_vehicle_raptor_blowup_edgedamage, - autocvar_g_vehicle_raptor_blowup_radius, world, world, - autocvar_g_vehicle_raptor_blowup_forceintensity, DEATH_VH_RAPT_DEATH, world); - - self.alpha = -1; - self.movetype = MOVETYPE_NONE; - self.effects = EF_NODRAW; - self.colormod = '0 0 0'; - self.avelocity = '0 0 0'; - self.velocity = '0 0 0'; - - setorigin(self, self.pos1); - self.touch = func_null; - self.nextthink = 0; -} - -void raptor_diethink() -{ - if(time >= self.wait) - self.think = raptor_blowup; - - if(random() < 0.1) - { - sound (self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); - pointparticles(particleeffectnum("explosion_small"), randomvec() * 80 + (self.origin + '0 0 100'), '0 0 0', 1); - } - self.nextthink = time + 0.1; -} - -void raptor_die() -{ - self.health = 0; - self.event_damage = func_null; - self.solid = SOLID_CORPSE; - self.takedamage = DAMAGE_NO; - self.deadflag = DEAD_DYING; - self.movetype = MOVETYPE_BOUNCE; - self.think = raptor_diethink; - self.nextthink = time; - self.wait = time + 5 + (random() * 5); - - pointparticles(particleeffectnum("explosion_medium"), findbetterlocation (self.origin, 16), '0 0 0', 1); - - self.velocity_z += 600; - - self.avelocity = '0 0.5 1' * (random() * 400); - self.avelocity -= '0 0.5 1' * (random() * 400); - - self.colormod = '-0.5 -0.5 -0.5'; - self.touch = raptor_blowup; -} - -void raptor_impact() -{ - if(autocvar_g_vehicle_raptor_bouncepain_x) - vehicles_impact(autocvar_g_vehicle_raptor_bouncepain_x, autocvar_g_vehicle_raptor_bouncepain_y, autocvar_g_vehicle_raptor_bouncepain_z); -} - -// If we dont do this ever now and then, the raptors rotors -// stop working, presumably due to angle overflow. cute. -void raptor_rotor_anglefix() -{ - self.gun1.angles_y = anglemods(self.gun1.angles_y); - self.gun2.angles_y = anglemods(self.gun2.angles_y); - self.nextthink = time + 15; -} - -float raptor_impulse(float _imp) -{ - switch(_imp) - { - case 10: - case 15: - case 18: - self.vehicle.vehicle_weapon2mode += 1; - if(self.vehicle.vehicle_weapon2mode > RSM_LAST) - self.vehicle.vehicle_weapon2mode = RSM_FIRST; - - CSQCVehicleSetup(self, 0); - return TRUE; - case 12: - case 16: - case 19: - self.vehicle.vehicle_weapon2mode -= 1; - if(self.vehicle.vehicle_weapon2mode < RSM_FIRST) - self.vehicle.vehicle_weapon2mode = RSM_LAST; - - CSQCVehicleSetup(self, 0); - return TRUE; - - /* - case 17: // toss gun, could be used to exit? - break; - case 20: // Manual minigun reload? - break; - */ - } - return FALSE; -} - -void raptor_spawn(float _f) -{ - if(!self.gun1) - { - entity spinner; - vector ofs; - - //FIXME: Camera is in a bad place in HUD model. - //setorigin(self.vehicle_viewport, '25 0 5'); - - self.vehicles_impulse = raptor_impulse; - - self.frame = 0; - - self.bomb1 = spawn(); - self.bomb2 = spawn(); - self.gun1 = spawn(); - self.gun2 = spawn(); - - setmodel(self.bomb1,"models/vehicles/clusterbomb_folded.md3"); - setmodel(self.bomb2,"models/vehicles/clusterbomb_folded.md3"); - setmodel(self.gun1, "models/vehicles/raptor_gun.dpm"); - setmodel(self.gun2, "models/vehicles/raptor_gun.dpm"); - setmodel(self.tur_head, "models/vehicles/raptor_body.dpm"); - - setattachment(self.bomb1, self, "bombmount_left"); - setattachment(self.bomb2, self, "bombmount_right"); - setattachment(self.tur_head, self,"root"); - - // FIXMODEL Guns mounts to angled bones - self.bomb1.angles = self.angles; - self.angles = '0 0 0'; - // This messes up gun-aim, so work arround it. - //setattachment(self.gun1, self, "gunmount_left"); - ofs = gettaginfo(self, gettagindex(self, "gunmount_left")); - ofs -= self.origin; - setattachment(self.gun1, self, ""); - setorigin(self.gun1, ofs); - - //setattachment(self.gun2, self, "gunmount_right"); - ofs = gettaginfo(self, gettagindex(self, "gunmount_right")); - ofs -= self.origin; - setattachment(self.gun2, self, ""); - setorigin(self.gun2, ofs); - - self.angles = self.bomb1.angles; - self.bomb1.angles = '0 0 0'; - - spinner = spawn(); - spinner.owner = self; - setmodel(spinner,"models/vehicles/spinner.dpm"); - setattachment(spinner, self, "engine_left"); - spinner.movetype = MOVETYPE_NOCLIP; - spinner.avelocity = '0 90 0'; - self.bomb1.gun1 = spinner; - - spinner = spawn(); - spinner.owner = self; - setmodel(spinner,"models/vehicles/spinner.dpm"); - setattachment(spinner, self, "engine_right"); - spinner.movetype = MOVETYPE_NOCLIP; - spinner.avelocity = '0 -90 0'; - self.bomb1.gun2 = spinner; - - // Sigh. - self.bomb1.think = raptor_rotor_anglefix; - self.bomb1.nextthink = time; - - self.mass = 1 ; - } - - - self.frame = 0; - self.vehicle_health = autocvar_g_vehicle_raptor_health; - self.vehicle_shield = autocvar_g_vehicle_raptor_shield; - self.movetype = MOVETYPE_TOSS; - self.solid = SOLID_SLIDEBOX; - self.vehicle_energy = 1; - - self.bomb1.gun1.avelocity_y = 90; - self.bomb1.gun2.avelocity_y = -90; - - setsize(self, RAPTOR_MIN, RAPTOR_MAX ); - self.delay = time; - - self.bouncefactor = autocvar_g_vehicle_raptor_bouncefactor; - self.bouncestop = autocvar_g_vehicle_raptor_bouncestop; - self.vehicle_impact = raptor_impact; - self.damageforcescale = 0.25; -} - -void spawnfunc_vehicle_raptor() -{ - if(!autocvar_g_vehicle_raptor) - { - remove(self); - return; - } - - self.vehicle_flags |= VHF_DMGSHAKE; - self.vehicle_flags |= VHF_DMGROLL; - - if(autocvar_g_vehicle_raptor_shield) - self.vehicle_flags |= VHF_HASSHIELD; - - if(autocvar_g_vehicle_raptor_shield_regen) - self.vehicle_flags |= VHF_SHIELDREGEN; - - if(autocvar_g_vehicle_raptor_health_regen) - self.vehicle_flags |= VHF_HEALTHREGEN; - - if(autocvar_g_vehicle_raptor_energy_regen) - self.vehicle_flags |= VHF_ENERGYREGEN; - - precache_model ("models/vehicles/raptor.dpm"); - precache_model ("models/vehicles/raptor_gun.dpm"); - precache_model ("models/vehicles/spinner.dpm"); - precache_model ("models/vehicles/raptor_cockpit.dpm"); - //precache_model ("models/vehicles/clusterbomb.md3"); - precache_model ("models/vehicles/clusterbomb_folded.md3"); - precache_model ("models/vehicles/raptor_body.dpm"); - - precache_sound ("vehicles/raptor_fly.wav"); - precache_sound ("vehicles/raptor_speed.wav"); - precache_sound ("vehicles/missile_alarm.wav"); - - if(!vehicle_initialize( - "Raptor", - "models/vehicles/raptor.dpm", - "", - "models/vehicles/raptor_cockpit.dpm", - "", "tag_hud", "tag_camera", - HUD_RAPTOR, - RAPTOR_MIN, RAPTOR_MAX, - FALSE, - raptor_spawn, autocvar_g_vehicle_raptor_respawntime, - raptor_frame, - raptor_enter, raptor_exit, - raptor_die, raptor_think, - FALSE, - autocvar_g_vehicle_raptor_health, - autocvar_g_vehicle_raptor_shield)) - { - remove(self); - return; - } - - -} -#endif // SVQC diff --git a/qcsrc/server/vehicles/spiderbot.qc b/qcsrc/server/vehicles/spiderbot.qc deleted file mode 100644 index c5eb546e8..000000000 --- a/qcsrc/server/vehicles/spiderbot.qc +++ /dev/null @@ -1,883 +0,0 @@ -const vector SPIDERBOT_MIN = '-75 -75 10'; -const vector SPIDERBOT_MAX = '75 75 125'; - -#ifdef SVQC -float autocvar_g_vehicle_spiderbot; - -float autocvar_g_vehicle_spiderbot_respawntime; - -float autocvar_g_vehicle_spiderbot_speed_stop; -float autocvar_g_vehicle_spiderbot_speed_strafe; -float autocvar_g_vehicle_spiderbot_speed_walk; -float autocvar_g_vehicle_spiderbot_turnspeed; -float autocvar_g_vehicle_spiderbot_turnspeed_strafe; -float autocvar_g_vehicle_spiderbot_movement_inertia; - -float autocvar_g_vehicle_spiderbot_springlength; -float autocvar_g_vehicle_spiderbot_springup; -float autocvar_g_vehicle_spiderbot_springblend; -float autocvar_g_vehicle_spiderbot_tiltlimit; - -float autocvar_g_vehicle_spiderbot_head_pitchlimit_down; -float autocvar_g_vehicle_spiderbot_head_pitchlimit_up; -float autocvar_g_vehicle_spiderbot_head_turnlimit; -float autocvar_g_vehicle_spiderbot_head_turnspeed; - -//float autocvar_g_vehicle_spiderbot_energy; -//float autocvar_g_vehicle_spiderbot_energy_regen; -//float autocvar_g_vehicle_spiderbot_energy_regen_pause; - -float autocvar_g_vehicle_spiderbot_health; -float autocvar_g_vehicle_spiderbot_health_regen; -float autocvar_g_vehicle_spiderbot_health_regen_pause; - -float autocvar_g_vehicle_spiderbot_shield; -float autocvar_g_vehicle_spiderbot_shield_regen; -float autocvar_g_vehicle_spiderbot_shield_regen_pause; - -float autocvar_g_vehicle_spiderbot_minigun_damage; -float autocvar_g_vehicle_spiderbot_minigun_refire; -float autocvar_g_vehicle_spiderbot_minigun_spread; -float autocvar_g_vehicle_spiderbot_minigun_ammo_cost; -float autocvar_g_vehicle_spiderbot_minigun_ammo_max; -float autocvar_g_vehicle_spiderbot_minigun_ammo_regen; -float autocvar_g_vehicle_spiderbot_minigun_ammo_regen_pause; -float autocvar_g_vehicle_spiderbot_minigun_force; -float autocvar_g_vehicle_spiderbot_minigun_solidpenetration; - -float autocvar_g_vehicle_spiderbot_rocket_damage; -float autocvar_g_vehicle_spiderbot_rocket_force; -float autocvar_g_vehicle_spiderbot_rocket_radius; -float autocvar_g_vehicle_spiderbot_rocket_speed; -float autocvar_g_vehicle_spiderbot_rocket_spread; -float autocvar_g_vehicle_spiderbot_rocket_refire; -float autocvar_g_vehicle_spiderbot_rocket_refire2; -float autocvar_g_vehicle_spiderbot_rocket_reload; -float autocvar_g_vehicle_spiderbot_rocket_health; -float autocvar_g_vehicle_spiderbot_rocket_noise; -float autocvar_g_vehicle_spiderbot_rocket_turnrate; -float autocvar_g_vehicle_spiderbot_rocket_lifetime; - -float autocvar_g_vehicle_spiderbot_blowup_radius; -float autocvar_g_vehicle_spiderbot_blowup_coredamage; -float autocvar_g_vehicle_spiderbot_blowup_edgedamage; -float autocvar_g_vehicle_spiderbot_blowup_forceintensity; - -vector autocvar_g_vehicle_spiderbot_bouncepain; - - -void spiderbot_exit(float eject); -void spiderbot_enter(); -void spiderbot_spawn(float); -#define SBRM_FIRST 0 -#define SBRM_VOLLY 0 -#define SBRM_GUIDE 1 -#define SBRM_ARTILLERY 2 -#define SBRM_LAST 2 - -void spiderbot_rocket_artillery() -{ - self.nextthink = time; - UpdateCSQCProjectile(self); -} - -void spiderbot_rocket_unguided() -{ - vector newdir, olddir; - - self.nextthink = time; - - olddir = normalize(self.velocity); - newdir = normalize(self.pos1 - self.origin) + randomvec() * autocvar_g_vehicle_spiderbot_rocket_noise; - self.velocity = normalize(olddir + newdir * autocvar_g_vehicle_spiderbot_rocket_turnrate) * autocvar_g_vehicle_spiderbot_rocket_speed; - - UpdateCSQCProjectile(self); - - if (self.owner.deadflag != DEAD_NO || self.cnt < time || vlen(self.pos1 - self.origin) < 16) - self.use(); -} - -void spiderbot_rocket_guided() -{ - vector newdir, olddir; - - self.nextthink = time; - - if (!self.realowner.vehicle) - self.think = spiderbot_rocket_unguided; - - crosshair_trace(self.realowner); - olddir = normalize(self.velocity); - newdir = normalize(trace_endpos - self.origin) + randomvec() * autocvar_g_vehicle_spiderbot_rocket_noise; - self.velocity = normalize(olddir + newdir * autocvar_g_vehicle_spiderbot_rocket_turnrate) * autocvar_g_vehicle_spiderbot_rocket_speed; - - UpdateCSQCProjectile(self); - - if (self.owner.deadflag != DEAD_NO || self.cnt < time) - self.use(); -} - -void spiderbot_guide_release() -{ - entity rkt; - rkt = findchainentity(realowner, self.owner); - if (!rkt) - return; - - crosshair_trace(self.owner); - while(rkt) - { - if(rkt.think == spiderbot_rocket_guided) - { - rkt.pos1 = trace_endpos; - rkt.think = spiderbot_rocket_unguided; - } - rkt = rkt.chain; - } -} - -float spiberbot_calcartillery_flighttime; -vector spiberbot_calcartillery(vector org, vector tgt, float ht) -{ - float grav, sdist, zdist, vs, vz, jumpheight; - vector sdir; - - grav = autocvar_sv_gravity; - zdist = tgt_z - org_z; - sdist = vlen(tgt - org - zdist * '0 0 1'); - sdir = normalize(tgt - org - zdist * '0 0 1'); - - // how high do we need to go? - jumpheight = fabs(ht); - if(zdist > 0) - jumpheight = jumpheight + zdist; - - // push so high... - vz = sqrt(2 * grav * jumpheight); // NOTE: sqrt(positive)! - - // we start with downwards velocity only if it's a downjump and the jump apex should be outside the jump! - if(ht < 0) - if(zdist < 0) - vz = -vz; - - vector solution; - solution = solve_quadratic(0.5 * grav, -vz, zdist); // equation "z(ti) = zdist" - // ALWAYS solvable because jumpheight >= zdist - if(!solution_z) - solution_y = solution_x; // just in case it is not solvable due to roundoff errors, assume two equal solutions at their center (this is mainly for the usual case with ht == 0) - if(zdist == 0) - solution_x = solution_y; // solution_x is 0 in this case, so don't use it, but rather use solution_y (which will be sqrt(0.5 * jumpheight / grav), actually) - - if(zdist < 0) - { - // down-jump - if(ht < 0) - { - // almost straight line type - // jump apex is before the jump - // we must take the larger one - spiberbot_calcartillery_flighttime = solution_y; - } - else - { - // regular jump - // jump apex is during the jump - // we must take the larger one too - spiberbot_calcartillery_flighttime = solution_y; - } - } - else - { - // up-jump - if(ht < 0) - { - // almost straight line type - // jump apex is after the jump - // we must take the smaller one - spiberbot_calcartillery_flighttime = solution_x; - } - else - { - // regular jump - // jump apex is during the jump - // we must take the larger one - spiberbot_calcartillery_flighttime = solution_y; - } - } - vs = sdist / spiberbot_calcartillery_flighttime; - - // finally calculate the velocity - return sdir * vs + '0 0 1' * vz; -} - -void spiderbot_rocket_do() -{ - - vector v; - entity rocket = world; - - if (self.wait != -10) - { - if (self.owner.BUTTON_ATCK2 && self.vehicle_weapon2mode == SBRM_GUIDE) - { - if (self.wait == 1) - if (self.tur_head.frame == 9 || self.tur_head.frame == 1) - { - if(self.gun2.cnt < time && self.tur_head.frame == 9) - self.tur_head.frame = 1; - - return; - } - self.wait = 1; - } - else - { - if(self.wait) - spiderbot_guide_release(); - - self.wait = 0; - } - } - - if(self.gun2.cnt > time) - return; - - if (self.tur_head.frame >= 9) - { - self.tur_head.frame = 1; - self.wait = 0; - } - - if (self.wait != -10) - if (!self.owner.BUTTON_ATCK2) - return; - - - v = gettaginfo(self.tur_head,gettagindex(self.tur_head,"tag_fire")); - - switch(self.vehicle_weapon2mode) - { - case SBRM_VOLLY: - rocket = vehicles_projectile("spiderbot_rocket_launch", "weapons/rocket_fire.wav", - v, normalize(randomvec() * autocvar_g_vehicle_spiderbot_rocket_spread + v_forward) * autocvar_g_vehicle_spiderbot_rocket_speed, - autocvar_g_vehicle_spiderbot_rocket_damage, autocvar_g_vehicle_spiderbot_rocket_radius, autocvar_g_vehicle_spiderbot_rocket_force, 1, - DEATH_VH_SPID_ROCKET, PROJECTILE_SPIDERROCKET, autocvar_g_vehicle_spiderbot_rocket_health, FALSE, TRUE, self.owner); - crosshair_trace(self.owner); - float _dist = (random() * autocvar_g_vehicle_spiderbot_rocket_radius) + vlen(v - trace_endpos); - _dist -= (random() * autocvar_g_vehicle_spiderbot_rocket_radius) ; - rocket.nextthink = time + (_dist / autocvar_g_vehicle_spiderbot_rocket_speed); - rocket.think = vehicles_projectile_explode; - - if(self.owner.BUTTON_ATCK2 && self.tur_head.frame == 1) - self.wait = -10; - break; - case SBRM_GUIDE: - rocket = vehicles_projectile("spiderbot_rocket_launch", "weapons/rocket_fire.wav", - v, normalize(v_forward) * autocvar_g_vehicle_spiderbot_rocket_speed, - autocvar_g_vehicle_spiderbot_rocket_damage, autocvar_g_vehicle_spiderbot_rocket_radius, autocvar_g_vehicle_spiderbot_rocket_force, 1, - DEATH_VH_SPID_ROCKET, PROJECTILE_SPIDERROCKET, autocvar_g_vehicle_spiderbot_rocket_health, FALSE, FALSE, self.owner); - crosshair_trace(self.owner); - rocket.pos1 = trace_endpos; - rocket.nextthink = time; - rocket.think = spiderbot_rocket_guided; - - - break; - case SBRM_ARTILLERY: - rocket = vehicles_projectile("spiderbot_rocket_launch", "weapons/rocket_fire.wav", - v, normalize(v_forward) * autocvar_g_vehicle_spiderbot_rocket_speed, - autocvar_g_vehicle_spiderbot_rocket_damage, autocvar_g_vehicle_spiderbot_rocket_radius, autocvar_g_vehicle_spiderbot_rocket_force, 1, - DEATH_VH_SPID_ROCKET, PROJECTILE_SPIDERROCKET, autocvar_g_vehicle_spiderbot_rocket_health, FALSE, TRUE, self.owner); - - crosshair_trace(self.owner); - - rocket.pos1 = trace_endpos + randomvec() * (0.75 * autocvar_g_vehicle_spiderbot_rocket_radius); - rocket.pos1_z = trace_endpos_z; - - traceline(v, v + '0 0 1' * MAX_SHOT_DISTANCE, MOVE_WORLDONLY, self); - float h1 = 0.75 * vlen(v - trace_endpos); - - //v = trace_endpos; - traceline(v , rocket.pos1 + '0 0 1' * MAX_SHOT_DISTANCE, MOVE_WORLDONLY, self); - float h2 = 0.75 * vlen(rocket.pos1 - v); - - rocket.velocity = spiberbot_calcartillery(v, rocket.pos1, ((h1 < h2) ? h1 : h2)); - rocket.movetype = MOVETYPE_TOSS; - rocket.gravity = 1; - //rocket.think = spiderbot_rocket_artillery; - break; - } - rocket.classname = "spiderbot_rocket"; - - rocket.cnt = time + autocvar_g_vehicle_spiderbot_rocket_lifetime; - - self.tur_head.frame += 1; - if (self.tur_head.frame == 9) - self.attack_finished_single = autocvar_g_vehicle_spiderbot_rocket_reload; - else - self.attack_finished_single = ((self.vehicle_weapon2mode == SBRM_VOLLY) ? autocvar_g_vehicle_spiderbot_rocket_refire2 : autocvar_g_vehicle_spiderbot_rocket_refire); - - self.gun2.cnt = time + self.attack_finished_single; -} - -float spiderbot_aiframe() -{ - return FALSE; -} - -float spiderbot_frame() -{ - vector ad, vf; - entity player, spider; - float ftmp; - - if(intermission_running) - return 1; - - player = self; - spider = self.vehicle; - self = spider; - - vehicles_painframe(); - - player.BUTTON_ZOOM = 0; - player.BUTTON_CROUCH = 0; - player.switchweapon = 0; - - -#if 1 // 0 to enable per-gun impact aux crosshairs - // Avarage gun impact point's -> aux cross - ad = gettaginfo(spider.tur_head, gettagindex(spider.tur_head, "tag_hardpoint01")); - vf = v_forward; - ad += gettaginfo(spider.tur_head, gettagindex(spider.tur_head, "tag_hardpoint02")); - vf += v_forward; - ad = ad * 0.5; - v_forward = vf * 0.5; - traceline(ad, ad + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, spider); - UpdateAuxiliaryXhair(player, trace_endpos, ('1 0 0' * player.vehicle_reload1) + ('0 1 0' * (1 - player.vehicle_reload1)), 0); -#else - ad = gettaginfo(spider.gun1, gettagindex(spider.gun1, "barrels")); - traceline(ad, ad + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, spider); - UpdateAuxiliaryXhair(player, trace_endpos, ('1 0 0' * player.vehicle_reload1) + ('0 1 0' * (1 - player.vehicle_reload1)), 0); - vf = ad; - ad = gettaginfo(spider.gun2, gettagindex(spider.gun2, "barrels")); - traceline(ad, ad + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, spider); - UpdateAuxiliaryXhair(player, trace_endpos, ('1 0 0' * player.vehicle_reload1) + ('0 1 0' * (1 - player.vehicle_reload1)), 1); - ad = 0.5 * (ad + vf); -#endif - - crosshair_trace(player); - ad = vectoangles(normalize(trace_endpos - ad)); - ad = AnglesTransform_ToAngles(AnglesTransform_LeftDivide(AnglesTransform_FromAngles(spider.angles), AnglesTransform_FromAngles(ad))) - spider.tur_head.angles; - ad = AnglesTransform_Normalize(ad, TRUE); - //UpdateAuxiliaryXhair(player, trace_endpos, ('1 0 0' * player.vehicle_reload2) + ('0 1 0' * (1 - player.vehicle_reload2)), 2); - - // Rotate head - ftmp = autocvar_g_vehicle_spiderbot_head_turnspeed * sys_frametime; - ad_y = bound(-ftmp, ad_y, ftmp); - spider.tur_head.angles_y = bound(autocvar_g_vehicle_spiderbot_head_turnlimit * -1, spider.tur_head.angles_y + ad_y, autocvar_g_vehicle_spiderbot_head_turnlimit); - - // Pitch head - ad_x = bound(ftmp * -1, ad_x, ftmp); - spider.tur_head.angles_x = bound(autocvar_g_vehicle_spiderbot_head_pitchlimit_down, spider.tur_head.angles_x + ad_x, autocvar_g_vehicle_spiderbot_head_pitchlimit_up); - - - //fixedmakevectors(spider.angles); - makevectors(spider.angles + '-2 0 0' * spider.angles_x); - - movelib_groundalign4point(autocvar_g_vehicle_spiderbot_springlength, autocvar_g_vehicle_spiderbot_springup, autocvar_g_vehicle_spiderbot_springblend, autocvar_g_vehicle_spiderbot_tiltlimit); - - if(spider.flags & FL_ONGROUND) - { - if(spider.frame == 4 && self.tur_head.wait != 0) - { - sound (self, CH_TRIGGER_SINGLE, "vehicles/spiderbot_land.wav", VOL_VEHICLEENGINE, ATTEN_NORM); - spider.frame = 5; - } - - if(player.BUTTON_JUMP && self.tur_head.wait < time) - { - sound (self, CH_TRIGGER_SINGLE, "vehicles/spiderbot_jump.wav", VOL_VEHICLEENGINE, ATTEN_NORM); - //dprint("spiderbot_jump:", ftos(soundlength("vehicles/spiderbot_jump.wav")), "\n"); - self.delay = 0; - - self.tur_head.wait = time + 2; - player.BUTTON_JUMP = 0; - spider.velocity = v_forward * 700 + v_up * 600; - spider.frame = 4; - } - else - { - if(vlen(player.movement) == 0) - { - if(self.sound_nexttime < time || self.delay != 3) - { - self.delay = 3; - self.sound_nexttime = time + 6.486500; //soundlength("vehicles/spiderbot_idle.wav"); - //dprint("spiderbot_idle:", ftos(soundlength("vehicles/spiderbot_idle.wav")), "\n"); - sound (self, CH_TRIGGER_SINGLE, "vehicles/spiderbot_idle.wav", VOL_VEHICLEENGINE, ATTEN_NORM); - } - movelib_beak_simple(autocvar_g_vehicle_spiderbot_speed_stop); - spider.frame = 5; - } - else - { - // Turn Body - if(player.movement_x == 0 && player.movement_y != 0) - ftmp = autocvar_g_vehicle_spiderbot_turnspeed_strafe * sys_frametime; - else - ftmp = autocvar_g_vehicle_spiderbot_turnspeed * sys_frametime; - - ftmp = bound(-ftmp, spider.tur_head.angles_y, ftmp); - spider.angles_y = anglemods(spider.angles_y + ftmp); - spider.tur_head.angles_y -= ftmp; - - if(player.movement_x != 0) - { - if(player.movement_x > 0) - { - player.movement_x = 1; - spider.frame = 0; - } - else if(player.movement_x < 0) - { - player.movement_x = -1; - spider.frame = 1; - } - player.movement_y = 0; - movelib_move_simple(normalize(v_forward * player.movement_x),autocvar_g_vehicle_spiderbot_speed_walk,autocvar_g_vehicle_spiderbot_movement_inertia); - - if(self.sound_nexttime < time || self.delay != 1) - { - self.delay = 1; - self.sound_nexttime = time + 6.486500; //soundlength("vehicles/spiderbot_walk.wav"); - sound (self, CH_TRIGGER_SINGLE, "vehicles/spiderbot_walk.wav", VOL_VEHICLEENGINE, ATTEN_NORM); - //dprint("spiderbot_walk:", ftos(soundlength("vehicles/spiderbot_walk.wav")), "\n"); - } - } - else if(player.movement_y != 0) - { - if(player.movement_y < 0) - { - player.movement_y = -1; - spider.frame = 2; - } - else if(player.movement_y > 0) - { - player.movement_y = 1; - spider.frame = 3; - } - movelib_move_simple(normalize(v_right * player.movement_y),autocvar_g_vehicle_spiderbot_speed_strafe,autocvar_g_vehicle_spiderbot_movement_inertia); - if(self.sound_nexttime < time || self.delay != 2) - { - self.delay = 2; - self.sound_nexttime = time + 6.486500; //soundlength("vehicles/spiderbot_strafe.wav"); - sound (self, CH_TRIGGER_SINGLE, "vehicles/spiderbot_strafe.wav", VOL_VEHICLEENGINE, ATTEN_NORM); - //dprint("spiderbot_strafe:", ftos(soundlength("vehicles/spiderbot_strafe.wav")), "\n"); - } - } - } - } - } - - self.angles_x = bound(-autocvar_g_vehicle_spiderbot_tiltlimit, self.angles_x, autocvar_g_vehicle_spiderbot_tiltlimit); - self.angles_z = bound(-autocvar_g_vehicle_spiderbot_tiltlimit, self.angles_z, autocvar_g_vehicle_spiderbot_tiltlimit); - - if(player.BUTTON_ATCK) - { - spider.cnt = time; - if(spider.vehicle_ammo1 >= autocvar_g_vehicle_spiderbot_minigun_ammo_cost && spider.tur_head.attack_finished_single <= time) - { - entity gun; - vector v; - spider.misc_bulletcounter += 1; - - self = player; - - (spider.misc_bulletcounter % 2) ? gun = spider.gun1 : gun = spider.gun2; - v = gettaginfo(gun, gettagindex(gun, "barrels")); - v_forward = normalize(v_forward); - v += v_forward * 50; - - fireBullet(v, v_forward, autocvar_g_vehicle_spiderbot_minigun_spread, autocvar_g_vehicle_spiderbot_minigun_solidpenetration, - autocvar_g_vehicle_spiderbot_minigun_damage, autocvar_g_vehicle_spiderbot_minigun_force, DEATH_VH_SPID_MINIGUN, 0); - -// fireBullet (v, v_forward, autocvar_g_vehicle_spiderbot_minigun_spread, autocvar_g_vehicle_spiderbot_minigun_damage, -// autocvar_g_vehicle_spiderbot_minigun_spread, DEATH_VH_SPID_MINIGUN, 0); - - sound (gun, CH_WEAPON_A, "weapons/uzi_fire.wav", VOL_BASE, ATTEN_NORM); - //trailparticles(self, particleeffectnum("spiderbot_minigun_trail"), v, trace_endpos); - pointparticles(particleeffectnum("spiderbot_minigun_muzzleflash"), v, v_forward * 2500, 1); - - self = spider; - - spider.vehicle_ammo1 -= autocvar_g_vehicle_spiderbot_minigun_ammo_cost; - spider.tur_head.attack_finished_single = time + autocvar_g_vehicle_spiderbot_minigun_refire; - player.vehicle_ammo1 = (spider.vehicle_ammo1 / autocvar_g_vehicle_spiderbot_minigun_ammo_max) * 100; - spider.gun1.angles_z += 45; - spider.gun2.angles_z -= 45; - if(spider.gun1.angles_z >= 360) - { - spider.gun1.angles_z = 0; - spider.gun2.angles_z = 0; - } - } - } - else - vehicles_regen(spider.cnt, vehicle_ammo1, autocvar_g_vehicle_spiderbot_minigun_ammo_max, - autocvar_g_vehicle_spiderbot_minigun_ammo_regen_pause, - autocvar_g_vehicle_spiderbot_minigun_ammo_regen, frametime, FALSE); - - - spiderbot_rocket_do(); - - if(self.vehicle_flags & VHF_SHIELDREGEN) - vehicles_regen(spider.dmg_time, vehicle_shield, autocvar_g_vehicle_spiderbot_shield, autocvar_g_vehicle_spiderbot_shield_regen_pause, autocvar_g_vehicle_spiderbot_shield_regen, frametime, TRUE); - - if(self.vehicle_flags & VHF_HEALTHREGEN) - vehicles_regen(spider.dmg_time, vehicle_health, autocvar_g_vehicle_spiderbot_health, autocvar_g_vehicle_spiderbot_health_regen_pause, autocvar_g_vehicle_spiderbot_health_regen, frametime, FALSE); - - player.BUTTON_ATCK = player.BUTTON_ATCK2 = 0; - player.vehicle_ammo2 = spider.tur_head.frame; - - if(spider.gun2.cnt <= time) - player.vehicle_reload2 = 100; - else - player.vehicle_reload2 = 100 - ((spider.gun2.cnt - time) / spider.attack_finished_single) * 100; - - setorigin(player, spider.origin + '0 0 1' * SPIDERBOT_MAX_z); - player.velocity = spider.velocity; - - VEHICLE_UPDATE_PLAYER(player, health, spiderbot); - - if(self.vehicle_flags & VHF_HASSHIELD) - VEHICLE_UPDATE_PLAYER(player, shield, spiderbot); - - self = player; - return 1; -} -void spiderbot_think() -{ - if(self.flags & FL_ONGROUND) - movelib_beak_simple(autocvar_g_vehicle_spiderbot_speed_stop); - - self.nextthink = time; -} - -void spiderbot_enter() -{ - self.vehicle_weapon2mode = SBRM_GUIDE; - self.movetype = MOVETYPE_WALK; - CSQCVehicleSetup(self.owner, 0); - self.owner.vehicle_health = (self.vehicle_health / autocvar_g_vehicle_spiderbot_health) * 100; - self.owner.vehicle_shield = (self.vehicle_shield / autocvar_g_vehicle_spiderbot_shield) * 100; - - if(self.owner.flagcarried) - { - setattachment(self.owner.flagcarried, self.tur_head, ""); - setorigin(self.owner.flagcarried, '-20 0 120'); - } -} - -void spiderbot_exit(float eject) -{ - entity e; - vector spot; - - e = findchain(classname,"spiderbot_rocket"); - while(e) - { - if(e.owner == self.owner) - { - e.realowner = self.owner; - e.owner = world; - } - e = e.chain; - } - - //self.velocity = '0 0 0'; - self.think = spiderbot_think; - self.nextthink = time; - self.frame = 5; - self.movetype = MOVETYPE_WALK; - - if (!self.owner) - return; - - makevectors(self.angles); - if(eject) - { - spot = self.origin + v_forward * 100 + '0 0 64'; - spot = vehicles_findgoodexit(spot); - setorigin(self.owner , spot); - self.owner.velocity = (v_up + v_forward * 0.25) * 750; - self.owner.oldvelocity = self.owner.velocity; - } - else - { - if(vlen(self.velocity) > autocvar_g_vehicle_spiderbot_speed_strafe) - { - self.owner.velocity = normalize(self.velocity) * vlen(self.velocity); - self.owner.velocity_z += 200; - spot = self.origin + v_forward * 128 + '0 0 64'; - spot = vehicles_findgoodexit(spot); - } - else - { - self.owner.velocity = self.velocity * 0.5; - self.owner.velocity_z += 10; - spot = self.origin + v_forward * 256 + '0 0 64'; - spot = vehicles_findgoodexit(spot); - } - self.owner.oldvelocity = self.owner.velocity; - setorigin(self.owner , spot); - } - - antilag_clear(self.owner); - self.owner = world; -} - -void spider_impact() -{ - if(autocvar_g_vehicle_spiderbot_bouncepain_x) - vehicles_impact(autocvar_g_vehicle_spiderbot_bouncepain_x, autocvar_g_vehicle_spiderbot_bouncepain_y, autocvar_g_vehicle_spiderbot_bouncepain_z); -} - -void spiderbot_headfade() -{ - self.think = spiderbot_headfade; - self.nextthink = self.fade_time; - self.alpha = 1 - (time - self.fade_time) * self.fade_rate; - - if(self.cnt < time || self.alpha < 0.1) - { - if(self.alpha > 0.1) - { - sound (self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); - pointparticles(particleeffectnum("explosion_big"), self.origin + '0 0 100', '0 0 0', 1); - } - remove(self); - } -} - -void spiderbot_blowup() -{ - if(self.cnt > time) - { - if(random() < 0.1) - { - sound (self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); - pointparticles(particleeffectnum("explosion_small"), randomvec() * 80 + (self.origin + '0 0 100'), '0 0 0', 1); - } - self.nextthink = time + 0.1; - return; - } - - entity h, g1, g2, b; - b = spawn(); - h = spawn(); - g1 = spawn(); - g2 = spawn(); - - setmodel(b, "models/vehicles/spiderbot.dpm"); - setmodel(h, "models/vehicles/spiderbot_top.dpm"); - setmodel(g1, "models/vehicles/spiderbot_barrels.dpm"); - setmodel(g2, "models/vehicles/spiderbot_barrels.dpm"); - - setorigin(b, self.origin); - b.frame = 11; - b.angles = self.angles; - setsize(b, self.mins, self.maxs); - - setorigin(h, gettaginfo(self, gettagindex(self, "tag_head"))); - h.movetype = MOVETYPE_BOUNCE; - h.solid = SOLID_BBOX; - h.velocity = v_up * (500 + random() * 500) + randomvec() * 128; - h.modelflags = MF_ROCKET; - h.effects = EF_FLAME | EF_LOWPRECISION; - h.avelocity = randomvec() * 360; - - h.alpha = 1; - h.cnt = time + (3.5 * random()); - h.fade_rate = 1 / min(autocvar_g_vehicle_spiderbot_respawntime, 10); - h.fade_time = time; - h.think = spiderbot_headfade; - h.nextthink = time; - - setorigin(g1, gettaginfo(self.tur_head, gettagindex(self.tur_head, "tag_hardpoint01"))); - g1.movetype = MOVETYPE_TOSS; - g1.solid = SOLID_CORPSE; - g1.velocity = v_forward * 700 + (randomvec() * 32); - g1.avelocity = randomvec() * 180; - - setorigin(g2, gettaginfo(self.tur_head, gettagindex(self.tur_head, "tag_hardpoint02"))); - g2.movetype = MOVETYPE_TOSS; - g2.solid = SOLID_CORPSE; - g2.velocity = v_forward * 700 + (randomvec() * 32); - g2.avelocity = randomvec() * 180; - - h.colormod = b.colormod = g1.colormod = g2.colormod = '-2 -2 -2'; - - SUB_SetFade(b, time + 5, min(autocvar_g_vehicle_spiderbot_respawntime, 1)); - //SUB_SetFade(h, time, min(autocvar_g_vehicle_spiderbot_respawntime, 10)); - SUB_SetFade(g1, time, min(autocvar_g_vehicle_spiderbot_respawntime, 10)); - SUB_SetFade(g2, time, min(autocvar_g_vehicle_spiderbot_respawntime, 10)); - - RadiusDamage(self, self.enemy, autocvar_g_vehicle_spiderbot_blowup_coredamage, - autocvar_g_vehicle_spiderbot_blowup_edgedamage, - autocvar_g_vehicle_spiderbot_blowup_radius, world, world, - autocvar_g_vehicle_spiderbot_blowup_forceintensity, DEATH_VH_SPID_DEATH, world); - - self.alpha = self.tur_head.alpha = self.gun1.alpha = self.gun2.alpha = -1; - self.movetype = MOVETYPE_NONE; - self.deadflag = DEAD_DEAD; - self.solid = SOLID_NOT; - self.tur_head.effects &= ~EF_FLAME; - self.vehicle_hudmodel.viewmodelforclient = self; - setorigin(self, self.pos1); -} - -void spiderbot_die() -{ - self.health = 0; - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - self.touch = func_null; - self.cnt = 3.4 + time + random() * 2; - self.think = spiderbot_blowup; - self.nextthink = time; - self.deadflag = DEAD_DYING; - self.frame = 5; - self.tur_head.effects |= EF_FLAME; - self.colormod = self.tur_head.colormod = '-1 -1 -1'; - self.frame = 10; - self.movetype = MOVETYPE_TOSS; -} - -float spiderbot_impulse(float _imp) -{ - switch(_imp) - { - case 10: - case 15: - case 18: - self.vehicle.vehicle_weapon2mode += 1; - if(self.vehicle.vehicle_weapon2mode > SBRM_LAST) - self.vehicle.vehicle_weapon2mode = SBRM_FIRST; - - //centerprint(self, strcat("Rocket mode is ", ftos(self.vehicle.vehicle_weapon2mode))); - CSQCVehicleSetup(self, 0); - return TRUE; - case 12: - case 16: - case 19: - self.vehicle.vehicle_weapon2mode -= 1; - if(self.vehicle.vehicle_weapon2mode < SBRM_FIRST) - self.vehicle.vehicle_weapon2mode = SBRM_LAST; - - //centerprint(self, strcat("Rocket mode is ", ftos(self.vehicle.vehicle_weapon2mode))); - CSQCVehicleSetup(self, 0); - return TRUE; - - /* - case 17: // toss gun, could be used to exit? - break; - case 20: // Manual minigun reload? - break; - */ - } - return FALSE; -} - -void spiderbot_spawn(float _f) -{ - if(!self.gun1) - { - self.vehicles_impulse = spiderbot_impulse; - self.gun1 = spawn(); - self.gun2 = spawn(); - setmodel(self.gun1, "models/vehicles/spiderbot_barrels.dpm"); - setmodel(self.gun2, "models/vehicles/spiderbot_barrels.dpm"); - setattachment(self.gun1, self.tur_head, "tag_hardpoint01"); - setattachment(self.gun2, self.tur_head, "tag_hardpoint02"); - self.gravity = 2; - self.mass = 5000; - } - - self.frame = 5; - self.tur_head.frame = 1; - self.think = spiderbot_think; - self.nextthink = time; - self.vehicle_health = autocvar_g_vehicle_spiderbot_health; - self.vehicle_shield = autocvar_g_vehicle_spiderbot_shield; - self.movetype = MOVETYPE_WALK; - self.solid = SOLID_SLIDEBOX; - self.alpha = self.tur_head.alpha = self.gun1.alpha = self.gun2.alpha = 1; - self.tur_head.angles = '0 0 0'; - - setorigin(self, self.pos1 + '0 0 128'); - self.angles = self.pos2; - self.vehicle_impact = spider_impact; - self.damageforcescale = 0.03; -} - -void spawnfunc_vehicle_spiderbot() -{ - if(!autocvar_g_vehicle_spiderbot) - { - remove(self); - return; - } - - self.vehicle_flags |= VHF_DMGSHAKE; - //self.vehicle_flags |= VHF_DMGROLL; - //self.vehicle_flags |= VHF_DMGHEADROLL; - - precache_model ( "models/vhshield.md3"); - precache_model ( "models/vehicles/spiderbot.dpm"); - precache_model ( "models/vehicles/spiderbot_top.dpm"); - precache_model ( "models/vehicles/spiderbot_barrels.dpm"); - precache_model ( "models/vehicles/spiderbot_cockpit.dpm"); - precache_model ( "models/uziflash.md3"); - - precache_sound ( "weapons/uzi_fire.wav" ); - precache_sound ( "weapons/rocket_impact.wav"); - - precache_sound ( "vehicles/spiderbot_die.wav"); - precache_sound ( "vehicles/spiderbot_idle.wav"); - precache_sound ( "vehicles/spiderbot_jump.wav"); - precache_sound ( "vehicles/spiderbot_strafe.wav"); - precache_sound ( "vehicles/spiderbot_walk.wav"); - precache_sound ( "vehicles/spiderbot_land.wav"); - - if(autocvar_g_vehicle_spiderbot_shield) - self.vehicle_flags |= VHF_HASSHIELD; - - if(autocvar_g_vehicle_spiderbot_shield_regen) - self.vehicle_flags |= VHF_SHIELDREGEN; - - if(autocvar_g_vehicle_spiderbot_health_regen) - self.vehicle_flags |= VHF_HEALTHREGEN; - - if(!vehicle_initialize( - "Spiderbot", - "models/vehicles/spiderbot.dpm", - "models/vehicles/spiderbot_top.dpm", - "models/vehicles/spiderbot_cockpit.dpm", - "tag_head", "tag_hud", "", - HUD_SPIDERBOT, - SPIDERBOT_MIN, SPIDERBOT_MAX, - FALSE, - spiderbot_spawn, autocvar_g_vehicle_spiderbot_respawntime, - spiderbot_frame, - spiderbot_enter, spiderbot_exit, - spiderbot_die, spiderbot_think, - FALSE, - autocvar_g_vehicle_spiderbot_health, - autocvar_g_vehicle_spiderbot_shield)) - { - remove(self); - return; - } -} -#endif // SVQC diff --git a/qcsrc/server/vehicles/vehicles.qc b/qcsrc/server/vehicles/vehicles.qc deleted file mode 100644 index 785d39516..000000000 --- a/qcsrc/server/vehicles/vehicles.qc +++ /dev/null @@ -1,1446 +0,0 @@ -float autocvar_g_vehicles_crush_dmg; -float autocvar_g_vehicles_crush_force; -float autocvar_g_vehicles_delayspawn; -float autocvar_g_vehicles_delayspawn_jitter; - -var float autocvar_g_vehicles_vortex_damagerate = 0.5; -var float autocvar_g_vehicles_machinegun_damagerate = 0.5; -var float autocvar_g_vehicles_rifle_damagerate = 0.75; -var float autocvar_g_vehicles_vaporizer_damagerate = 0.001; -var float autocvar_g_vehicles_tag_damagerate = 5; - -float autocvar_g_vehicles; - -void vehicles_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force); -void vehicles_return(); -void vehicles_enter(); -void vehicles_touch(); -void vehicles_reset_colors(); -void vehicles_clearreturn(); -void vehicles_setreturn(); - - -/** AuxiliaryXhair* - Send additional points of interest to be drawn, to vehicle owner -**/ -const float MAX_AXH = 4; -.entity AuxiliaryXhair[MAX_AXH]; - -float SendAuxiliaryXhair(entity to, float sf) -{ - - WriteByte(MSG_ENTITY, ENT_CLIENT_AUXILIARYXHAIR); - - WriteByte(MSG_ENTITY, self.cnt); - - WriteCoord(MSG_ENTITY, self.origin_x); - WriteCoord(MSG_ENTITY, self.origin_y); - WriteCoord(MSG_ENTITY, self.origin_z); - - WriteByte(MSG_ENTITY, rint(self.colormod_x * 255)); - WriteByte(MSG_ENTITY, rint(self.colormod_y * 255)); - WriteByte(MSG_ENTITY, rint(self.colormod_z * 255)); - - return TRUE; -} - -void UpdateAuxiliaryXhair(entity own, vector loc, vector clr, float axh_id) -{ - if (!IS_REAL_CLIENT(own)) - return; - - entity axh; - - axh_id = bound(0, axh_id, MAX_AXH); - axh = own.(AuxiliaryXhair[axh_id]); - - if(axh == world || wasfreed(axh)) // MADNESS? THIS IS QQQQCCCCCCCCC (wasfreed, why do you exsist?) - { - axh = spawn(); - axh.cnt = axh_id; - axh.drawonlytoclient = own; - axh.owner = own; - Net_LinkEntity(axh, FALSE, 0, SendAuxiliaryXhair); - } - - setorigin(axh, loc); - axh.colormod = clr; - axh.SendFlags = 0x01; - own.(AuxiliaryXhair[axh_id]) = axh; -} - -/* -// SVC_TEMPENTITY based, horrible with even 50 ping. hm. -// WriteByte(MSG_ONE, SVC_TEMPENTITY) uses reliable messagess, never use for thinsg that need continous updates. -void SendAuxiliaryXhair2(entity own, vector loc, vector clr, float axh_id) -{ - msgexntity = own; - - WriteByte(MSG_ONE, SVC_TEMPENTITY); - WriteByte(MSG_ONE, TE_CSQC_AUXILIARYXHAIR); - - WriteByte(MSG_ONE, axh_id); - - WriteCoord(MSG_ONE, loc_x); - WriteCoord(MSG_ONE, loc_y); - WriteCoord(MSG_ONE, loc_z); - - WriteByte(MSG_ONE, rint(clr_x * 255)); - WriteByte(MSG_ONE, rint(clr_y * 255)); - WriteByte(MSG_ONE, rint(clr_z * 255)); - -} -*/ -// End AuxiliaryXhair - -/** - Notifies the client that he enterd a vehicle, and sends - realavent data. - - only sends vehicle_id atm (wich is a HUD_* constant, ex. HUD_SPIDERBOT) -**/ -void CSQCVehicleSetup(entity own, float vehicle_id) -{ - if (!IS_REAL_CLIENT(own)) - return; - - msg_entity = own; - - WriteByte(MSG_ONE, SVC_TEMPENTITY); - WriteByte(MSG_ONE, TE_CSQC_VEHICLESETUP); - if(vehicle_id != 0) - WriteByte(MSG_ONE, vehicle_id); - else - WriteByte(MSG_ONE, 1 + own.vehicle.vehicle_weapon2mode + HUD_VEHICLE_LAST); -} - -/** vehicles_locktarget - - Generic target locking. - - Figure out if what target is "locked" (if any), for missile tracking as such. - - after calling, "if(self.lock_target != world && self.lock_strength == 1)" mean - you have a locked in target. - - Exspects a crosshair_trace() or equivalent to be - dont before calling. - -**/ -.entity lock_target; -.float lock_strength; -.float lock_time; -.float lock_soundtime; -const float DAMAGE_TARGETDRONE = 10; - -vector targetdrone_getnewspot() -{ - - vector spot; - float i; - for(i = 0; i < 100; ++i) - { - spot = self.origin + randomvec() * 1024; - tracebox(spot, self.mins, self.maxs, spot, MOVE_NORMAL, self); - if(trace_fraction == 1.0 && trace_startsolid == 0 && trace_allsolid == 0) - return spot; - } - return self.origin; -} - -#if 0 -void targetdrone_think(); -void targetdrone_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force); -void targetdrone_renwe() -{ - self.think = targetdrone_think; - self.nextthink = time + 0.1; - setorigin(self, targetdrone_getnewspot()); - self.health = 200; - self.takedamage = DAMAGE_TARGETDRONE; - self.event_damage = targetdrone_damage; - self.solid = SOLID_BBOX; - setmodel(self, "models/runematch/rune.mdl"); - self.effects = EF_LOWPRECISION; - self.scale = 10; - self.movetype = MOVETYPE_BOUNCEMISSILE; - setsize(self, '-100 -100 -100', '100 100 100'); - -} -void targetdrone_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - self.health -= damage; - if(self.health <= 0) - { - pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1); - - if(!self.cnt) - remove(self); - else - { - self.think = targetdrone_renwe; - self.nextthink = time + 1 + random() * 2; - self.solid = SOLID_NOT; - setmodel(self, ""); - } - } -} -entity targetdrone_getfear() -{ - entity fear; - float i; - - for(i = 64; i <= 1024; i += 64) - { - fear = findradius(self.origin, i); - while(fear) - { - if(fear.bot_dodge) - return fear; - - fear = fear.chain; - } - } - - return world; -} -void targetdrone_think() -{ - self.nextthink = time + 0.1; - - if(self.wp00) - if(self.wp00.deadflag != DEAD_NO) - self.wp00 = targetdrone_getfear(); - - if(!self.wp00) - self.wp00 = targetdrone_getfear(); - - vector newdir; - - if(self.wp00) - newdir = steerlib_push(self.wp00.origin) + randomvec() * 0.75; - else - newdir = randomvec() * 0.75; - - newdir = newdir * 0.5 + normalize(self.velocity) * 0.5; - - if(self.wp00) - self.velocity = normalize(newdir) * (500 + (1024 / min(vlen(self.wp00.origin - self.origin), 1024)) * 700); - else - self.velocity = normalize(newdir) * 750; - - tracebox(self.origin, self.mins, self.maxs, self.origin + self.velocity * 2, MOVE_NORMAL, self); - if(trace_fraction != 1.0) - self.velocity = self.velocity * -1; - - //normalize((normalize(self.velocity) * 0.5 + newdir * 0.5)) * 750; -} - -void targetdrone_spawn(vector _where, float _autorenew) -{ - entity drone = spawn(); - setorigin(drone, _where); - drone.think = targetdrone_renwe; - drone.nextthink = time + 0.1; - drone.cnt = _autorenew; -} -#endif - -void vehicles_locktarget(float incr, float decr, float _lock_time) -{ - if(self.lock_target && self.lock_target.deadflag != DEAD_NO) - { - self.lock_target = world; - self.lock_strength = 0; - self.lock_time = 0; - } - - if(self.lock_time > time) - { - if(self.lock_target) - if(self.lock_soundtime < time) - { - self.lock_soundtime = time + 0.5; - play2(self.owner, "vehicles/locked.wav"); - } - - return; - } - - if(trace_ent != world) - { - if(teamplay && trace_ent.team == self.team) - trace_ent = world; - - if(trace_ent.deadflag != DEAD_NO) - trace_ent = world; - - if(!trace_ent.vehicle_flags & VHF_ISVEHICLE || - trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET || - trace_ent.takedamage == DAMAGE_TARGETDRONE) - trace_ent = world; - } - - if(self.lock_target == world && trace_ent != world) - self.lock_target = trace_ent; - - if(self.lock_target && trace_ent == self.lock_target) - { - if(self.lock_strength != 1 && self.lock_strength + incr >= 1) - { - play2(self.owner, "vehicles/lock.wav"); - self.lock_soundtime = time + 0.8; - } - else if (self.lock_strength != 1 && self.lock_soundtime < time) - { - play2(self.owner, "vehicles/locking.wav"); - self.lock_soundtime = time + 0.3; - } - - } - - // Have a locking target - // Trace hit current target - if(trace_ent == self.lock_target && trace_ent != world) - { - self.lock_strength = min(self.lock_strength + incr, 1); - if(self.lock_strength == 1) - self.lock_time = time + _lock_time; - } - else - { - if(trace_ent) - self.lock_strength = max(self.lock_strength - decr * 2, 0); - else - self.lock_strength = max(self.lock_strength - decr, 0); - - if(self.lock_strength == 0) - self.lock_target = world; - } -} - -#define VEHICLE_UPDATE_PLAYER(ply,fld,vhname) \ -ply.vehicle_##fld = (self.vehicle_##fld / autocvar_g_vehicle_##vhname##_##fld) * 100 - -#define vehicles_sweap_collision(orig,vel,dt,acm,mult) \ -traceline(orig, orig + vel * dt, MOVE_NORMAL, self); \ -if(trace_fraction != 1) \ - acm += normalize(self.origin - trace_endpos) * (vlen(vel) * mult) - -// Hover movement support -float force_fromtag_power; -float force_fromtag_normpower; -vector force_fromtag_origin; -vector vehicles_force_fromtag_hover(string tag_name, float spring_length, float max_power) -{ - force_fromtag_origin = gettaginfo(self, gettagindex(self, tag_name)); - v_forward = normalize(v_forward) * -1; - traceline(force_fromtag_origin, force_fromtag_origin - (v_forward * spring_length), MOVE_NORMAL, self); - - force_fromtag_power = (1 - trace_fraction) * max_power; - force_fromtag_normpower = force_fromtag_power / max_power; - - return v_forward * force_fromtag_power; -} - -// Experimental hovermode wich uses attraction/repulstion from surface insted of gravity/repulsion -// Can possibly be use to move abt any surface (inclusing walls/celings) -vector vehicles_force_fromtag_maglev(string tag_name, float spring_length, float max_power) -{ - - force_fromtag_origin = gettaginfo(self, gettagindex(self, tag_name)); - v_forward = normalize(v_forward) * -1; - traceline(force_fromtag_origin, force_fromtag_origin - (v_forward * spring_length), MOVE_NORMAL, self); - - // TODO - this may NOT be compatible with wall/celing movement, unhardcode 0.25 (engine count multiplier) - if(trace_fraction == 1.0) - { - force_fromtag_normpower = -0.25; - return '0 0 -200'; - } - - force_fromtag_power = ((1 - trace_fraction) - trace_fraction) * max_power; - force_fromtag_normpower = force_fromtag_power / max_power; - - return v_forward * force_fromtag_power; -} - -// Generic vehile projectile system -void vehicles_projectile_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - // Ignore damage from oterh projectiles from my owner (dont mess up volly's) - if(inflictor.owner == self.owner) - return; - - self.health -= damage; - self.velocity += force; - if(self.health < 1) - { - self.takedamage = DAMAGE_NO; - self.event_damage = func_null; - self.think = self.use; - self.nextthink = time; - } -} - -void vehicles_projectile_explode() -{ - if(self.owner && other != world) - { - if(other == self.owner.vehicle) - return; - - if(other == self.owner.vehicle.tur_head) - return; - } - - PROJECTILE_TOUCH; - - self.event_damage = func_null; - RadiusDamage (self, self.realowner, self.shot_dmg, 0, self.shot_radius, self, world, self.shot_force, self.totalfrags, other); - - remove (self); -} - -entity vehicles_projectile(string _mzlfx, string _mzlsound, - vector _org, vector _vel, - float _dmg, float _radi, float _force, float _size, - float _deahtype, float _projtype, float _health, - float _cull, float _clianim, entity _owner) -{ - entity proj; - - proj = spawn(); - - PROJECTILE_MAKETRIGGER(proj); - setorigin(proj, _org); - - proj.shot_dmg = _dmg; - proj.shot_radius = _radi; - proj.shot_force = _force; - proj.totalfrags = _deahtype; - proj.solid = SOLID_BBOX; - proj.movetype = MOVETYPE_FLYMISSILE; - proj.flags = FL_PROJECTILE; - proj.bot_dodge = TRUE; - proj.bot_dodgerating = _dmg; - proj.velocity = _vel; - proj.touch = vehicles_projectile_explode; - proj.use = vehicles_projectile_explode; - proj.owner = self; - proj.realowner = _owner; - proj.think = SUB_Remove; - proj.nextthink = time + 30; - - if(_health) - { - proj.takedamage = DAMAGE_AIM; - proj.event_damage = vehicles_projectile_damage; - proj.health = _health; - } - else - proj.flags = FL_PROJECTILE | FL_NOTARGET; - - if(_mzlsound) - sound (self, CH_WEAPON_A, _mzlsound, VOL_BASE, ATTEN_NORM); - - if(_mzlfx) - pointparticles(particleeffectnum(_mzlfx), proj.origin, proj.velocity, 1); - - - setsize (proj, '-1 -1 -1' * _size, '1 1 1' * _size); - - CSQCProjectile(proj, _clianim, _projtype, _cull); - - return proj; -} -// End generic vehile projectile system - -void vehicles_reset() -{ - if(self.owner) - { - entity oldself = self; - self = self.owner; - vehicles_exit(VHEF_RELESE); - self = oldself; - } - self.alpha = -1; - self.movetype = MOVETYPE_NONE; - self.effects = EF_NODRAW; - self.colormod = '0 0 0'; - self.avelocity = '0 0 0'; - self.velocity = '0 0 0'; - self.event_damage = func_null; - self.solid = SOLID_NOT; - self.deadflag = DEAD_NO; - - self.touch = func_null; - self.nextthink = 0; - vehicles_setreturn(); -} - -/** vehicles_spawn - Exetuted for all vehicles on (re)spawn. - Sets defaults for newly spawned units. -**/ -void vehicles_spawn() -{ - dprint("Spawning vehicle: ", self.netname, "\n"); - - // De-own & reset - self.vehicle_hudmodel.viewmodelforclient = self; - - self.owner = world; - self.touch = vehicles_touch; - self.event_damage = vehicles_damage; - self.reset = vehicles_reset; - self.iscreature = TRUE; - self.teleportable = FALSE; // no teleporting for vehicles, too buggy - self.damagedbycontents = TRUE; - self.movetype = MOVETYPE_WALK; - self.solid = SOLID_SLIDEBOX; - self.takedamage = DAMAGE_AIM; - self.deadflag = DEAD_NO; - self.bot_attack = TRUE; - self.flags = FL_NOTARGET; - self.avelocity = '0 0 0'; - self.velocity = '0 0 0'; - - // Reset locking - self.lock_strength = 0; - self.lock_target = world; - self.misc_bulletcounter = 0; - - // Return to spawn - self.angles = self.pos2; - setorigin(self, self.pos1 + '0 0 0'); - // Show it - pointparticles(particleeffectnum("teleport"), self.origin + '0 0 64', '0 0 0', 1); - - if(self.vehicle_controller) - self.team = self.vehicle_controller.team; - - vehicles_reset_colors(); - self.vehicle_spawn(VHSF_NORMAL); -} - -// Better way of determening whats crushable needed! (fl_crushable?) -float vehicles_crushable(entity e) -{ - if(IS_PLAYER(e)) - return TRUE; - - if(e.flags & FL_MONSTER) - return TRUE; - - return FALSE; -} - -void vehicles_impact(float _minspeed, float _speedfac, float _maxpain) -{ - if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) - return; - - if(self.play_time < time) - { - float wc = vlen(self.velocity - self.oldvelocity); - //dprint("oldvel: ", vtos(self.oldvelocity), "\n"); - //dprint("vel: ", vtos(self.velocity), "\n"); - if(_minspeed < wc) - { - float take = min(_speedfac * wc, _maxpain); - Damage (self, world, world, take, DEATH_FALL, self.origin, '0 0 0'); - self.play_time = time + 0.25; - - //dprint("wc: ", ftos(wc), "\n"); - //dprint("take: ", ftos(take), "\n"); - } - } -} - -.void() vehicle_impact; -void vehicles_touch() -{ - if(MUTATOR_CALLHOOK(VehicleTouch)) - return; - - // Vehicle currently in use - if(self.owner) - { - if(other != world) - if(vehicles_crushable(other)) - { - if(vlen(self.velocity) != 0) - Damage(other, self, self.owner, autocvar_g_vehicles_crush_dmg, DEATH_VH_CRUSH, '0 0 0', normalize(other.origin - self.origin) * autocvar_g_vehicles_crush_force); - - return; // Dont do selfdamage when hitting "soft targets". - } - - if(self.play_time < time) - if(self.vehicle_impact) - self.vehicle_impact(); - - return; - } - - if (!IS_PLAYER(other)) - return; - - if(other.deadflag != DEAD_NO) - return; - - if(other.vehicle != world) - return; - - vehicles_enter(); -} -var float autocvar_g_vehicles_allow_bots = 0; -void vehicles_enter() -{ - // Remove this when bots know how to use vehicles - - if (IS_BOT_CLIENT(other)) - if (autocvar_g_vehicles_allow_bots) - dprint("Bot enters vehicle\n"); // This is where we need to disconnect (some, all?) normal bot AI and hand over to vehicle's _aiframe() - else - return; - - if(self.phase > time) - return; - if(other.frozen) - return; - if(other.vehicle) - return; - if(other.deadflag != DEAD_NO) - return; - - if(teamplay) - if(self.team) - if(self.team != other.team) - return; - - RemoveGrapplingHook(other); - - self.vehicle_ammo1 = 0; - self.vehicle_ammo2 = 0; - self.vehicle_reload1 = 0; - self.vehicle_reload2 = 0; - self.vehicle_energy = 0; - - self.owner = other; - self.switchweapon = other.switchweapon; - - // .viewmodelforclient works better. - //self.vehicle_hudmodel.drawonlytoclient = self.owner; - - self.vehicle_hudmodel.viewmodelforclient = self.owner; - - self.event_damage = vehicles_damage; - self.nextthink = 0; - self.owner.angles = self.angles; - self.owner.takedamage = DAMAGE_NO; - self.owner.solid = SOLID_NOT; - self.owner.movetype = MOVETYPE_NOCLIP; - self.owner.alpha = -1; - self.owner.vehicle = self; - self.owner.event_damage = func_null; - self.owner.view_ofs = '0 0 0'; - self.colormap = self.owner.colormap; - if(self.tur_head) - self.tur_head.colormap = self.owner.colormap; - - self.owner.hud = self.hud; - self.owner.PlayerPhysplug = self.PlayerPhysplug; - - self.owner.vehicle_ammo1 = self.vehicle_ammo1; - self.owner.vehicle_ammo2 = self.vehicle_ammo2; - self.owner.vehicle_reload1 = self.vehicle_reload1; - self.owner.vehicle_reload2 = self.vehicle_reload2; - - // Cant do this, hides attached objects too. - //self.exteriormodeltoclient = self.owner; - //self.tur_head.exteriormodeltoclient = self.owner; - - other.flags &= ~FL_ONGROUND; - self.flags &= ~FL_ONGROUND; - - self.team = self.owner.team; - self.flags -= FL_NOTARGET; - self.monster_attack = TRUE; - - if (IS_REAL_CLIENT(other)) - { - msg_entity = other; - WriteByte (MSG_ONE, SVC_SETVIEWPORT); - WriteEntity(MSG_ONE, self.vehicle_viewport); - - WriteByte (MSG_ONE, SVC_SETVIEWANGLES); - if(self.tur_head) - { - WriteAngle(MSG_ONE, self.tur_head.angles_x + self.angles_x); // tilt - WriteAngle(MSG_ONE, self.tur_head.angles_y + self.angles_y); // yaw - WriteAngle(MSG_ONE, 0); // roll - } - else - { - WriteAngle(MSG_ONE, self.angles_x * -1); // tilt - WriteAngle(MSG_ONE, self.angles_y); // yaw - WriteAngle(MSG_ONE, 0); // roll - } - } - - vehicles_clearreturn(); - - CSQCVehicleSetup(self.owner, self.hud); - - vh_player = other; - vh_vehicle = self; - MUTATOR_CALLHOOK(VehicleEnter); - other = vh_player; - self = vh_vehicle; - - self.vehicle_enter(); - antilag_clear(other); -} - -/** vehicles_findgoodexit - Locates a valid location for the player to exit the vehicle. - Will first try prefer_spot, then up 100 random spots arround the vehicle - wich are in direct line of sight and empty enougth to hold a players bbox -**/ -vector vehicles_findgoodexit(vector prefer_spot) -{ - //vector exitspot; - float mysize; - - tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, prefer_spot, MOVE_NORMAL, self.owner); - if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) - return prefer_spot; - - mysize = 1.5 * vlen(self.maxs - self.mins); - float i; - vector v, v2; - v2 = 0.5 * (self.absmin + self.absmax); - for(i = 0; i < 100; ++i) - { - v = randomvec(); - v_z = 0; - v = v2 + normalize(v) * mysize; - tracebox(v2, PL_MIN, PL_MAX, v, MOVE_NORMAL, self.owner); - if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) - return v; - } - - /* - exitspot = (self.origin + '0 0 48') + v_forward * mysize; - tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner); - if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) - return exitspot; - - exitspot = (self.origin + '0 0 48') - v_forward * mysize; - tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner); - if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) - return exitspot; - - exitspot = (self.origin + '0 0 48') + v_right * mysize; - tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner); - if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) - return exitspot; - - exitspot = (self.origin + '0 0 48') - v_right * mysize; - tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner); - if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) - return exitspot; - */ - - return self.origin; -} - -/** vehicles_exit - Standarrd vehicle release fucntion. - custom code goes in self.vehicle_exit -**/ -float vehicles_exit_running; -void vehicles_exit(float eject) -{ - entity _vehicle; - entity _player; - entity _oldself = self; - - if(vehicles_exit_running) - { - dprint("^1vehicles_exit allready running! this is not good..\n"); - return; - } - - vehicles_exit_running = TRUE; - if(IS_CLIENT(self)) - { - _vehicle = self.vehicle; - - if (_vehicle.vehicle_flags & VHF_PLAYERSLOT) - { - _vehicle.vehicle_exit(eject); - self = _oldself; - vehicles_exit_running = FALSE; - return; - } - } - else - _vehicle = self; - - _player = _vehicle.owner; - - self = _vehicle; - - if (_player) - { - if (IS_REAL_CLIENT(_player)) - { - msg_entity = _player; - WriteByte (MSG_ONE, SVC_SETVIEWPORT); - WriteEntity( MSG_ONE, _player); - - WriteByte (MSG_ONE, SVC_SETVIEWANGLES); - WriteAngle(MSG_ONE, 0); - WriteAngle(MSG_ONE, _vehicle.angles_y); - WriteAngle(MSG_ONE, 0); - } - - setsize(_player, PL_MIN,PL_MAX); - - _player.takedamage = DAMAGE_AIM; - _player.solid = SOLID_SLIDEBOX; - _player.movetype = MOVETYPE_WALK; - _player.effects &= ~EF_NODRAW; - _player.alpha = 1; - _player.PlayerPhysplug = func_null; - _player.vehicle = world; - _player.view_ofs = PL_VIEW_OFS; - _player.event_damage = PlayerDamage; - _player.hud = HUD_NORMAL; - _player.switchweapon = _vehicle.switchweapon; - - CSQCVehicleSetup(_player, HUD_NORMAL); - } - _vehicle.flags |= FL_NOTARGET; - - if(_vehicle.deadflag == DEAD_NO) - _vehicle.avelocity = '0 0 0'; - - _vehicle.tur_head.nodrawtoclient = world; - - if(!teamplay) - _vehicle.team = 0; - - vh_player = _player; - vh_vehicle = _vehicle; - MUTATOR_CALLHOOK(VehicleExit); - _player = vh_player; - _vehicle = vh_vehicle; - - _vehicle.team = _vehicle.tur_head.team; - - sound (_vehicle, CH_TRIGGER_SINGLE, "misc/null.wav", 1, ATTEN_NORM); - _vehicle.vehicle_hudmodel.viewmodelforclient = _vehicle; - _vehicle.phase = time + 1; - _vehicle.monster_attack = FALSE; - - _vehicle.vehicle_exit(eject); - - vehicles_setreturn(); - vehicles_reset_colors(); - _vehicle.owner = world; - self = _oldself; - - vehicles_exit_running = FALSE; -} - - -void vehicles_regen(float timer, .float regen_field, float field_max, float rpause, float regen, float delta_time, float _healthscale) -{ - if(self.regen_field < field_max) - if(timer + rpause < time) - { - if(_healthscale) - regen = regen * (self.vehicle_health / self.tur_health); - - self.regen_field = min(self.regen_field + regen * delta_time, field_max); - - if(self.owner) - self.owner.regen_field = (self.regen_field / field_max) * 100; - } -} - -void shieldhit_think() -{ - self.alpha -= 0.1; - if (self.alpha <= 0) - { - //setmodel(self, ""); - self.alpha = -1; - self.effects |= EF_NODRAW; - } - else - { - self.nextthink = time + 0.1; - } -} - -void vehicles_painframe() -{ - if(self.owner.vehicle_health <= 50) - if(self.pain_frame < time) - { - float _ftmp; - _ftmp = self.owner.vehicle_health / 50; - self.pain_frame = time + 0.1 + (random() * 0.5 * _ftmp); - pointparticles(particleeffectnum("smoke_small"), (self.origin + (randomvec() * 80)), '0 0 0', 1); - - if(self.vehicle_flags & VHF_DMGSHAKE) - self.velocity += randomvec() * 30; - - if(self.vehicle_flags & VHF_DMGROLL) - if(self.vehicle_flags & VHF_DMGHEADROLL) - self.tur_head.angles += randomvec(); - else - self.angles += randomvec(); - - } -} - -void vehicles_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - self.dmg_time = time; - - // WEAPONTODO - if(DEATH_ISWEAPON(deathtype, WEP_VORTEX)) - damage *= autocvar_g_vehicles_vortex_damagerate; - - if(DEATH_ISWEAPON(deathtype, WEP_MACHINEGUN)) - damage *= autocvar_g_vehicles_machinegun_damagerate; - - if(DEATH_ISWEAPON(deathtype, WEP_RIFLE)) - damage *= autocvar_g_vehicles_rifle_damagerate; - - if(DEATH_ISWEAPON(deathtype, WEP_VAPORIZER)) - damage *= autocvar_g_vehicles_vaporizer_damagerate; - - if(DEATH_ISWEAPON(deathtype, WEP_SEEKER)) - damage *= autocvar_g_vehicles_tag_damagerate; - - self.enemy = attacker; - - if((self.vehicle_flags & VHF_HASSHIELD) && (self.vehicle_shield > 0)) - { - if (wasfreed(self.vehicle_shieldent) || self.vehicle_shieldent == world) - { - self.vehicle_shieldent = spawn(); - self.vehicle_shieldent.effects = EF_LOWPRECISION; - - setmodel(self.vehicle_shieldent, "models/vhshield.md3"); - setattachment(self.vehicle_shieldent, self, ""); - setorigin(self.vehicle_shieldent, real_origin(self) - self.origin); - self.vehicle_shieldent.scale = 256 / vlen(self.maxs - self.mins); - self.vehicle_shieldent.think = shieldhit_think; - } - - self.vehicle_shieldent.colormod = '1 1 1'; - self.vehicle_shieldent.alpha = 0.45; - self.vehicle_shieldent.angles = vectoangles(normalize(hitloc - (self.origin + self.vehicle_shieldent.origin))) - self.angles; - self.vehicle_shieldent.nextthink = time; - self.vehicle_shieldent.effects &= ~EF_NODRAW; - - self.vehicle_shield -= damage; - - if(self.vehicle_shield < 0) - { - self.vehicle_health -= fabs(self.vehicle_shield); - self.vehicle_shieldent.colormod = '2 0 0'; - self.vehicle_shield = 0; - self.vehicle_shieldent.alpha = 0.75; - - if(sound_allowed(MSG_BROADCAST, attacker)) - spamsound (self, CH_PAIN, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER - } - else - if(sound_allowed(MSG_BROADCAST, attacker)) - spamsound (self, CH_PAIN, "onslaught/electricity_explode.wav", VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER - - } - else - { - self.vehicle_health -= damage; - - if(sound_allowed(MSG_BROADCAST, attacker)) - spamsound (self, CH_PAIN, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER - } - - if(self.damageforcescale < 1 && self.damageforcescale > 0) - self.velocity += force * self.damageforcescale; - else - self.velocity += force; - - if(self.vehicle_health <= 0) - { - if(self.owner) - if(self.vehicle_flags & VHF_DEATHEJECT) - vehicles_exit(VHEF_EJECT); - else - vehicles_exit(VHEF_RELESE); - - - antilag_clear(self); - - self.vehicle_die(); - vehicles_setreturn(); - } -} - -void vehicles_clearreturn() -{ - entity ret; - // Remove "return helper", if any. - ret = findchain(classname, "vehicle_return"); - while(ret) - { - if(ret.wp00 == self) - { - ret.classname = ""; - ret.think = SUB_Remove; - ret.nextthink = time + 0.1; - - if(ret.waypointsprite_attached) - WaypointSprite_Kill(ret.waypointsprite_attached); - - return; - } - ret = ret.chain; - } -} - -void vehicles_return() -{ - pointparticles(particleeffectnum("teleport"), self.wp00.origin + '0 0 64', '0 0 0', 1); - - self.wp00.think = vehicles_spawn; - self.wp00.nextthink = time; - - if(self.waypointsprite_attached) - WaypointSprite_Kill(self.waypointsprite_attached); - - remove(self); -} - -void vehicles_showwp_goaway() -{ - if(self.waypointsprite_attached) - WaypointSprite_Kill(self.waypointsprite_attached); - - remove(self); - -} - -void vehicles_showwp() -{ - entity oldself = world; - vector rgb; - - if(self.cnt) - { - self.think = vehicles_return; - self.nextthink = self.cnt; - } - else - { - self.think = vehicles_return; - self.nextthink = time +1; - - oldself = self; - self = spawn(); - setmodel(self, "null"); - self.team = oldself.wp00.team; - self.wp00 = oldself.wp00; - setorigin(self, oldself.wp00.pos1); - - self.nextthink = time + 5; - self.think = vehicles_showwp_goaway; - } - - if(teamplay && self.team) - rgb = Team_ColorRGB(self.team); - else - rgb = '1 1 1'; - WaypointSprite_Spawn("vehicle", 0, 0, self, '0 0 64', world, 0, self, waypointsprite_attached, TRUE, RADARICON_POWERUP, rgb); - if(self.waypointsprite_attached) - { - WaypointSprite_UpdateRule(self.waypointsprite_attached, self.wp00.team, SPRITERULE_DEFAULT); - if(oldself == world) - WaypointSprite_UpdateBuildFinished(self.waypointsprite_attached, self.nextthink); - WaypointSprite_Ping(self.waypointsprite_attached); - } - - if(oldself != world) - self = oldself; -} - -void vehicles_setreturn() -{ - entity ret; - - vehicles_clearreturn(); - - ret = spawn(); - ret.classname = "vehicle_return"; - ret.wp00 = self; - ret.team = self.team; - ret.think = vehicles_showwp; - - if(self.deadflag != DEAD_NO) - { - ret.cnt = max(game_starttime, time) + self.vehicle_respawntime; - ret.nextthink = max(game_starttime, time) + max(0, self.vehicle_respawntime - 5); - } - else - ret.nextthink = max(game_starttime, time) + max(0, self.vehicle_respawntime - 1); - - setmodel(ret, "null"); - setorigin(ret, self.pos1 + '0 0 96'); -} - -void vehicles_reset_colors() -{ - entity e; - float _effects = 0, _colormap; - vector _glowmod, _colormod; - - if(autocvar_g_nodepthtestplayers) - _effects |= EF_NODEPTHTEST; - - if(autocvar_g_fullbrightplayers) - _effects |= EF_FULLBRIGHT; - - if(self.team) - _colormap = 1024 + (self.team - 1) * 17; - else - _colormap = 1024; - - _glowmod = '0 0 0'; - _colormod = '0 0 0'; - - // Find all ents attacked to main model and setup effects, colormod etc. - e = findchainentity(tag_entity, self); - while(e) - { - if(e != self.vehicle_shieldent) - { - e.effects = _effects; // | EF_LOWPRECISION; - e.colormod = _colormod; - e.colormap = _colormap; - e.alpha = 1; - } - e = e.chain; - } - - self.vehicle_hudmodel.effects = self.effects = _effects; // | EF_LOWPRECISION; - self.vehicle_hudmodel.colormod = self.colormod = _colormod; - self.vehicle_hudmodel.colormap = self.colormap = _colormap; - self.vehicle_viewport.effects = (EF_ADDITIVE | EF_DOUBLESIDED | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NOGUNBOB | EF_NOSHADOW | EF_LOWPRECISION | EF_SELECTABLE | EF_TELEPORT_BIT); - - self.alpha = 1; - self.avelocity = '0 0 0'; - self.velocity = '0 0 0'; - self.effects = _effects; -} - -void vehicle_use() -{ - dprint("vehicle ",self.netname, " used by ", activator.classname, "\n"); - - self.tur_head.team = activator.team; - - if(self.tur_head.team == 0) - self.active = ACTIVE_NOT; - else - self.active = ACTIVE_ACTIVE; - - if(self.active == ACTIVE_ACTIVE && self.deadflag == DEAD_NO) - { - dprint("^3Eat shit yall!\n"); - vehicles_setreturn(); - vehicles_reset_colors(); - } - else if(self.active == ACTIVE_NOT && self.deadflag != DEAD_NO) - { - - } -} - -float vehicle_addplayerslot( entity _owner, - entity _slot, - float _hud, - string _hud_model, - float() _framefunc, - void(float) _exitfunc) -{ - if (!(_owner.vehicle_flags & VHF_MULTISLOT)) - _owner.vehicle_flags |= VHF_MULTISLOT; - - _slot.PlayerPhysplug = _framefunc; - _slot.vehicle_exit = _exitfunc; - _slot.hud = _hud; - _slot.vehicle_flags = VHF_PLAYERSLOT; - _slot.vehicle_viewport = spawn(); - _slot.vehicle_hudmodel = spawn(); - _slot.vehicle_hudmodel.viewmodelforclient = _slot; - _slot.vehicle_viewport.effects = (EF_ADDITIVE | EF_DOUBLESIDED | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NOGUNBOB | EF_NOSHADOW | EF_LOWPRECISION | EF_SELECTABLE | EF_TELEPORT_BIT); - - setmodel(_slot.vehicle_hudmodel, _hud_model); - setmodel(_slot.vehicle_viewport, "null"); - - setattachment(_slot.vehicle_hudmodel, _slot, ""); - setattachment(_slot.vehicle_viewport, _slot.vehicle_hudmodel, ""); - - return TRUE; -} - -float vehicle_initialize(string net_name, - string bodymodel, - string topmodel, - string hudmodel, - string toptag, - string hudtag, - string viewtag, - float vhud, - vector min_s, - vector max_s, - float nodrop, - void(float _spawnflag) spawnproc, - float _respawntime, - float() physproc, - void() enterproc, - void(float extflag) exitfunc, - void() dieproc, - void() thinkproc, - float use_csqc, - float _max_health, - float _max_shield) -{ - if(!autocvar_g_vehicles) - return FALSE; - - if(self.targetname) - { - self.vehicle_controller = find(world, target, self.targetname); - if(!self.vehicle_controller) - { - bprint("^1WARNING: ^7Vehicle with invalid .targetname\n"); - } - else - { - self.team = self.vehicle_controller.team; - self.use = vehicle_use; - - if(teamplay) - { - if(self.vehicle_controller.team == 0) - self.active = ACTIVE_NOT; - else - self.active = ACTIVE_ACTIVE; - } - } - } - - precache_sound("onslaught/ons_hit2.wav"); - precache_sound("onslaught/electricity_explode.wav"); - - - addstat(STAT_HUD, AS_INT, hud); - addstat(STAT_VEHICLESTAT_HEALTH, AS_INT, vehicle_health); - addstat(STAT_VEHICLESTAT_SHIELD, AS_INT, vehicle_shield); - addstat(STAT_VEHICLESTAT_ENERGY, AS_INT, vehicle_energy); - - addstat(STAT_VEHICLESTAT_AMMO1, AS_INT, vehicle_ammo1); - addstat(STAT_VEHICLESTAT_RELOAD1, AS_INT, vehicle_reload1); - - addstat(STAT_VEHICLESTAT_AMMO2, AS_INT, vehicle_ammo2); - addstat(STAT_VEHICLESTAT_RELOAD2, AS_INT, vehicle_reload2); - - if(bodymodel == "") - error("vehicles: missing bodymodel!"); - - if(hudmodel == "") - error("vehicles: missing hudmodel!"); - - if(net_name == "") - self.netname = self.classname; - else - self.netname = net_name; - - if(self.team && !teamplay) - self.team = 0; - - self.vehicle_flags |= VHF_ISVEHICLE; - - setmodel(self, bodymodel); - - self.vehicle_viewport = spawn(); - self.vehicle_hudmodel = spawn(); - self.tur_head = spawn(); - self.tur_head.owner = self; - self.takedamage = DAMAGE_AIM; - self.bot_attack = TRUE; - self.iscreature = TRUE; - self.teleportable = FALSE; // no teleporting for vehicles, too buggy - self.damagedbycontents = TRUE; - self.hud = vhud; - self.tur_health = _max_health; - self.tur_head.tur_health = _max_shield; - self.vehicle_die = dieproc; - self.vehicle_exit = exitfunc; - self.vehicle_enter = enterproc; - self.PlayerPhysplug = physproc; - self.event_damage = func_null; - self.touch = vehicles_touch; - self.think = vehicles_spawn; - self.vehicle_spawn = spawnproc; - self.vehicle_respawntime = max(0, _respawntime); - self.effects = EF_NODRAW; - self.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_SOLID; - if(!autocvar_g_vehicles_delayspawn || !self.vehicle_respawntime) - self.nextthink = time; - else - self.nextthink = max(time, game_starttime) + max(0, self.vehicle_respawntime + ((random() * 2 - 1) * autocvar_g_vehicles_delayspawn_jitter)); - - if(autocvar_g_playerclip_collisions) - self.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP; - - if(autocvar_g_nodepthtestplayers) - self.effects = self.effects | EF_NODEPTHTEST; - - if(autocvar_g_fullbrightplayers) - self.effects = self.effects | EF_FULLBRIGHT; - - setmodel(self.vehicle_hudmodel, hudmodel); - setmodel(self.vehicle_viewport, "null"); - - if(topmodel != "") - { - setmodel(self.tur_head, topmodel); - setattachment(self.tur_head, self, toptag); - setattachment(self.vehicle_hudmodel, self.tur_head, hudtag); - setattachment(self.vehicle_viewport, self.vehicle_hudmodel, viewtag); - } - else - { - setattachment(self.tur_head, self, ""); - setattachment(self.vehicle_hudmodel, self, hudtag); - setattachment(self.vehicle_viewport, self.vehicle_hudmodel, viewtag); - } - - setsize(self, min_s, max_s); - if (!nodrop) - { - setorigin(self, self.origin); - tracebox(self.origin + '0 0 100', min_s, max_s, self.origin - '0 0 10000', MOVE_WORLDONLY, self); - setorigin(self, trace_endpos); - } - - self.pos1 = self.origin; - self.pos2 = self.angles; - self.tur_head.team = self.team; - - if(MUTATOR_CALLHOOK(VehicleSpawn)) - return FALSE; - - return TRUE; -} - -vector vehicle_aimturret(entity _vehic, vector _target, entity _turrret, string _tagname, - float _pichlimit_min, float _pichlimit_max, - float _rotlimit_min, float _rotlimit_max, float _aimspeed) -{ - vector vtmp, vtag; - float ftmp; - vtag = gettaginfo(_turrret, gettagindex(_turrret, _tagname)); - vtmp = vectoangles(normalize(_target - vtag)); - vtmp = AnglesTransform_ToAngles(AnglesTransform_LeftDivide(AnglesTransform_FromAngles(_vehic.angles), AnglesTransform_FromAngles(vtmp))) - _turrret.angles; - vtmp = AnglesTransform_Normalize(vtmp, TRUE); - ftmp = _aimspeed * frametime; - vtmp_y = bound(-ftmp, vtmp_y, ftmp); - vtmp_x = bound(-ftmp, vtmp_x, ftmp); - _turrret.angles_y = bound(_rotlimit_min, _turrret.angles_y + vtmp_y, _rotlimit_max); - _turrret.angles_x = bound(_pichlimit_min, _turrret.angles_x + vtmp_x, _pichlimit_max); - return vtag; -} - -void vehicles_gib_explode() -{ - sound (self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); - pointparticles(particleeffectnum("explosion_small"), randomvec() * 80 + (self.origin + '0 0 100'), '0 0 0', 1); - remove(self); -} - -void vehicles_gib_think() -{ - self.alpha -= 0.1; - if(self.cnt >= time) - remove(self); - else - self.nextthink = time + 0.1; -} - -entity vehicle_tossgib(entity _template, vector _vel, string _tag, float _burn, float _explode, float _maxtime, vector _rot) -{ - entity _gib = spawn(); - setmodel(_gib, _template.model); - setorigin(_gib, gettaginfo(self, gettagindex(self, _tag))); - _gib.velocity = _vel; - _gib.movetype = MOVETYPE_TOSS; - _gib.solid = SOLID_CORPSE; - _gib.colormod = '-0.5 -0.5 -0.5'; - _gib.effects = EF_LOWPRECISION; - _gib.avelocity = _rot; - - if(_burn) - _gib.effects |= EF_FLAME; - - if(_explode) - { - _gib.think = vehicles_gib_explode; - _gib.nextthink = time + random() * _explode; - _gib.touch = vehicles_gib_explode; - } - else - { - _gib.cnt = time + _maxtime; - _gib.think = vehicles_gib_think; - _gib.nextthink = time + _maxtime - 1; - _gib.alpha = 1; - } - return _gib; -} - -/* -vector predict_target(entity _targ, vector _from, float _shot_speed) -{ - float i; // loop - float _distance; // How far to target - float _impact_time; // How long untill projectile impacts - vector _predict_pos; // Predicted enemy location - vector _original_origin;// Where target is before predicted - - _original_origin = real_origin(_targ); // Typicaly center of target BBOX - - _predict_pos = _original_origin; - for(i = 0; i < 4; ++i) // Loop a few times to increase prediction accuracy (increase loop count if accuracy is to low) - { - _distance = vlen(_predict_pos - _from); // Get distance to previos predicted location - _impact_time = _distance / _shot_speed; // Calculate impact time - _predict_pos = _original_origin + _targ.velocity * _impact_time; // Calculate new predicted location - } - - return _predict_pos; -} -*/ diff --git a/qcsrc/server/vehicles/vehicles.qh b/qcsrc/server/vehicles/vehicles.qh deleted file mode 100644 index 549dfeab7..000000000 --- a/qcsrc/server/vehicles/vehicles.qh +++ /dev/null @@ -1,10 +0,0 @@ -#ifdef VEHICLES_ENABLED -#include "vehicles.qc" - -#include "racer.qc" -#include "spiderbot.qc" -#include "raptor.qc" -#ifndef VEHICLES_NO_UNSTABLE -#include "bumblebee.qc" -#endif -#endif diff --git a/qcsrc/server/waypointsprites.qc b/qcsrc/server/waypointsprites.qc index 736cc564c..0045374a2 100644 --- a/qcsrc/server/waypointsprites.qc +++ b/qcsrc/server/waypointsprites.qc @@ -301,12 +301,27 @@ float WaypointSprite_SendEntity(entity to, float sendflags) WriteCoord(MSG_ENTITY, self.fade_time); WriteCoord(MSG_ENTITY, self.teleport_time); WriteShort(MSG_ENTITY, self.fade_rate); // maxdist - float f; - f = 0; + float f = 0; if(self.currentammo) f |= 1; // hideable if(self.exteriormodeltoclient == to) f |= 2; // my own + if(g_onslaught) + { + if(self.owner.classname == "onslaught_controlpoint") + { + entity wp_owner = self.owner; + entity e = WaypointSprite_getviewentity(to); + if(SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { f |= 2; } + if(!ons_ControlPoint_Attackable(wp_owner, e.team)) { f |= 2; } + } + if(self.owner.classname == "onslaught_generator") + { + entity wp_owner = self.owner; + if(wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { f |= 2; } + if(wp_owner.health <= 0) { f |= 2; } + } + } WriteByte(MSG_ENTITY, f); } diff --git a/qcsrc/server/weapons/common.qc b/qcsrc/server/weapons/common.qc index 6e54cb060..623aedca6 100644 --- a/qcsrc/server/weapons/common.qc +++ b/qcsrc/server/weapons/common.qc @@ -17,18 +17,6 @@ void W_GiveWeapon (entity e, float 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, 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); @@ -78,6 +66,7 @@ void W_PrepareExplosionByDamage(entity attacker, void() explode) if(IS_CLIENT(attacker) && !autocvar_g_projectiles_keep_owner) { + self.jeff_projowner = self.realowner; self.owner = attacker; self.realowner = attacker; } diff --git a/qcsrc/server/weapons/common.qh b/qcsrc/server/weapons/common.qh index 8f9454ed5..563a65784 100644 --- a/qcsrc/server/weapons/common.qh +++ b/qcsrc/server/weapons/common.qh @@ -1,7 +1,8 @@ void W_GiveWeapon (entity e, float wep); -.float prevstrengthsound; -.float prevstrengthsoundattempt; -void W_PlayStrengthSound(entity player); float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception); void W_PrepareExplosionByDamage(entity attacker, void() explode); + + +// jeff +.entity jeff_projowner; diff --git a/qcsrc/server/weapons/hitplot.qc b/qcsrc/server/weapons/hitplot.qc index 685741dab..7796f9074 100644 --- a/qcsrc/server/weapons/hitplot.qc +++ b/qcsrc/server/weapons/hitplot.qc @@ -60,7 +60,7 @@ void W_HitPlotAnalysis(entity player, vector screenforward, vector screenright, org = player.origin + player.view_ofs; traceline_antilag_force(player, org, org + screenforward * MAX_SHOT_DISTANCE, MOVE_NORMAL, player, lag); - if(IS_CLIENT(trace_ent) || (trace_ent.flags & FL_MONSTER)) + if(IS_CLIENT(trace_ent) || IS_MONSTER(trace_ent)) { antilag_takeback(trace_ent, time - lag); hitplot = W_HitPlotNormalizedUntransform(org, trace_ent, screenforward, screenright, screenup, trace_endpos); diff --git a/qcsrc/server/weapons/selection.qc b/qcsrc/server/weapons/selection.qc index f7f3cde51..107d52e1a 100644 --- a/qcsrc/server/weapons/selection.qc +++ b/qcsrc/server/weapons/selection.qc @@ -56,7 +56,7 @@ float client_hasweapon(entity cl, float wpn, float andammo, float complain) if (complain) if(IS_REAL_CLIENT(cl)) { - play2(cl, "weapons/unavailable.wav"); + play2(cl, W_Sound("unavailable")); Send_WeaponComplain (cl, wpn, 0); } return FALSE; @@ -99,7 +99,7 @@ float client_hasweapon(entity cl, float wpn, float andammo, float complain) Send_WeaponComplain (cl, wpn, 2); } - play2(cl, "weapons/unavailable.wav"); + play2(cl, W_Sound("unavailable")); } return FALSE; } @@ -224,6 +224,10 @@ void W_SwitchWeapon_Force(entity e, float w) e.cnt = e.switchweapon; e.switchweapon = w; e.selectweapon = w; + +#ifdef JEFF + e.jeff_weaponswitch_count += 1; +#endif } // perform weapon to attack (weaponstate and attack_finished check is here) @@ -253,7 +257,7 @@ void W_SwitchWeapon(float imp) else self.selectweapon = imp; // update selectweapon ANYWAY } - else if(!forbidWeaponUse()) { WEP_ACTION(self.weapon, WR_RELOAD); } + else if(!forbidWeaponUse(self)) { WEP_ACTION(self.weapon, WR_RELOAD); } } void W_CycleWeapon(string weaponorder, float dir) diff --git a/qcsrc/server/weapons/tracing.qc b/qcsrc/server/weapons/tracing.qc index 55e6d5415..9355d716b 100644 --- a/qcsrc/server/weapons/tracing.qc +++ b/qcsrc/server/weapons/tracing.qc @@ -114,7 +114,6 @@ void W_SetupShot_Dir_ProjectileSize_Range(entity ent, vector s_forward, vector m if (snd != "") { sound (ent, chan, snd, VOL_BASE, ATTN_NORM); - W_PlayStrengthSound(ent); } // nudge w_shotend so a trace to w_shotend hits @@ -170,6 +169,27 @@ void W_SetupProjVelocity_Explicit(entity proj, vector dir, vector upDir, float p proj.velocity = W_CalculateProjectileVelocity(proj.owner.velocity, pSpeed * dir, forceAbsolute); } +float Headshot(entity targ, entity ent, vector hitloc, vector start, vector end) +{ + if(!autocvar_sv_headshot) + return FALSE; + if(ent.weapon != WEP_RIFLE && ent.weapon != WEP_VAPORIZER && ent.weapon != WEP_VORTEX && ent.weapon != WEP_REVOLVER) + return FALSE; + if(!IS_PLAYER(targ)) + return FALSE; + if(Player_Trapped(targ) || !targ.takedamage) + return FALSE; + vector headmins, headmaxs, org; + org = antilag_takebackorigin(targ, time - ANTILAG_LATENCY(ent)); + headmins = org + '0.6 0 0' * targ.mins_x + '0 0.6 0' * targ.mins_y + '0 0 1' * (1.3 * targ.view_ofs_z - 0.3 * targ.maxs_z); + headmaxs = org + '0.6 0 0' * targ.maxs_x + '0 0.6 0' * targ.maxs_y + '0 0 1' * targ.maxs_z; + if(trace_hits_box(start, end, headmins, headmaxs)) + { + return TRUE; + } + return FALSE; +} + // ==================== // Ballistics Tracing @@ -269,7 +289,7 @@ void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, f if(f <= 0) continue; - snd = strcat("weapons/nexwhoosh", ftos(floor(random() * 3) + 1), ".wav"); + snd = W_Sound(strcat("nexwhoosh", ftos(floor(random() * 3) + 1))); if(!pseudoprojectile) pseudoprojectile = spawn(); // we need this so the sound uses the "entchannel4" volume @@ -290,6 +310,16 @@ void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, f f = ExponentialFalloff(mindist, maxdist, halflifedist, ent.railgundistance); ffs = ExponentialFalloff(mindist, maxdist, forcehalflifedist, ent.railgundistance); + if(Headshot(ent, self, hitloc, start, end)) + { + if(deathtype == WEP_RIFLE) + bdamage *= autocvar_sv_headshot_damage; // more damage if a headshot + + Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_HEADSHOT); + } + else + jeff_Announcer_FireBullet(ent, self, hitloc, start, end); + if(accuracy_isgooddamage(self, ent)) totaldmg += bdamage * f; @@ -321,7 +351,7 @@ void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, f void fireBullet_trace_callback(vector start, vector hit, vector end) { if(vlen(hit - start) > 16) - trailparticles(world, fireBullet_trace_callback_eff, start, hit); + Send_Effect(fireBullet_trace_callback_eff, start, hit, 0); WarpZone_trace_forent = world; fireBullet_last_hit = world; } @@ -339,11 +369,11 @@ void fireBullet(vector start, vector dir, float spread, float max_solid_penetrat float total_damage = 0; if(tracereffects & EF_RED) - fireBullet_trace_callback_eff = particleeffectnum("tr_rifle"); + fireBullet_trace_callback_eff = EFFECT_RIFLE; else if(tracereffects & EF_BLUE) - fireBullet_trace_callback_eff = particleeffectnum("tr_rifle_weak"); + fireBullet_trace_callback_eff = EFFECT_RIFLE_WEAK; else - fireBullet_trace_callback_eff = particleeffectnum("tr_bullet"); + fireBullet_trace_callback_eff = EFFECT_BULLET; float lag = ANTILAG_LATENCY(self); if(lag < 0.001) @@ -404,6 +434,13 @@ void fireBullet(vector start, vector dir, float spread, float max_solid_penetrat { fireBullet_last_hit = hit; yoda = 0; + if(Headshot(hit, self, end, start, end)) + { + damage *= autocvar_sv_headshot_damage; // more damage + Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_HEADSHOT); + } + else + jeff_Announcer_FireBullet(hit, self, end, start, end); float g = accuracy_isgooddamage(self, hit); Damage(hit, self, self, damage * solid_penetration_left, dtype, start, force * dir * solid_penetration_left); // calculate hits for ballistic weapons @@ -451,7 +488,7 @@ void fireBullet(vector start, vector dir, float spread, float max_solid_penetrat // Only show effect when going through a player (invisible otherwise) if (hit && (hit.solid != SOLID_BSP)) if(vlen(trace_endpos - start) > 4) - trailparticles(self, fireBullet_trace_callback_eff, start, trace_endpos); + Send_Effect(fireBullet_trace_callback_eff, start, trace_endpos, 0); // TODO: check if we need check for self? start = trace_endpos; diff --git a/qcsrc/server/weapons/weaponsystem.qc b/qcsrc/server/weapons/weaponsystem.qc index 25fce59a1..16936196e 100644 --- a/qcsrc/server/weapons/weaponsystem.qc +++ b/qcsrc/server/weapons/weaponsystem.qc @@ -99,12 +99,12 @@ void CL_WeaponEntity_SetModel(string 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 + setmodel(self, W_Model(strcat("v_", name, ".md3"))); 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 + setmodel(self, W_Model(strcat("h_", name, ".iqm"))); // 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'); @@ -117,14 +117,14 @@ void CL_WeaponEntity_SetModel(string name) { if (!self.weaponentity) self.weaponentity = spawn(); - setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter + setmodel(self.weaponentity, W_Model(strcat("v_", name, ".md3"))); 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 + setmodel(self.weaponentity, W_Model(strcat("v_", name, ".md3"))); setattachment(self.weaponentity, self, "tag_weapon"); } else @@ -361,7 +361,7 @@ void CL_ExteriorWeaponentity_Think() 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 + setmodel(self, W_Model(strcat("v_", self.owner.weaponname, ".md3"))); else self.model = ""; @@ -464,7 +464,7 @@ float weapon_prepareattack_checkammo(float secondary) if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons { - sound (self, CH_WEAPON_A, "weapons/dryfire.wav", VOL_BASE, ATTEN_NORM); + sound (self, CH_WEAPON_A, W_Sound("dryfire"), VOL_BASE, ATTEN_NORM); self.prevdryfire = time; } @@ -619,7 +619,7 @@ void weapon_thinkf(float fr, float t, void() func) if((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t) { - if((self.weapon == WEP_SHOCKWAVE || self.weapon == WEP_SHOTGUN) && fr == WFRAME_FIRE2) + if(((self.weapon == WEP_SHOCKWAVE || self.weapon == WEP_SHOTGUN) && fr == WFRAME_FIRE2) || self.weapon == WEP_LIGHTSABRE) animdecide_setaction(self, ANIMACTION_MELEE, restartanim); else animdecide_setaction(self, ANIMACTION_SHOOT, restartanim); @@ -631,18 +631,18 @@ void weapon_thinkf(float fr, float t, void() func) } } -float forbidWeaponUse() +float forbidWeaponUse(entity player) { if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown) return 1; + if(player.player_blocked) + return 3; + if(player.weapon_blocked) + return 4; + if(player.frozen) + return 5; if(round_handler_IsActive() && !round_handler_IsRoundStarted()) - return 1; - if(self.player_blocked) - return 1; - if(self.frozen) - return 1; - if(self.weapon_blocked) - return 1; + return 2; return 0; } @@ -656,7 +656,7 @@ void W_WeaponFrame() if (!self.weaponentity || self.health < 1) return; // Dead player can't use weapons and injure impulse commands - if(forbidWeaponUse()) + if(forbidWeaponUse(self)) if(self.weaponentity.state != WS_CLEAR) { w_ready(); @@ -722,7 +722,7 @@ void W_WeaponFrame() if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5) { #endif - sound(self, CH_WEAPON_SINGLE, "weapons/weapon_switch.wav", VOL_BASE, ATTN_NORM); + sound(self, CH_WEAPON_SINGLE, W_Sound("weapon_switch"), VOL_BASE, ATTN_NORM); self.weaponentity.state = WS_DROP; weapon_thinkf(WFRAME_DONTCHANGE, oldwep.switchdelay_drop, w_clear); #ifndef INDEPENDENT_ATTACK_FINISHED @@ -917,7 +917,7 @@ void W_Reload(float sent_ammo_min, string sent_sound) { if(IS_REAL_CLIENT(self) && self.reload_complain < time) { - play2(self, "weapons/unavailable.wav"); + play2(self, W_Sound("unavailable")); sprint(self, strcat("You don't have enough ammo to reload the ^2", WEP_NAME(self.weapon), "\n")); self.reload_complain = time + 1; } diff --git a/qcsrc/server/weapons/weaponsystem.qh b/qcsrc/server/weapons/weaponsystem.qh index ddb1ee695..03dd69bbe 100644 --- a/qcsrc/server/weapons/weaponsystem.qh +++ b/qcsrc/server/weapons/weaponsystem.qh @@ -3,4 +3,4 @@ float internalteam; void W_DropEvent(float event, entity player, float weapon_type, entity weapon_item); -float forbidWeaponUse(); +float forbidWeaponUse(entity player); -- 2.39.2