]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Port the so-called "mod pack" to a branch for easier maintenance and eventually porti...
authorMario <zacjardine@y7mail.com>
Sun, 1 Feb 2015 08:11:27 +0000 (19:11 +1100)
committerMario <zacjardine@y7mail.com>
Sun, 1 Feb 2015 08:11:27 +0000 (19:11 +1100)
306 files changed:
qcsrc/Makefile
qcsrc/client/Main.qc
qcsrc/client/View.qc
qcsrc/client/announcer.qc
qcsrc/client/autocvars.qh
qcsrc/client/command/cl_cmd.qc
qcsrc/client/conquest.qc [new file with mode: 0644]
qcsrc/client/conquest.qh [new file with mode: 0644]
qcsrc/client/controlpoint.qc [new file with mode: 0644]
qcsrc/client/controlpoint.qh [new file with mode: 0644]
qcsrc/client/csqcmodel_hooks.qc
qcsrc/client/damage.qc
qcsrc/client/generator.qc [new file with mode: 0644]
qcsrc/client/generator.qh [new file with mode: 0644]
qcsrc/client/hook.qc
qcsrc/client/hud.qc
qcsrc/client/hud.qh
qcsrc/client/hud_config.qc
qcsrc/client/main.qh
qcsrc/client/mapvoting.qc
qcsrc/client/miscfunctions.qc
qcsrc/client/particles.qc
qcsrc/client/player_skeleton.qc
qcsrc/client/player_skeleton.qh
qcsrc/client/progs.src
qcsrc/client/scoreboard.qc
qcsrc/client/teamradar.qc
qcsrc/client/waypointsprites.qc
qcsrc/client/waypointsprites.qh
qcsrc/client/weapons/projectile.qc
qcsrc/common/animdecide.qc
qcsrc/common/animdecide.qh
qcsrc/common/buffs.qc
qcsrc/common/buffs.qh
qcsrc/common/command/generic.qc
qcsrc/common/command/script.qc [new file with mode: 0644]
qcsrc/common/command/script.qh [new file with mode: 0644]
qcsrc/common/command/script/ast.qh [new file with mode: 0644]
qcsrc/common/command/script/lex.qh [new file with mode: 0644]
qcsrc/common/command/script/parse.qh [new file with mode: 0644]
qcsrc/common/command/script/string.qh [new file with mode: 0644]
qcsrc/common/constants.qh
qcsrc/common/csqcmodel_settings.qh
qcsrc/common/deathtypes.qh
qcsrc/common/effects.qc [new file with mode: 0644]
qcsrc/common/effects.qh [new file with mode: 0644]
qcsrc/common/jeff.qh [new file with mode: 0644]
qcsrc/common/mapinfo.qc
qcsrc/common/mapinfo.qh
qcsrc/common/minigames/cl_minigames.qc [new file with mode: 0644]
qcsrc/common/minigames/cl_minigames.qh [new file with mode: 0644]
qcsrc/common/minigames/cl_minigames_hud.qc [new file with mode: 0644]
qcsrc/common/minigames/minigame/all.qh [new file with mode: 0644]
qcsrc/common/minigames/minigame/nmm.qc [new file with mode: 0644]
qcsrc/common/minigames/minigame/ttt.qc [new file with mode: 0644]
qcsrc/common/minigames/minigames.qc [new file with mode: 0644]
qcsrc/common/minigames/minigames.qh [new file with mode: 0644]
qcsrc/common/minigames/sv_minigames.qc [new file with mode: 0644]
qcsrc/common/minigames/sv_minigames.qh [new file with mode: 0644]
qcsrc/common/monsters/all.qh
qcsrc/common/monsters/monster/afrit.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/creeper.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/demon.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/enforcer.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/mage.qc
qcsrc/common/monsters/monster/ogre.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/rotfish.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/rottweiler.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/scrag.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/shambler.qc
qcsrc/common/monsters/monster/spawn.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/spider.qc
qcsrc/common/monsters/monster/vore.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/wyvern.qc
qcsrc/common/monsters/monster/zombie.qc
qcsrc/common/monsters/monsters.qc
qcsrc/common/monsters/monsters.qh
qcsrc/common/monsters/spawn.qc
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/monsters/sv_monsters.qh
qcsrc/common/notifications.qc
qcsrc/common/notifications.qh
qcsrc/common/playerstats.qc
qcsrc/common/playerstats.qh
qcsrc/common/stats.qh
qcsrc/common/teams.qh
qcsrc/common/turrets/all.qh [new file with mode: 0644]
qcsrc/common/turrets/checkpoint.qc [new file with mode: 0644]
qcsrc/common/turrets/cl_turrets.qc [new file with mode: 0644]
qcsrc/common/turrets/cl_turrets.qh [new file with mode: 0644]
qcsrc/common/turrets/sv_turrets.qc [new file with mode: 0644]
qcsrc/common/turrets/sv_turrets.qh [new file with mode: 0644]
qcsrc/common/turrets/targettrigger.qc [new file with mode: 0644]
qcsrc/common/turrets/turrets.qc [new file with mode: 0644]
qcsrc/common/turrets/turrets.qh [new file with mode: 0644]
qcsrc/common/turrets/unit/ewheel.qc [new file with mode: 0644]
qcsrc/common/turrets/unit/flac.qc [new file with mode: 0644]
qcsrc/common/turrets/unit/fusionreactor.qc [new file with mode: 0644]
qcsrc/common/turrets/unit/hellion.qc [new file with mode: 0644]
qcsrc/common/turrets/unit/hk.qc [new file with mode: 0644]
qcsrc/common/turrets/unit/machinegun.qc [new file with mode: 0644]
qcsrc/common/turrets/unit/mlrs.qc [new file with mode: 0644]
qcsrc/common/turrets/unit/phaser.qc [new file with mode: 0644]
qcsrc/common/turrets/unit/plasma.qc [new file with mode: 0644]
qcsrc/common/turrets/unit/plasma_dual.qc [new file with mode: 0644]
qcsrc/common/turrets/unit/tesla.qc [new file with mode: 0644]
qcsrc/common/turrets/unit/walker.qc [new file with mode: 0644]
qcsrc/common/turrets/util.qc [new file with mode: 0644]
qcsrc/common/util.qc
qcsrc/common/util.qh
qcsrc/common/vehicles/all.qh [new file with mode: 0644]
qcsrc/common/vehicles/cl_vehicles.qc [new file with mode: 0644]
qcsrc/common/vehicles/cl_vehicles.qh [new file with mode: 0644]
qcsrc/common/vehicles/sv_vehicles.qc [new file with mode: 0644]
qcsrc/common/vehicles/sv_vehicles.qh [new file with mode: 0644]
qcsrc/common/vehicles/unit/bumblebee.qc [new file with mode: 0644]
qcsrc/common/vehicles/unit/racer.qc [new file with mode: 0644]
qcsrc/common/vehicles/unit/raptor.qc [new file with mode: 0644]
qcsrc/common/vehicles/unit/spiderbot.qc [new file with mode: 0644]
qcsrc/common/vehicles/unit/tankll48.qc [new file with mode: 0644]
qcsrc/common/vehicles/unit/yugo.qc [new file with mode: 0644]
qcsrc/common/vehicles/vehicles.qc [new file with mode: 0644]
qcsrc/common/vehicles/vehicles.qh [new file with mode: 0644]
qcsrc/common/vehicles/vehicles_include.qc [new file with mode: 0644]
qcsrc/common/vehicles/vehicles_include.qh [new file with mode: 0644]
qcsrc/common/weapons/all.qh
qcsrc/common/weapons/w_arc.qc
qcsrc/common/weapons/w_blaster.qc
qcsrc/common/weapons/w_crylink.qc
qcsrc/common/weapons/w_devastator.qc
qcsrc/common/weapons/w_electro.qc
qcsrc/common/weapons/w_fireball.qc
qcsrc/common/weapons/w_hagar.qc
qcsrc/common/weapons/w_hlac.qc
qcsrc/common/weapons/w_hmg.qc
qcsrc/common/weapons/w_hook.qc
qcsrc/common/weapons/w_lightsabre.qc [new file with mode: 0644]
qcsrc/common/weapons/w_machinegun.qc
qcsrc/common/weapons/w_minelayer.qc
qcsrc/common/weapons/w_mortar.qc
qcsrc/common/weapons/w_porto.qc
qcsrc/common/weapons/w_revolver.qc [new file with mode: 0644]
qcsrc/common/weapons/w_rifle.qc
qcsrc/common/weapons/w_rpc.qc
qcsrc/common/weapons/w_seeker.qc
qcsrc/common/weapons/w_shockwave.qc
qcsrc/common/weapons/w_shotgun.qc
qcsrc/common/weapons/w_tuba.qc
qcsrc/common/weapons/w_vaporizer.qc
qcsrc/common/weapons/w_vortex.qc
qcsrc/common/weapons/weapons.qc
qcsrc/common/weapons/weapons.qh
qcsrc/csqcmodellib/cl_player.qc
qcsrc/dpdefs/csprogsdefs.qc
qcsrc/dpdefs/menudefs.qc
qcsrc/menu/progs.src
qcsrc/server/autocvars.qh
qcsrc/server/bot/aim.qc
qcsrc/server/bot/bot.qc
qcsrc/server/bot/bot.qh
qcsrc/server/bot/havocbot/havocbot.qc
qcsrc/server/bot/havocbot/role_keyhunt.qc [deleted file]
qcsrc/server/bot/havocbot/role_onslaught.qc [deleted file]
qcsrc/server/bot/havocbot/roles.qc
qcsrc/server/cheats.qc
qcsrc/server/cl_client.qc
qcsrc/server/cl_impulse.qc
qcsrc/server/cl_physics.qc
qcsrc/server/cl_player.qc
qcsrc/server/cl_weapons.qc [new file with mode: 0644]
qcsrc/server/command/cmd.qc
qcsrc/server/command/common.qc
qcsrc/server/command/sv_cmd.qc
qcsrc/server/command/sv_cmd.qh
qcsrc/server/command/vote.qc
qcsrc/server/controlpoint.qc [new file with mode: 0644]
qcsrc/server/controlpoint.qh [new file with mode: 0644]
qcsrc/server/defs.qh
qcsrc/server/func_breakable.qc
qcsrc/server/g_damage.qc
qcsrc/server/g_hook.qc
qcsrc/server/g_subs.qc
qcsrc/server/g_tetris.qc [deleted file]
qcsrc/server/g_triggers.qc
qcsrc/server/g_world.qc
qcsrc/server/generator.qc [new file with mode: 0644]
qcsrc/server/generator.qh [new file with mode: 0644]
qcsrc/server/jeff.qc [new file with mode: 0644]
qcsrc/server/miscfunctions.qc
qcsrc/server/mutators/base.qc
qcsrc/server/mutators/base.qh
qcsrc/server/mutators/gamemode_assault.qc
qcsrc/server/mutators/gamemode_assault.qh
qcsrc/server/mutators/gamemode_ca.qc
qcsrc/server/mutators/gamemode_conquest.qc [new file with mode: 0644]
qcsrc/server/mutators/gamemode_conquest.qh [new file with mode: 0644]
qcsrc/server/mutators/gamemode_ctf.qc
qcsrc/server/mutators/gamemode_ctf.qh
qcsrc/server/mutators/gamemode_cts.qc
qcsrc/server/mutators/gamemode_deathmatch.qc [new file with mode: 0644]
qcsrc/server/mutators/gamemode_domination.qc
qcsrc/server/mutators/gamemode_domination.qh
qcsrc/server/mutators/gamemode_freezetag.qc
qcsrc/server/mutators/gamemode_freezetag.qh [new file with mode: 0644]
qcsrc/server/mutators/gamemode_infection.qc [new file with mode: 0644]
qcsrc/server/mutators/gamemode_infection.qh [new file with mode: 0644]
qcsrc/server/mutators/gamemode_invasion.qc
qcsrc/server/mutators/gamemode_jailbreak.qc [new file with mode: 0644]
qcsrc/server/mutators/gamemode_jailbreak.qh [new file with mode: 0644]
qcsrc/server/mutators/gamemode_keepaway.qc
qcsrc/server/mutators/gamemode_keyhunt.qc
qcsrc/server/mutators/gamemode_keyhunt.qh
qcsrc/server/mutators/gamemode_lms.qc
qcsrc/server/mutators/gamemode_nexball.qc
qcsrc/server/mutators/gamemode_onslaught.qc
qcsrc/server/mutators/gamemode_onslaught.qh [new file with mode: 0644]
qcsrc/server/mutators/gamemode_race.qc
qcsrc/server/mutators/gamemode_tdm.qc
qcsrc/server/mutators/gamemode_vip.qc [new file with mode: 0644]
qcsrc/server/mutators/gamemode_vip.qh [new file with mode: 0644]
qcsrc/server/mutators/mutator_bloodloss.qc
qcsrc/server/mutators/mutator_buffs.qc
qcsrc/server/mutators/mutator_buffs.qh
qcsrc/server/mutators/mutator_campcheck.qc
qcsrc/server/mutators/mutator_dodging.qc
qcsrc/server/mutators/mutator_freeze.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator_freeze.qh [new file with mode: 0644]
qcsrc/server/mutators/mutator_hats.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator_instagib.qc
qcsrc/server/mutators/mutator_instagib.qh [new file with mode: 0644]
qcsrc/server/mutators/mutator_itemeditor.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator_multijump.qc
qcsrc/server/mutators/mutator_nades.qc
qcsrc/server/mutators/mutator_nades.qh
qcsrc/server/mutators/mutator_new_toys.qc
qcsrc/server/mutators/mutator_nix.qc
qcsrc/server/mutators/mutator_overkill.qc
qcsrc/server/mutators/mutator_physical_items.qc
qcsrc/server/mutators/mutator_piggyback.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator_piggyback.qh [new file with mode: 0644]
qcsrc/server/mutators/mutator_random_vehicles.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator_riflearena.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator_spawn_near_teammate.qc
qcsrc/server/mutators/mutator_superspec.qc
qcsrc/server/mutators/mutator_touchexplode.qc
qcsrc/server/mutators/mutator_walljump.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator_zombie_apocalypse.qc [new file with mode: 0644]
qcsrc/server/mutators/mutators.qc
qcsrc/server/mutators/mutators.qh
qcsrc/server/mutators/mutators_include.qc
qcsrc/server/mutators/mutators_include.qh
qcsrc/server/mutators/sandbox.qc
qcsrc/server/portals.qc
qcsrc/server/progs.src
qcsrc/server/race.qc
qcsrc/server/race.qh
qcsrc/server/scores.qc
qcsrc/server/scores.qh
qcsrc/server/scores_rules.qc
qcsrc/server/spawnpoints.qc
qcsrc/server/spawnpoints.qh
qcsrc/server/steerlib.qc
qcsrc/server/sv_main.qc
qcsrc/server/t_halflife.qc
qcsrc/server/t_items.qc
qcsrc/server/t_items.qh
qcsrc/server/t_jumppads.qc
qcsrc/server/t_quake3.qc
qcsrc/server/t_teleporters.qc
qcsrc/server/teamplay.qc
qcsrc/server/teamplay.qh [new file with mode: 0644]
qcsrc/server/tturrets/include/turrets.qh [deleted file]
qcsrc/server/tturrets/include/turrets_early.qh [deleted file]
qcsrc/server/tturrets/system/system_aimprocs.qc [deleted file]
qcsrc/server/tturrets/system/system_damage.qc [deleted file]
qcsrc/server/tturrets/system/system_main.qc [deleted file]
qcsrc/server/tturrets/system/system_misc.qc [deleted file]
qcsrc/server/tturrets/system/system_scoreprocs.qc [deleted file]
qcsrc/server/tturrets/units/unit_checkpoint.qc [deleted file]
qcsrc/server/tturrets/units/unit_ewheel.qc [deleted file]
qcsrc/server/tturrets/units/unit_flac.qc [deleted file]
qcsrc/server/tturrets/units/unit_fusionreactor.qc [deleted file]
qcsrc/server/tturrets/units/unit_hellion.qc [deleted file]
qcsrc/server/tturrets/units/unit_hk.qc [deleted file]
qcsrc/server/tturrets/units/unit_machinegun.qc [deleted file]
qcsrc/server/tturrets/units/unit_mlrs.qc [deleted file]
qcsrc/server/tturrets/units/unit_phaser.qc [deleted file]
qcsrc/server/tturrets/units/unit_plasma.qc [deleted file]
qcsrc/server/tturrets/units/unit_targettrigger.qc [deleted file]
qcsrc/server/tturrets/units/unit_tessla.qc [deleted file]
qcsrc/server/tturrets/units/unit_walker.qc [deleted file]
qcsrc/server/vehicles/bumblebee.qc [deleted file]
qcsrc/server/vehicles/racer.qc [deleted file]
qcsrc/server/vehicles/raptor.qc [deleted file]
qcsrc/server/vehicles/spiderbot.qc [deleted file]
qcsrc/server/vehicles/vehicles.qc [deleted file]
qcsrc/server/vehicles/vehicles.qh [deleted file]
qcsrc/server/vehicles/vehicles_def.qh [deleted file]
qcsrc/server/waypointsprites.qc
qcsrc/server/weapons/common.qc
qcsrc/server/weapons/common.qh
qcsrc/server/weapons/hitplot.qc
qcsrc/server/weapons/selection.qc
qcsrc/server/weapons/tracing.qc
qcsrc/server/weapons/weaponsystem.qc
qcsrc/server/weapons/weaponsystem.qh

index 9fde71e103e2bdeb1030c1c06545d60ee9aac231..f24c1cc127c9b79cc769c9cab48ab23e0b970df2 100644 (file)
@@ -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) \
index d6b00ec9f55182fdee763ce20adb3a7a0639d275..2f3cd456bc8cb5354b44ac537fb3092fb937bbd8 100644 (file)
@@ -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;
index 310b47d70def78e40a15d8873b5e964f7f0ba6b4..6ec92b9edee4a4b195a96932893b2eaa78c65441 100644 (file)
@@ -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();
 
index f49b847d69d05c2a3bd8fecca9d932fe7ec95cc6..e77a54431aa8e2da1c9fbb78bc42b3424aa3b89d 100644 (file)
@@ -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);
index 1355bafe7687ffddf316a450a79e01c00ab070ac..c799cb2b14c8e49b3c594ebe5f85fd91a32ff7e3 100644 (file)
@@ -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?
index 9aae77dde687ef5417a61b71ae29b4524365e963..ff1dd763decb696b1cfd3df142cdc4a843ccebab 100644 (file)
@@ -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 (file)
index 0000000..8397092
--- /dev/null
@@ -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 (file)
index 0000000..6cf1b7c
--- /dev/null
@@ -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 (file)
index 0000000..c01835c
--- /dev/null
@@ -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 (file)
index 0000000..5b18f86
--- /dev/null
@@ -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();
index 4cb416063509daca3b6ad6b8b920dbf2ce0a7a53..7d326f01d0afc6dde34138b6733da64a80b8a3d4 100644 (file)
@@ -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
index 162ef6a07eb17560ebf4ffeb4d7a6c745affdaae..e01659c4ebca45b7bc50e3f4e71d4bde3ab98f7a 100644 (file)
@@ -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 (file)
index 0000000..4ee798f
--- /dev/null
@@ -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 (file)
index 0000000..6ba1ce8
--- /dev/null
@@ -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
index 8b2ffca19cdb6e9997edddd5f6c566d93a7b104a..1b5d83ec1bcd99ecbf0e377a531e4df4f4257867 100644 (file)
@@ -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);
index 413f05cff777619ed13c8202355d5f037bf4ee34..a8bd4ce65122e1ba1b443260f32f38516cfade66 100644 (file)
@@ -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();
 
index d56caf1331e88fa48e9624f20b2cf7982c581f70..fb4816726c2afdc01252a35284c5383364a393f1 100644 (file)
@@ -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);
index e19e2378754b1d4052fd3ecdf9eb1195b2ca3cdf..cfde7c24cd8b30e6a3ba985e30b8af423f9d4051 100644 (file)
@@ -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
index c9aa2fb40f8a9e86b105dcf317edc9a49cd63f1d..1c2ccdf8ca7d4d84f114fa8572cb6b9a36401111 100644 (file)
@@ -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;
index 3f30a6a722f7d3e805e6423facaca0b7ad81ab00..302cdc3264dc989cd27336ec419a817c06f3ff5f 100644 (file)
@@ -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)
index 8b674e7826c2ccd6529e46a703d55ee00a21a2ae..566212dc0b97fdc429fcf72ba3c845c60ffdf0b2 100644 (file)
@@ -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)
 {
index f16519ab5c71dbcc7e726ef26c4342e90c3330de..1e6d3fac8a7a4f243a5d10f9b25a1bc575873c5c 100644 (file)
@@ -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);
 }
index ad5e23aa88335e382a221201828870ad848af471..b66ebe67cd277081a1f8ea16d767ae83fc77a55f 100644 (file)
@@ -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
        }
 }
index d369bac4ec192db99f04978d17c38c28128015b4..7ee8d3a8bf2032d0406db420cde19f265e22c54e 100644 (file)
@@ -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;
index c21726b4dcc655433bbe3d4f80365b0e3dc6e94f..87e319e6f501271b86af4971aff10ba0fa0cf489 100644 (file)
@@ -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
index f5b3206ba3bbbb6bc259df0d920675a01a96d7a3..92cae0095b882557b1d9a4eea24c186e23ad2327 100644 (file)
@@ -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;
index 0c0f10204a4773c2da21e2aa6669a46a88d4740a..b12f435710554cb999bcb7e25c9d48f5deefef95 100644 (file)
@@ -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;
index 2df3dd411bc6d9f4da76ef4124a81fa51658c8fc..4d1a27066f07b896c1bf78e02bc0c23302fa0598 100644 (file)
@@ -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;
        }
 }
index 94895083323964aeb3c3e1f6037cfb28acae90ad..d52a048075dda1b7653c00bbaecad8cb6d9d9773 100644 (file)
@@ -1,3 +1,6 @@
 // they are drawn using a .draw function
 void Ent_WaypointSprite();
 void Ent_RemoveWaypointSprite();
+
+
+.float health;
index e019b6755514c10957a91c9d43042827e137de72..e55d739f81b8d780115a8224da5629ff37a9e49e 100644 (file)
@@ -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';
index d2b849f580e4832621dd5c16467dcbeb8cfb39cb..fa1f8b9c5d666b1f4f0624c098f0052a472453fa 100644 (file)
@@ -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)
index b9d5260e6e7449e6cdf57b73cab455d65111241f..2f67a37275a75d78850e1b374904f2a4adefd50b 100644 (file)
@@ -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
index 2f8e0fc23895dd34e773773a8cddf67f6bc266c8..9a00bcf75bdff58d8e284d645a5584c66e477cbd 100644 (file)
@@ -43,7 +43,6 @@ float Buff_Type_FromSprite(string buff_sprite)
        return 0;
 }
 
-
 float Buff_Skin(float buff_id)
 {
        entity e;
index c29dad6dddbc49ce5d467fe5fa0be34d3fb8b9c8..1c597b63d6b5e79edd845162628f72612a1bed6f 100644 (file)
@@ -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);
index 743793bad02db23bbfc88e5bbf211fdedc9a7b50..81ab30945ef0a24ac72397b60ea2d4f37ff59408 100644 (file)
@@ -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 (file)
index 0000000..077dbdc
--- /dev/null
@@ -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 (file)
index 0000000..a357170
--- /dev/null
@@ -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 (file)
index 0000000..29939f6
--- /dev/null
@@ -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_lt(entity operand1, entity operand2)
+{
+       entity ast_temp = spawn();
+       ast_temp.ast_operand1 = operand1;
+       ast_temp.ast_operand2 = operand2;
+       ast_temp.ast_evaluate = ast_lt_evaluate;
+       ast_temp.classname="ast_lt";
+       return ast_temp;
+}
+entity ast_le_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_le(entity operand1, entity operand2)
+{
+       entity ast_temp = spawn();
+       ast_temp.ast_operand1 = operand1;
+       ast_temp.ast_operand2 = operand2;
+       ast_temp.ast_evaluate = ast_le_evaluate;
+       ast_temp.classname="ast_le";
+       return ast_temp;
+}
+entity ast_gt_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 (file)
index 0000000..814c4dd
--- /dev/null
@@ -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 (file)
index 0000000..715d0d6
--- /dev/null
@@ -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 (file)
index 0000000..87d747a
--- /dev/null
@@ -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;
+}
+
index 8586cffa9d539ff5f56067ab570df0e132f43ce6..d5fe35966c42420f850781dd7f4d82b658cb0ed6 100644 (file)
@@ -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;
index f521d7de9da2b9635d7b13495a94711b1e612f47..56f94dfde7fe1e60a0876a1089155d75ab832316 100644 (file)
@@ -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
        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) \
index c8512cdcf25c7039bc352946e328bdb0566c19a8..4f99fefdfc0af71f3d9cf0d4fa7fcb93fc3d860a 100644 (file)
@@ -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) \
        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 (file)
index 0000000..50b5d13
--- /dev/null
@@ -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 (file)
index 0000000..9d7b05d
--- /dev/null
@@ -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 (file)
index 0000000..f3da8cc
--- /dev/null
@@ -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) 
index ccf02e71d821387f886f6c49da96c3a77564e13a..1d13569a3a1d1c852f3ba414470f8ffef3b927a5 100644 (file)
@@ -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
index bbcc267b2bdbf5d05005155768419c357516e84c..c431bf53bbcd13fe586b2a325e50ec89b38cd1cc 100644 (file)
@@ -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 (file)
index 0000000..cb73452
--- /dev/null
@@ -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 (file)
index 0000000..201b6ca
--- /dev/null
@@ -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 (file)
index 0000000..0c40085
--- /dev/null
@@ -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 (file)
index 0000000..1330648
--- /dev/null
@@ -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_<id>(entity minigame, string event, ...count)
+               see ../minigames.qh for a detailed explanation
+CSQC:
+       void minigame_hud_board_<id>(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_<id>(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_<id>(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 (file)
index 0000000..aaebe69
--- /dev/null
@@ -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 (file)
index 0000000..85b9727
--- /dev/null
@@ -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 (file)
index 0000000..6b8dbf3
--- /dev/null
@@ -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 (file)
index 0000000..a9aa922
--- /dev/null
@@ -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 (file)
index 0000000..0683f6a
--- /dev/null
@@ -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 <minigame>\n");
+       sprint(self, "  Start a new minigame session\n");
+       sprint(self, "Usage:^3 cmd minigame join <session>\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 <player>\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 (file)
index 0000000..a8d7d6f
--- /dev/null
@@ -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);
index d30f29894ec12ccf4a952a11b8bb2307a8593842..d827d45d8fd1e0004aa14a4302cf11dd2d1e8fc7 100644 (file)
@@ -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 (file)
index 0000000..5d9c01d
--- /dev/null
@@ -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 (file)
index 0000000..1db708c
--- /dev/null
@@ -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 (file)
index 0000000..19fb151
--- /dev/null
@@ -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 (file)
index 0000000..3143238
--- /dev/null
@@ -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
index 3adc59a3020c8fc7cc1c8da0341284672dbf41bd..907bb5101e64cb2a2267f05229c6975d2d08e97a 100644 (file)
@@ -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 (file)
index 0000000..fae371c
--- /dev/null
@@ -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 (file)
index 0000000..8d2a395
--- /dev/null
@@ -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 (file)
index 0000000..c4ed126
--- /dev/null
@@ -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 (file)
index 0000000..32ceb6f
--- /dev/null
@@ -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
index 7c46a1da15f087bf1cbf15e001bbb5f84978dafc..1383463ddf1cb5e369c4ffe775b504500f653258 100644 (file)
@@ -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 (file)
index 0000000..7040f3c
--- /dev/null
@@ -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
index c6c454547f4898bb96e946cb5994bd585b073e02..5ef819fd2aa886469530a942e45be4813689ae33 100644 (file)
@@ -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 (file)
index 0000000..768826f
--- /dev/null
@@ -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
index a44f6d9c7ab2fe089dea4c821a8857fa3e98c0c0..0ce51e78bd54c2a6fa1ff8e750ee5ba51a4ed0ea 100644 (file)
@@ -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
index 25afaf76f30b50d27c622fe6c854b5602c4df197..d8f9dc91645e20567097c5bb6c62c2a7743c7a54 100644 (file)
@@ -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
index 67e176cf26395b6001ca42c7090fbade0bf5040d..a63c0f8420a0caba4704b49570e6837df0da9d19 100644 (file)
@@ -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';
index c355e12a75a31b197fadd75476db33290f270564..dc68b0a2fe77dd297ea107e6059b5968d58fd559 100644 (file)
@@ -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"
index be5accf5ede2d095a2d40cc398970aa406a49a74..42c19edf9d232abaeca7c769fc26d050cc162ec3 100644 (file)
@@ -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;
 }
index fd966f5d2edf20499aa7adf681b3aebd2a88fc4a..5fa87e526ea87a5a7a1cc023806320bdabe75720 100644 (file)
@@ -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);
 
index 239db02bd225cb3928453f7ca4bef8f4133209e7..05b71b10da7fd1a9b593b896e075e42a1233df74 100644 (file)
@@ -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;
index a5a2ff445e60d376463d1265af9ba19657cd5575..f24d130d0413c525bfc10bbbb2560e0e96528cbd 100644 (file)
@@ -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,
index 0bfd2e5f3075f60746f5160b137ece590c4f43cc..8710079fb1e92302edc73e198f6911954649768d 100644 (file)
@@ -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; \
index 2c4a355e0a8c00a58a7c14649dde91a32b744fd4..a624846f81ebb38321e3ae17deba600dd79e7cce 100644 (file)
@@ -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));
index ab4bc166f0b29f05134bf953f16a4ae75deb0c69..5f26205d3037fd36ec35b7c92b06605e0677182e 100644 (file)
@@ -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";
index 2954917100df08dd739aaf3e3d5a496dbb8364ec..cd141c83b25cd42025430cfce3e175948a0278cd 100644 (file)
@@ -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?
index 069904290b7d45361bdb7a1828ead5888bc3bca2..77dc7b94febe18afa9023ffc58aa8ff1b738bcdc 100644 (file)
@@ -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 (file)
index 0000000..04bb10f
--- /dev/null
@@ -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/common/turrets/checkpoint.qc b/qcsrc/common/turrets/checkpoint.qc
new file mode 100644 (file)
index 0000000..dde5c28
--- /dev/null
@@ -0,0 +1,82 @@
+/**
+    turret_checkpoint
+**/
+
+
+//.entity checkpoint_target;
+
+/*
+#define checkpoint_cache_who  flagcarried
+#define checkpoint_cache_from lastrocket
+#define checkpoint_cache_to   selected_player
+*/
+
+.entity pathgoal;
+
+/*
+entity path_makeorcache(entity forwho,entity start, entity end)
+{
+    entity oldself;
+    entity pth;
+    oldself = self;
+    self = forwho;
+
+    //pth = pathlib_makepath(start.origin,end.origin,PFL_GROUNDSNAP,500,1.5,PT_QUICKSTAR);
+
+    self = oldself;
+    return pth;
+}
+*/
+
+void turret_checkpoint_use()
+{
+}
+
+#if 0
+void turret_checkpoint_think()
+{
+    if(self.enemy)
+        te_lightning1(self,self.origin, self.enemy.origin);
+    
+    self.nextthink = time + 0.25;
+}
+#endif
+/*QUAKED turret_checkpoint (1 0 1) (-32 -32 -32) (32 32 32)
+-----------KEYS------------
+target: .targetname of next waypoint in chain.
+wait:   Pause at this point # seconds.
+-----------SPAWNFLAGS-----------
+---------NOTES----------
+If a loop is of targets are formed, any unit entering this loop will patrol it indefinitly.
+If the checkpoint chain in not looped, the unit will go "Roaming" when the last point is reached.
+*/
+//float tc_acum;
+void turret_checkpoint_init()
+{
+    traceline(self.origin + '0 0 16', self.origin - '0 0 1024', MOVE_WORLDONLY, self);
+    setorigin(self, trace_endpos + '0 0 32');
+
+    if(self.target != "")
+    {
+        self.enemy = find(world, targetname, self.target);
+        if(self.enemy == world)
+            dprint("A turret_checkpoint faild to find its target!\n");
+    }
+    //self.think = turret_checkpoint_think;
+    //self.nextthink = time + tc_acum + 0.25;
+    //tc_acum += 0.25;
+}
+
+void spawnfunc_turret_checkpoint()
+{
+    setorigin(self,self.origin);
+    self.think = turret_checkpoint_init;
+    self.nextthink = time + 0.2;    
+}
+
+// Compat.
+void spawnfunc_walker_checkpoint()
+{
+    self.classname = "turret_checkpoint";
+    spawnfunc_turret_checkpoint();
+}
diff --git a/qcsrc/common/turrets/cl_turrets.qc b/qcsrc/common/turrets/cl_turrets.qc
new file mode 100644 (file)
index 0000000..3365b92
--- /dev/null
@@ -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 (file)
index 0000000..b616782
--- /dev/null
@@ -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 (file)
index 0000000..c5bb1fb
--- /dev/null
@@ -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 (file)
index 0000000..f89ef15
--- /dev/null
@@ -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/common/turrets/targettrigger.qc b/qcsrc/common/turrets/targettrigger.qc
new file mode 100644 (file)
index 0000000..6551064
--- /dev/null
@@ -0,0 +1,38 @@
+void spawnfunc_turret_targettrigger();
+void turret_targettrigger_touch();
+
+void turret_targettrigger_touch()
+{
+    entity e;
+    if (self.cnt > time) return;
+    entity oldself;
+    oldself = self;
+
+    e = find(world, targetname, self.target);
+    while (e)
+    {
+        if (e.turret_flags & TUR_FLAG_RECIEVETARGETS)
+        {
+            self = e;
+            if(e.turret_addtarget)
+                e.turret_addtarget(other,oldself);
+        }
+
+        e = find(e, targetname, oldself.target);
+    }
+
+    oldself.cnt = time + 0.5;
+
+    self = oldself;
+}
+
+/*QUAKED turret_targettrigger (.5 .5 .5) ?
+*/
+void spawnfunc_turret_targettrigger()
+{
+    if(!autocvar_g_turrets) { remove(self); return; }
+
+    InitTrigger ();
+
+    self.touch = turret_targettrigger_touch;
+}
diff --git a/qcsrc/common/turrets/turrets.qc b/qcsrc/common/turrets/turrets.qc
new file mode 100644 (file)
index 0000000..4c588e9
--- /dev/null
@@ -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 (file)
index 0000000..5491746
--- /dev/null
@@ -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 (file)
index 0000000..fea43eb
--- /dev/null
@@ -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 (file)
index 0000000..02bd710
--- /dev/null
@@ -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 (file)
index 0000000..d2a3c40
--- /dev/null
@@ -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 (file)
index 0000000..b097cae
--- /dev/null
@@ -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 (file)
index 0000000..4fc8dcb
--- /dev/null
@@ -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 (file)
index 0000000..57267ef
--- /dev/null
@@ -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 (file)
index 0000000..1c97996
--- /dev/null
@@ -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 (file)
index 0000000..4fd0a65
--- /dev/null
@@ -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 (file)
index 0000000..8f925aa
--- /dev/null
@@ -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 (file)
index 0000000..22f272b
--- /dev/null
@@ -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 (file)
index 0000000..2878c31
--- /dev/null
@@ -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 (file)
index 0000000..644db2c
--- /dev/null
@@ -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 (file)
index 0000000..6de5902
--- /dev/null
@@ -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
index db6f04c62e2eaacc803b2998572c3c2161359f90..451a092af8401a01d4c39fcbe4006592f51e9ce2 100644 (file)
@@ -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)))
index 65b4e67a427b5eca7ba99208d874cec288d466be..4e59c584671c405a7343a3774c2a0915de15cea7 100644 (file)
@@ -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 (file)
index 0000000..de1740a
--- /dev/null
@@ -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 (file)
index 0000000..9a2598f
--- /dev/null
@@ -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 (file)
index 0000000..67701e4
--- /dev/null
@@ -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 (file)
index 0000000..25e622a
--- /dev/null
@@ -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/common/vehicles/sv_vehicles.qh b/qcsrc/common/vehicles/sv_vehicles.qh
new file mode 100644 (file)
index 0000000..2e8844e
--- /dev/null
@@ -0,0 +1,104 @@
+// #define VEHICLES_USE_ODE
+
+// 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;
+.entity vehicle_shieldent;  /// Entity to disply the shild effect on damage
+.entity vehicle;
+.entity vehicle_viewport;
+.entity vehicle_hudmodel;
+.entity vehicle_controller;
+
+.entity gunner1;
+.entity gunner2;
+
+.float vehicle_health;      /// If self is player this is 0..100 indicating precentage of health left on vehicle. If self is vehile, this is the real health value.
+.float vehicle_energy;      /// If self is player this is 0..100 indicating precentage of energy left on vehicle. If self is vehile, this is the real energy value.
+.float vehicle_shield;      /// If self is player this is 0..100 indicating precentage of shield left on vehicle. If self is vehile, this is the real shield value.
+
+.float vehicle_ammo1;   /// If self is player this field's use depends on the individual vehile. If self is vehile, this is the real ammo1 value.
+.float vehicle_reload1; /// If self is player this field's use depends on the individual vehile. If self is vehile, this is the real reload1 value.
+.float vehicle_ammo2;   /// If self is player this field's use depends on the individual vehile. If self is vehile, this is the real ammo2 value.
+.float vehicle_reload2; /// If self is player this field's use depends on the individual vehile. If self is vehile, this is the real reload2 value.
+
+.float sound_nexttime;
+#define VOL_VEHICLEENGINE 1
+
+const float SVC_SETVIEWPORT   = 5;   // Net.Protocol 0x05
+const float SVC_SETVIEWANGLES = 10;  // Net.Protocol 0x0A
+const float SVC_UPDATEENTITY  = 128; // Net.Protocol 0x80
+
+#define VHSF_NORMAL 0
+#define VHSF_FACTORY 2
+
+.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
+
+// 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/common/vehicles/unit/bumblebee.qc b/qcsrc/common/vehicles/unit/bumblebee.qc
new file mode 100644 (file)
index 0000000..cd76b30
--- /dev/null
@@ -0,0 +1,1388 @@
+#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
+float autocvar_g_vehicle_bumblebee_speed_forward;
+float autocvar_g_vehicle_bumblebee_speed_strafe;
+float autocvar_g_vehicle_bumblebee_speed_up;
+float autocvar_g_vehicle_bumblebee_speed_down;
+float autocvar_g_vehicle_bumblebee_turnspeed;
+float autocvar_g_vehicle_bumblebee_pitchspeed;
+float autocvar_g_vehicle_bumblebee_pitchlimit;
+float autocvar_g_vehicle_bumblebee_friction;
+
+float autocvar_g_vehicle_bumblebee_energy;
+float autocvar_g_vehicle_bumblebee_energy_regen;
+float autocvar_g_vehicle_bumblebee_energy_regen_pause;
+
+float autocvar_g_vehicle_bumblebee_health;
+float autocvar_g_vehicle_bumblebee_health_regen;
+float autocvar_g_vehicle_bumblebee_health_regen_pause;
+
+float autocvar_g_vehicle_bumblebee_shield;
+float autocvar_g_vehicle_bumblebee_shield_regen;
+float autocvar_g_vehicle_bumblebee_shield_regen_pause;
+
+float autocvar_g_vehicle_bumblebee_cannon_cost;
+float autocvar_g_vehicle_bumblebee_cannon_damage;
+float autocvar_g_vehicle_bumblebee_cannon_radius;
+float autocvar_g_vehicle_bumblebee_cannon_refire;
+float autocvar_g_vehicle_bumblebee_cannon_speed;
+float autocvar_g_vehicle_bumblebee_cannon_spread;
+float autocvar_g_vehicle_bumblebee_cannon_force;
+
+float autocvar_g_vehicle_bumblebee_cannon_ammo;
+float autocvar_g_vehicle_bumblebee_cannon_ammo_regen;
+float autocvar_g_vehicle_bumblebee_cannon_ammo_regen_pause;
+
+var float autocvar_g_vehicle_bumblebee_cannon_lock = 0;
+
+float autocvar_g_vehicle_bumblebee_cannon_turnspeed;
+float autocvar_g_vehicle_bumblebee_cannon_pitchlimit_down;
+float autocvar_g_vehicle_bumblebee_cannon_pitchlimit_up;
+float autocvar_g_vehicle_bumblebee_cannon_turnlimit_in;
+float autocvar_g_vehicle_bumblebee_cannon_turnlimit_out;
+
+
+float autocvar_g_vehicle_bumblebee_raygun_turnspeed;
+float autocvar_g_vehicle_bumblebee_raygun_pitchlimit_down;
+float autocvar_g_vehicle_bumblebee_raygun_pitchlimit_up;
+float autocvar_g_vehicle_bumblebee_raygun_turnlimit_sides;
+
+float autocvar_g_vehicle_bumblebee_raygun_range;
+float autocvar_g_vehicle_bumblebee_raygun_dps;
+float autocvar_g_vehicle_bumblebee_raygun_aps;
+float autocvar_g_vehicle_bumblebee_raygun_fps;
+
+float autocvar_g_vehicle_bumblebee_raygun;
+float autocvar_g_vehicle_bumblebee_healgun_hps;
+float autocvar_g_vehicle_bumblebee_healgun_hmax;
+float autocvar_g_vehicle_bumblebee_healgun_aps;
+float autocvar_g_vehicle_bumblebee_healgun_amax;
+float autocvar_g_vehicle_bumblebee_healgun_sps;
+float autocvar_g_vehicle_bumblebee_healgun_locktime;
+
+float autocvar_g_vehicle_bumblebee_respawntime;
+
+float autocvar_g_vehicle_bumblebee_blowup_radius;
+float autocvar_g_vehicle_bumblebee_blowup_coredamage;
+float autocvar_g_vehicle_bumblebee_blowup_edgedamage;
+float autocvar_g_vehicle_bumblebee_blowup_forceintensity;
+var vector autocvar_g_vehicle_bumblebee_bouncepain;
+
+var float autocvar_g_vehicle_bumblebee = 0;
+
+
+float bumble_raygun_send(entity to, float sf);
+
+void bumblebee_fire_cannon(entity _gun, string _tagname, entity _owner)
+{
+       vector v = gettaginfo(_gun, gettagindex(_gun, _tagname));
+       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 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;
+
+       float _in, _out;
+       vehic.angles_x *= -1;
+       makevectors(vehic.angles);
+       vehic.angles_x *= -1;
+       if((gun == vehic.gun1))
+       {
+               _in = autocvar_g_vehicle_bumblebee_cannon_turnlimit_in;
+               _out = autocvar_g_vehicle_bumblebee_cannon_turnlimit_out;
+               setorigin(gunner, vehic.origin + v_up * -16 + v_forward * -16 + v_right * 128);
+       }
+       else
+       {
+               _in = autocvar_g_vehicle_bumblebee_cannon_turnlimit_out;
+               _out = autocvar_g_vehicle_bumblebee_cannon_turnlimit_in;
+               setorigin(gunner, vehic.origin + v_up * -16 + v_forward * -16 + v_right * -128);
+       }
+
+       crosshair_trace(gunner);
+       vector _ct = trace_endpos;
+       vector ad;
+
+       if(autocvar_g_vehicle_bumblebee_cannon_lock)
+       {
+               if(gun.lock_time < time)
+                       gun.enemy = world;
+
+               if(trace_ent)
+                       if(trace_ent.movetype)
+                               if(trace_ent.takedamage)
+                                       if(!trace_ent.deadflag)
+                                       {
+                                               if(teamplay)
+                                               {
+                                                       if(trace_ent.team != gunner.team)
+                                                       {
+                                                               gun.enemy = trace_ent;
+                                                               gun.lock_time = time + 5;
+                                                       }
+                                               }
+                                               else
+                                               {
+                                                       gun.enemy = trace_ent;
+                                                       gun.lock_time = time + 5;
+                                               }
+                                       }
+       }
+
+       if(gun.enemy)
+       {
+               float distance, impact_time;
+
+               vector vf = real_origin(gun.enemy);
+               vector _vel = gun.enemy.velocity;
+               if(gun.enemy.movetype == MOVETYPE_WALK)
+                       _vel_z *= 0.1;
+
+
+               ad = vf;
+               distance = vlen(ad - gunner.origin);
+               impact_time = distance / autocvar_g_vehicle_bumblebee_cannon_speed;
+               ad = vf + _vel * impact_time;
+               trace_endpos = ad;
+
+
+               UpdateAuxiliaryXhair(gunner, ad, '1 0 1', 1);
+               vehicle_aimturret(vehic, trace_endpos, gun, "fire",
+                                                 autocvar_g_vehicle_bumblebee_cannon_pitchlimit_down * -1, autocvar_g_vehicle_bumblebee_cannon_pitchlimit_up,
+                                                 _out * -1,  _in,  autocvar_g_vehicle_bumblebee_cannon_turnspeed);
+
+       }
+       else
+               vehicle_aimturret(vehic, _ct, gun, "fire",
+                                                 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;
+                               bumblebee_fire_cannon(gun, "fire", gunner);
+                               gun.delay = time;
+                               gun.attack_finished_single = time + autocvar_g_vehicle_bumblebee_cannon_refire;
+                       }
+
+       VEHICLE_UPDATE_PLAYER(gunner, health, bumblebee);
+
+       if(vehic.vehicle_flags & VHF_HASSHIELD)
+               VEHICLE_UPDATE_PLAYER(gunner, shield, bumblebee);
+
+       ad = gettaginfo(gun, gettagindex(gun, "fire"));
+       traceline(ad, ad + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, gun);
+
+       UpdateAuxiliaryXhair(gunner, trace_endpos, ('1 0 0' * gunner.vehicle_reload1) + ('0 1 0' *(1 - gunner.vehicle_reload1)), 0);
+
+       if(vehic.owner)
+               UpdateAuxiliaryXhair(vehic.owner, trace_endpos, ('1 0 0' * gunner.vehicle_reload1) + ('0 1 0' *(1 - gunner.vehicle_reload1)), ((gunner == vehic.gunner1) ? 1 : 2));
+
+       vehic.solid = SOLID_BBOX;
+       gunner.BUTTON_ATCK = gunner.BUTTON_ATCK2 = gunner.BUTTON_CROUCH = 0;
+       gunner.vehicle_energy = (gun.vehicle_energy / autocvar_g_vehicle_bumblebee_cannon_ammo) * 100;
+
+       self = gunner;
+       return 1;
+}
+
+void bumblebee_gunner_exit(float _exitflag)
+{
+       if(IS_REAL_CLIENT(self))
+       {
+               msg_entity = self;
+               WriteByte(MSG_ONE, SVC_SETVIEWPORT);
+               WriteEntity(MSG_ONE, self);
+
+               WriteByte(MSG_ONE, SVC_SETVIEWANGLES);
+               WriteAngle(MSG_ONE, 0);
+               WriteAngle(MSG_ONE, self.vehicle.angles_y);
+               WriteAngle(MSG_ONE, 0);
+       }
+
+       CSQCVehicleSetup(self, HUD_NORMAL);
+       setsize(self, PL_MIN, PL_MAX);
+
+       self.takedamage     = DAMAGE_AIM;
+       self.solid          = SOLID_SLIDEBOX;
+       self.movetype       = MOVETYPE_WALK;
+       self.effects        &= ~EF_NODRAW;
+       self.alpha          = 1;
+       self.PlayerPhysplug = func_null;
+       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;
+    vh_vehicle = self.vehicle;
+    MUTATOR_CALLHOOK(VehicleExit);
+    self = vh_player;
+    self.vehicle = vh_vehicle;
+
+       self.vehicle.vehicle_hudmodel.viewmodelforclient = self.vehicle;
+
+       fixedmakevectors(self.vehicle.owner.angles);
+
+       if(self == self.vehicle.owner.gunner1)
+       {
+               self.vehicle.owner.gunner1 = world;
+       }
+       else if(self == self.vehicle.owner.gunner2)
+       {
+               self.vehicle.owner.gunner2 = world;
+               v_right *= -1;
+       }
+       else
+               dprint("^1self != gunner1 or gunner2, this is a BIG PROBLEM, tell tZork this happend.\n");
+
+       vector spot = self.vehicle.owner.origin + + v_up * 128 + v_right * 300;
+       spot = vehicles_findgoodexit(spot);
+       //setorigin(self , spot);
+
+       self.velocity = 0.75 * self.vehicle.owner.velocity + normalize(spot - self.vehicle.owner.origin) * 200;
+       self.velocity_z += 10;
+
+       self.vehicle.phase = time + 5;
+       self.vehicle        = world;
+}
+
+float bumblebee_gunner_enter()
+{
+       RemoveGrapplingHook(other);
+       entity _gun, _gunner;
+       if(!self.gunner1)
+       {
+               _gun = self.gun1;
+               _gunner = self.gunner1;
+               self.gunner1 = other;
+       }
+       else if(!self.gunner2)
+       {
+               _gun = self.gun2;
+               _gunner = self.gunner2;
+               self.gunner2 = other;
+       }
+       else
+       {
+               dprint("^1ERROR:^7Tried to enter a fully occupied vehicle!\n");
+               return FALSE;
+       }
+
+       _gunner            = other;
+       _gunner.vehicle    = _gun;
+       _gun.switchweapon  = other.switchweapon;
+       _gun.vehicle_exit  = bumblebee_gunner_exit;
+
+       other.angles            = self.angles;
+       other.takedamage        = DAMAGE_NO;
+       other.solid             = SOLID_NOT;
+       other.movetype          = MOVETYPE_NOCLIP;
+       other.alpha             = -1;
+       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    = bumblebee_gunner_frame;
+       other.flags             &= ~FL_ONGROUND;
+
+       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);
+
+    vh_player = other;
+    vh_vehicle = _gun;
+    MUTATOR_CALLHOOK(VehicleEnter);
+    other = vh_player;
+    _gun = vh_vehicle;
+
+       return TRUE;
+}
+
+float vehicles_valid_pilot()
+{
+       if (!IS_PLAYER(other))
+               return FALSE;
+
+       if(other.deadflag != DEAD_NO)
+               return FALSE;
+
+       if(other.vehicle != world)
+               return FALSE;
+
+       if (!IS_REAL_CLIENT(other))
+               if(!autocvar_g_vehicles_allow_bots)
+                       return FALSE;
+
+       if(teamplay && other.team != self.team)
+               return FALSE;
+
+       return TRUE;
+}
+
+void bumblebee_touch()
+{
+       if(autocvar_g_vehicles_enter) { return; }
+
+       if(self.gunner1 != world && self.gunner2 != world)
+       {
+               vehicles_touch();
+               return;
+       }
+
+       if(vehicles_valid_pilot())
+       {
+               if(self.gun1.phase <= time)
+                       if(bumblebee_gunner_enter())
+                               return;
+
+               if(self.gun2.phase <= time)
+                       if(bumblebee_gunner_enter())
+                               return;
+       }
+
+       vehicles_touch();
+}
+
+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,
+                                                                          self.gun1.vehicle_energy + autocvar_g_vehicle_bumblebee_cannon_ammo_regen * frametime);
+
+       if(self.gun2.delay + autocvar_g_vehicle_bumblebee_cannon_ammo_regen_pause < time)
+               self.gun2.vehicle_energy = min(autocvar_g_vehicle_bumblebee_cannon_ammo,
+                                                                          self.gun2.vehicle_energy + autocvar_g_vehicle_bumblebee_cannon_ammo_regen * frametime);
+
+       if(self.vehicle_flags  & VHF_SHIELDREGEN)
+               vehicles_regen(self.dmg_time, vehicle_shield, autocvar_g_vehicle_bumblebee_shield, autocvar_g_vehicle_bumblebee_shield_regen_pause, autocvar_g_vehicle_bumblebee_shield_regen, frametime, TRUE);
+
+       if(self.vehicle_flags  & VHF_HEALTHREGEN)
+               vehicles_regen(self.dmg_time, vehicle_health, autocvar_g_vehicle_bumblebee_health, autocvar_g_vehicle_bumblebee_health_regen_pause, autocvar_g_vehicle_bumblebee_health_regen, frametime, FALSE);
+
+       if(self.vehicle_flags  & VHF_ENERGYREGEN)
+               vehicles_regen(self.wait, vehicle_energy, autocvar_g_vehicle_bumblebee_energy, autocvar_g_vehicle_bumblebee_energy_regen_pause, autocvar_g_vehicle_bumblebee_energy_regen, frametime, FALSE);
+
+}
+
+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;
+               pilot.BUTTON_ATCK = pilot.BUTTON_ATCK2 = 0;
+               return 1;
+       }
+
+       bumblebee_regen();
+
+       crosshair_trace(pilot);
+
+       vector vang;
+       float ftmp;
+
+       vang = vehic.angles;
+       newvel = vectoangles(normalize(trace_endpos - self.origin + '0 0 32'));
+       vang_x *= -1;
+       newvel_x *= -1;
+       if(newvel_x > 180)  newvel_x -= 360;
+       if(newvel_x < -180) newvel_x += 360;
+       if(newvel_y > 180)  newvel_y -= 360;
+       if(newvel_y < -180) newvel_y += 360;
+
+       ftmp = shortangle_f(pilot.v_angle_y - vang_y, vang_y);
+       if(ftmp > 180)  ftmp -= 360;
+       if(ftmp < -180) ftmp += 360;
+       vehic.avelocity_y = bound(-autocvar_g_vehicle_bumblebee_turnspeed, ftmp + vehic.avelocity_y * 0.9, autocvar_g_vehicle_bumblebee_turnspeed);
+
+       // Pitch
+       ftmp = 0;
+       if(pilot.movement_x > 0 && vang_x < autocvar_g_vehicle_bumblebee_pitchlimit)
+               ftmp = 4;
+       else if(pilot.movement_x < 0 && vang_x > -autocvar_g_vehicle_bumblebee_pitchlimit)
+               ftmp = -8;
+
+       newvel_x = bound(-autocvar_g_vehicle_bumblebee_pitchlimit, newvel_x , autocvar_g_vehicle_bumblebee_pitchlimit);
+       ftmp = vang_x - bound(-autocvar_g_vehicle_bumblebee_pitchlimit, newvel_x + ftmp, autocvar_g_vehicle_bumblebee_pitchlimit);
+       vehic.avelocity_x = bound(-autocvar_g_vehicle_bumblebee_pitchspeed, ftmp + vehic.avelocity_x * 0.9, autocvar_g_vehicle_bumblebee_pitchspeed);
+
+       vehic.angles_x = anglemods(vehic.angles_x);
+       vehic.angles_y = anglemods(vehic.angles_y);
+       vehic.angles_z = anglemods(vehic.angles_z);
+
+       makevectors('0 1 0' * vehic.angles_y);
+       newvel = vehic.velocity * -autocvar_g_vehicle_bumblebee_friction;
+
+       if(pilot.movement_x != 0)
+       {
+               if(pilot.movement_x > 0)
+                       newvel += v_forward  * autocvar_g_vehicle_bumblebee_speed_forward;
+               else if(pilot.movement_x < 0)
+                       newvel -= v_forward  * autocvar_g_vehicle_bumblebee_speed_forward;
+       }
+
+       if(pilot.movement_y != 0)
+       {
+               if(pilot.movement_y < 0)
+                       newvel -= v_right * autocvar_g_vehicle_bumblebee_speed_strafe;
+               else if(pilot.movement_y > 0)
+                       newvel += v_right * autocvar_g_vehicle_bumblebee_speed_strafe;
+               ftmp = newvel * v_right;
+               ftmp *= frametime * 0.1;
+               vehic.angles_z = bound(-15, vehic.angles_z + ftmp, 15);
+       }
+       else
+       {
+               vehic.angles_z *= 0.95;
+               if(vehic.angles_z >= -1 && vehic.angles_z <= -1)
+                       vehic.angles_z = 0;
+       }
+
+       if(pilot.BUTTON_CROUCH)
+               newvel -=   v_up * autocvar_g_vehicle_bumblebee_speed_down;
+       else if(pilot.BUTTON_JUMP)
+               newvel +=  v_up * autocvar_g_vehicle_bumblebee_speed_up;
+
+       vehic.velocity  += newvel * frametime;
+       pilot.velocity = pilot.movement  = vehic.velocity;
+
+
+       if(autocvar_g_vehicle_bumblebee_healgun_locktime)
+       {
+               if(vehic.tur_head.lock_time < time || vehic.tur_head.enemy.deadflag)
+                       vehic.tur_head.enemy = world;
+
+               if(trace_ent)
+               if(trace_ent.movetype)
+               if(trace_ent.takedamage)
+               if(!trace_ent.deadflag)
+               {
+                       if(teamplay)
+                       {
+                               if(trace_ent.team == pilot.team)
+                               {
+                                       vehic.tur_head.enemy = trace_ent;
+                                       vehic.tur_head.lock_time = time + autocvar_g_vehicle_bumblebee_healgun_locktime;
+                               }
+                       }
+                       else
+                       {
+                               vehic.tur_head.enemy = trace_ent;
+                               vehic.tur_head.lock_time = time + autocvar_g_vehicle_bumblebee_healgun_locktime;
+                       }
+               }
+
+               if(vehic.tur_head.enemy)
+               {
+                       trace_endpos = real_origin(vehic.tur_head.enemy);
+                       UpdateAuxiliaryXhair(pilot, trace_endpos, '0 0.75 0', 0);
+               }
+       }
+
+       vang = vehicle_aimturret(vehic, trace_endpos, self.gun3, "fire",
+                                         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;
+               vehic.gun3.enemy.effects &= ~EF_NODRAW;
+
+               vehic.gun3.enemy.hook_start = gettaginfo(vehic.gun3, gettagindex(vehic.gun3, "fire"));
+               vehic.gun3.enemy.SendFlags |= BRG_START;
+
+               traceline(vehic.gun3.enemy.hook_start, vehic.gun3.enemy.hook_start + v_forward * autocvar_g_vehicle_bumblebee_raygun_range, MOVE_NORMAL, vehic);
+
+               if(trace_ent)
+               {
+                       if(autocvar_g_vehicle_bumblebee_raygun)
+                       {
+                               Damage(trace_ent, vehic, pilot, autocvar_g_vehicle_bumblebee_raygun_dps * sys_frametime, DEATH_GENERIC, trace_endpos, v_forward * autocvar_g_vehicle_bumblebee_raygun_fps * sys_frametime);
+                               vehic.vehicle_energy -= autocvar_g_vehicle_bumblebee_raygun_aps * sys_frametime;
+                       }
+                       else
+                       {
+                               if(trace_ent.deadflag == DEAD_NO)
+                                       if((teamplay && trace_ent.team == pilot.team) || !teamplay)
+                                       {
+
+                                               if(trace_ent.vehicle_flags & VHF_ISVEHICLE)
+                                               {
+                                                       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.max_health);
+                                               }
+                                               else if(IS_CLIENT(trace_ent))
+                                               {
+                                                       if(trace_ent.health <= autocvar_g_vehicle_bumblebee_healgun_hmax && autocvar_g_vehicle_bumblebee_healgun_hps)
+                                                               trace_ent.health = min(trace_ent.health + autocvar_g_vehicle_bumblebee_healgun_hps * frametime, autocvar_g_vehicle_bumblebee_healgun_hmax);
+
+                                                       if(trace_ent.armorvalue <= autocvar_g_vehicle_bumblebee_healgun_amax && autocvar_g_vehicle_bumblebee_healgun_aps)
+                                                               trace_ent.armorvalue = min(trace_ent.armorvalue + autocvar_g_vehicle_bumblebee_healgun_aps * frametime, autocvar_g_vehicle_bumblebee_healgun_amax);
+
+                                                       trace_ent.health = min(trace_ent.health + autocvar_g_vehicle_bumblebee_healgun_hps * frametime, autocvar_g_vehicle_bumblebee_healgun_hmax);
+                                               }
+                                               else if(IS_TURRET(trace_ent))
+                                               {
+                                                       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;
+                                               }
+                                       }
+                       }
+               }
+
+               vehic.gun3.enemy.hook_end = trace_endpos;
+               setorigin(vehic.gun3.enemy, trace_endpos);
+               vehic.gun3.enemy.SendFlags |= BRG_END;
+
+               vehic.wait = time + 1;
+       }
+       else
+               vehic.gun3.enemy.effects |= EF_NODRAW;
+       /*{
+               if(vehic.gun3.enemy)
+                       remove(vehic.gun3.enemy);
+
+               vehic.gun3.enemy = world;
+       }
+       */
+
+       VEHICLE_UPDATE_PLAYER(pilot, health, bumblebee);
+       VEHICLE_UPDATE_PLAYER(pilot, energy, bumblebee);
+
+       pilot.vehicle_ammo1 = (vehic.gun1.vehicle_energy / autocvar_g_vehicle_bumblebee_cannon_ammo) * 100;
+       pilot.vehicle_ammo2 = (vehic.gun2.vehicle_energy / autocvar_g_vehicle_bumblebee_cannon_ammo) * 100;
+
+       if(vehic.vehicle_flags & VHF_HASSHIELD)
+               VEHICLE_UPDATE_PLAYER(pilot, shield, bumblebee);
+
+       vehic.angles_x *= -1;
+       makevectors(vehic.angles);
+       vehic.angles_x *= -1;
+       setorigin(pilot, vehic.origin + v_up * 48 + v_forward * 160);
+
+       pilot.BUTTON_ATCK = pilot.BUTTON_ATCK2 = pilot.BUTTON_CROUCH = 0;
+       self = pilot;
+
+       return 1;
+}
+
+void bumblebee_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 < 16)
+               self.think      = vehicles_think;
+
+       self.nextthink = time;
+       
+       CSQCMODEL_AUTOUPDATE();
+}
+
+void bumblebee_exit(float eject)
+{
+       if(self.owner.vehicleid == VEH_BUMBLEBEE)
+       {
+               bumblebee_gunner_exit(eject);
+               return;
+       }
+
+       self.touch = vehicles_touch;
+
+       if(self.deadflag == DEAD_NO)
+       {
+               self.think = bumblebee_land;
+               self.nextthink  = time;
+       }
+       
+       self.movetype = MOVETYPE_TOSS;
+
+       if(!self.owner)
+               return;
+
+       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 * 300;
+       else
+               spot = self.origin + v_up * 128 - v_forward * 300;
+
+       spot = vehicles_findgoodexit(spot);
+
+       // Hide beam
+       if(self.gun3.enemy || !wasfreed(self.gun3.enemy)) {
+               self.gun3.enemy.effects |= EF_NODRAW;
+    }
+
+       self.owner.velocity = 0.75 * self.vehicle.velocity + normalize(spot - self.vehicle.origin) * 200;
+       self.owner.velocity_z += 10;
+       setorigin(self.owner, spot);
+
+       antilag_clear(self.owner);
+    self.owner = world;
+}
+
+void bumblebee_blowup()
+{
+       RadiusDamage(self, self.enemy, autocvar_g_vehicle_bumblebee_blowup_coredamage,
+                                autocvar_g_vehicle_bumblebee_blowup_edgedamage,
+                                autocvar_g_vehicle_bumblebee_blowup_radius, self, world,
+                                autocvar_g_vehicle_bumblebee_blowup_forceintensity,
+                                DEATH_VH_BUMB_DEATH, world);
+
+       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;
+
+       remove(self);
+}
+
+void bumblebee_diethink()
+{
+       if(time >= self.wait)
+               self.think = bumblebee_blowup;
+
+       if(random() < 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;
+}
+
+float bumble_raygun_send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_BUMBLE_RAYGUN);
+
+       WriteByte(MSG_ENTITY, sf);
+       if(sf & BRG_SETUP)
+       {
+               WriteByte(MSG_ENTITY, num_for_edict(self.realowner));
+               WriteByte(MSG_ENTITY, self.realowner.team);
+               WriteByte(MSG_ENTITY, self.cnt);
+       }
+
+       if(sf & BRG_START)
+       {
+               WriteCoord(MSG_ENTITY, self.hook_start_x);
+               WriteCoord(MSG_ENTITY, self.hook_start_y);
+               WriteCoord(MSG_ENTITY, self.hook_start_z);
+       }
+
+       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);
+       }
+
+       return TRUE;
+}
+
+void spawnfunc_vehicle_bumblebee()
+{
+       if(!autocvar_g_vehicle_bumblebee) { remove(self); return; }
+       if(!vehicle_initialize(VEH_BUMBLEBEE, FALSE)) { remove(self); return; }
+}
+
+float v_bumblebee(float req)
+{
+       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;
+                       }
+
+                       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:
+               {
+                       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;
+               }
+       }
+
+       return TRUE;
+}
+
+#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_r_letterbox)
+               return;
+
+       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(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)
+       {
+               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 = 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);
+
+       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()
+{
+       float _len;
+       vector _dir;
+       vector _vtmp1, _vtmp2;
+
+       _len = vlen(self.origin - self.move_origin);
+       _dir = normalize(self.move_origin - self.origin);
+
+       if(self.total_damages < time)
+       {
+               boxparticles(self.traileffect, self, self.origin, self.origin + _dir * -64, _dir * -_len , _dir * -_len, 1, PARTICLES_USEALPHA);
+               boxparticles(self.lip, self, self.move_origin, self.move_origin + _dir * -64, _dir * -200 , _dir * -200, 1, PARTICLES_USEALPHA);
+               self.total_damages = time + 0.1;
+       }
+
+       float i, df, sz, al;
+       for(i = -0.1; i < 0.2; i += 0.1)
+       {
+               df = DRAWFLAG_NORMAL; //((random() < 0.5) ? DRAWFLAG_ADDITIVE : DRAWFLAG_SCREEN);
+               sz = 5 + random() * 5;
+               al = 0.25 + random() * 0.5;
+               _vtmp1 = self.origin + _dir * _len * (0.25 + i);
+               _vtmp1 += (randomvec() * (_len * 0.2) * (frametime * 2));       //self.raygun_l1;
+               Draw_CylindricLine(self.origin, _vtmp1, sz, "gfx/colors/white.tga", 1, 1, self.colormod, al, df, view_origin);
+
+               _vtmp2 = self.origin + _dir * _len * (0.5 + i);
+               _vtmp2 += (randomvec() * (_len * 0.2) * (frametime * 5));       //self.raygun_l2;
+               Draw_CylindricLine(_vtmp1, _vtmp2, sz, "gfx/colors/white.tga", 1, 1, self.colormod, al, df, view_origin);
+
+               _vtmp1 = self.origin + _dir * _len * (0.75 + i);
+               _vtmp1 += randomvec() * (_len * 0.2) * (frametime * 10);     //self.raygun_l3;
+               Draw_CylindricLine(_vtmp2, _vtmp1, sz, "gfx/colors/white.tga", 1, 1, self.colormod, al, df, view_origin);
+
+               Draw_CylindricLine(_vtmp1, self.move_origin +  randomvec() * 32, sz, "gfx/colors/white.tga", 1, 1, self.colormod, al, df, view_origin);
+       }
+}
+
+void bumble_raygun_read(float bIsNew)
+{
+       float sf = ReadByte();
+
+       if(sf & BRG_SETUP)
+       {
+               self.cnt  = ReadByte();
+               self.team = ReadByte();
+               self.cnt  = ReadByte();
+
+               if(self.cnt)
+                       self.colormod = '1 0 0';
+               else
+                       self.colormod = '0 1 0';
+
+               self.traileffect = particleeffectnum("healray_muzzleflash");
+               self.lip = particleeffectnum("healray_impact");
+
+               self.draw = bumble_raygun_draw;
+       }
+
+
+       if(sf & BRG_START)
+       {
+               self.origin_x = ReadCoord();
+               self.origin_y = ReadCoord();
+               self.origin_z = ReadCoord();
+               setorigin(self, self.origin);
+       }
+
+       if(sf & BRG_END)
+       {
+               self.move_origin_x = ReadCoord();
+               self.move_origin_y = ReadCoord();
+               self.move_origin_z = ReadCoord();
+       }
+}
+
+float v_bumblebee(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(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 // REGISTER_VEHICLE
diff --git a/qcsrc/common/vehicles/unit/racer.qc b/qcsrc/common/vehicles/unit/racer.qc
new file mode 100644 (file)
index 0000000..dae5219
--- /dev/null
@@ -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 (file)
index 0000000..ed5c00a
--- /dev/null
@@ -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 (file)
index 0000000..8ecdb36
--- /dev/null
@@ -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 (file)
index 0000000..94266d2
--- /dev/null
@@ -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 (file)
index 0000000..7facf75
--- /dev/null
@@ -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 (file)
index 0000000..83fe5e3
--- /dev/null
@@ -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 (file)
index 0000000..db5bb8d
--- /dev/null
@@ -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 (file)
index 0000000..85b923a
--- /dev/null
@@ -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 (file)
index 0000000..d37d749
--- /dev/null
@@ -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
index 4f4cd2b3d8f35a7bf82d3f2fdda523034f1f9475..6576fcd60380ac125ed4afc9e5fac3d414d63901 100644 (file)
@@ -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"
index 80fcf5524b15afed5294ed374eac22bfa73bef48..2607e47cdd4382af758cb9242a17849b5d633264 100644 (file)
@@ -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);
index aa8d0a89b665bbbcd18a58bf3db6887fc98c00f8..fdb0e9e52d378ca86d954457af7ec98b28a2c9ef 100644 (file)
@@ -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;
                }
index d17826a9247908f7c1bceb42dcfc2d91645d585c..c0571cc20c24fc7dda9e49f74b117c6877f0c935 100644 (file)
@@ -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:
index cff16702dfd25fefbf9576594bdcebd8ee096df8..7460fe6d21ff9e37984bbee51badaf3da8d0081e 100644 (file)
@@ -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:
index 3ec86c782de9d781c9d8945d9c3eef04dcbd1902..c02c6be9f463d4359b3d6136c172d7fb8da5b29e 100644 (file)
@@ -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:
index 80c453a4d735a79a0efdea85065f3d40f6342d95..ead6567c88d15ad558afc333a05e5e70fb299ea1 100644 (file)
@@ -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;
                }
index fe3abf0a9a6d9f550c7cadcc2a37dced29f90274..9ad4997a958507b6b103769f1c0550db580bc48f 100644 (file)
@@ -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;
                }
index d3dbed2f9c9fd9a88d974d227c378460016570b4..b34ba4acd197f2bb0cdf26c91519157000daa38b 100644 (file)
@@ -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:
index 69777c4c2ccafcef98d0860e244ccfba854a447e..0aef9d82cbdc75db498f69acd1f0923e3dd82a27 100644 (file)
@@ -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:
index 3713ca9c75c417fe91dddbd2f3b44d0fc24e696e..f30b749014da79abcac622869bcba1ebfbcf6cbe 100644 (file)
@@ -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 (file)
index 0000000..f10ea5e
--- /dev/null
@@ -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
index 9c69c8d824b3d03c7db8ed18b32d7db39d7782b2..8714aa717a15361cc73d57fc318084078ee3c47b 100644 (file)
@@ -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:
index 2dac41e1badd5a236b30bba74ebddbb4c2b84930..b6df9e4a0cfde8b57d754f0c7848f97436e0b656 100644 (file)
@@ -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:
index de40fcb3933ec04462de65c03cd9287fdfbda626..e531756eff3a81c094af072ec99c1148e0097b56 100644 (file)
@@ -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:
index e1fb82f8f2bd427a0eb7767e43d5434f5fc7fb7d..fb14e9861881ac26a93588f7730fb2171c0370d8 100644 (file)
@@ -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 (file)
index 0000000..7f309cb
--- /dev/null
@@ -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
index 03c396cebabcc91f9b7e2086af6404d984283cdc..7257c669435b1c56dcfa16b3e8425a8e3b882cf0 100644 (file)
@@ -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:
index 81e1144434287fde8164cc48e27679ef7a20f117..d624c197fc5f2b0c425dd4b7a905de27d9c835d4 100644 (file)
@@ -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:
index d4c4d33941efc18591d9a168c918beda8f6d5422..dac77797b23d0b35a092888d999a6a5ddee1e656 100644 (file)
@@ -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:
index 759dc35eaecbb4496924c3b1e25c492f4b26ab0c..abf7afa852ea2a458d59f768c9a6f98b72df64d2 100644 (file)
@@ -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;
                }
index 9227cdab4ee15b1e2c9257e7e167aa319645709e..d6cd414348637cfdfc6342108b36f1093c05eacd 100644 (file)
@@ -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:
index e08cf676fb76eb92508241aec738317d3d824b0d..9a327a679b226a2188b524240a98e1b8f436c5a2 100644 (file)
@@ -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);
                        }
index 90ea15d5c473dc88c4bda842d46f5114e0c816a5..37aae84a39274e298ba63e46a20f25cf8eecaac6 100644 (file)
@@ -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);
index 6512d0430809ffdff549f8834460c49b0d254ad5..c61cd75bd693aa550357dab084afed1238d731b8 100644 (file)
@@ -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);
                                
index 284d9811df50c44c98205ca041a659162079493d..90287b789cef417482fac40eb71b59b10a4850d3 100644 (file)
@@ -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
index dca226f42018ec13421d15cc6b2329137ca96b8c..99ad7eb519a78a3f98046ff79e7bec2bc3aff0c8 100644 (file)
@@ -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;
index 63db0ac14b6ea777aef4e349f39e0d360682c9f5..c8a821be1b3a3e7a7ce9b33dbd64c5d83adc3285 100644 (file)
@@ -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();
 
index 8f4ec8b414f34fc2a42177f15cda496f59aa1a9b..6190e30b7cbb068a6632f5736019610b539c6ddb 100644 (file)
@@ -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
 
index 0d6c253709540b5f4f82ce8a9d50d7472a2dd21f..1bc57709724e97fa255bff319627ee45853aa5ef 100644 (file)
@@ -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
index 79b0f9023cd28dedfbde120252393dd53ffb172b..0e0378790593a8387572d6ca47526dc4f5970bac 100644 (file)
@@ -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
index 168045bd039230257c6a6e13e087b8855e3b2026..808d5a929d9bd3fee5c5923f4144e264a9c8db59 100644 (file)
@@ -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;
index 0ae33124717ba894248ec42f55b928117baf8b4c..96165fccaf2177bc4ed8f8be49f4a2c011db46c4 100644 (file)
@@ -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;
index 40b769ddd3fb4a78f3b156995a2cf53153800869..7114d004c1578e22ebab2f01574ed8a427012813 100644 (file)
@@ -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
index 043f8332c9c1fe47b2bc3b086526eb64a457a8de..e677e0cf18c664341a821380cf19345d1203cbea 100644 (file)
@@ -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);
index e58e6709730282677350265c9c55a953c8945850..9a0a70405d8d85398fa0656933a669ec2658a01b 100644 (file)
@@ -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 (file)
index 3b641d9..0000000
+++ /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 (file)
index dc942a3..0000000
+++ /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<bestvalue)
-               {
-                       bestvalue = cp1.wpcost;
-                       cp = cp1;
-                       self.havocbot_ons_target = cp1;
-               }
-       }
-
-       if (!cp)
-               return;
-
-//     dprint(self.netname, " chose cp ranked ", ftos(bestvalue), "\n");
-
-       if(cp.goalentity)
-       {
-               // Should be attacked
-               // Rate waypoints near it
-               found = FALSE;
-               best = world;
-               bestvalue = 99999999999;
-               for(radius=0; radius<1000 && !found; radius+=500)
-               {
-                       for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
-                       {
-                               if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
-                               if(wp.classname=="waypoint")
-                               if(checkpvs(wp.origin,cp))
-                               {
-                                       found = TRUE;
-                                       if(wp.cnt<bestvalue)
-                                       {
-                                               best = wp;
-                                               bestvalue = wp.cnt;
-                                       }
-                               }
-                       }
-               }
-
-               if(best)
-               {
-                       navigation_routerating(best, ratingscale, 10000);
-                       best.cnt += 1;
-
-                       self.havocbot_attack_time = 0;
-                       if(checkpvs(self.view_ofs,cp))
-                       if(checkpvs(self.view_ofs,best))
-                               self.havocbot_attack_time = time + 2;
-               }
-               else
-               {
-                       navigation_routerating(cp, ratingscale, 10000);
-               }
-       //      dprint(self.netname, " found an attackable controlpoint at ", vtos(cp.origin) ,"\n");
-       }
-       else
-       {
-               // Should be touched
-               // dprint(self.netname, " found a touchable controlpoint at ", vtos(cp.origin) ,"\n");
-               found = FALSE;
-
-               // Look for auto generated waypoint
-               if (!bot_waypoints_for_items)
-               for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
-               {
-                       if(wp.classname=="waypoint")
-                       {
-                               navigation_routerating(wp, ratingscale, 10000);
-                               found = TRUE;
-                       }
-               }
-
-               // Nothing found, rate the controlpoint itself
-               if (!found)
-                       navigation_routerating(cp, ratingscale, 10000);
-       }
-}
-
-float havocbot_goalrating_ons_generator_attack(float ratingscale)
-{
-       entity g, wp, bestwp;
-       float found, best;
-
-       for (g = findchain(classname, "onslaught_generator"); g; g = g.chain)
-       {
-               if(g.team == self.team || g.isshielded)
-                       continue;
-
-               // Should be attacked
-               // Rate waypoints near it
-               found = FALSE;
-               bestwp = world;
-               best = 99999999999;
-
-               for(wp=findradius(g.origin,400); wp; wp=wp.chain)
-               {
-                       if(wp.classname=="waypoint")
-                       if(checkpvs(wp.origin,g))
-                       {
-                               found = TRUE;
-                               if(wp.cnt<best)
-                               {
-                                       bestwp = wp;
-                                       best = wp.cnt;
-                               }
-                       }
-               }
-
-               if(bestwp)
-               {
-               //      dprint("waypoints found around generator\n");
-                       navigation_routerating(bestwp, ratingscale, 10000);
-                       bestwp.cnt += 1;
-
-                       self.havocbot_attack_time = 0;
-                       if(checkpvs(self.view_ofs,g))
-                       if(checkpvs(self.view_ofs,bestwp))
-                               self.havocbot_attack_time = time + 5;
-
-                       return TRUE;
-               }
-               else
-               {
-               //      dprint("generator found without waypoints around\n");
-                       // if there aren't waypoints near the generator go straight to it
-                       navigation_routerating(g, ratingscale, 10000);
-                       self.havocbot_attack_time = 0;
-                       return TRUE;
-               }
-       }
-       return FALSE;
-}
-
-void havocbot_role_ons_offense()
-{
-       if(self.deadflag != DEAD_NO)
-       {
-               self.havocbot_attack_time = 0;
-               havocbot_ons_reset_role(self);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + 120;
-
-       if (time > 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);
-}
index 7e3ddbb4340dd820253fccc87b5d2d2449ae2af1..59ca5274fecabcb89813e975ac1ac1e40a69d254 100644 (file)
@@ -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;
 }
index c1a637514275849c26b573a565e8fb499d9db86b..003e3bc92bd66b7fcdbb2d04b72aa132ab0db40f 100644 (file)
@@ -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)
index 03ab777b941c151ce93fe5cccc92e87cd1d9248e..b569e519dea56513c39649b76c32e0710ec2afc0 100644 (file)
@@ -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)
index 7446b7022a229c2c46589d7cfc9c1d92423a05c2..f8323142dfb54f8f3ab85cf62c340582147246ff 100644 (file)
@@ -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
 }
index fb283bb2a3a488b0bc8bc6d2ed627d7f6917c26a..12d69600074e67a4c43a9230998bbe261f9fb59e 100644 (file)
 .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);
                }
        }
index d899b3db3a8f85587c1c9ab152f043b2db29f93e..64eadf4a3b554ccdb53f82371cc17bfb1b343623 100644 (file)
@@ -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 (file)
index 0000000..f27ec9f
--- /dev/null
@@ -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);
+}
index 898e7db18faf59a78afe5988320a8a21baa02ccb..1a4768d27db0e55d6db13294f244b9a6c4828027 100644 (file)
@@ -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 <random> <monster> [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 <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()
index 04ed4b2840491a0bb2026386dc66e9c31215ec60..3156bc114aaadd97a80987c2a024169193fa0550 100644 (file)
@@ -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") \
index 27ed6b8694e16ce8406b98b76cb7d95f020a19f7..f3d619d2f72ecce07ee52d7707249de9f484a3a9 100644 (file)
@@ -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
index 03bd80cefc2f71fea3ddcdb4d7cbaa60bef1b2be..be19cbcf32ea7f61e02929def3b5a03f2e9741d7 100644 (file)
@@ -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);
index 522ef4b7a4d5f2cda5960f3e3dffd0ee18273e8e..8659fecf3dfa6267e91e3ea148541a8230e2d7f5 100644 (file)
@@ -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 (file)
index 0000000..d088e2e
--- /dev/null
@@ -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 (file)
index 0000000..e489f90
--- /dev/null
@@ -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;
index 757ee65e2799449c1afd71e68aaf2339596faada..533b4ccb946215e4e328d1de115ef00a24f25b55 100644 (file)
@@ -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;
index 0bf71059e6029ae10d9e94b1ed0ba861842cd609..46c5ed9360ff5becd21016180ff51a0f29ee5ed5 100644 (file)
@@ -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)
        {
index 4840e15dd25d6916239fb221de8fbd721d47cec0..55bf4cb48de4f50be8fb296804fcb243f5f5e418 100644 (file)
@@ -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)
        {
index eab482618bc3833d47cd74250cd706fbf827063b..99294703c9e753de4adc6d0b5576add8e9ca6906 100644 (file)
@@ -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);
index fd0dc7861e20b1cee25cdfaa0545231952d78d57..b27c6528c60248c758e3bb20f75116224e710101 100644 (file)
@@ -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 (file)
index f0ea33b..0000000
+++ /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
index c5fb08c965c2afa88d6f6f3aecf1d58974c333bd..d3500da9a991504602883c7039cd5ce960406db0 100644 (file)
@@ -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); }
+
 
 //=============================================================================
 
index 0fd5d2de3c2eee8f0e2a8076f22ee753b6965992..d05a458eae2963714a76ee5e9155cf267ed14bb0 100644 (file)
@@ -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 (file)
index 0000000..13aa35a
--- /dev/null
@@ -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 (file)
index 0000000..a991874
--- /dev/null
@@ -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 (file)
index 0000000..b3cca02
--- /dev/null
@@ -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
index 81306a8b1c263497687e155ed7f23f0c0ecf09af..a8323e92888ba44ad28fafd6869f95084c87f23c 100644 (file)
@@ -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":
index 1ba8f2663e730b8796da2e6fb8cd3b1592d61eb0..248f923d017d00b32acb9a462de96a93b3ba1ff9 100644 (file)
@@ -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)
 {
index 1c33e39739af41cf886143ee6aacdce77ec75b98..9bab139fee9e27f1290eb40f062b15ed650c6683 100644 (file)
@@ -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
index 8a8c50b38b8ab18ab31f40ee71b1fe39f4119937..ec353346c2db6f4c99b303a8d411b4f205785856 100644 (file)
@@ -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
index 9aecf87f29824919cefe61c6f6cb79ca6d4f4a68..1d04413158ba8fe4a5f73e688afb12b649c933b1 100644 (file)
@@ -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();
index 4ba1830048ec4f7733aa62454842e816b4c8c082..b44e8ab31182e1de88523761340b59d1a64bcfbf 100644 (file)
@@ -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 (file)
index 0000000..07990f2
--- /dev/null
@@ -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 (file)
index 0000000..ee6a1fd
--- /dev/null
@@ -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
index 4e051d197298ba1ac9cdb0b6da519a515fd42d3c..eff1c082e3dd5b536d45c174d33351829e992350 100644 (file)
@@ -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
        {
index ca4961eddc24231a6056943a0b2bffcdb5dcec06..2e80aa45f33d320058672a0a4c456a29d1e659e3 100644 (file)
@@ -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;
index 9c674d45d89933fa9f24e8bc13e31e7f2a7995d4..2d054c204b8a22654f47f7a049befd523dffb021 100644 (file)
@@ -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 (file)
index 0000000..81caee5
--- /dev/null
@@ -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;
+}
index 8e4d929beefed58d4163d6c5b5c119c6e4f9fc78..182ec1d0f9e1106b88bbfe520d534841819cfab4 100644 (file)
@@ -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;
 }
index 6b5b334e4906ef969597e3600070d172e8ecb49e..fe962f8f67e54a91d470a15a75a7d4dd4a4944f6 100644 (file)
@@ -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;
index 5ab96277b9e4a286a04999ab8c0832fb09d0fdc2..b1306a8d0fccc0afacd1656da605489cd87ad470 100644 (file)
@@ -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 (file)
index 0000000..48ddda2
--- /dev/null
@@ -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 (file)
index 0000000..97cfc20
--- /dev/null
@@ -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 (file)
index 0000000..9f75f89
--- /dev/null
@@ -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
index dcdd3365af9d9ca468d805071809d26acd3c2dc6..ec4da5fb790a59bb0160a0725f0bf20c32e3a1d3 100644 (file)
@@ -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 (file)
index 0000000..b9f9a9f
--- /dev/null
@@ -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 (file)
index 0000000..4d3301c
--- /dev/null
@@ -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
index 7e1006ec1d92252e624fda40366e08dee47feb99..f46d6883b7a237355e99ef34b375dc384e65c182 100644 (file)
@@ -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
        {
index 7b3ea98af6dc0dc558224870e604164259e969d3..e31bf110aa5611e04196a969189de0c2e2fd671b 100644 (file)
-#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
        {
index 611f7f065e10be5eda8f984c10166c76ea99fdf6..d63b0568570ef48d7ef149efb05244897a644f67 100644 (file)
-// 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;
index 0684ac8edb20b90dbbbcc1ceb3d3d66cae1d8201..a548e1082e2d1695317472c76eb0f7a611731200 100644 (file)
@@ -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
        {
index 346ae1eee4493eed30b0649d18a2747d8d824e23..8c894cc53991492947d6d5e863fa2e6117ba8068 100644 (file)
@@ -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
        {
index 74cba2897387862d8893b708cd263c1c8b8c8c69..752a6638764766befd1d46f3fbb2f260ff8e7aa8 100644 (file)
-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<bestvalue)
+               {
+                       bestvalue = cp1.wpcost;
+                       cp = cp1;
+                       self.havocbot_ons_target = cp1;
+               }
+       }
+
+       if (!cp)
+               return;
+
+       ons_debug(strcat(self.netname, " chose cp ranked ", ftos(bestvalue), "\n"));
+
+       if(cp.goalentity)
+       {
+               // Should be attacked
+               // Rate waypoints near it
+               found = FALSE;
+               best = world;
+               bestvalue = 99999999999;
+               for(radius=0; radius<1000 && !found; radius+=500)
+               {
+                       for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
+                       {
+                               if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
+                               if(wp.classname=="waypoint")
+                               if(checkpvs(wp.origin,cp))
+                               {
+                                       found = TRUE;
+                                       if(wp.cnt<bestvalue)
+                                       {
+                                               best = wp;
+                                               bestvalue = wp.cnt;
+                                       }
+                               }
+                       }
+               }
+
+               if(best)
+               {
+                       navigation_routerating(best, ratingscale, 10000);
+                       best.cnt += 1;
+
+                       self.havocbot_attack_time = 0;
+                       if(checkpvs(self.view_ofs,cp))
+                       if(checkpvs(self.view_ofs,best))
+                               self.havocbot_attack_time = time + 2;
+               }
+               else
+               {
+                       navigation_routerating(cp, ratingscale, 10000);
+               }
+               ons_debug(strcat(self.netname, " found an attackable controlpoint at ", vtos(cp.origin) ,"\n"));
+       }
+       else
+       {
+               // Should be touched
+               ons_debug(strcat(self.netname, " found a touchable controlpoint at ", vtos(cp.origin) ,"\n"));
+               found = FALSE;
+
+               // Look for auto generated waypoint
+               if (!bot_waypoints_for_items)
+               for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
+               {
+                       if(wp.classname=="waypoint")
+                       {
+                               navigation_routerating(wp, ratingscale, 10000);
+                               found = TRUE;
+                       }
+               }
+
+               // Nothing found, rate the controlpoint itself
+               if (!found)
+                       navigation_routerating(cp, ratingscale, 10000);
+       }
 }
 
+float havocbot_goalrating_ons_generator_attack(float ratingscale)
+{
+       entity g, wp, bestwp;
+       float found, best;
+
+       for(g = ons_worldgeneratorlist; g; g = g.ons_worldgeneratornext)
+       {
+               if(SAME_TEAM(g, self) || g.isshielded)
+                       continue;
+
+               // Should be attacked
+               // Rate waypoints near it
+               found = FALSE;
+               bestwp = world;
+               best = 99999999999;
+
+               for(wp=findradius(g.origin,400); wp; wp=wp.chain)
+               {
+                       if(wp.classname=="waypoint")
+                       if(checkpvs(wp.origin,g))
+                       {
+                               found = TRUE;
+                               if(wp.cnt<best)
+                               {
+                                       bestwp = wp;
+                                       best = wp.cnt;
+                               }
+                       }
+               }
+
+               if(bestwp)
+               {
+                       ons_debug("waypoints found around generator\n");
+                       navigation_routerating(bestwp, ratingscale, 10000);
+                       bestwp.cnt += 1;
 
+                       self.havocbot_attack_time = 0;
+                       if(checkpvs(self.view_ofs,g))
+                       if(checkpvs(self.view_ofs,bestwp))
+                               self.havocbot_attack_time = time + 5;
 
+                       return TRUE;
+               }
+               else
+               {
+                       ons_debug("generator found without waypoints around\n");
+                       // if there aren't waypoints near the generator go straight to it
+                       navigation_routerating(g, ratingscale, 10000);
+                       self.havocbot_attack_time = 0;
+                       return TRUE;
+               }
+       }
+       return FALSE;
+}
 
-void onslaught_controlpoint_touch()
+void havocbot_role_ons_offense()
 {
-       entity e;
-       float a;
-       if (!IS_PLAYER(other))
+       if(self.deadflag != DEAD_NO)
+       {
+               self.havocbot_attack_time = 0;
+               havocbot_ons_reset_role(self);
                return;
-       a = onslaught_controlpoint_attackable(self, other.team);
-       if(a != 2 && a != 4)
+       }
+
+       // Set the role timeout if necessary
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 120;
+
+       if (time > 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 (file)
index 0000000..cb4aeb4
--- /dev/null
@@ -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
index da5ca4c10e9d23a89f347f3f6e5c306902144e18..dacb24b800570f3efee8a4490da7ff1da7a01b13 100644 (file)
@@ -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
        {
index 59f0927e80c98e6d6307772a1425623a7ac7f626..a4266fa48dd3bbe72f47bdc58515462338b2794c 100644 (file)
@@ -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 (file)
index 0000000..39b7557
--- /dev/null
@@ -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 (file)
index 0000000..33e9483
--- /dev/null
@@ -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;
index 4d990b3cf50608d151a22d771d8b660f54a790f5..fb411ee33c0325f28323c9844e64f60e364f0faf 100644 (file)
@@ -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;
                }
index 765619df7e488305bc0222f0737d3bdb974c609f..60bd1d1a000104ef6c603d21e86e6af1ea28d82f 100644 (file)
@@ -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);
index 66540b87604e3a5b897a25cd019a16f36ff716be..d8fc740d268c07a7ac97d6aa9f2c0b440b818eec 100644 (file)
@@ -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;
index 2ec584db4cb23d265c5b105aceb2dbf6a3f33c7b..59a7cc308f1b84cc6ef2f4a693551b9c965bd4d8 100644 (file)
@@ -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;
 }
 
index b26fe1b9fff657fa13c38d35689ee689153d7c51..5f8f095642038aad6ace8b2f80f659ccdabfda66 100644 (file)
@@ -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 (file)
index 0000000..4074a00
--- /dev/null
@@ -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 (file)
index 0000000..ad307ae
--- /dev/null
@@ -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 (file)
index 0000000..de107be
--- /dev/null
@@ -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;
+}
index 4200b2207a9c5872def34c817b6efd3e772ae234..671d49894a1de202b36f52a9ba645e3937a78e9e 100644 (file)
@@ -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 (file)
index 0000000..0fbd3f4
--- /dev/null
@@ -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 (file)
index 0000000..1027684
--- /dev/null
@@ -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;
+}
index 868ddf246e0b5f0c88c93e7b594e412238110df7..090bdfe2a97875c6ff19d8b646ae10df0412a264 100644 (file)
@@ -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);
 
index 91bd53e5aa070d93b78c5e43f289e56aba4c8a6d..72db5fde8cb23749eae6d8ca6b17b9cd59748305 100644 (file)
@@ -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");
 }
 
index c6c30c6d53210dfee9143e12646831e7b727f147..35c98fca3ff8d2dc72236b8858334fcbed4da428 100644 (file)
@@ -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;
index 3e41c42fe022215ae6152a92b0eb7a7a0ff48065..5a6342999a5b3acf6ade08393aa56f5617728c3b 100644 (file)
@@ -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;
index 6a980af08df86e86e60f86b2b393a3ab694d97b1..fc4755edda5df98107c67e794891fc40e5f567d7 100644 (file)
@@ -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
index 7a5e62a7e5189c55e725579376425aa167ce89ca..7c3a35fe4b0585d898d4145a0d0d2a687c967ead 100644 (file)
@@ -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);
index c99228673d701cf37b7d67f7aea72a999383d11e..1b781571610c8a79171fc38aedbe8889529a6cc0 100644 (file)
@@ -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 (file)
index 0000000..6687667
--- /dev/null
@@ -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 (file)
index 0000000..d9cd8e8
--- /dev/null
@@ -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 (file)
index 0000000..14af758
--- /dev/null
@@ -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 (file)
index 0000000..9e70d7e
--- /dev/null
@@ -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;
+}
index ffae9543b95c663527fe29a6abe49f527453c94f..9e69ebd1324473785445ce7f6755f5374b30f685 100644 (file)
@@ -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;
 }
index 74e17a6a1422fb5b97bb989920dfdb8fa8a13891..e6ff994198603e4b0fab858f8982cbfd97ef9f9a 100644 (file)
@@ -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)
index 5f02a8aba2f38ffd8fb783291d06a02fa7db70bc..d50536cc5696fa734f0e5c9c46c3b16fcbf58edc 100644 (file)
@@ -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 (file)
index 0000000..489e55b
--- /dev/null
@@ -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 (file)
index 0000000..ce2f119
--- /dev/null
@@ -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;
+}
index 0fa2caab27c78bbdb679682d7527be4b97afbb17..7ab7fe812deec18308d4e0f5d93e906d72d6d6f1 100644 (file)
@@ -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
 }
index 4dcc9df2fab87443a85906eddfe402dac5ca7e52..9e78011d11b49e31ecbe97b066cfe226585fecef 100644 (file)
@@ -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);
-
index 0f52e34f033aa6b9f6f1231f9043494cea3d3e59..3727924c6e3aff0e6b17c1dfd7ad176a3c1c86f6 100644 (file)
@@ -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"
 #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"
index c869ab69669a6eab1da1abc924c94da93c5bd70e..4c27cbe8684d8ec554c17b2a4e7fd79c7211c033 100644 (file)
@@ -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"
index e84c6d696a91e691ccadabf6c68cd927febbc97f..e8db22137c6c38070856d61ed473a7520a6966f6 100644 (file)
@@ -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;
index aff0652d4a569061d7fb39a43d654185f264b229..540ae1a36bd94b52a0a1017aee0da8ba09df89ed 100644 (file)
@@ -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
index 78c0b091eb7ecf70205b4086adf96146084a73e9..7c1374d9c3e8d00518d0ce3796e256422835dafa 100644 (file)
@@ -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
index 1c12058d0d9019ee4bb0ae36366a4db58c588c96..f3cee85efb5e7119b474b740ac479cf261f620b8 100644 (file)
@@ -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);
+}
index 09b4b36ce4aadbb1619ae6d56c1d1b2df8a54143..a18e9773ea717a1a2ce1ea4318d75a954c904e5c 100644 (file)
@@ -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);
index 620ce8ce50e3a4a38812ab85b38b8fc1b2c4b195..08497eb8e6f1635c6aa36cc9a9574c45306640c5 100644 (file)
@@ -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;
index c26a4d295ea78cc9ab977d447f336ce7dbcd424f..a62475a6673e3921d78247382127369e1064fde4 100644 (file)
@@ -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.
index 6343625c0fd0e8f1c2e79f0b03b1fd6eb54711ba..2fb6740dc5f8da77a56e82e58c062ea4735e502d 100644 (file)
@@ -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;
index 3f4e72c3b3f584b67c0cc4de06f9f033d568f6db..ec8807e1b4e51b8c92bcbff817e8390ea849c792 100644 (file)
@@ -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
index 607629e42578834ed10e98c99ac66b28c82a8148..eb9b186bd888b1e2abd637bd9c4a5b93aa16e1fe 100644 (file)
@@ -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);
index 2f59924df6e894c9a4d96a8281fb671bec802abb..77e97fa2f9b89ff1be44814f08dc884fa86ca69f 100644 (file)
@@ -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;
index b25528e1645ccf1f635a20e5ae2866dc05eaf045..13aa61902f1f64a91ae7dba49f31d7c1b259bf1c 100644 (file)
@@ -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;
index bb2254c977d91e4cd427b5d3e6c2c3fcfea115a0..de208120c5552f4fc93e81763822d1eb3301d306 100644 (file)
@@ -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;
index d4af7e3fd59bf0e61af5d867e848be6381f6cd83..75281cc506c5eeee92f26f6f2aebd4aa78fdc6d7 100644 (file)
@@ -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
index 13cc3796fe1a0539a3bd779e0efec7971f21cdfb..7f6caefd0382841965d0037ac3c65ded89eb61dd 100644 (file)
@@ -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;
index f52b492a7042d520c03c80764ef8b2f75d1258ca..76f90e958e0a7eedff85bf801fdd8b811c06b8a4 100644 (file)
@@ -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;
                }
index 7c1a582879ea5f3a64cc68de0869c075994df2ae..6c64df5ef028a52a64b1293c19b41dd33077312b 100644 (file)
@@ -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;
        }
index 543c1cf0bb66919b8576259587a058f8e7a2fd99..78d79c708dcee8e5526b4c0f99a4787892993ab3 100644 (file)
@@ -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)
index 2f6963bf71b9f0d85c1a86cae7fcafffebadc602..dd7d4c1258500898089f949b9df11ac54f38595b 100644 (file)
@@ -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 (file)
index 0000000..5cf8365
--- /dev/null
@@ -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 (file)
index 6093641..0000000
+++ /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 (file)
index 4ce95fc..0000000
+++ /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 (file)
index c3dbe55..0000000
+++ /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 (file)
index a8feaeb..0000000
+++ /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 (file)
index d56a81b..0000000
+++ /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 (file)
index 3fdd5eb..0000000
+++ /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 (file)
index 539be2a..0000000
+++ /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_checkpoint.qc b/qcsrc/server/tturrets/units/unit_checkpoint.qc
deleted file mode 100644 (file)
index c919601..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
-    turret_checkpoint
-**/
-
-
-//.entity checkpoint_target;
-
-/*
-#define checkpoint_cache_who  flagcarried
-#define checkpoint_cache_from lastrocket
-#define checkpoint_cache_to   selected_player
-*/
-
-.entity pathgoal;
-.entity pathcurrent;
-
-/*
-entity path_makeorcache(entity forwho,entity start, entity end)
-{
-    entity oldself;
-    entity pth;
-    oldself = self;
-    self = forwho;
-
-    //pth = pathlib_makepath(start.origin,end.origin,PFL_GROUNDSNAP,500,1.5,PT_QUICKSTAR);
-
-    self = oldself;
-    return pth;
-}
-*/
-
-void turret_checkpoint_use()
-{
-}
-
-#if 0
-void turret_checkpoint_think()
-{
-    if(self.enemy)
-        te_lightning1(self,self.origin, self.enemy.origin);
-
-    self.nextthink = time + 0.25;
-}
-#endif
-/*QUAKED turret_checkpoint (1 0 1) (-32 -32 -32) (32 32 32)
------------KEYS------------
-target: .targetname of next waypoint in chain.
-wait:   Pause at this point # seconds.
------------SPAWNFLAGS-----------
----------NOTES----------
-If a loop is of targets are formed, any unit entering this loop will patrol it indefinitly.
-If the checkpoint chain in not looped, the unit will go "Roaming" when the last point is reached.
-*/
-//float tc_acum;
-void turret_checkpoint_init()
-{
-    traceline(self.origin + '0 0 16', self.origin - '0 0 1024', MOVE_WORLDONLY, self);
-    setorigin(self, trace_endpos + '0 0 32');
-
-    if(self.target != "")
-    {
-        self.enemy = find(world, targetname, self.target);
-        if(self.enemy == world)
-            dprint("A turret_checkpoint faild to find its target!\n");
-    }
-    //self.think = turret_checkpoint_think;
-    //self.nextthink = time + tc_acum + 0.25;
-    //tc_acum += 0.25;
-}
-
-void spawnfunc_turret_checkpoint()
-{
-    setorigin(self,self.origin);
-    self.think = turret_checkpoint_init;
-    self.nextthink = time + 0.2;
-}
-
-// Compat.
-void spawnfunc_walker_checkpoint()
-{
-    self.classname = "turret_checkpoint";
-    spawnfunc_turret_checkpoint();
-}
diff --git a/qcsrc/server/tturrets/units/unit_ewheel.qc b/qcsrc/server/tturrets/units/unit_ewheel.qc
deleted file mode 100644 (file)
index e8e677a..0000000
+++ /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 (file)
index 3c9e558..0000000
+++ /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 (file)
index 014fa25..0000000
+++ /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 (file)
index e1b88b0..0000000
+++ /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 (file)
index 6ce6c72..0000000
+++ /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 (file)
index d235dfb..0000000
+++ /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 (file)
index 7839660..0000000
+++ /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 (file)
index c704aa1..0000000
+++ /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 (file)
index 26a3dc0..0000000
+++ /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_targettrigger.qc b/qcsrc/server/tturrets/units/unit_targettrigger.qc
deleted file mode 100644 (file)
index 0f2de3c..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-void spawnfunc_turret_targettrigger();
-void turret_targettrigger_touch();
-
-void turret_targettrigger_touch()
-{
-    entity e;
-    if (self.cnt > time) return;
-    entity oldself;
-    oldself = self;
-
-    e = find(world, targetname, self.target);
-    while (e)
-    {
-        if (e.turrcaps_flags & TFL_TURRCAPS_RECIVETARGETS)
-        {
-            self = e;
-            if(e.turret_addtarget)
-                e.turret_addtarget(other,oldself);
-        }
-
-        e = find(e, targetname, oldself.target);
-    }
-
-    oldself.cnt = time + 0.5;
-
-    self = oldself;
-}
-
-/*QUAKED turret_targettrigger (.5 .5 .5) ?
-*/
-void spawnfunc_turret_targettrigger()
-{
-    if (!autocvar_g_turrets)
-    {
-        remove(self);
-        return;
-    }
-
-    InitTrigger ();
-
-    self.touch = turret_targettrigger_touch;
-}
diff --git a/qcsrc/server/tturrets/units/unit_tessla.qc b/qcsrc/server/tturrets/units/unit_tessla.qc
deleted file mode 100644 (file)
index 4989b24..0000000
+++ /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 (file)
index a91daa1..0000000
+++ /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/bumblebee.qc b/qcsrc/server/vehicles/bumblebee.qc
deleted file mode 100644 (file)
index 8b8d308..0000000
+++ /dev/null
@@ -1,1058 +0,0 @@
-#define BRG_SETUP 2
-#define BRG_START 4
-#define 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;
-float autocvar_g_vehicle_bumblebee_speed_down;
-float autocvar_g_vehicle_bumblebee_turnspeed;
-float autocvar_g_vehicle_bumblebee_pitchspeed;
-float autocvar_g_vehicle_bumblebee_pitchlimit;
-float autocvar_g_vehicle_bumblebee_friction;
-
-float autocvar_g_vehicle_bumblebee_energy;
-float autocvar_g_vehicle_bumblebee_energy_regen;
-float autocvar_g_vehicle_bumblebee_energy_regen_pause;
-
-float autocvar_g_vehicle_bumblebee_health;
-float autocvar_g_vehicle_bumblebee_health_regen;
-float autocvar_g_vehicle_bumblebee_health_regen_pause;
-
-float autocvar_g_vehicle_bumblebee_shield;
-float autocvar_g_vehicle_bumblebee_shield_regen;
-float autocvar_g_vehicle_bumblebee_shield_regen_pause;
-
-float autocvar_g_vehicle_bumblebee_cannon_cost;
-float autocvar_g_vehicle_bumblebee_cannon_damage;
-float autocvar_g_vehicle_bumblebee_cannon_radius;
-float autocvar_g_vehicle_bumblebee_cannon_refire;
-float autocvar_g_vehicle_bumblebee_cannon_speed;
-float autocvar_g_vehicle_bumblebee_cannon_spread;
-float autocvar_g_vehicle_bumblebee_cannon_force;
-
-float autocvar_g_vehicle_bumblebee_cannon_ammo;
-float autocvar_g_vehicle_bumblebee_cannon_ammo_regen;
-float autocvar_g_vehicle_bumblebee_cannon_ammo_regen_pause;
-
-var float autocvar_g_vehicle_bumblebee_cannon_lock = 0;
-
-float autocvar_g_vehicle_bumblebee_cannon_turnspeed;
-float autocvar_g_vehicle_bumblebee_cannon_pitchlimit_down;
-float autocvar_g_vehicle_bumblebee_cannon_pitchlimit_up;
-float autocvar_g_vehicle_bumblebee_cannon_turnlimit_in;
-float autocvar_g_vehicle_bumblebee_cannon_turnlimit_out;
-
-
-float autocvar_g_vehicle_bumblebee_raygun_turnspeed;
-float autocvar_g_vehicle_bumblebee_raygun_pitchlimit_down;
-float autocvar_g_vehicle_bumblebee_raygun_pitchlimit_up;
-float autocvar_g_vehicle_bumblebee_raygun_turnlimit_sides;
-
-float autocvar_g_vehicle_bumblebee_raygun_range;
-float autocvar_g_vehicle_bumblebee_raygun_dps;
-float autocvar_g_vehicle_bumblebee_raygun_aps;
-float autocvar_g_vehicle_bumblebee_raygun_fps;
-
-float autocvar_g_vehicle_bumblebee_raygun;
-float autocvar_g_vehicle_bumblebee_healgun_hps;
-float autocvar_g_vehicle_bumblebee_healgun_hmax;
-float autocvar_g_vehicle_bumblebee_healgun_aps;
-float autocvar_g_vehicle_bumblebee_healgun_amax;
-float autocvar_g_vehicle_bumblebee_healgun_sps;
-float autocvar_g_vehicle_bumblebee_healgun_locktime;
-
-float autocvar_g_vehicle_bumblebee_respawntime;
-
-float autocvar_g_vehicle_bumblebee_blowup_radius;
-float autocvar_g_vehicle_bumblebee_blowup_coredamage;
-float autocvar_g_vehicle_bumblebee_blowup_edgedamage;
-float autocvar_g_vehicle_bumblebee_blowup_forceintensity;
-var vector autocvar_g_vehicle_bumblebee_bouncepain;
-
-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)
-{
-       vector v = gettaginfo(_gun, gettagindex(_gun, _tagname));
-       vehicles_projectile("bigplasma_muzzleflash", "weapons/flacexp3.wav",
-                                               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()
-{
-       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;
-
-       float _in, _out;
-       vehic.angles_x *= -1;
-       makevectors(vehic.angles);
-       vehic.angles_x *= -1;
-       if((gun == vehic.gun1))
-       {
-               _in = autocvar_g_vehicle_bumblebee_cannon_turnlimit_in;
-               _out = autocvar_g_vehicle_bumblebee_cannon_turnlimit_out;
-               setorigin(gunner, vehic.origin + v_up * -16 + v_forward * -16 + v_right * 128);
-       }
-       else
-       {
-               _in = autocvar_g_vehicle_bumblebee_cannon_turnlimit_out;
-               _out = autocvar_g_vehicle_bumblebee_cannon_turnlimit_in;
-               setorigin(gunner, vehic.origin + v_up * -16 + v_forward * -16 + v_right * -128);
-       }
-
-       crosshair_trace(gunner);
-       vector _ct = trace_endpos;
-       vector ad;
-
-       if(autocvar_g_vehicle_bumblebee_cannon_lock)
-       {
-               if(gun.lock_time < time)
-                       gun.enemy = world;
-
-               if(trace_ent)
-                       if(trace_ent.movetype)
-                               if(trace_ent.takedamage)
-                                       if(!trace_ent.deadflag)
-                                       {
-                                               if(teamplay)
-                                               {
-                                                       if(trace_ent.team != gunner.team)
-                                                       {
-                                                               gun.enemy = trace_ent;
-                                                               gun.lock_time = time + 5;
-                                                       }
-                                               }
-                                               else
-                                               {
-                                                       gun.enemy = trace_ent;
-                                                       gun.lock_time = time + 5;
-                                               }
-                                       }
-       }
-
-       if(gun.enemy)
-       {
-               float i, distance, impact_time;
-
-               vector vf = real_origin(gun.enemy);
-               vector _vel = gun.enemy.velocity;
-               if(gun.enemy.movetype == MOVETYPE_WALK)
-                       _vel_z *= 0.1;
-
-
-               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;
-               }
-               trace_endpos = ad;
-
-
-               UpdateAuxiliaryXhair(gunner, ad, '1 0 1', 1);
-               vehicle_aimturret(vehic, trace_endpos, gun, "fire",
-                                                 autocvar_g_vehicle_bumblebee_cannon_pitchlimit_down * -1, autocvar_g_vehicle_bumblebee_cannon_pitchlimit_up,
-                                                 _out * -1,  _in,  autocvar_g_vehicle_bumblebee_cannon_turnspeed);
-
-       }
-       else
-               vehicle_aimturret(vehic, _ct, gun, "fire",
-                                                 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(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);
-                               gun.delay = time;
-                               gun.attack_finished_single = time + autocvar_g_vehicle_bumblebee_cannon_refire;
-                       }
-
-       VEHICLE_UPDATE_PLAYER(gunner, health, bumblebee);
-
-       if(vehic.vehicle_flags & VHF_HASSHIELD)
-               VEHICLE_UPDATE_PLAYER(gunner, shield, bumblebee);
-
-       ad = gettaginfo(gun, gettagindex(gun, "fire"));
-       traceline(ad, ad + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, gun);
-
-       UpdateAuxiliaryXhair(gunner, trace_endpos, ('1 0 0' * gunner.vehicle_reload1) + ('0 1 0' *(1 - gunner.vehicle_reload1)), 0);
-
-       if(vehic.owner)
-               UpdateAuxiliaryXhair(vehic.owner, trace_endpos, ('1 0 0' * gunner.vehicle_reload1) + ('0 1 0' *(1 - gunner.vehicle_reload1)), ((gunner == vehic.gunner1) ? 1 : 2));
-
-       vehic.solid = SOLID_BBOX;
-       gunner.BUTTON_ATCK = gunner.BUTTON_ATCK2 = gunner.BUTTON_CROUCH = 0;
-       gunner.vehicle_energy = (gun.vehicle_energy / autocvar_g_vehicle_bumblebee_cannon_ammo) * 100;
-
-       self = gunner;
-       return 1;
-}
-
-void bumb_gunner_exit(float _exitflag)
-{
-       if(IS_REAL_CLIENT(self))
-       {
-               msg_entity = self;
-               WriteByte(MSG_ONE, SVC_SETVIEWPORT);
-               WriteEntity(MSG_ONE, self);
-
-               WriteByte(MSG_ONE, SVC_SETVIEWANGLES);
-               WriteAngle(MSG_ONE, 0);
-               WriteAngle(MSG_ONE, self.vehicle.angles_y);
-               WriteAngle(MSG_ONE, 0);
-       }
-
-       CSQCVehicleSetup(self, HUD_NORMAL);
-       setsize(self, PL_MIN, PL_MAX);
-
-       self.takedamage     = DAMAGE_AIM;
-       self.solid          = SOLID_SLIDEBOX;
-       self.movetype       = MOVETYPE_WALK;
-       self.effects        &= ~EF_NODRAW;
-       self.alpha          = 1;
-       self.PlayerPhysplug = func_null;
-       self.view_ofs       = PL_VIEW_OFS;
-       self.event_damage   = PlayerDamage;
-       self.hud            = HUD_NORMAL;
-       self.switchweapon   = self.vehicle.switchweapon;
-
-    vh_player = self;
-    vh_vehicle = self.vehicle;
-    MUTATOR_CALLHOOK(VehicleExit);
-    self = vh_player;
-    self.vehicle = vh_vehicle;
-
-       self.vehicle.vehicle_hudmodel.viewmodelforclient = self.vehicle;
-
-       fixedmakevectors(self.vehicle.owner.angles);
-
-       if(self == self.vehicle.owner.gunner1)
-       {
-               self.vehicle.owner.gunner1 = world;
-       }
-       else if(self == self.vehicle.owner.gunner2)
-       {
-               self.vehicle.owner.gunner2 = world;
-               v_right *= -1;
-       }
-       else
-               dprint("^1self != gunner1 or gunner2, this is a BIG PROBLEM, tell tZork this happend.\n");
-
-       vector spot = self.vehicle.owner.origin + + v_up * 128 + v_right * 300;
-       spot = vehicles_findgoodexit(spot);
-       //setorigin(self , spot);
-
-       self.velocity = 0.75 * self.vehicle.owner.velocity + normalize(spot - self.vehicle.owner.origin) * 200;
-       self.velocity_z += 10;
-
-       self.vehicle.phase = time + 5;
-       self.vehicle        = world;
-}
-
-float bumb_gunner_enter()
-{
-       RemoveGrapplingHook(other);
-       entity _gun, _gunner;
-       if(!self.gunner1)
-       {
-               _gun = self.gun1;
-               _gunner = self.gunner1;
-               self.gunner1 = other;
-       }
-       else if(!self.gunner2)
-       {
-               _gun = self.gun2;
-               _gunner = self.gunner2;
-               self.gunner2 = other;
-       }
-       else
-       {
-               dprint("^1ERROR:^7Tried to enter a fully occupied vehicle!\n");
-               return FALSE;
-       }
-
-       _gunner            = other;
-       _gunner.vehicle    = _gun;
-       _gun.switchweapon  = other.switchweapon;
-       _gun.vehicle_exit  = bumb_gunner_exit;
-
-       other.angles            = self.angles;
-       other.takedamage        = DAMAGE_NO;
-       other.solid             = SOLID_NOT;
-       other.movetype          = MOVETYPE_NOCLIP;
-       other.alpha             = -1;
-       other.event_damage      = func_null;
-       other.view_ofs          = '0 0 0';
-       other.hud               = _gun.hud;
-       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.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
-       _gun.vehicle_hudmodel.viewmodelforclient = other;
-
-       CSQCVehicleSetup(other, other.hud);
-
-    vh_player = other;
-    vh_vehicle = _gun;
-    MUTATOR_CALLHOOK(VehicleEnter);
-    other = vh_player;
-    _gun = vh_vehicle;
-
-       return TRUE;
-}
-
-float vehicles_valid_pilot()
-{
-       if (!IS_PLAYER(other))
-               return FALSE;
-
-       if(other.deadflag != DEAD_NO)
-               return FALSE;
-
-       if(other.vehicle != world)
-               return FALSE;
-
-       if (!IS_REAL_CLIENT(other))
-               if(!autocvar_g_vehicles_allow_bots)
-                       return FALSE;
-
-       if(teamplay && other.team != self.team)
-               return FALSE;
-
-       return TRUE;
-}
-
-void bumb_touch()
-{
-
-       if(self.gunner1 != world && self.gunner2 != world)
-       {
-               vehicles_touch();
-               return;
-       }
-
-       if(vehicles_valid_pilot())
-       {
-               if(self.gun1.phase <= time)
-                       if(bumb_gunner_enter())
-                               return;
-
-               if(self.gun2.phase <= time)
-                       if(bumb_gunner_enter())
-                               return;
-       }
-
-       vehicles_touch();
-}
-
-void bumb_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,
-                                                                          self.gun1.vehicle_energy + autocvar_g_vehicle_bumblebee_cannon_ammo_regen * frametime);
-
-       if(self.gun2.delay + autocvar_g_vehicle_bumblebee_cannon_ammo_regen_pause < time)
-               self.gun2.vehicle_energy = min(autocvar_g_vehicle_bumblebee_cannon_ammo,
-                                                                          self.gun2.vehicle_energy + autocvar_g_vehicle_bumblebee_cannon_ammo_regen * frametime);
-
-       if(self.vehicle_flags  & VHF_SHIELDREGEN)
-               vehicles_regen(self.dmg_time, vehicle_shield, autocvar_g_vehicle_bumblebee_shield, autocvar_g_vehicle_bumblebee_shield_regen_pause, autocvar_g_vehicle_bumblebee_shield_regen, frametime, TRUE);
-
-       if(self.vehicle_flags  & VHF_HEALTHREGEN)
-               vehicles_regen(self.dmg_time, vehicle_health, autocvar_g_vehicle_bumblebee_health, autocvar_g_vehicle_bumblebee_health_regen_pause, autocvar_g_vehicle_bumblebee_health_regen, frametime, FALSE);
-
-       if(self.vehicle_flags  & VHF_ENERGYREGEN)
-               vehicles_regen(self.wait, vehicle_energy, autocvar_g_vehicle_bumblebee_energy, autocvar_g_vehicle_bumblebee_energy_regen_pause, autocvar_g_vehicle_bumblebee_energy_regen, frametime, FALSE);
-
-}
-
-float bumb_pilot_frame()
-{
-       entity pilot, vehic;
-       vector newvel;
-
-       pilot = self;
-       vehic = self.vehicle;
-       self   = vehic;
-
-
-       if(vehic.deadflag != DEAD_NO)
-       {
-               self = pilot;
-               pilot.BUTTON_ATCK = pilot.BUTTON_ATCK2 = 0;
-               return 1;
-       }
-
-       bumb_regen();
-
-       crosshair_trace(pilot);
-
-       vector vang;
-       float ftmp;
-
-       vang = vehic.angles;
-       newvel = vectoangles(normalize(trace_endpos - self.origin + '0 0 32'));
-       vang_x *= -1;
-       newvel_x *= -1;
-       if(newvel_x > 180)  newvel_x -= 360;
-       if(newvel_x < -180) newvel_x += 360;
-       if(newvel_y > 180)  newvel_y -= 360;
-       if(newvel_y < -180) newvel_y += 360;
-
-       ftmp = shortangle_f(pilot.v_angle_y - vang_y, vang_y);
-       if(ftmp > 180)  ftmp -= 360;
-       if(ftmp < -180) ftmp += 360;
-       vehic.avelocity_y = bound(-autocvar_g_vehicle_bumblebee_turnspeed, ftmp + vehic.avelocity_y * 0.9, autocvar_g_vehicle_bumblebee_turnspeed);
-
-       // Pitch
-       ftmp = 0;
-       if(pilot.movement_x > 0 && vang_x < autocvar_g_vehicle_bumblebee_pitchlimit)
-               ftmp = 4;
-       else if(pilot.movement_x < 0 && vang_x > -autocvar_g_vehicle_bumblebee_pitchlimit)
-               ftmp = -8;
-
-       newvel_x = bound(-autocvar_g_vehicle_bumblebee_pitchlimit, newvel_x , autocvar_g_vehicle_bumblebee_pitchlimit);
-       ftmp = vang_x - bound(-autocvar_g_vehicle_bumblebee_pitchlimit, newvel_x + ftmp, autocvar_g_vehicle_bumblebee_pitchlimit);
-       vehic.avelocity_x = bound(-autocvar_g_vehicle_bumblebee_pitchspeed, ftmp + vehic.avelocity_x * 0.9, autocvar_g_vehicle_bumblebee_pitchspeed);
-
-       vehic.angles_x = anglemods(vehic.angles_x);
-       vehic.angles_y = anglemods(vehic.angles_y);
-       vehic.angles_z = anglemods(vehic.angles_z);
-
-       makevectors('0 1 0' * vehic.angles_y);
-       newvel = vehic.velocity * -autocvar_g_vehicle_bumblebee_friction;
-
-       if(pilot.movement_x != 0)
-       {
-               if(pilot.movement_x > 0)
-                       newvel += v_forward  * autocvar_g_vehicle_bumblebee_speed_forward;
-               else if(pilot.movement_x < 0)
-                       newvel -= v_forward  * autocvar_g_vehicle_bumblebee_speed_forward;
-       }
-
-       if(pilot.movement_y != 0)
-       {
-               if(pilot.movement_y < 0)
-                       newvel -= v_right * autocvar_g_vehicle_bumblebee_speed_strafe;
-               else if(pilot.movement_y > 0)
-                       newvel += v_right * autocvar_g_vehicle_bumblebee_speed_strafe;
-               ftmp = newvel * v_right;
-               ftmp *= frametime * 0.1;
-               vehic.angles_z = bound(-15, vehic.angles_z + ftmp, 15);
-       }
-       else
-       {
-               vehic.angles_z *= 0.95;
-               if(vehic.angles_z >= -1 && vehic.angles_z <= -1)
-                       vehic.angles_z = 0;
-       }
-
-       if(pilot.BUTTON_CROUCH)
-               newvel -=   v_up * autocvar_g_vehicle_bumblebee_speed_down;
-       else if(pilot.BUTTON_JUMP)
-               newvel +=  v_up * autocvar_g_vehicle_bumblebee_speed_up;
-
-       vehic.velocity  += newvel * frametime;
-       pilot.velocity = pilot.movement  = vehic.velocity;
-
-
-       if(autocvar_g_vehicle_bumblebee_healgun_locktime)
-       {
-               if(vehic.tur_head.lock_time < time || vehic.tur_head.enemy.deadflag)
-                       vehic.tur_head.enemy = world;
-
-               if(trace_ent)
-               if(trace_ent.movetype)
-               if(trace_ent.takedamage)
-               if(!trace_ent.deadflag)
-               {
-                       if(teamplay)
-                       {
-                               if(trace_ent.team == pilot.team)
-                               {
-                                       vehic.tur_head.enemy = trace_ent;
-                                       vehic.tur_head.lock_time = time + autocvar_g_vehicle_bumblebee_healgun_locktime;
-                               }
-                       }
-                       else
-                       {
-                               vehic.tur_head.enemy = trace_ent;
-                               vehic.tur_head.lock_time = time + autocvar_g_vehicle_bumblebee_healgun_locktime;
-                       }
-               }
-
-               if(vehic.tur_head.enemy)
-               {
-                       trace_endpos = real_origin(vehic.tur_head.enemy);
-                       UpdateAuxiliaryXhair(pilot, trace_endpos, '0 0.75 0', 0);
-               }
-       }
-
-       vang = vehicle_aimturret(vehic, trace_endpos, self.gun3, "fire",
-                                         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((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;
-               vehic.gun3.enemy.effects &= ~EF_NODRAW;
-
-               vehic.gun3.enemy.hook_start = gettaginfo(vehic.gun3, gettagindex(vehic.gun3, "fire"));
-               vehic.gun3.enemy.SendFlags |= BRG_START;
-
-               traceline(vehic.gun3.enemy.hook_start, vehic.gun3.enemy.hook_start + v_forward * autocvar_g_vehicle_bumblebee_raygun_range, MOVE_NORMAL, vehic);
-
-               if(trace_ent)
-               {
-                       if(autocvar_g_vehicle_bumblebee_raygun)
-                       {
-                               Damage(trace_ent, vehic, pilot, autocvar_g_vehicle_bumblebee_raygun_dps * sys_frametime, DEATH_GENERIC, trace_endpos, v_forward * autocvar_g_vehicle_bumblebee_raygun_fps * sys_frametime);
-                               vehic.vehicle_energy -= autocvar_g_vehicle_bumblebee_raygun_aps * sys_frametime;
-                       }
-                       else
-                       {
-                               if(trace_ent.deadflag == DEAD_NO)
-                                       if((teamplay && trace_ent.team == pilot.team) || !teamplay)
-                                       {
-
-                                               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_hps)
-                                                               trace_ent.vehicle_health = min(trace_ent.vehicle_health + autocvar_g_vehicle_bumblebee_healgun_hps * frametime, trace_ent.tur_health);
-                                               }
-                                               else if(IS_CLIENT(trace_ent))
-                                               {
-                                                       if(trace_ent.health <= autocvar_g_vehicle_bumblebee_healgun_hmax && autocvar_g_vehicle_bumblebee_healgun_hps)
-                                                               trace_ent.health = min(trace_ent.health + autocvar_g_vehicle_bumblebee_healgun_hps * frametime, autocvar_g_vehicle_bumblebee_healgun_hmax);
-
-                                                       if(trace_ent.armorvalue <= autocvar_g_vehicle_bumblebee_healgun_amax && autocvar_g_vehicle_bumblebee_healgun_aps)
-                                                               trace_ent.armorvalue = min(trace_ent.armorvalue + autocvar_g_vehicle_bumblebee_healgun_aps * frametime, autocvar_g_vehicle_bumblebee_healgun_amax);
-
-                                                       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)
-                                               {
-                                                       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);
-                                                       //else ..hmmm what? ammo?
-
-                                                       trace_ent.SendFlags |= TNSF_STATUS;
-                                               }
-                                       }
-                       }
-               }
-
-               vehic.gun3.enemy.hook_end = trace_endpos;
-               setorigin(vehic.gun3.enemy, trace_endpos);
-               vehic.gun3.enemy.SendFlags |= BRG_END;
-
-               vehic.wait = time + 1;
-       }
-       else
-               vehic.gun3.enemy.effects |= EF_NODRAW;
-       /*{
-               if(vehic.gun3.enemy)
-                       remove(vehic.gun3.enemy);
-
-               vehic.gun3.enemy = world;
-       }
-       */
-
-       VEHICLE_UPDATE_PLAYER(pilot, health, bumblebee);
-       VEHICLE_UPDATE_PLAYER(pilot, energy, bumblebee);
-
-       pilot.vehicle_ammo1 = (vehic.gun1.vehicle_energy / autocvar_g_vehicle_bumblebee_cannon_ammo) * 100;
-       pilot.vehicle_ammo2 = (vehic.gun2.vehicle_energy / autocvar_g_vehicle_bumblebee_cannon_ammo) * 100;
-
-       if(vehic.vehicle_flags & VHF_HASSHIELD)
-               VEHICLE_UPDATE_PLAYER(pilot, shield, bumblebee);
-
-       vehic.angles_x *= -1;
-       makevectors(vehic.angles);
-       vehic.angles_x *= -1;
-       setorigin(pilot, vehic.origin + v_up * 48 + v_forward * 160);
-
-       pilot.BUTTON_ATCK = pilot.BUTTON_ATCK2 = pilot.BUTTON_CROUCH = 0;
-       self = pilot;
-
-       return 1;
-}
-
-void bumb_think()
-{
-       self.movetype = MOVETYPE_TOSS;
-
-               //self.velocity = self.velocity * 0.5;
-       self.angles_z *= 0.8;
-       self.angles_x *= 0.8;
-
-       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;
-               }
-       }
-
-}
-
-void bumb_enter()
-{
-       self.touch = bumb_touch;
-       self.nextthink = 0;
-       self.movetype = MOVETYPE_BOUNCEMISSILE;
-       //setattachment(self.owner, self.vehicle_viewport, "");
-}
-
-void bumb_exit(float eject)
-{
-       self.touch = vehicles_touch;
-       self.think = bumb_think;
-       self.nextthink = time;
-
-       if(!self.owner)
-               return;
-
-       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;
-       else
-               spot = self.origin + v_up * 128 - v_forward * 200;
-
-       spot = vehicles_findgoodexit(spot);
-
-       // Hide beam
-       if(self.gun3.enemy || !wasfreed(self.gun3.enemy)) {
-               self.gun3.enemy.effects |= EF_NODRAW;
-    }
-
-       self.owner.velocity = 0.75 * self.vehicle.velocity + normalize(spot - self.vehicle.origin) * 200;
-       self.owner.velocity_z += 10;
-       setorigin(self.owner, spot);
-
-       antilag_clear(self.owner);
-    self.owner = world;
-}
-
-void bumb_blowup()
-{
-       RadiusDamage(self, self.enemy, autocvar_g_vehicle_bumblebee_blowup_coredamage,
-                                autocvar_g_vehicle_bumblebee_blowup_edgedamage,
-                                autocvar_g_vehicle_bumblebee_blowup_radius, self, world,
-                                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);
-
-       if(self.owner.deadflag == DEAD_DYING)
-               self.owner.deadflag = DEAD_DEAD;
-
-       remove(self);
-}
-
-void bumb_diethink()
-{
-       if(time >= self.wait)
-               self.think = bumb_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 bumb_die()
-{
-       entity oldself = self;
-
-       // 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;
-       }
-
-       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 = 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);
-}
-
-void bumb_impact()
-{
-       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);
-}
-
-void bumb_spawn(float _f)
-{
-       /*
-       float i;
-       for(i=1; gettaginfo(self.gun1, i), gettaginfo_name; ++i)
-       {
-
-           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)
-               {
-                       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_BOUNCEMISSILE;
-       self.movetype           = MOVETYPE_TOSS;
-       self.vehicle_impact = bumb_impact;
-       self.damageforcescale = 0.025;
-
-       setorigin(self, self.origin + '0 0 25');
-}
-
-void spawnfunc_vehicle_bumblebee()
-{
-       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);
-               return;
-       }
-}
-
-float bumble_raygun_send(entity to, float sf)
-{
-       WriteByte(MSG_ENTITY, ENT_CLIENT_BUMBLE_RAYGUN);
-
-       WriteByte(MSG_ENTITY, sf);
-       if(sf & BRG_SETUP)
-       {
-               WriteByte(MSG_ENTITY, num_for_edict(self.realowner));
-               WriteByte(MSG_ENTITY, self.realowner.team);
-               WriteByte(MSG_ENTITY, self.cnt);
-       }
-
-       if(sf & BRG_START)
-       {
-               WriteCoord(MSG_ENTITY, self.hook_start_x);
-               WriteCoord(MSG_ENTITY, self.hook_start_y);
-               WriteCoord(MSG_ENTITY, self.hook_start_z);
-       }
-
-       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);
-       }
-
-       return TRUE;
-}
-#endif // SVQC
-
-#ifdef CSQC
-/*
-.vector raygun_l1
-.vector raygun_l2;
-.vector raygun_l3;
-*/
-
-void bumble_raygun_draw()
-{
-       float _len;
-       vector _dir;
-       vector _vtmp1, _vtmp2;
-
-       _len = vlen(self.origin - self.move_origin);
-       _dir = normalize(self.move_origin - self.origin);
-
-       if(self.total_damages < time)
-       {
-               boxparticles(self.traileffect, self, self.origin, self.origin + _dir * -64, _dir * -_len , _dir * -_len, 1, PARTICLES_USEALPHA);
-               boxparticles(self.lip, self, self.move_origin, self.move_origin + _dir * -64, _dir * -200 , _dir * -200, 1, PARTICLES_USEALPHA);
-               self.total_damages = time + 0.1;
-       }
-
-       float i, df, sz, al;
-       for(i = -0.1; i < 0.2; i += 0.1)
-       {
-               df = DRAWFLAG_NORMAL; //((random() < 0.5) ? DRAWFLAG_ADDITIVE : DRAWFLAG_SCREEN);
-               sz = 5 + random() * 5;
-               al = 0.25 + random() * 0.5;
-               _vtmp1 = self.origin + _dir * _len * (0.25 + i);
-               _vtmp1 += (randomvec() * (_len * 0.2) * (frametime * 2));       //self.raygun_l1;
-               Draw_CylindricLine(self.origin, _vtmp1, sz, "gfx/colors/white.tga", 1, 1, self.colormod, al, df, view_origin);
-
-               _vtmp2 = self.origin + _dir * _len * (0.5 + i);
-               _vtmp2 += (randomvec() * (_len * 0.2) * (frametime * 5));       //self.raygun_l2;
-               Draw_CylindricLine(_vtmp1, _vtmp2, sz, "gfx/colors/white.tga", 1, 1, self.colormod, al, df, view_origin);
-
-               _vtmp1 = self.origin + _dir * _len * (0.75 + i);
-               _vtmp1 += randomvec() * (_len * 0.2) * (frametime * 10);     //self.raygun_l3;
-               Draw_CylindricLine(_vtmp2, _vtmp1, sz, "gfx/colors/white.tga", 1, 1, self.colormod, al, df, view_origin);
-
-               Draw_CylindricLine(_vtmp1, self.move_origin +  randomvec() * 32, sz, "gfx/colors/white.tga", 1, 1, self.colormod, al, df, view_origin);
-       }
-}
-
-void bumble_raygun_read(float bIsNew)
-{
-       float sf = ReadByte();
-
-       if(sf & BRG_SETUP)
-       {
-               self.cnt  = ReadByte();
-               self.team = ReadByte();
-               self.cnt  = ReadByte();
-
-               if(self.cnt)
-                       self.colormod = '1 0 0';
-               else
-                       self.colormod = '0 1 0';
-
-               self.traileffect = particleeffectnum("healray_muzzleflash");
-               self.lip = particleeffectnum("healray_impact");
-
-               self.draw = bumble_raygun_draw;
-       }
-
-
-       if(sf & BRG_START)
-       {
-               self.origin_x = ReadCoord();
-               self.origin_y = ReadCoord();
-               self.origin_z = ReadCoord();
-               setorigin(self, self.origin);
-       }
-
-       if(sf & BRG_END)
-       {
-               self.move_origin_x = ReadCoord();
-               self.move_origin_y = ReadCoord();
-               self.move_origin_z = ReadCoord();
-       }
-}
-
-void bumblebee_draw()
-{
-
-}
-
-void bumblebee_draw2d()
-{
-
-}
-
-void bumblebee_read_extra()
-{
-
-}
-
-void vehicle_bumblebee_assemble()
-{
-
-}
-#endif //CSQC
diff --git a/qcsrc/server/vehicles/racer.qc b/qcsrc/server/vehicles/racer.qc
deleted file mode 100644 (file)
index fc9cee8..0000000
+++ /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 (file)
index 53e4d35..0000000
+++ /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 (file)
index c5eb546..0000000
+++ /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 (file)
index 785d395..0000000
+++ /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 (file)
index 549dfea..0000000
+++ /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/vehicles/vehicles_def.qh b/qcsrc/server/vehicles/vehicles_def.qh
deleted file mode 100644 (file)
index 56fc9ea..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-// #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
-
-.entity gun1;
-.entity gun2;
-.entity gun3;
-.entity vehicle_shieldent;  /// Entity to disply the shild effect on damage
-.entity vehicle;
-.entity vehicle_viewport;
-.entity vehicle_hudmodel;
-.entity vehicle_controller;
-
-.entity gunner1;
-.entity gunner2;
-
-.float vehicle_health;      /// If self is player this is 0..100 indicating precentage of health left on vehicle. If self is vehile, this is the real health value.
-.float vehicle_energy;      /// If self is player this is 0..100 indicating precentage of energy left on vehicle. If self is vehile, this is the real energy value.
-.float vehicle_shield;      /// If self is player this is 0..100 indicating precentage of shield left on vehicle. If self is vehile, this is the real shield value.
-
-.float vehicle_ammo1;   /// If self is player this field's use depends on the individual vehile. If self is vehile, this is the real ammo1 value.
-.float vehicle_reload1; /// If self is player this field's use depends on the individual vehile. If self is vehile, this is the real reload1 value.
-.float vehicle_ammo2;   /// If self is player this field's use depends on the individual vehile. If self is vehile, this is the real ammo2 value.
-.float vehicle_reload2; /// If self is player this field's use depends on the individual vehile. If self is vehile, this is the real reload2 value.
-
-.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;
-
-#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
index 736cc564cc4c81bffaae6096cddc11b46c8ac867..0045374a23ce8f7a01a8e0bdd4c674da69fe4d4f 100644 (file)
@@ -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);
        }
 
index 6e54cb0601a89df25175edb1a90fa1c44af6cd74..623aedca6f45130fcc9dbcca402522b04584aa48 100644 (file)
@@ -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;
        }
index 8f9454ed541b748d8bdb1350e6def24091c7cb36..563a657847a863d622a9cef136e07a92854c9dea 100644 (file)
@@ -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;
index 685741dabc0cf4be042412decea63555fe344ca7..7796f9074cc5c416028a9118bf2c52b451428ab1 100644 (file)
@@ -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);
index f7f3cde51230e2af87ee885741e67be068b7473d..107d52e1ac97fbd31b36a063291bf3d3b5c77edd 100644 (file)
@@ -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)
index 55e6d5415e154bf583c1f5892c485a336aea40b8..9355d716b2592dd89816bd9ff5b8e345c6f96a15 100644 (file)
@@ -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;
 
index 25fce59a15f510d878355f0b084f2bf0be54bc03..16936196ee56a463133289af2b852ae849ed97d3 100644 (file)
@@ -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;
                }
index ddb1ee69588aa578c3394b427fa158eb27ab7a96..03dd69bbeb12d54d09682a10b24db18fa8b8575d 100644 (file)
@@ -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);