]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'Mario/hook_nade_stuff' into 'master'
authorMario <zacjardine@y7mail.com>
Sun, 30 Aug 2015 09:55:00 +0000 (09:55 +0000)
committerMario <zacjardine@y7mail.com>
Sun, 30 Aug 2015 09:55:00 +0000 (09:55 +0000)
Merge branch Mario/hook_nade_stuff (L merge request)

Some useful mutators for the instagib servers

See merge request !210

87 files changed:
_hud_common.cfg
_hud_descriptions.cfg
binds-xonotic.cfg
gfx/hud/default/checkbox_checked.tga [new file with mode: 0644]
gfx/hud/default/checkbox_empty.tga [new file with mode: 0644]
gfx/hud/default/checkbox_undefined.tga [new file with mode: 0644]
hud_luma.cfg
hud_luminos.cfg
hud_luminos_minimal.cfg
hud_luminos_minimal_xhair.cfg
hud_luminos_old.cfg
hud_nexuiz.cfg
keybinds.txt
qcsrc/client/autocvars.qh
qcsrc/client/command/cl_cmd.qc
qcsrc/client/hud.qc
qcsrc/client/hud.qh
qcsrc/client/hud_config.qc
qcsrc/client/main.qc
qcsrc/client/quickmenu.qc [new file with mode: 0644]
qcsrc/client/scoreboard.qc
qcsrc/client/view.qc
qcsrc/client/weapons/projectile.qc
qcsrc/common/monsters/all.qh
qcsrc/common/monsters/monster/spider.qc
qcsrc/common/monsters/monster/wyvern.qc
qcsrc/common/monsters/monster/zombie.qc
qcsrc/common/mutators/mutator/damagetext.qc
qcsrc/common/weapons/all.inc
qcsrc/common/weapons/w_arc.qc [deleted file]
qcsrc/common/weapons/w_blaster.qc [deleted file]
qcsrc/common/weapons/w_crylink.qc [deleted file]
qcsrc/common/weapons/w_devastator.qc [deleted file]
qcsrc/common/weapons/w_electro.qc [deleted file]
qcsrc/common/weapons/w_fireball.qc [deleted file]
qcsrc/common/weapons/w_hagar.qc [deleted file]
qcsrc/common/weapons/w_hlac.qc [deleted file]
qcsrc/common/weapons/w_hmg.qc [deleted file]
qcsrc/common/weapons/w_hook.qc [deleted file]
qcsrc/common/weapons/w_machinegun.qc [deleted file]
qcsrc/common/weapons/w_minelayer.qc [deleted file]
qcsrc/common/weapons/w_mortar.qc [deleted file]
qcsrc/common/weapons/w_porto.qc [deleted file]
qcsrc/common/weapons/w_rifle.qc [deleted file]
qcsrc/common/weapons/w_rpc.qc [deleted file]
qcsrc/common/weapons/w_seeker.qc [deleted file]
qcsrc/common/weapons/w_shockwave.qc [deleted file]
qcsrc/common/weapons/w_shotgun.qc [deleted file]
qcsrc/common/weapons/w_tuba.qc [deleted file]
qcsrc/common/weapons/w_vaporizer.qc [deleted file]
qcsrc/common/weapons/w_vortex.qc [deleted file]
qcsrc/common/weapons/weapon/arc.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/blaster.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/crylink.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/devastator.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/electro.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/fireball.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/hagar.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/hlac.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/hmg.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/hook.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/machinegun.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/minelayer.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/mortar.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/porto.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/rifle.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/rpc.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/seeker.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/shockwave.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/shotgun.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/tuba.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/vaporizer.qc [new file with mode: 0644]
qcsrc/common/weapons/weapon/vortex.qc [new file with mode: 0644]
qcsrc/lib/Cvar.qh
qcsrc/menu/classes.inc
qcsrc/menu/xonotic/dialog_hudpanel_quickmenu.qc [new file with mode: 0644]
qcsrc/menu/xonotic/mainwindow.qc
qcsrc/server/bot/aim.qc
qcsrc/server/bot/bot.qh
qcsrc/server/bot/havocbot/roles.qc
qcsrc/server/cheats.qc
qcsrc/server/cl_client.qc
qcsrc/server/cl_player.qc
qcsrc/server/command/cmd.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/mutators/events.qh
qcsrc/server/mutators/gamemode_keepaway.qc

index f17e7ac60b33c2040f94c7f67836cbb19e68c896..c486afb4ba284c4f986d6e730b2b0fd07332fb4e 100644 (file)
@@ -45,7 +45,13 @@ seta hud_panel_itemstime_progressbar_maxtime "30" "when left time is at least th
 seta hud_panel_itemstime_hidespawned "1" "if 1 hide an item from the panel when all the occurrences of it are available again; if 2 hide it when at least one occurrence is available again"
 seta hud_panel_itemstime_hidelarge "0" "if 1 hide large armor and health from the panel"
 
+seta hud_panel_quickmenu_file "" "load the quick menu from this file (empty or 0 to disable)"
+seta hud_panel_quickmenu_translatecommands 0 "when the game is translated, translate strings inside commands too (useful for chat commands)"
+seta hud_panel_quickmenu_time 5 "quickmenu expires after this number of seconds in the same page"
+
 // hud panel aliases
+alias quickmenu "cl_cmd hud quickmenu ${* ?}"
+
 alias hud_panel_radar_rotate "toggle hud_panel_radar_rotation 0 1 2 3 4"
 alias +hud_panel_radar_maximized "cl_cmd hud radar 1"
 alias -hud_panel_radar_maximized "cl_cmd hud radar 0"
index 1de28cb9fddd12aec713359503d8127d760b903c..c882738b2720782d440b5855e0b2626f294dfb14 100644 (file)
@@ -367,3 +367,14 @@ seta hud_panel_itemstime_progressbar_reduced "" "progressbar is displayed only i
 seta hud_panel_itemstime_text "" "show text"
 seta hud_panel_itemstime_ratio "" "ratio between space reserved for text and icon width of each item entry (min value is 2)"
 seta hud_panel_itemstime_dynamicsize "" "allow panel size reduction by removing spacing among items"
+
+seta hud_panel_quickmenu "" "enable/disable this panel"
+seta hud_panel_quickmenu_pos "" "position of this base of the panel"
+seta hud_panel_quickmenu_size "" "size of this panel"
+seta hud_panel_quickmenu_bg "" "if set to something else than \"\" = override default background"
+seta hud_panel_quickmenu_bg_color "" "if set to something else than \"\" = override default panel background color"
+seta hud_panel_quickmenu_bg_color_team "" "override panel color with team color in team based games"
+seta hud_panel_quickmenu_bg_alpha "" "if set to something else than \"\" = override default panel background alpha"
+seta hud_panel_quickmenu_bg_border "" "if set to something else than \"\" = override default size of border around the background"
+seta hud_panel_quickmenu_bg_padding "" "if set to something else than \"\" = override default padding of contents from border"
+seta hud_panel_quickmenu_align "" "text alignment: 0 left, 0.5 center, 1 right"
index 759c1c00e8a78088514df991625e3d1fa0eaf904..361621c6a83b413a270f1458f56a4ab362ffd338 100644 (file)
@@ -56,6 +56,7 @@ bind y messagemode2
 bind z messagemode2
 bind u "+con_chat_maximize"
 bind m +hud_panel_radar_maximized
+bind b "quickmenu"
 bind i +show_info
 bind PAUSE pause
 bind F9 "cl_cmd hud minigame"
diff --git a/gfx/hud/default/checkbox_checked.tga b/gfx/hud/default/checkbox_checked.tga
new file mode 100644 (file)
index 0000000..ff8941d
Binary files /dev/null and b/gfx/hud/default/checkbox_checked.tga differ
diff --git a/gfx/hud/default/checkbox_empty.tga b/gfx/hud/default/checkbox_empty.tga
new file mode 100644 (file)
index 0000000..e7d644e
Binary files /dev/null and b/gfx/hud/default/checkbox_empty.tga differ
diff --git a/gfx/hud/default/checkbox_undefined.tga b/gfx/hud/default/checkbox_undefined.tga
new file mode 100644 (file)
index 0000000..1b80a37
Binary files /dev/null and b/gfx/hud/default/checkbox_undefined.tga differ
index 70d0764b67e25e28d419eacb8977f5b587bf10e7..639d08af67f1cb71fef547a81451d55390b4836f 100644 (file)
@@ -366,4 +366,15 @@ seta hud_panel_minigamemenu_bg_alpha ""
 seta hud_panel_minigamemenu_bg_border ""
 seta hud_panel_minigamemenu_bg_padding ""
 
+seta hud_panel_quickmenu 1
+seta hud_panel_quickmenu_pos "0.600000 0.445000"
+seta hud_panel_quickmenu_size "0.220000 0.240000"
+seta hud_panel_quickmenu_bg ""
+seta hud_panel_quickmenu_bg_color ""
+seta hud_panel_quickmenu_bg_color_team ""
+seta hud_panel_quickmenu_bg_alpha ""
+seta hud_panel_quickmenu_bg_border ""
+seta hud_panel_quickmenu_bg_padding ""
+seta hud_panel_quickmenu_align "0"
+
 menu_sync
index a1509ae7bf7679fa349091f02613536f8fd7bd0a..4184531eb6c81f904b86028d242ec97e0a25c284 100644 (file)
@@ -26,7 +26,7 @@ seta hud_progressbar_acceleration_neg_color "0.125 0.25 0.5"
 seta hud_progressbar_vehicles_ammo1_color "0.8 0.7 0"
 seta hud_progressbar_vehicles_ammo2_color "0.7 0.4 0"
 
-seta _hud_panelorder "17 15 12 9 10 5 6 14 0 7 4 11 2 1 3 8 13 16 18"
+seta _hud_panelorder "17 15 12 9 10 5 6 14 0 7 4 11 2 1 3 8 13 16 18 23 "
 
 seta hud_configure_grid "1"
 seta hud_configure_grid_xsize "0.010000"
@@ -366,4 +366,15 @@ seta hud_panel_minigamemenu_bg_alpha ""
 seta hud_panel_minigamemenu_bg_border ""
 seta hud_panel_minigamemenu_bg_padding ""
 
+seta hud_panel_quickmenu 1
+seta hud_panel_quickmenu_pos "0.610000 0.450000"
+seta hud_panel_quickmenu_size "0.200000 0.210000"
+seta hud_panel_quickmenu_bg ""
+seta hud_panel_quickmenu_bg_color ""
+seta hud_panel_quickmenu_bg_color_team ""
+seta hud_panel_quickmenu_bg_alpha ""
+seta hud_panel_quickmenu_bg_border ""
+seta hud_panel_quickmenu_bg_padding ""
+seta hud_panel_quickmenu_align "0"
+
 menu_sync
index e5aec23184124939077331665dde381e2c1dfe89..bfa36b80a26b4dbc9cba9f298e2aa8252f3fb101 100644 (file)
@@ -26,7 +26,7 @@ seta hud_progressbar_acceleration_neg_color "0.125 0.25 0.5"
 seta hud_progressbar_vehicles_ammo1_color "0.8 0.7 0"
 seta hud_progressbar_vehicles_ammo2_color "0.7 0.4 0"
 
-seta _hud_panelorder "17 10 3 0 14 6 9 13 4 1 2 11 12 7 5 8 15 16 18"
+seta _hud_panelorder "17 10 3 0 14 6 9 13 4 1 2 11 12 7 5 8 15 16 18 23 "
 
 seta hud_configure_grid "1"
 seta hud_configure_grid_xsize "0.010000"
@@ -366,4 +366,15 @@ seta hud_panel_minigamemenu_bg_alpha ""
 seta hud_panel_minigamemenu_bg_border ""
 seta hud_panel_minigamemenu_bg_padding ""
 
+seta hud_panel_quickmenu 1
+seta hud_panel_quickmenu_pos "0.600000 0.460000"
+seta hud_panel_quickmenu_size "0.190000 0.190000"
+seta hud_panel_quickmenu_bg ""
+seta hud_panel_quickmenu_bg_color ""
+seta hud_panel_quickmenu_bg_color_team ""
+seta hud_panel_quickmenu_bg_alpha ""
+seta hud_panel_quickmenu_bg_border ""
+seta hud_panel_quickmenu_bg_padding ""
+seta hud_panel_quickmenu_align "0"
+
 menu_sync
index f313b4c435530af0b6654c20c4ab16cadfabe2f5..8189ade09c6f667a7a2d4832265744ca95c78de5 100644 (file)
@@ -26,7 +26,7 @@ seta hud_progressbar_acceleration_neg_color "0.125 0.25 0.5"
 seta hud_progressbar_vehicles_ammo1_color "0.8 0.7 0"
 seta hud_progressbar_vehicles_ammo2_color "0.7 0.4 0"
 
-seta _hud_panelorder "17 15 3 1 2 11 10 0 14 6 9 13 4 12 7 5 8 16 18"
+seta _hud_panelorder "17 15 3 1 2 11 10 0 14 6 9 13 4 12 7 5 8 16 18 23 "
 
 seta hud_configure_grid "1"
 seta hud_configure_grid_xsize "0.010000"
@@ -366,4 +366,15 @@ seta hud_panel_minigamemenu_bg_alpha ""
 seta hud_panel_minigamemenu_bg_border ""
 seta hud_panel_minigamemenu_bg_padding ""
 
+seta hud_panel_quickmenu 1
+seta hud_panel_quickmenu_pos "0.600000 0.460000"
+seta hud_panel_quickmenu_size "0.190000 0.190000"
+seta hud_panel_quickmenu_bg ""
+seta hud_panel_quickmenu_bg_color ""
+seta hud_panel_quickmenu_bg_color_team ""
+seta hud_panel_quickmenu_bg_alpha ""
+seta hud_panel_quickmenu_bg_border ""
+seta hud_panel_quickmenu_bg_padding ""
+seta hud_panel_quickmenu_align "0"
+
 menu_sync
index 4d27482b09e81d8b33077b146c255c9bf6be25b0..29fa1df665b3819665ad929c26a9e79027a95017 100644 (file)
@@ -26,7 +26,7 @@ seta hud_progressbar_acceleration_neg_color "0.125 0.25 0.5"
 seta hud_progressbar_vehicles_ammo1_color "0.8 0.7 0"
 seta hud_progressbar_vehicles_ammo2_color "0.7 0.4 0"
 
-seta _hud_panelorder "17 15 10 9 6 8 14 5 0 4 13 2 7 1 3 11 12 16 18"
+seta _hud_panelorder "17 15 10 9 6 8 14 5 0 4 13 2 7 1 3 11 12 16 18 23 "
 
 seta hud_configure_grid "1"
 seta hud_configure_grid_xsize "0.010000"
@@ -366,4 +366,15 @@ seta hud_panel_minigamemenu_bg_alpha ""
 seta hud_panel_minigamemenu_bg_border ""
 seta hud_panel_minigamemenu_bg_padding ""
 
+seta hud_panel_quickmenu 1
+seta hud_panel_quickmenu_pos "0.700000 0.460000"
+seta hud_panel_quickmenu_size "0.210000 0.250000"
+seta hud_panel_quickmenu_bg ""
+seta hud_panel_quickmenu_bg_color ""
+seta hud_panel_quickmenu_bg_color_team ""
+seta hud_panel_quickmenu_bg_alpha ""
+seta hud_panel_quickmenu_bg_border ""
+seta hud_panel_quickmenu_bg_padding ""
+seta hud_panel_quickmenu_align "1"
+
 menu_sync
index 5eb75cfde006cc314a56fa2c19e3fe6d6f5f7e75..c706b7a7c7ad84278dceef00b0dc60990f1ea53d 100644 (file)
@@ -26,7 +26,7 @@ seta hud_progressbar_acceleration_neg_color "0.125 0.25 0.5"
 seta hud_progressbar_vehicles_ammo1_color "0.8 0.7 0"
 seta hud_progressbar_vehicles_ammo2_color "0.7 0.4 0"
 
-seta _hud_panelorder "17 15 0 11 8 5 6 14 9 13 7 2 3 1 10 12 4 16 18"
+seta _hud_panelorder "17 15 0 11 8 5 6 14 9 13 7 2 3 1 10 12 4 16 18 23 "
 
 seta hud_configure_grid "1"
 seta hud_configure_grid_xsize "0.01"
@@ -366,4 +366,15 @@ seta hud_panel_minigamemenu_bg_alpha ""
 seta hud_panel_minigamemenu_bg_border ""
 seta hud_panel_minigamemenu_bg_padding ""
 
+seta hud_panel_quickmenu 1
+seta hud_panel_quickmenu_pos "0.010000 0.380000"
+seta hud_panel_quickmenu_size "0.210000 0.250000"
+seta hud_panel_quickmenu_bg ""
+seta hud_panel_quickmenu_bg_color ""
+seta hud_panel_quickmenu_bg_color_team ""
+seta hud_panel_quickmenu_bg_alpha ""
+seta hud_panel_quickmenu_bg_border ""
+seta hud_panel_quickmenu_bg_padding ""
+seta hud_panel_quickmenu_align "0"
+
 menu_sync
index 9cfbccaa9d14891780ebb2c9de0f520205749745..c57cb648914981a38f52f99993ce3db9b4ed038d 100644 (file)
@@ -46,6 +46,7 @@
 "ready"                                 "ready"
 ""                                      ""
 ""                                      "Client"
+"quickmenu"                             "quick menu"
 "+show_info"                            "server info"
 "toggleconsole"                         "enter console"
 "disconnect"                            "disconnect"
index b128f01a7fc12945f605676a4248a557334374b1..7a1d66450b06347a505f1ffa077399e1ae52302f 100644 (file)
@@ -285,6 +285,9 @@ bool autocvar_hud_panel_powerups_text;
 int autocvar_hud_panel_pressedkeys;
 float autocvar_hud_panel_pressedkeys_aspect;
 bool autocvar_hud_panel_pressedkeys_attack;
+float autocvar_hud_panel_quickmenu_translatecommands;
+string autocvar_hud_panel_quickmenu_file;
+float autocvar_hud_panel_quickmenu_time;
 bool autocvar_hud_panel_racetimer;
 int autocvar_hud_panel_radar;
 float autocvar_hud_panel_radar_foreground_alpha;
@@ -333,6 +336,8 @@ float autocvar_hud_panel_weapons_timeout_fadebgmin;
 float autocvar_hud_panel_weapons_timeout_fadefgmin;
 float autocvar_hud_panel_weapons_timeout_speed_in = 0.25;
 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;
index 34e891cca23b6a617927e6d6c354aceb92f59700..2d6093868cc1ea496e5822880e0c69f5900c4f00 100644 (file)
@@ -254,6 +254,15 @@ void LocalCommand_hud(int request, int argc)
                                        return;
                                }
 
+                               case "quickmenu":
+                               {
+                                       if(QuickMenu_IsOpened())
+                                               QuickMenu_Close();
+                                       else
+                                               QuickMenu_Open(argv(2), argv(3)); // mode, submenu
+                                       return;
+                               }
+
                                case "minigame":
                                {
                                        if(HUD_MinigameMenu_IsOpened())
@@ -314,6 +323,9 @@ void LocalCommand_hud(int request, int argc)
                        LOG_INFO("  'configname' is the name to save to for \"save\" action,\n");
                        LOG_INFO("  'radartoggle' is to control hud_panel_radar_maximized for \"radar\" action,\n");
                        LOG_INFO("  and 'layout' is how to organize the scoreboard columns for the set action.\n");
+                       LOG_INFO("  quickmenu [[default | file | \"\"] submenu]\n");
+                       LOG_INFO("    Called without options (or with "") loads either the default quickmenu or a quickmenu file if hud_panel_quickmenu_file is set to a valid filename.\n");
+                       LOG_INFO("    Submenu option allows to open quickmenu directly in a submenu, it requires to specify 'default', 'file' or '\"\"' option.\n");
                        LOG_INFO("  Full list of commands here: \"configure, minigame, save, scoreboard_columns_help, scoreboard_columns_set, radar.\"\n");
                        return;
                }
index 81ae0bb5c68a49c58f6c60a638facf16f1a8d079..eb50ebc094090437a069d5a7fcc87ae368b583b0 100644 (file)
@@ -4674,6 +4674,12 @@ void HUD_CenterPrint (void)
 //
 #include "../common/minigames/cl_minigames_hud.qc"
 
+
+// QuickMenu (#23)
+//
+#include "quickmenu.qc"
+
+
 /*
 ==================
 Main HUD system
@@ -4836,6 +4842,8 @@ void HUD_Main (void)
                HUD_Panel_Draw(HUD_PANEL(RADAR));
        if(autocvar__con_chat_maximized)
                HUD_Panel_Draw(HUD_PANEL(CHAT));
+       if(hud_panel_quickmenu)
+               HUD_Panel_Draw(HUD_PANEL(QUICKMENU));
 
        HUD_Configure_PostDraw();
 
index 9850b78d20b91696d465b69f4a92fe72f3c2d22b..d30eea145de3c29e86281185819f3732f4344bb0 100644 (file)
@@ -35,6 +35,8 @@ int vote_prev; // previous state of vote_active to check for a change
 float vote_alpha;
 float vote_change; // "time" when vote_active changed
 
+float hud_panel_quickmenu;
+
 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
@@ -125,12 +127,11 @@ float chat_sizey;
 
 float current_player;
 
-float GetPlayerColorForce(int i);
-
 float stringwidth_colors(string s, vector theSize);
+float stringwidth_nocolors(string s, vector theSize);
+float GetPlayerColorForce(int i);
 int GetPlayerColor(int i);
 string GetPlayerName(int i);
-float stringwidth_nocolors(string s, vector theSize);
 void HUD_Panel_DrawProgressBar(vector theOrigin, vector theSize, string pic, float length_ratio, bool vertical, float baralign, vector theColor, float theAlpha, int drawflag);
 
 .int panel_showflags;
@@ -178,7 +179,10 @@ void HUD_ItemsTime();
        HUD_PANEL(MINIGAME_HELP,  HUD_MinigameHelp  ,minigamehelp,  PANEL_SHOW_MINIGAME ) \
        HUD_PANEL(MINIGAME_MENU,  HUD_MinigameMenu  ,minigamemenu,  PANEL_SHOW_ALWAYS   ) \
        HUD_PANEL(MAPVOTE      ,  MapVote_Draw      ,mapvote,       PANEL_SHOW_ALWAYS   ) \
-       HUD_PANEL(ITEMSTIME    ,  HUD_ItemsTime     ,itemstime,     PANEL_SHOW_MAINGAME )
+       HUD_PANEL(ITEMSTIME    ,  HUD_ItemsTime     ,itemstime,     PANEL_SHOW_MAINGAME ) \
+       HUD_PANEL(QUICKMENU    , HUD_QuickMenu    , quickmenu,      PANEL_SHOW_MAINGAME ) \
+       // always add new panels to the end of list
+
 
 #define HUD_PANEL(NAME, draw_func, name, showflags)                                                                                                                                                    \
        int HUD_PANEL_##NAME;                                                                                                                                                                                   \
index 33908855036027b485b0c2178af9af7c02f0a921..0216a43b087b6b0352148f12ac95ba41a8cc1a7d 100644 (file)
@@ -209,6 +209,8 @@ void HUD_Panel_ExportCfg(string cfgname)
                                        HUD_Write_PanelCvar_q("_text");
                                        HUD_Write_PanelCvar_q("_ratio");
                                        HUD_Write_PanelCvar_q("_dynamicsize");
+                               case HUD_PANEL_QUICKMENU:
+                                       HUD_Write_PanelCvar_q("_align");
                                        break;
                        }
                        HUD_Write("\n");
index 6f40c3970d5b902156abc73521b10dc62e091dd2..a5085e893da413c6db62620736aabb7fef9d6fec 100644 (file)
@@ -87,7 +87,7 @@ string forcefog;
 void ConsoleCommand_macro_init();
 void CSQC_Init(void)
 {
-       prvm_language = cvar_string("prvm_language");
+       prvm_language = strzone(cvar_string("prvm_language"));
 
 #ifdef WATERMARK
        LOG_TRACEF("^4CSQC Build information: ^1%s\n", WATERMARK);
@@ -368,6 +368,9 @@ float CSQC_InputEvent(float bInputType, float nPrimary, float nSecondary)
        if (HUD_Panel_InputEvent(bInputType, nPrimary, nSecondary))
                return true;
 
+       if (QuickMenu_InputEvent(bInputType, nPrimary, nSecondary))
+               return true;
+
        if ( HUD_Radar_InputEvent(bInputType, nPrimary, nSecondary) )
                return true;
 
diff --git a/qcsrc/client/quickmenu.qc b/qcsrc/client/quickmenu.qc
new file mode 100644 (file)
index 0000000..4846532
--- /dev/null
@@ -0,0 +1,869 @@
+#include "_all.qh"
+
+#include "hud_config.qh"
+
+#include "../dpdefs/keycodes.qh"
+
+// QUICKMENU_MAXLINES must be <= 10
+const int QUICKMENU_MAXLINES = 10;
+// visible entries are loaded from QuickMenu_Buffer into QuickMenu_Page_* arrays
+string QuickMenu_Page_Command[QUICKMENU_MAXLINES];
+string QuickMenu_Page_Description[QUICKMENU_MAXLINES];
+int QuickMenu_Page_Command_Type[QUICKMENU_MAXLINES];
+int QuickMenu_Page_Entries;
+int QuickMenu_Page;
+int QuickMenu_Page_ActivatedEntry = -1;
+bool QuickMenu_Page_ActivatedEntry_Close;
+float QuickMenu_Page_ActivatedEntry_Time;
+bool QuickMenu_IsLastPage;
+// all the entries are loaded into QuickMenu_Buffer
+// each entry (submenu or command) is composed of 2 entries
+const int QUICKMENU_MAXENTRIES = 256;
+const int QUICKMENU_BUFFER_MAXENTRIES = 2 * QUICKMENU_MAXENTRIES;
+int QuickMenu_Buffer = -1;
+int QuickMenu_Buffer_Size;
+int QuickMenu_Buffer_Index;
+string QuickMenu_CurrentSubMenu;
+float QuickMenu_TimeOut;
+
+// QuickMenu_Buffer are labeled with these tags
+#define QM_TAG_TITLE "T"
+#define QM_TAG_SUBMENU "S"
+#define QM_TAG_COMMAND "C"
+#define QM_TAG_PLCOMMAND "P"
+
+#define QuickMenu_Buffer_Set(tag, string) bufstr_set(QuickMenu_Buffer, QuickMenu_Buffer_Size, strcat(tag, string))
+#define QuickMenu_Buffer_Get() bufstr_get(QuickMenu_Buffer, QuickMenu_Buffer_Index)
+
+// if s1 is not empty s will be displayed as command otherwise as submenu
+void QuickMenu_Page_LoadEntry(int i, string s, string s1)
+{
+       //printf("^xc80 entry %d: %s, %s\n", i, s, s1);
+       if (QuickMenu_Page_Description[i])
+               strunzone(QuickMenu_Page_Description[i]);
+       QuickMenu_Page_Description[i] = strzone(s);
+       if (QuickMenu_Page_Command[i])
+               strunzone(QuickMenu_Page_Command[i]);
+       QuickMenu_Page_Command[i] = strzone(s1);
+}
+
+void QuickMenu_Page_ClearEntry(int i)
+{
+       if (QuickMenu_Page_Description[i])
+               strunzone(QuickMenu_Page_Description[i]);
+       QuickMenu_Page_Description[i] = string_null;
+       if (QuickMenu_Page_Command[i])
+               strunzone(QuickMenu_Page_Command[i]);
+       QuickMenu_Page_Command[i] = string_null;
+}
+
+float QuickMenu_Page_Load(string target_submenu, float new_page);
+void QuickMenu_Default(string submenu);
+bool QuickMenu_Open(string mode, string submenu)
+{
+       int fh = -1;
+       string s;
+
+       if(mode == "")
+       {
+               if(autocvar_hud_panel_quickmenu_file == "" || autocvar_hud_panel_quickmenu_file == "0")
+                       mode = "default";
+               else
+                       mode = "file";
+       }
+
+       if(mode == "file")
+       {
+               if(autocvar_hud_panel_quickmenu_file == "" || autocvar_hud_panel_quickmenu_file == "0")
+                       printf("No file name is set in hud_panel_quickmenu_file, loading default quickmenu\n");
+               else
+               {
+                       fh = fopen(autocvar_hud_panel_quickmenu_file, FILE_READ);
+                       if(fh < 0)
+                               printf("Couldn't open file \"%s\", loading default quickmenu\n", autocvar_hud_panel_quickmenu_file);
+               }
+               if(fh < 0)
+                       mode = "default";
+       }
+
+       if(mode == "default")
+       {
+               QuickMenu_Buffer = buf_create();
+               if(QuickMenu_Buffer < 0)
+                       return false;
+
+               QuickMenu_Default(submenu);
+       }
+       else if(mode == "file")
+       {
+               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 || argv(0) == "")
+                               continue;
+                       if(argc == 1)
+                               QuickMenu_Buffer_Set(QM_TAG_SUBMENU, argv(0));
+                       else if(argc == 2)
+                       {
+                               if(argv(1) == "")
+                                       continue;
+                               QuickMenu_Buffer_Set(QM_TAG_TITLE, argv(0)); 
+                               ++QuickMenu_Buffer_Size;
+                               QuickMenu_Buffer_Set(QM_TAG_COMMAND, argv(1));
+                       }
+                       else if(argc == 3)
+                       {
+                               // check for special keywords
+                               float teamplayers = 0, without_me = 0;
+                               switch(argv(2))
+                               {
+                                       case "ALLPLAYERS_BUT_ME":               without_me = 1; // fall through
+                                       case "ALLPLAYERS":                              teamplayers = 0; break;
+                                       case "OWNTEAMPLAYERS_BUT_ME":   without_me = 1; // fall through
+                                       case "OWNTEAMPLAYERS":                  teamplayers = 1; break;
+                                       case "ENEMYTEAMPLAYERS":                teamplayers = 2; break;
+                                       default: continue;
+                               }
+
+                               if(QuickMenu_Buffer_Size + 3 < QUICKMENU_BUFFER_MAXENTRIES)
+                               {
+                                       QuickMenu_Buffer_Set(QM_TAG_SUBMENU, argv(0));
+                                       ++QuickMenu_Buffer_Size;
+                                       QuickMenu_Buffer_Set(QM_TAG_TITLE, strcat(ftos(teamplayers), ftos(without_me))); // put PLCOMMAND arguments in the title string
+                                       ++QuickMenu_Buffer_Size;
+                                       QuickMenu_Buffer_Set(QM_TAG_PLCOMMAND, argv(1));
+                                       ++QuickMenu_Buffer_Size;
+                                       QuickMenu_Buffer_Set(QM_TAG_SUBMENU, argv(0));
+                               }
+                       }
+                       ++QuickMenu_Buffer_Size;
+               }
+               fclose(fh);
+       }
+       else
+       {
+               printf("Unrecognized mode %s\n", mode);
+               return false;
+       }
+
+       if (QuickMenu_Buffer_Size <= 0)
+       {
+               buf_del(QuickMenu_Buffer);
+               QuickMenu_Buffer = -1;
+               return false;
+       }
+
+       if(mode == "file")
+               QuickMenu_Page_Load(submenu, 0);
+       else
+               QuickMenu_Page_Load("", 0);
+
+       hud_panel_quickmenu = 1;
+       if(autocvar_hud_cursormode)
+               setcursormode(1);
+       hudShiftState = 0;
+
+       QuickMenu_TimeOut = time + autocvar_hud_panel_quickmenu_time;
+       return true;
+}
+
+void QuickMenu_Buffer_Close()
+{
+       if (QuickMenu_Buffer >= 0)
+       {
+               buf_del(QuickMenu_Buffer);
+               QuickMenu_Buffer = -1;
+               QuickMenu_Buffer_Size = 0;
+       }
+}
+
+void QuickMenu_Close()
+{
+       if (QuickMenu_CurrentSubMenu)
+               strunzone(QuickMenu_CurrentSubMenu);
+       QuickMenu_CurrentSubMenu = string_null;
+       int i;
+       for (i = 0; i < QUICKMENU_MAXLINES; ++i)
+               QuickMenu_Page_ClearEntry(i);
+       QuickMenu_Page_Entries = 0;
+       hud_panel_quickmenu = 0;
+       mouseClicked = 0;
+       prevMouseClicked = 0;
+       QuickMenu_Buffer_Close();
+
+       if(autocvar_hud_cursormode)
+       if(!mv_active)
+               setcursormode(0);
+}
+
+// It assumes submenu open tag is already detected
+void 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 = QuickMenu_Buffer_Get();
+               if(substring(s, 0, 1) != QM_TAG_SUBMENU)
+                       continue;
+               if(substring(s, 1, -1) == z_submenu) // submenu end
+                       break;
+               QuickMenu_skip_submenu(substring(s, 1, -1));
+       }
+       strunzone(z_submenu);
+}
+
+bool QuickMenu_IsOpened()
+{
+       return (QuickMenu_Page_Entries > 0);
+}
+
+void HUD_Quickmenu_PlayerListEntries(string cmd, int teamplayers, float without_me);
+bool HUD_Quickmenu_PlayerListEntries_Create(string cmd, int teamplayers, float without_me)
+{
+       int i;
+       for(i = 0; i < QUICKMENU_MAXLINES; ++i)
+               QuickMenu_Page_ClearEntry(i);
+       QuickMenu_Buffer_Close();
+
+       QuickMenu_Buffer = buf_create();
+       if(QuickMenu_Buffer < 0)
+               return false;
+
+       HUD_Quickmenu_PlayerListEntries(cmd, teamplayers, without_me);
+
+       if(QuickMenu_Buffer_Size <= 0)
+       {
+               buf_del(QuickMenu_Buffer);
+               QuickMenu_Buffer = -1;
+               return false;
+       }
+       return true;
+}
+
+// new_page 0 means page 0, new_page != 0 means next page
+int QuickMenu_Buffer_Index_Prev;
+bool QuickMenu_Page_Load(string target_submenu, int new_page)
+{
+       string s = string_null, cmd = string_null, z_submenu;
+
+       if (new_page == 0)
+               QuickMenu_Page = 0;
+       else
+               ++QuickMenu_Page;
+
+       z_submenu = strzone(target_submenu);
+       if (QuickMenu_CurrentSubMenu)
+               strunzone(QuickMenu_CurrentSubMenu);
+       QuickMenu_CurrentSubMenu = strzone(z_submenu);
+
+       QuickMenu_IsLastPage = true;
+       QuickMenu_Page_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 = QuickMenu_Buffer_Get();
+                       if(substring(s, 0, 1) == QM_TAG_SUBMENU && substring(s, 1, -1) == z_submenu)
+                       {
+                               // printf("^3 beginning of %s\n", z_submenu);
+                               ++QuickMenu_Buffer_Index;
+                               break; // target_submenu found!
+                       }
+                       // printf("^1 skipping %s\n", s);
+               }
+               if(QuickMenu_Buffer_Index == QuickMenu_Buffer_Size)
+                       printf("Couldn't find submenu \"%s\"\n", z_submenu);
+       }
+
+       // only the last page can contain up to QUICKMENU_MAXLINES entries
+       // the other ones contain only (QUICKMENU_MAXLINES - 2) entries
+       // so that the panel can show an empty row and "Continue..."
+       float first_entry = QuickMenu_Page * (QUICKMENU_MAXLINES - 2);
+       int entry_num = 0; // counts entries in target_submenu
+       for( ; QuickMenu_Buffer_Index < QuickMenu_Buffer_Size; ++QuickMenu_Buffer_Index)
+       {
+               s = QuickMenu_Buffer_Get();
+
+               if(z_submenu != "" && substring(s, 1, -1) == z_submenu)
+               {
+                       // printf("^3 end of %s\n", z_submenu);
+                       break;
+               }
+
+               if(entry_num >= first_entry)
+               {
+                       ++QuickMenu_Page_Entries;
+                       if(QuickMenu_Page_Entries == QUICKMENU_MAXLINES - 2)
+                               QuickMenu_Buffer_Index_Prev = QuickMenu_Buffer_Index;
+                       else if(QuickMenu_Page_Entries == QUICKMENU_MAXLINES)
+                       {
+                               QuickMenu_Page_ClearEntry(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) == QM_TAG_SUBMENU)
+               {
+                       if(entry_num >= first_entry)
+                               QuickMenu_Page_LoadEntry(QuickMenu_Page_Entries, substring(s, 1, -1), "");
+                       QuickMenu_skip_submenu(substring(s, 1, -1));
+               }
+               else if(entry_num >= first_entry && substring(s, 0, 1) == QM_TAG_TITLE)
+               {
+                       ++QuickMenu_Buffer_Index;
+                       cmd = QuickMenu_Buffer_Get();
+                       string command_code = substring(cmd, 0, 1);
+                       if(command_code == QM_TAG_COMMAND)
+                               cmd = substring(cmd, 1, -1);
+                       else if(command_code == QM_TAG_PLCOMMAND)
+                       {
+                               // throw away the current quickmenu buffer and load a new one
+                               cmd = substring(cmd, 1, -1);
+                               strunzone(z_submenu);
+                               if(HUD_Quickmenu_PlayerListEntries_Create(cmd, stof(substring(s, 1, 1)), stof(substring(s, 2, 1))))
+                                       return QuickMenu_Page_Load("", 0);
+                               QuickMenu_Close();
+                               return false;
+                       }
+
+                       tokenize_console(cmd);
+                       QuickMenu_Page_Command_Type[QuickMenu_Page_Entries] = (argv(1) && argv(0) == "toggle");
+
+                       QuickMenu_Page_LoadEntry(QuickMenu_Page_Entries, substring(s, 1, -1), cmd);
+               }
+
+               ++entry_num;
+       }
+       strunzone(z_submenu);
+       if (QuickMenu_Page_Entries == 0)
+       {
+               QuickMenu_Close();
+               return false;
+       }
+       QuickMenu_TimeOut = time + autocvar_hud_panel_quickmenu_time;
+       return true;
+}
+
+bool QuickMenu_ActionForNumber(int num)
+{
+       if (!QuickMenu_IsLastPage)
+       {
+               if (num < 0 || num >= QUICKMENU_MAXLINES)
+                       return false;
+               if (num == QUICKMENU_MAXLINES - 1)
+                       return false;
+               if (num == 0)
+               {
+                       QuickMenu_Page_Load(QuickMenu_CurrentSubMenu, +1);
+                       return false;
+               }
+       } else if (num <= 0 || num > QuickMenu_Page_Entries)
+               return false;
+
+       if (QuickMenu_Page_Command[num] != "")
+       {
+               localcmd(strcat("\n", QuickMenu_Page_Command[num], "\n"));
+               QuickMenu_TimeOut = time + autocvar_hud_panel_quickmenu_time;
+               return true;
+       }
+       if (QuickMenu_Page_Description[num] != "")
+               QuickMenu_Page_Load(QuickMenu_Page_Description[num], 0);
+       return false;
+}
+
+void QuickMenu_Page_ActiveEntry(float entry_num)
+{
+       QuickMenu_Page_ActivatedEntry = entry_num;
+       QuickMenu_Page_ActivatedEntry_Time = time + 0.1;
+       if(QuickMenu_Page_Command[QuickMenu_Page_ActivatedEntry])
+       {
+               bool f = QuickMenu_ActionForNumber(QuickMenu_Page_ActivatedEntry);
+               // toggle commands don't close the quickmenu
+               if(QuickMenu_Page_Command_Type[QuickMenu_Page_ActivatedEntry] == 1)
+                       QuickMenu_Page_ActivatedEntry_Close = false;
+               else
+                       QuickMenu_Page_ActivatedEntry_Close = (f && !(hudShiftState & S_CTRL));
+       }
+       else
+               QuickMenu_Page_ActivatedEntry_Close = (!(hudShiftState & S_CTRL));
+}
+
+bool QuickMenu_InputEvent(float bInputType, float nPrimary, float nSecondary)
+{
+       // we only care for keyboard events
+       if(bInputType == 2)
+               return false;
+
+       if(!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
+
+       bool hit_con_bind = false;
+       int i;
+       for (i = 0; i < keys; ++i)
+       {
+               if(nPrimary == stof(argv(i)))
+                       hit_con_bind = true;
+       }
+
+       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;
+               QuickMenu_Close();
+       }
+       else if(nPrimary >= '0' && nPrimary <= '9')
+       {
+               if (bInputType == 1)
+                       return true;
+               QuickMenu_Page_ActiveEntry(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 QuickMenu_Mouse()
+{
+       if(mv_active) return;
+
+       if(!mouseClicked)
+       if(prevMouseClicked & S_MOUSE2)
+       {
+               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_Page_Entries) * fontsize.y) / 2;
+       entries_height = panel_size.y - ((QUICKMENU_MAXLINES - QuickMenu_Page_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))
+                               QuickMenu_Page_ActiveEntry((entry_num < QUICKMENU_MAXLINES - 1) ? entry_num + 1 : 0);
+               }
+       }
+
+       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 desc, string option, vector fontsize)
+{
+       string entry;
+       float offset;
+       float desc_width = panel_size.x;
+       if(option)
+       {
+               string pic = strcat(hud_skin_path, "/", option);
+               if(precache_pic(pic) == "")
+                       pic = strcat("gfx/hud/default/", option);
+               vector option_size = '1 1 0' * fontsize.y * 0.8;
+               desc_width -= option_size.x;
+               drawpic(pos + eX * desc_width + eY * (fontsize.y - option_size.y) / 2, pic, option_size, '1 1 1', panel_fg_alpha, DRAWFLAG_ADDITIVE);
+               desc_width -= fontsize.x / 4;
+       }
+       entry = textShortenToWidth(desc, desc_width, fontsize, stringwidth_colors);
+       if (autocvar_hud_panel_quickmenu_align > 0)
+       {
+               float real_desc_width = stringwidth_colors(entry, fontsize);
+               offset = (desc_width - real_desc_width) * min(autocvar_hud_panel_quickmenu_align, 1);
+
+               if(option)
+               {
+                       // when there's enough room align description regardless the checkbox
+                       float extra_offset = (panel_size.x - desc_width) * min(autocvar_hud_panel_quickmenu_align, 1);
+                       if(offset + real_desc_width + extra_offset < desc_width)
+                               offset += extra_offset;
+                       else
+                               offset = max(0, desc_width - real_desc_width);
+               }
+               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)
+                       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)
+               {
+                       QuickMenu_Close();
+                       return;
+               }
+       }
+       else
+       {
+               if(!QuickMenu_IsOpened())
+               {
+                       QuickMenu_Page_Entries = 1;
+                       QuickMenu_Page_LoadEntry(QuickMenu_Page_Entries, sprintf(_("Submenu%d"), QuickMenu_Page_Entries), "");
+                       ++QuickMenu_Page_Entries;
+                       QuickMenu_Page_LoadEntry(QuickMenu_Page_Entries, sprintf(_("Submenu%d"), QuickMenu_Page_Entries), "");
+                       ++QuickMenu_Page_Entries;
+                       // although real command doesn't matter here, it must not be empty
+                       // otherwise the entry is displayed like a submenu
+                       for (; QuickMenu_Page_Entries < QUICKMENU_MAXLINES - 1; ++QuickMenu_Page_Entries)
+                               QuickMenu_Page_LoadEntry(QuickMenu_Page_Entries, sprintf(_("Command%d"), QuickMenu_Page_Entries), "-");
+                       ++QuickMenu_Page_Entries;
+                       QuickMenu_Page_ClearEntry(QuickMenu_Page_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;
+       }
+
+       int 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...")), string_null, fontsize);
+       }
+       else
+               panel_pos.y += ((QUICKMENU_MAXLINES - QuickMenu_Page_Entries) * fontsize.y) / 2;
+
+       for (i = 1; i <= QuickMenu_Page_Entries; ++i) {
+               if (QuickMenu_Page_Description[i] == "")
+                       break;
+               string option = string_null;
+               if (QuickMenu_Page_Command[i] == "")
+                       color = "^4";
+               else
+               {
+                       color = "^3";
+                       if(QuickMenu_Page_Command_Type[i] == 1) // toggle command
+                       {
+                               int end = strstrofs(QuickMenu_Page_Command[i], ";", 0);
+                               if(end < 0)
+                                       tokenize_console(QuickMenu_Page_Command[i]);
+                               else
+                                       tokenize_console(substring(QuickMenu_Page_Command[i], 0, end));
+
+                               //if(argv(1) && argv(0) == "toggle") // already checked
+                               {
+                                       // "enable feature xxx" "toggle xxx" (or "toggle xxx 1 0")
+                                       // "disable feature xxx" "toggle xxx 0 1"
+                                       float ON_value = 1, OFF_value = 0;
+                                       if(argv(2))
+                                               ON_value = stof(argv(2));
+
+                                       if(argv(3))
+                                               OFF_value = stof(argv(3));
+                                       else
+                                               OFF_value = !ON_value;
+
+                                       float value = cvar(argv(1));
+                                       if(value == ON_value)
+                                               option = "checkbox_checked";
+                                       else if(value == OFF_value)
+                                               option = "checkbox_empty";
+                                       else
+                                               option = "checkbox_undefined";
+                               }
+                       }
+               }
+               HUD_Quickmenu_DrawEntry(panel_pos, sprintf("%d: %s%s", i, color, QuickMenu_Page_Description[i]), option, fontsize);
+
+               if(QuickMenu_Page_ActivatedEntry_Time && time < QuickMenu_Page_ActivatedEntry_Time
+                       && QuickMenu_Page_ActivatedEntry == i)
+                       drawfill(panel_pos, eX * panel_size.x + eY * fontsize.y, '0.5 1 0.5', .2, DRAWFLAG_NORMAL);
+
+               panel_pos.y += fontsize.y;
+       }
+
+       if(QuickMenu_Page_ActivatedEntry >= 0 && time >= QuickMenu_Page_ActivatedEntry_Time)
+       {
+               if(!QuickMenu_Page_Command[QuickMenu_Page_ActivatedEntry])
+               {
+                       bool f = QuickMenu_ActionForNumber(QuickMenu_Page_ActivatedEntry);
+                       if(f && QuickMenu_Page_ActivatedEntry_Close)
+                               QuickMenu_Close();
+               }
+               else if(QuickMenu_Page_ActivatedEntry_Close)
+                       QuickMenu_Close();
+               QuickMenu_Page_ActivatedEntry = -1;
+               QuickMenu_Page_ActivatedEntry_Time = 0;
+       }
+}
+
+
+#define QUICKMENU_SMENU(submenu,eng_submenu) { \
+       if(target_submenu == eng_submenu && target_submenu_found) \
+               return; /* target_submenu entries are now loaded, exit */ \
+       if(QuickMenu_Buffer_Size < QUICKMENU_BUFFER_MAXENTRIES) \
+               QuickMenu_Buffer_Set(QM_TAG_SUBMENU, submenu); \
+       ++QuickMenu_Buffer_Size; \
+       if(target_submenu == eng_submenu && !target_submenu_found) { \
+               QuickMenu_Buffer_Size = 0; /* enable load of next entries */ \
+               target_submenu_found = true; \
+       } \
+}
+
+#define QUICKMENU_ENTRY(title,command) { \
+       if(QuickMenu_Buffer_Size + 1 < QUICKMENU_BUFFER_MAXENTRIES) \
+       { \
+               QuickMenu_Buffer_Set(QM_TAG_TITLE, title); \
+               ++QuickMenu_Buffer_Size; \
+               QuickMenu_Buffer_Set(QM_TAG_COMMAND, command); \
+       } \
+       ++QuickMenu_Buffer_Size; \
+}
+
+#define QUICKMENU_SMENU_PL(submenu,eng_submenu,command,teamplayers,without_me) { \
+       if(QuickMenu_Buffer_Size + 3 < QUICKMENU_BUFFER_MAXENTRIES) {\
+               QUICKMENU_SMENU(submenu,eng_submenu) \
+               QuickMenu_Buffer_Set(QM_TAG_TITLE, strcat(ftos(teamplayers), ftos(without_me))); \
+               ++QuickMenu_Buffer_Size; \
+               QuickMenu_Buffer_Set(QM_TAG_PLCOMMAND, command); \
+               ++QuickMenu_Buffer_Size; \
+               QUICKMENU_SMENU(submenu,eng_submenu) \
+       } \
+}
+
+
+
+// useful to Translate a string inside the Command
+#define QUICKMENU_ENTRY_TC(title,command,text,translated_text) {\
+       if(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("(", prvm_language, ")", title), sprintf(command, translated_text)) \
+}
+
+void HUD_Quickmenu_PlayerListEntries(string cmd, float teamplayers, float without_me)
+{
+       entity pl;
+       if(teamplayers && !team_count)
+               return;
+
+       for(pl = players.sort_next; pl; pl = pl.sort_next)
+       {
+               if(teamplayers == 1 && (pl.team != myteam || pl.team == NUM_SPECTATOR)) // only own team players
+                       continue;
+               if(teamplayers == 2 && (pl.team == myteam || pl.team == NUM_SPECTATOR)) // only enemy team players
+                       continue;
+               if(without_me && pl.sv_entnum == player_localnum)
+                       continue;
+               QUICKMENU_ENTRY(GetPlayerName(pl.sv_entnum), sprintf(cmd, GetPlayerName(pl.sv_entnum)))
+       }
+
+       return;
+}
+
+
+// Specifying target_submenu, this function only loads entries inside target_submenu
+// NOTE: alternatively we could have loaded the whole default quickmenu and
+// then called QuickMenu_Page_Load(target_submenu, 0);
+// but this sytem is more reliable since we can always refer to target_submenu
+// with the English title even if a translation is active
+void QuickMenu_Default(string target_submenu)
+{
+       bool target_submenu_found = false;
+       if(target_submenu != "")
+               QuickMenu_Buffer_Size = QUICKMENU_BUFFER_MAXENTRIES; // forbids load of next entries until target_submenu
+
+       QUICKMENU_SMENU(CTX(_("QMCMD^Chat")), "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")), "Chat")
+
+       if(teamplay)
+       {
+       QUICKMENU_SMENU(CTX(_("QMCMD^Team chat")), "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")), "Team chat")
+       }
+
+       QUICKMENU_SMENU_PL(CTX(_("QMCMD^Send private message to")), "Send private message to", "commandmode tell \"%s^7\"", 0, 1)
+
+       QUICKMENU_SMENU(CTX(_("QMCMD^Settings")), "Settings")
+               QUICKMENU_SMENU(CTX(_("QMCMD^View/HUD settings")), "View/HUD settings")
+                       QUICKMENU_ENTRY(CTX(_("QMCMD^3rd person view")), "toggle chase_active")
+                       QUICKMENU_ENTRY(CTX(_("QMCMD^Player models like mine")), "toggle cl_forceplayermodels")
+                       QUICKMENU_ENTRY(CTX(_("QMCMD^Names above players")), "toggle hud_shownames")
+                       QUICKMENU_ENTRY(CTX(_("QMCMD^Crosshair per weapon")), "toggle crosshair_per_weapon")
+                       QUICKMENU_ENTRY(CTX(_("QMCMD^FPS")), "toggle hud_panel_engineinfo")
+                       QUICKMENU_ENTRY(CTX(_("QMCMD^Net graph")), "toggle shownetgraph")
+               QUICKMENU_SMENU(CTX(_("QMCMD^View/HUD settings")), "View/HUD settings")
+
+               QUICKMENU_SMENU(CTX(_("QMCMD^Sound settings")), "Sound settings")
+                       QUICKMENU_ENTRY(CTX(_("QMCMD^Hit sound")), "toggle cl_hitsound")
+                       QUICKMENU_ENTRY(CTX(_("QMCMD^Chat sound")), "toggle con_chatsound")
+               QUICKMENU_SMENU(CTX(_("QMCMD^Sound settings")), "Sound settings")
+
+               if(spectatee_status > 0)
+               {
+               QUICKMENU_SMENU(CTX(_("QMCMD^Spectator camera")), "Spectator camera")
+                       QUICKMENU_ENTRY(CTX(_("QMCMD^1st person")), "chase_active 0; -use")
+                       QUICKMENU_ENTRY(CTX(_("QMCMD^3rd person around player")), "chase_active 1; +use")
+                       QUICKMENU_ENTRY(CTX(_("QMCMD^3rd person behind")), "chase_active 1; -use")
+               QUICKMENU_SMENU(CTX(_("QMCMD^Spectator camera")), "Spectator camera")
+               }
+
+               if(spectatee_status == -1)
+               {
+               QUICKMENU_SMENU(CTX(_("QMCMD^Observer camera")), "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")), "Observer camera")
+               }
+
+               QUICKMENU_ENTRY(CTX(_("QMCMD^Fullscreen")), "toggle vid_fullscreen; vid_restart")
+               if(prvm_language != "en")
+               QUICKMENU_ENTRY(CTX(_("QMCMD^Translate chat messages")), "toggle hud_panel_quickmenu_translatecommands")
+       QUICKMENU_SMENU(CTX(_("QMCMD^Settings")), "Settings")
+
+       QUICKMENU_SMENU(CTX(_("QMCMD^Call a vote")), "Call a vote")
+               QUICKMENU_ENTRY(CTX(_("QMCMD^Restart the map")), "vcall restart")
+               QUICKMENU_ENTRY(CTX(_("QMCMD^End match")), "vcall endmatch")
+               if(getstatf(STAT_TIMELIMIT) > 0)
+               {
+               QUICKMENU_ENTRY(CTX(_("QMCMD^Reduce match time")), "vcall reducematchtime")
+               QUICKMENU_ENTRY(CTX(_("QMCMD^Extend match time")), "vcall extendmatchtime")
+               }
+               if(teamplay)
+               QUICKMENU_ENTRY(CTX(_("QMCMD^Shuffle teams")), "vcall shuffleteams")
+       QUICKMENU_SMENU(CTX(_("QMCMD^Call a vote")), "Call a vote")
+
+       if(target_submenu != "" && !target_submenu_found)
+       {
+               printf("Couldn't find submenu \"%s\"\n", target_submenu);
+               if(prvm_language != "en")
+                       printf("^3Warning: submenu must be in English\n", target_submenu);
+               QuickMenu_Buffer_Size = 0;
+       }
+}
+#undef QUICKMENU_SMENU
+#undef QUICKMENU_ENTRY
+#undef QUICKMENU_ENTRY_TC
index a601ae5d2be1a1515c478c2cfd1642163a47c434..da5a7b2a771ab9cdb85a5101be323089fb21d9f7 100644 (file)
@@ -956,6 +956,8 @@ vector HUD_Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_siz
 float HUD_WouldDrawScoreboard() {
        if (autocvar__hud_configure)
                return 0;
+       else if (QuickMenu_IsOpened())
+               return 0;
        else if (HUD_Radar_Clickable())
                return 0;
        else if (scoreboard_showscores)
index b68270990a5868e5896683f8ab8eb21bed7df2f3..701eb9f1e7419bb5328bcdd6e0651bd94b86e1c0 100644 (file)
@@ -1192,7 +1192,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 || QuickMenu_IsOpened())))
        {
                setproperty(VF_ORIGIN, freeze_org);
                setproperty(VF_ANGLES, freeze_ang);
@@ -1855,6 +1855,8 @@ void CSQC_UpdateView(float w, float h)
                HUD_Panel_Mouse();
        else if ( HUD_MinigameMenu_IsOpened() || minigame_isactive() )
                HUD_Minigame_Mouse();
+       else if(QuickMenu_IsOpened())
+               QuickMenu_Mouse();
        else
                HUD_Radar_Mouse();
 
index 573d55633af351e506fa6730fa1e31af73b2f959..668228b649828dae983f200969d209a477b633d0 100644 (file)
@@ -328,7 +328,7 @@ void Ent_Projectile()
 
                                if (Nade_FromProjectile(self.cnt) != NADE_TYPE_NULL)
                                {
-                                       setmodel(self, W_Model("v_ok_grenade"));
+                                       setmodel(self, W_Model("v_ok_grenade.md3"));
                                        self.traileffect = _particleeffectnum(Nade_TrailEffect(self.cnt, self.team));
                                        break;
                                }
index 0f2f13c5e40c0b036228092bf8ae143af35bfbef..20a2103785f0bffb5b6a14cfe347eac076cc1b6a 100644 (file)
@@ -42,6 +42,8 @@ const int MONSTER_SIZE_BROKEN = 128; // TODO: remove when bad models are replace
 const int MON_FLAG_SUPERMONSTER = 256; // incredibly powerful monster
 const int MON_FLAG_RANGED = 512; // monster shoots projectiles
 const int MON_FLAG_MELEE = 1024;
+const int MON_FLAG_CRUSH = 2048; // monster can be stomped in special modes
+const int MON_FLAG_RIDE = 4096; // monster can be ridden in special modes
 
 // entity properties of monsterinfo:
 .string netname; // short name
index dae97c187ba3a29ecc2b4894a8e79f37f0ac1307..0179c66b9add72b73c2a165e64fbd0f4e9fd996d 100644 (file)
@@ -3,7 +3,7 @@ bool M_Spider(int);
 #endif
 REGISTER_MONSTER_SIMPLE(
 /* MON_##id   */ SPIDER,
-/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED,
+/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED | MON_FLAG_RIDE,
 /* mins,maxs  */ '-18 -18 -25', '18 18 30',
 /* model      */ "spider.dpm",
 /* netname    */ "spider",
index 4dd3fd71f5cc68a78633b866a46149675d7bfd0a..9bdc4c63afa57ed7bd000df7894ace290844d187 100644 (file)
@@ -3,7 +3,7 @@ bool M_Wyvern(int);
 #endif
 REGISTER_MONSTER_SIMPLE(
 /* MON_##id   */ WYVERN,
-/* spawnflags */ MONSTER_TYPE_FLY | MONSTER_SIZE_BROKEN | MON_FLAG_RANGED,
+/* 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",
index 1ccfa82c816879865e75faf2eeb1c8d0148bae88..780cf74b381dadb7fc5db70ee4f45bef5fa63e11 100644 (file)
@@ -3,7 +3,7 @@ bool M_Zombie(int);
 #endif
 REGISTER_MONSTER_SIMPLE(
 /* MON_##id   */ ZOMBIE,
-/* spawnflags */ MON_FLAG_MELEE,
+/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RIDE,
 /* mins,maxs  */ '-18 -18 -25', '18 18 47',
 /* model      */ "zombie.dpm",
 /* netname    */ "zombie",
index 44648ab80971fdf576a7e6ea3fbde22f7f0440da..eec2d356b01b72ca3f9ef6509b2e026a872a0227 100644 (file)
@@ -1,27 +1,15 @@
 REGISTER_MUTATOR(damagetext, true);
 
 #if defined(CSQC) || defined(MENUQC)
-bool autocvar_cl_damagetext = false;
-string autocvar_cl_damagetext_format = "-%3$d";
-vector autocvar_cl_damagetext_color = '1 1 0';
-float autocvar_cl_damagetext_size = 8;
-float autocvar_cl_damagetext_alpha_start = 1;
-float autocvar_cl_damagetext_alpha_lifetime = 3;
-vector autocvar_cl_damagetext_velocity = '0 0 20';
-vector autocvar_cl_damagetext_offset = '0 -40 0';
-float autocvar_cl_damagetext_accumulate_range = 30;
-
-STATIC_INIT(cl_damagetext) {
-    CVAR_DESCRIBE(cl_damagetext, _("Draw damage dealt. 0: disabled, 1: enabled"));
-    CVAR_DESCRIBESTR(cl_damagetext_format, _("How to format the damage text. 1$ is health, 2$ is armor, 3$ is both"));
-    CVAR_DESCRIBEVEC(cl_damagetext_color, _("Default damage text color"));
-    CVAR_DESCRIBE(cl_damagetext_size, _("Damage text font size"));
-    CVAR_DESCRIBE(cl_damagetext_alpha_start, _("Damage text initial alpha"));
-    CVAR_DESCRIBE(cl_damagetext_alpha_lifetime, _("Damage text lifetime in seconds"));
-    CVAR_DESCRIBEVEC(cl_damagetext_velocity, _("Damage text move direction"));
-    CVAR_DESCRIBEVEC(cl_damagetext_offset, _("Damage text offset"));
-    CVAR_DESCRIBE(cl_damagetext_accumulate_range, _("Damage text spawned within this range is accumulated"));
-}
+AUTOCVAR_SAVE(cl_damagetext,                    bool,   false,      _("Draw damage dealt. 0: disabled, 1: enabled"));
+AUTOCVAR_SAVE(cl_damagetext_format,             string, "-%3$d",    _("How to format the damage text. 1$ is health, 2$ is armor, 3$ is both"));
+AUTOCVAR_SAVE(cl_damagetext_color,              vector, '1 1 0',    _("Default damage text color"));
+AUTOCVAR_SAVE(cl_damagetext_size,               float,  8,          _("Damage text font size"));
+AUTOCVAR_SAVE(cl_damagetext_alpha_start,        float,  1,          _("Damage text initial alpha"));
+AUTOCVAR_SAVE(cl_damagetext_alpha_lifetime,     float,  3,          _("Damage text lifetime in seconds"));
+AUTOCVAR_SAVE(cl_damagetext_velocity,           vector, '0 0 20',   _("Damage text move direction"));
+AUTOCVAR_SAVE(cl_damagetext_offset,             vector, '0 -40 0',  _("Damage text offset"));
+AUTOCVAR_SAVE(cl_damagetext_accumulate_range,   float,  30,         _("Damage text spawned within this range is accumulated"));
 #endif
 
 #ifdef CSQC
@@ -68,10 +56,7 @@ ENDCLASS(DamageText)
 #endif
 
 #ifdef SVQC
-int autocvar_sv_damagetext = 2;
-STATIC_INIT(sv_damagetext) {
-    CVAR_DESCRIBE(sv_damagetext, _("<= 0: disabled, >= 1: spectators, >= 2: players, >= 3: all players"));
-}
+AUTOCVAR(sv_damagetext, int, 2, _("<= 0: disabled, >= 1: spectators, >= 2: players, >= 3: all players"));
 #define SV_DAMAGETEXT_DISABLED()        (autocvar_sv_damagetext <= 0 /* disabled */)
 #define SV_DAMAGETEXT_SPECTATORS_ONLY() (autocvar_sv_damagetext >= 1 /* spectators only */)
 #define SV_DAMAGETEXT_PLAYERS()         (autocvar_sv_damagetext >= 2 /* players */)
index 4f4cd2b3d8f35a7bf82d3f2fdda523034f1f9475..fc89a0cd20360dc8f447542253a57c3e886d61d6 100644 (file)
@@ -3,27 +3,27 @@
 // IF YOU DISREGARD THIS NOTICE, I'LL KILL YOU WITH THE @!#%'N TUBA
 
 // core weapons
-#include "w_blaster.qc"
-#include "w_shotgun.qc"
-#include "w_machinegun.qc"
-#include "w_mortar.qc"
-#include "w_minelayer.qc"
-#include "w_electro.qc"
-#include "w_crylink.qc"
-#include "w_vortex.qc"
-#include "w_hagar.qc"
-#include "w_devastator.qc"
+#include "weapon/blaster.qc"
+#include "weapon/shotgun.qc"
+#include "weapon/machinegun.qc"
+#include "weapon/mortar.qc"
+#include "weapon/minelayer.qc"
+#include "weapon/electro.qc"
+#include "weapon/crylink.qc"
+#include "weapon/vortex.qc"
+#include "weapon/hagar.qc"
+#include "weapon/devastator.qc"
 
 // other weapons
-#include "w_porto.qc"
-#include "w_vaporizer.qc"
-#include "w_hook.qc"
-#include "w_hlac.qc"
-#include "w_tuba.qc"
-#include "w_rifle.qc"
-#include "w_fireball.qc"
-#include "w_seeker.qc"
-#include "w_shockwave.qc"
-#include "w_arc.qc"
-#include "w_hmg.qc"
-#include "w_rpc.qc"
+#include "weapon/porto.qc"
+#include "weapon/vaporizer.qc"
+#include "weapon/hook.qc"
+#include "weapon/hlac.qc"
+#include "weapon/tuba.qc"
+#include "weapon/rifle.qc"
+#include "weapon/fireball.qc"
+#include "weapon/seeker.qc"
+#include "weapon/shockwave.qc"
+#include "weapon/arc.qc"
+#include "weapon/hmg.qc"
+#include "weapon/rpc.qc"
diff --git a/qcsrc/common/weapons/w_arc.qc b/qcsrc/common/weapons/w_arc.qc
deleted file mode 100644 (file)
index c101a8c..0000000
+++ /dev/null
@@ -1,1546 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ ARC,
-/* function  */ W_Arc,
-/* ammotype  */ ammo_cells,
-/* impulse   */ 3,
-/* flags     */ WEP_FLAG_NORMAL,
-/* rating    */ BOT_PICKUP_RATING_HIGH,
-/* color     */ '1 1 1',
-/* modelname */ "arc",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairhlac 0.7",
-/* wepimg    */ "weaponarc",
-/* refname   */ "arc",
-/* wepname   */ _("Arc")
-);
-
-#define ARC_SETTINGS(w_cvar,w_prop) ARC_SETTINGS_LIST(w_cvar, w_prop, ARC, arc)
-#define ARC_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, NONE, beam_ammo) \
-       w_cvar(id, sn, NONE, beam_animtime) \
-       w_cvar(id, sn, NONE, beam_botaimspeed) \
-       w_cvar(id, sn, NONE, beam_botaimlifetime) \
-       w_cvar(id, sn, NONE, beam_damage) \
-       w_cvar(id, sn, NONE, beam_degreespersegment) \
-       w_cvar(id, sn, NONE, beam_distancepersegment) \
-       w_cvar(id, sn, NONE, beam_falloff_halflifedist) \
-       w_cvar(id, sn, NONE, beam_falloff_maxdist) \
-       w_cvar(id, sn, NONE, beam_falloff_mindist) \
-       w_cvar(id, sn, NONE, beam_force) \
-       w_cvar(id, sn, NONE, beam_healing_amax) \
-       w_cvar(id, sn, NONE, beam_healing_aps) \
-       w_cvar(id, sn, NONE, beam_healing_hmax) \
-       w_cvar(id, sn, NONE, beam_healing_hps) \
-       w_cvar(id, sn, NONE, beam_maxangle) \
-       w_cvar(id, sn, NONE, beam_nonplayerdamage) \
-       w_cvar(id, sn, NONE, beam_range) \
-       w_cvar(id, sn, NONE, beam_refire) \
-       w_cvar(id, sn, NONE, beam_returnspeed) \
-       w_cvar(id, sn, NONE, beam_tightness) \
-       w_cvar(id, sn, NONE, burst_ammo) \
-       w_cvar(id, sn, NONE, burst_damage) \
-       w_cvar(id, sn, NONE, burst_healing_aps) \
-       w_cvar(id, sn, NONE, burst_healing_hps) \
-       w_cvar(id, sn, NONE, overheat_max)/* maximum heat before jamming */ \
-       w_cvar(id, sn, NONE, overheat_min)/* minimum heat to wait for cooldown */ \
-       w_cvar(id, sn, NONE, beam_heat)   /* heat increase per second (primary) */ \
-       w_cvar(id, sn, NONE, burst_heat)  /* heat increase per second (secondary) */ \
-       w_cvar(id, sn, NONE, cooldown)    /* heat decrease per second when resting */ \
-       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)
-
-#ifndef MENUQC
-const float ARC_MAX_SEGMENTS = 20;
-vector arc_shotorigin[4];
-.vector beam_start;
-.vector beam_dir;
-.vector beam_wantdir;
-.int beam_type;
-
-const int ARC_BT_MISS =        0x00;
-const int ARC_BT_WALL =        0x01;
-const int ARC_BT_HEAL =        0x02;
-const int ARC_BT_HIT =         0x03;
-const int ARC_BT_BURST_MISS =  0x10;
-const int ARC_BT_BURST_WALL =  0x11;
-const int ARC_BT_BURST_HEAL =  0x12;
-const int ARC_BT_BURST_HIT =   0x13;
-const int ARC_BT_BURSTMASK =   0x10;
-
-const int ARC_SF_SETTINGS =    1;
-const int ARC_SF_START =       2;
-const int ARC_SF_WANTDIR =     4;
-const int ARC_SF_BEAMDIR =     8;
-const int ARC_SF_BEAMTYPE =    16;
-const int ARC_SF_LOCALMASK =   14;
-#endif
-#ifdef SVQC
-ARC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-.entity arc_beam;
-.float arc_BUTTON_ATCK_prev; // for better animation control
-.float beam_prev;
-.float beam_initialized;
-.float beam_bursting;
-.float beam_teleporttime;
-.float beam_heat; // (beam) amount of heat produced
-.float arc_overheat; // (dropped arc/player) time during which it's too hot
-.float arc_cooldown; // (dropped arc/player) cooling speed
-.float arc_heat_percent; // (player) arc heat in [0,1] (stat)
-.float arc_smoke_sound;
-#endif
-#ifdef CSQC
-void Ent_ReadArcBeam(float isnew);
-
-.vector beam_color;
-.float beam_alpha;
-.float beam_thickness;
-.float beam_traileffect;
-.float beam_hiteffect;
-.float beam_hitlight[4]; // 0: radius, 123: rgb
-.float beam_muzzleeffect;
-.float beam_muzzlelight[4]; // 0: radius, 123: rgb
-.string beam_image;
-
-.entity beam_muzzleentity;
-
-.float beam_degreespersegment;
-.float beam_distancepersegment;
-.float beam_usevieworigin;
-.float beam_initialized;
-.float beam_maxangle;
-.float beam_range;
-.float beam_returnspeed;
-.float beam_tightness;
-.vector beam_shotorigin;
-
-entity Draw_ArcBeam_callback_entity;
-float Draw_ArcBeam_callback_last_thickness;
-vector Draw_ArcBeam_callback_last_top; // NOTE: in same coordinate system as player.
-vector Draw_ArcBeam_callback_last_bottom; // NOTE: in same coordinate system as player.
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_arc(void) { weapon_defaultspawnfunc(WEP_ARC.m_id); }
-
-float W_Arc_Beam_Send(entity to, int sf)
-{
-       WriteByte(MSG_ENTITY, ENT_CLIENT_ARC_BEAM);
-
-       // Truncate information when this beam is displayed to the owner client
-       // - The owner client has no use for beam start position or directions,
-       //    it always figures this information out for itself with csqc code.
-       // - Spectating the owner also truncates this information.
-       float drawlocal = ((to == self.owner) || ((to.enemy == self.owner) && IS_SPEC(to)));
-       if(drawlocal) { sf &= ~ARC_SF_LOCALMASK; }
-
-       WriteByte(MSG_ENTITY, sf);
-
-       if(sf & ARC_SF_SETTINGS) // settings information
-       {
-               WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_degreespersegment));
-               WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_distancepersegment));
-               WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_maxangle));
-               WriteCoord(MSG_ENTITY, WEP_CVAR(arc, beam_range));
-               WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_returnspeed));
-               WriteByte(MSG_ENTITY, WEP_CVAR(arc, beam_tightness) * 10);
-
-               WriteByte(MSG_ENTITY, drawlocal);
-       }
-       if(sf & ARC_SF_START) // starting location
-       {
-               WriteCoord(MSG_ENTITY, self.beam_start.x);
-               WriteCoord(MSG_ENTITY, self.beam_start.y);
-               WriteCoord(MSG_ENTITY, self.beam_start.z);
-       }
-       if(sf & ARC_SF_WANTDIR) // want/aim direction
-       {
-               WriteCoord(MSG_ENTITY, self.beam_wantdir.x);
-               WriteCoord(MSG_ENTITY, self.beam_wantdir.y);
-               WriteCoord(MSG_ENTITY, self.beam_wantdir.z);
-       }
-       if(sf & ARC_SF_BEAMDIR) // beam direction
-       {
-               WriteCoord(MSG_ENTITY, self.beam_dir.x);
-               WriteCoord(MSG_ENTITY, self.beam_dir.y);
-               WriteCoord(MSG_ENTITY, self.beam_dir.z);
-       }
-       if(sf & ARC_SF_BEAMTYPE) // beam type
-       {
-               WriteByte(MSG_ENTITY, self.beam_type);
-       }
-
-       return true;
-}
-
-void Reset_ArcBeam(entity player, vector forward)
-{
-       if (!player.arc_beam) {
-               return;
-       }
-       player.arc_beam.beam_dir = forward;
-       player.arc_beam.beam_teleporttime = time;
-}
-
-float Arc_GetHeat_Percent(entity player)
-{
-       if ( WEP_CVAR(arc, overheat_max) <= 0 ||  WEP_CVAR(arc, overheat_max) <= 0 )
-       {
-               player.arc_overheat = 0;
-               return 0;
-       }
-
-       if ( player.arc_beam )
-               return player.arc_beam.beam_heat/WEP_CVAR(arc, overheat_max);
-
-       if ( player.arc_overheat > time )
-       {
-               return (player.arc_overheat-time) / WEP_CVAR(arc, overheat_max)
-                       * player.arc_cooldown;
-       }
-
-       return 0;
-}
-void Arc_Player_SetHeat(entity player)
-{
-       player.arc_heat_percent = Arc_GetHeat_Percent(player);
-       //dprint("Heat: ",ftos(player.arc_heat_percent*100),"%\n");
-}
-
-void W_Arc_Beam_Think(void)
-{
-       if(self != self.owner.arc_beam)
-       {
-               remove(self);
-               return;
-       }
-
-
-       float burst = 0;
-       if( self.owner.BUTTON_ATCK2 || self.beam_bursting)
-       {
-               if(!self.beam_bursting)
-                       self.beam_bursting = true;
-               burst = ARC_BT_BURSTMASK;
-       }
-
-       if(
-               !IS_PLAYER(self.owner)
-               ||
-               (self.owner.WEP_AMMO(ARC) <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO))
-               ||
-               self.owner.deadflag != DEAD_NO
-               ||
-               (!self.owner.BUTTON_ATCK && !burst )
-               ||
-               self.owner.frozen
-               ||
-               self.owner.vehicle
-               ||
-               (WEP_CVAR(arc, overheat_max) > 0 && self.beam_heat >= WEP_CVAR(arc, overheat_max))
-       )
-       {
-               if ( WEP_CVAR(arc, cooldown) > 0 )
-               {
-                       float cooldown_speed = 0;
-                       if ( self.beam_heat > WEP_CVAR(arc, overheat_min) && WEP_CVAR(arc, cooldown) > 0 )
-                       {
-                               cooldown_speed = WEP_CVAR(arc, cooldown);
-                       }
-                       else if ( !burst )
-                       {
-                               cooldown_speed = self.beam_heat / WEP_CVAR(arc, beam_refire);
-                       }
-
-                       if ( cooldown_speed )
-                       {
-                               self.owner.arc_overheat = time + self.beam_heat / cooldown_speed;
-                               self.owner.arc_cooldown = cooldown_speed;
-                       }
-
-                       if ( WEP_CVAR(arc, overheat_max) > 0 && self.beam_heat >= WEP_CVAR(arc, overheat_max) )
-                       {
-                               Send_Effect_("arc_overheat",
-                                       self.beam_start, self.beam_wantdir, 1 );
-                               sound(self, CH_WEAPON_A, W_Sound("arc_stop"), VOL_BASE, ATTN_NORM);
-                       }
-               }
-
-               if(self == self.owner.arc_beam) { self.owner.arc_beam = world; }
-               entity oldself = self;
-               self = self.owner;
-               if(!WEP_ACTION(WEP_ARC.m_id, WR_CHECKAMMO1) && !WEP_ACTION(WEP_ARC.m_id, WR_CHECKAMMO2))
-               if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
-               {
-                       // note: this doesn't force the switch
-                       W_SwitchToOtherWeapon(self);
-               }
-               self = oldself;
-               remove(self);
-               return;
-       }
-
-       // decrease ammo
-       float coefficient = frametime;
-       if(!(self.owner.items & IT_UNLIMITED_WEAPON_AMMO))
-       {
-               float rootammo;
-               if(burst)
-                       { rootammo = WEP_CVAR(arc, burst_ammo); }
-               else
-                       { rootammo = WEP_CVAR(arc, beam_ammo); }
-
-               if(rootammo)
-               {
-                       coefficient = min(coefficient, self.owner.WEP_AMMO(ARC) / rootammo);
-                       self.owner.WEP_AMMO(ARC) = max(0, self.owner.WEP_AMMO(ARC) - (rootammo * frametime));
-               }
-       }
-       float heat_speed = burst ? WEP_CVAR(arc, burst_heat) : WEP_CVAR(arc, beam_heat);
-       self.beam_heat = min( WEP_CVAR(arc, overheat_max), self.beam_heat + heat_speed*frametime );
-
-       makevectors(self.owner.v_angle);
-
-       W_SetupShot_Range(
-               self.owner,
-               true,
-               0,
-               "",
-               0,
-               WEP_CVAR(arc, beam_damage) * coefficient,
-               WEP_CVAR(arc, beam_range)
-       );
-
-       // After teleport, "lock" the beam until the teleport is confirmed.
-       if (time < self.beam_teleporttime + ANTILAG_LATENCY(self.owner)) {
-               w_shotdir = self.beam_dir;
-       }
-
-       // network information: shot origin and want/aim direction
-       if(self.beam_start != w_shotorg)
-       {
-               self.SendFlags |= ARC_SF_START;
-               self.beam_start = w_shotorg;
-       }
-       if(self.beam_wantdir != w_shotdir)
-       {
-               self.SendFlags |= ARC_SF_WANTDIR;
-               self.beam_wantdir = w_shotdir;
-       }
-
-       if(!self.beam_initialized)
-       {
-               self.beam_dir = w_shotdir;
-               self.beam_initialized = true;
-       }
-
-       // WEAPONTODO: Detect player velocity so that the beam curves when moving too
-       // idea: blend together self.beam_dir with the inverted direction the player is moving in
-       // might have to make some special accomodation so that it only uses view_right and view_up
-
-       // note that if we do this, it'll always be corrected to a maximum angle by beam_maxangle handling
-
-       float segments;
-       if(self.beam_dir != w_shotdir)
-       {
-               // calculate how much we're going to move the end of the beam to the want position
-               // WEAPONTODO (server and client):
-               // blendfactor never actually becomes 0 in this situation, which is a problem
-               // regarding precision... this means that self.beam_dir and w_shotdir approach
-               // eachother, however they never actually become the same value with this method.
-               // Perhaps we should do some form of rounding/snapping?
-               float angle = vlen(w_shotdir - self.beam_dir) * RAD2DEG;
-               if(angle && (angle > WEP_CVAR(arc, beam_maxangle)))
-               {
-                       // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor
-                       float blendfactor = bound(
-                               0,
-                               (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)),
-                               min(WEP_CVAR(arc, beam_maxangle) / angle, 1)
-                       );
-                       self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
-               }
-               else
-               {
-                       // the radius is not too far yet, no worries :D
-                       float blendfactor = bound(
-                               0,
-                               (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)),
-                               1
-                       );
-                       self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
-               }
-
-               // network information: beam direction
-               self.SendFlags |= ARC_SF_BEAMDIR;
-
-               // calculate how many segments are needed
-               float max_allowed_segments;
-
-               if(WEP_CVAR(arc, beam_distancepersegment))
-               {
-                       max_allowed_segments = min(
-                               ARC_MAX_SEGMENTS,
-                               1 + (vlen(w_shotdir / WEP_CVAR(arc, beam_distancepersegment)))
-                       );
-               }
-               else { max_allowed_segments = ARC_MAX_SEGMENTS; }
-
-               if(WEP_CVAR(arc, beam_degreespersegment))
-               {
-                       segments = bound(
-                               1,
-                               (
-                                       min(
-                                               angle,
-                                               WEP_CVAR(arc, beam_maxangle)
-                                       )
-                                       /
-                                       WEP_CVAR(arc, beam_degreespersegment)
-                               ),
-                               max_allowed_segments
-                       );
-               }
-               else { segments = 1; }
-       }
-       else { segments = 1; }
-
-       vector beam_endpos = (w_shotorg + (self.beam_dir * WEP_CVAR(arc, beam_range)));
-       vector beam_controlpoint = w_shotorg + w_shotdir * (WEP_CVAR(arc, beam_range) * (1 - WEP_CVAR(arc, beam_tightness)));
-
-       float i;
-       float new_beam_type = 0;
-       vector last_origin = w_shotorg;
-       for(i = 1; i <= segments; ++i)
-       {
-               // WEAPONTODO (client):
-               // In order to do nice fading and pointing on the starting segment, we must always
-               // have that drawn as a separate triangle... However, that is difficult to do when
-               // keeping in mind the above problems and also optimizing the amount of segments
-               // drawn on screen at any given time. (Automatic beam quality scaling, essentially)
-
-               vector new_origin = bezier_quadratic_getpoint(
-                       w_shotorg,
-                       beam_controlpoint,
-                       beam_endpos,
-                       i / segments);
-               vector new_dir = normalize(new_origin - last_origin);
-
-               WarpZone_traceline_antilag(
-                       self.owner,
-                       last_origin,
-                       new_origin,
-                       MOVE_NORMAL,
-                       self.owner,
-                       ANTILAG_LATENCY(self.owner)
-               );
-
-               // Do all the transforms for warpzones right now, as we already
-               // "are" in the post-trace system (if we hit a player, that's
-               // always BEHIND the last passed wz).
-               last_origin = trace_endpos;
-               w_shotorg = WarpZone_TransformOrigin(WarpZone_trace_transform, w_shotorg);
-               beam_controlpoint = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_controlpoint);
-               beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos);
-               new_dir = WarpZone_TransformVelocity(WarpZone_trace_transform, new_dir);
-
-               float is_player = (
-                       IS_PLAYER(trace_ent)
-                       ||
-                       trace_ent.classname == "body"
-                       ||
-                       IS_MONSTER(trace_ent)
-               );
-
-               if(trace_ent && trace_ent.takedamage && (is_player || WEP_CVAR(arc, beam_nonplayerdamage)))
-               {
-                       // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
-                       // NO. trace_endpos should be just fine. If not,
-                       // that's an engine bug that needs proper debugging.
-                       vector hitorigin = trace_endpos;
-
-                       float falloff = ExponentialFalloff(
-                               WEP_CVAR(arc, beam_falloff_mindist),
-                               WEP_CVAR(arc, beam_falloff_maxdist),
-                               WEP_CVAR(arc, beam_falloff_halflifedist),
-                               vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg)
-                       );
-
-                       if(is_player && SAME_TEAM(self.owner, trace_ent))
-                       {
-                               float roothealth, rootarmor;
-                               if(burst)
-                               {
-                                       roothealth = WEP_CVAR(arc, burst_healing_hps);
-                                       rootarmor = WEP_CVAR(arc, burst_healing_aps);
-                               }
-                               else
-                               {
-                                       roothealth = WEP_CVAR(arc, beam_healing_hps);
-                                       rootarmor = WEP_CVAR(arc, beam_healing_aps);
-                               }
-
-                               if(trace_ent.health <= WEP_CVAR(arc, beam_healing_hmax) && roothealth)
-                               {
-                                       trace_ent.health = min(
-                                               trace_ent.health + (roothealth * coefficient),
-                                               WEP_CVAR(arc, beam_healing_hmax)
-                                       );
-                               }
-                               if(trace_ent.armorvalue <= WEP_CVAR(arc, beam_healing_amax) && rootarmor)
-                               {
-                                       trace_ent.armorvalue = min(
-                                               trace_ent.armorvalue + (rootarmor * coefficient),
-                                               WEP_CVAR(arc, beam_healing_amax)
-                                       );
-                               }
-
-                               // stop rot, set visual effect
-                               if(roothealth || rootarmor)
-                               {
-                                       trace_ent.pauserothealth_finished = max(
-                                               trace_ent.pauserothealth_finished,
-                                               time + autocvar_g_balance_pause_health_rot
-                                       );
-                                       trace_ent.pauserotarmor_finished = max(
-                                               trace_ent.pauserotarmor_finished,
-                                               time + autocvar_g_balance_pause_armor_rot
-                                       );
-                                       new_beam_type = ARC_BT_HEAL;
-                               }
-                       }
-                       else
-                       {
-                               float rootdamage;
-                               if(is_player)
-                               {
-                                       if(burst)
-                                               { rootdamage = WEP_CVAR(arc, burst_damage); }
-                                       else
-                                               { rootdamage = WEP_CVAR(arc, beam_damage); }
-                               }
-                               else
-                                       { rootdamage = WEP_CVAR(arc, beam_nonplayerdamage); }
-
-                               if(accuracy_isgooddamage(self.owner, trace_ent))
-                               {
-                                       accuracy_add(
-                                               self.owner,
-                                               WEP_ARC.m_id,
-                                               0,
-                                               rootdamage * coefficient * falloff
-                                       );
-                               }
-
-                               Damage(
-                                       trace_ent,
-                                       self.owner,
-                                       self.owner,
-                                       rootdamage * coefficient * falloff,
-                                       WEP_ARC.m_id,
-                                       hitorigin,
-                                       WEP_CVAR(arc, beam_force) * new_dir * coefficient * falloff
-                               );
-
-                               new_beam_type = ARC_BT_HIT;
-                       }
-                       break;
-               }
-               else if(trace_fraction != 1)
-               {
-                       // we collided with geometry
-                       new_beam_type = ARC_BT_WALL;
-                       break;
-               }
-       }
-
-       // te_explosion(trace_endpos);
-
-       // if we're bursting, use burst visual effects
-       new_beam_type |= burst;
-
-       // network information: beam type
-       if(new_beam_type != self.beam_type)
-       {
-               self.SendFlags |= ARC_SF_BEAMTYPE;
-               self.beam_type = new_beam_type;
-       }
-
-       self.owner.beam_prev = time;
-       self.nextthink = time;
-}
-
-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, W_Sound("arc_fire"), VOL_BASE, ATTN_NORM);
-
-       entity beam = self.arc_beam = spawn();
-       beam.classname = "W_Arc_Beam";
-       beam.solid = SOLID_NOT;
-       beam.think = W_Arc_Beam_Think;
-       beam.owner = self;
-       beam.movetype = MOVETYPE_NONE;
-       beam.bot_dodge = true;
-       beam.bot_dodgerating = WEP_CVAR(arc, beam_damage);
-       beam.beam_bursting = burst;
-       Net_LinkEntity(beam, false, 0, W_Arc_Beam_Send);
-
-       entity oldself = self;
-       self = beam;
-       self.think();
-       self = oldself;
-}
-
-void Arc_Smoke()
-{
-       makevectors(self.v_angle);
-       W_SetupShot_Range(self,true,0,"",0,0,0);
-
-       vector smoke_origin = w_shotorg + self.velocity*frametime;
-       if ( self.arc_overheat > time )
-       {
-               if ( random() < self.arc_heat_percent )
-                       Send_Effect_("arc_smoke", smoke_origin, '0 0 0', 1 );
-               if ( self.BUTTON_ATCK || self.BUTTON_ATCK2 )
-               {
-                       Send_Effect_("arc_overheat_fire", smoke_origin, w_shotdir, 1 );
-                       if ( !self.arc_smoke_sound )
-                       {
-                               self.arc_smoke_sound = 1;
-                               sound(self, CH_SHOTS_SINGLE, W_Sound("arc_loop_overheat"), VOL_BASE, ATTN_NORM);
-                       }
-               }
-       }
-       else if ( self.arc_beam && WEP_CVAR(arc, overheat_max) > 0 &&
-                       self.arc_beam.beam_heat > WEP_CVAR(arc, overheat_min) )
-       {
-               if ( random() < (self.arc_beam.beam_heat-WEP_CVAR(arc, overheat_min)) /
-                               ( WEP_CVAR(arc, overheat_max)-WEP_CVAR(arc, overheat_min) ) )
-                       Send_Effect_("arc_smoke", smoke_origin, '0 0 0', 1 );
-       }
-
-       if (  self.arc_smoke_sound && ( self.arc_overheat <= time ||
-               !( self.BUTTON_ATCK || self.BUTTON_ATCK2 ) ) || self.switchweapon != WEP_ARC.m_id )
-       {
-               self.arc_smoke_sound = 0;
-               sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM);
-       }
-}
-
-bool W_Arc(int req)
-{
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       if(WEP_CVAR(arc, beam_botaimspeed))
-                       {
-                               self.BUTTON_ATCK = bot_aim(
-                                       WEP_CVAR(arc, beam_botaimspeed),
-                                       0,
-                                       WEP_CVAR(arc, beam_botaimlifetime),
-                                       false
-                               );
-                       }
-                       else
-                       {
-                               self.BUTTON_ATCK = bot_aim(
-                                       1000000,
-                                       0,
-                                       0.001,
-                                       false
-                               );
-                       }
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       Arc_Player_SetHeat(self);
-                       Arc_Smoke();
-
-                       if ( self.arc_overheat <= time )
-                       if(self.BUTTON_ATCK || self.BUTTON_ATCK2 || self.arc_beam.beam_bursting )
-                       {
-
-                               if(self.arc_BUTTON_ATCK_prev)
-                               {
-                                       #if 0
-                                       if(self.animstate_startframe == self.anim_shoot.x && self.animstate_numframes == self.anim_shoot.y)
-                                               weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_arc_primary_animtime, w_ready);
-                                       else
-                                       #endif
-                                               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
-                               }
-
-                               if((!self.arc_beam) || wasfreed(self.arc_beam))
-                               {
-                                       if(weapon_prepareattack(!!self.BUTTON_ATCK2, 0))
-                                       {
-                                               W_Arc_Beam(!!self.BUTTON_ATCK2);
-
-                                               if(!self.arc_BUTTON_ATCK_prev)
-                                               {
-                                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
-                                                       self.arc_BUTTON_ATCK_prev = 1;
-                                               }
-                                       }
-                               }
-
-                               return true;
-                       }
-
-                       if(self.arc_BUTTON_ATCK_prev != 0)
-                       {
-                               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();
-                       }
-                       self.arc_BUTTON_ATCK_prev = 0;
-
-                       #if 0
-                       if(self.BUTTON_ATCK2)
-                       if(weapon_prepareattack(1, autocvar_g_balance_arc_secondary_refire))
-                       {
-                               W_Arc_Attack2();
-                               self.arc_count = autocvar_g_balance_arc_secondary_count;
-                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_arc_secondary_animtime, w_arc_checkattack);
-                               self.arc_secondarytime = time + autocvar_g_balance_arc_secondary_refire2 * W_WeaponRateFactor();
-                       }
-                       #endif
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       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.m_id), false, false, 1);
-                               arc_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 2);
-                               arc_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 3);
-                               arc_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 4);
-                       }
-                       ARC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP);
-                       return true;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       return ((!WEP_CVAR(arc, beam_ammo)) || (self.WEP_AMMO(ARC) > 0));
-               }
-               case WR_CHECKAMMO2:
-               {
-                       return WEP_CVAR(arc, overheat_max) > 0 &&
-                               ((!WEP_CVAR(arc, burst_ammo)) || (self.WEP_AMMO(ARC) > 0));
-               }
-               case WR_CONFIG:
-               {
-                       ARC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       return WEAPON_ARC_MURDER;
-               }
-               case WR_DROP:
-               {
-                       weapon_dropevent_item.arc_overheat = self.arc_overheat;
-                       weapon_dropevent_item.arc_cooldown = self.arc_cooldown;
-                       self.arc_overheat = 0;
-                       self.arc_cooldown = 0;
-                       return true;
-               }
-               case WR_PICKUP:
-               {
-                       if ( !client_hasweapon(self, WEP_ARC.m_id, false, false) &&
-                               weapon_dropevent_item.arc_overheat > time )
-                       {
-                               self.arc_overheat = weapon_dropevent_item.arc_overheat;
-                               self.arc_cooldown = weapon_dropevent_item.arc_cooldown;
-                       }
-                       return true;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-void Draw_ArcBeam_callback(vector start, vector hit, vector end)
-{
-       entity beam = Draw_ArcBeam_callback_entity;
-       vector transformed_view_org;
-       transformed_view_org = WarpZone_TransformOrigin(WarpZone_trace_transform, view_origin);
-
-       // Thickdir shall be perpendicular to the beam and to the view-to-beam direction (WEAPONTODO: WHY)
-       // WEAPONTODO: Wouldn't it be better to be perpendicular to the beam and to the view FORWARD direction?
-       vector thickdir = normalize(cross(normalize(start - hit), transformed_view_org - start));
-
-       vector hitorigin;
-
-       // draw segment
-       #if 0
-       if(trace_fraction != 1)
-       {
-               // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
-               hitorigin = start + (Draw_ArcBeam_callback_new_dir * Draw_ArcBeam_callback_segmentdist * trace_fraction);
-               hitorigin = WarpZone_TransformOrigin(WarpZone_trace_transform, hitorigin);
-       }
-       else
-       {
-               hitorigin = hit;
-       }
-       #else
-       hitorigin = hit;
-       #endif
-
-       // decide upon thickness
-       float thickness = beam.beam_thickness;
-
-       // draw primary beam render
-       vector top    = hitorigin + (thickdir * thickness);
-       vector bottom = hitorigin - (thickdir * thickness);
-
-       vector last_top = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_top);
-       vector last_bottom = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_bottom);
-
-       R_BeginPolygon(beam.beam_image, DRAWFLAG_NORMAL); // DRAWFLAG_ADDITIVE
-       R_PolygonVertex(
-               top,
-               '0 0.5 0' + ('0 0.5 0' * (thickness / beam.beam_thickness)),
-               beam.beam_color,
-               beam.beam_alpha
-       );
-       R_PolygonVertex(
-               last_top,
-               '0 0.5 0' + ('0 0.5 0' * (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)),
-               beam.beam_color,
-               beam.beam_alpha
-       );
-       R_PolygonVertex(
-               last_bottom,
-               '0 0.5 0' * (1 - (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)),
-               beam.beam_color,
-               beam.beam_alpha
-       );
-       R_PolygonVertex(
-               bottom,
-               '0 0.5 0' * (1 - (thickness / beam.beam_thickness)),
-               beam.beam_color,
-               beam.beam_alpha
-       );
-       R_EndPolygon();
-
-       // draw trailing particles
-       // NOTES:
-       //  - Don't use spammy particle counts here, use a FEW small particles around the beam
-       //  - We're not using WarpZone_TrailParticles here because we will handle warpzones ourselves.
-       if(beam.beam_traileffect)
-       {
-               trailparticles(beam, beam.beam_traileffect, start, hitorigin);
-       }
-
-       // set up for the next
-       Draw_ArcBeam_callback_last_thickness = thickness;
-       Draw_ArcBeam_callback_last_top = WarpZone_UnTransformOrigin(WarpZone_trace_transform, top);
-       Draw_ArcBeam_callback_last_bottom = WarpZone_UnTransformOrigin(WarpZone_trace_transform, bottom);
-}
-
-void Reset_ArcBeam(void)
-{
-       entity e;
-       for (e = world; (e = findfloat(e, beam_usevieworigin, 1)); ) {
-               e.beam_initialized = false;
-       }
-       for (e = world; (e = findfloat(e, beam_usevieworigin, 2)); ) {
-               e.beam_initialized = false;
-       }
-}
-
-void Draw_ArcBeam(void)
-{
-       float dt = time - self.move_time;
-       self.move_time = time;
-       if(dt <= 0) { return; }
-
-       if(!self.beam_usevieworigin)
-       {
-               InterpolateOrigin_Do();
-       }
-
-       // origin = beam starting origin
-       // v_angle = wanted/aim direction
-       // angles = current direction of beam
-
-       vector start_pos;
-       vector wantdir; //= view_forward;
-       vector beamdir; //= self.beam_dir;
-
-       float segments;
-       if(self.beam_usevieworigin)
-       {
-               // WEAPONTODO:
-               // Currently we have to replicate nearly the same method of figuring
-               // out the shotdir that the server does... Ideally in the future we
-               // should be able to acquire this from a generalized function built
-               // into a weapon system for client code.
-
-               // find where we are aiming
-               makevectors(warpzone_save_view_angles);
-               vector forward = v_forward;
-               vector right = v_right;
-               vector up = v_up;
-
-               // decide upon start position
-               if(self.beam_usevieworigin == 2)
-                       { start_pos = warpzone_save_view_origin; }
-               else
-                       { start_pos = self.origin; }
-
-               // trace forward with an estimation
-               WarpZone_TraceLine(
-                       start_pos,
-                       start_pos + forward * self.beam_range,
-                       MOVE_NOMONSTERS,
-                       self
-               );
-
-               // untransform in case our trace went through a warpzone
-               vector end_pos = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
-
-               // un-adjust trueaim if shotend is too close
-               if(vlen(end_pos - start_pos) < g_trueaim_minrange)
-                       end_pos = start_pos + (forward * g_trueaim_minrange);
-
-               // move shot origin to the actual gun muzzle origin
-               vector origin_offset =
-                         right * -self.beam_shotorigin.y
-                       + up * self.beam_shotorigin.z;
-
-               start_pos = start_pos + origin_offset;
-
-               // Move it also forward, but only as far as possible without hitting anything. Don't poke into walls!
-               traceline(start_pos, start_pos + forward * self.beam_shotorigin.x, MOVE_NORMAL, self);
-               start_pos = trace_endpos;
-
-               // calculate the aim direction now
-               wantdir = normalize(end_pos - start_pos);
-
-               if(!self.beam_initialized)
-               {
-                       self.beam_dir = wantdir;
-                       self.beam_initialized = true;
-               }
-
-               if(self.beam_dir != wantdir)
-               {
-                       // calculate how much we're going to move the end of the beam to the want position
-                       // WEAPONTODO (server and client):
-                       // blendfactor never actually becomes 0 in this situation, which is a problem
-                       // regarding precision... this means that self.beam_dir and w_shotdir approach
-                       // eachother, however they never actually become the same value with this method.
-                       // Perhaps we should do some form of rounding/snapping?
-                       float angle = vlen(wantdir - self.beam_dir) * RAD2DEG;
-                       if(angle && (angle > self.beam_maxangle))
-                       {
-                               // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor
-                               float blendfactor = bound(
-                                       0,
-                                       (1 - (self.beam_returnspeed * frametime)),
-                                       min(self.beam_maxangle / angle, 1)
-                               );
-                               self.beam_dir = normalize((wantdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
-                       }
-                       else
-                       {
-                               // the radius is not too far yet, no worries :D
-                               float blendfactor = bound(
-                                       0,
-                                       (1 - (self.beam_returnspeed * frametime)),
-                                       1
-                               );
-                               self.beam_dir = normalize((wantdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
-                       }
-
-                       // calculate how many segments are needed
-                       float max_allowed_segments;
-
-                       if(self.beam_distancepersegment)
-                       {
-                               max_allowed_segments = min(
-                                       ARC_MAX_SEGMENTS,
-                                       1 + (vlen(wantdir / self.beam_distancepersegment))
-                               );
-                       }
-                       else { max_allowed_segments = ARC_MAX_SEGMENTS; }
-
-                       if(self.beam_degreespersegment)
-                       {
-                               segments = bound(
-                                       1,
-                                       (
-                                               min(
-                                                       angle,
-                                                       self.beam_maxangle
-                                               )
-                                               /
-                                               self.beam_degreespersegment
-                                       ),
-                                       max_allowed_segments
-                               );
-                       }
-                       else { segments = 1; }
-               }
-               else { segments = 1; }
-
-               // set the beam direction which the rest of the code will refer to
-               beamdir = self.beam_dir;
-
-               // finally, set self.angles to the proper direction so that muzzle attachment points in proper direction
-               self.angles = fixedvectoangles2(forward, up); // TODO(Samual): is this == warpzone_save_view_angles?
-       }
-       else
-       {
-               // set the values from the provided info from the networked entity
-               start_pos = self.origin;
-               wantdir = self.v_angle;
-               beamdir = self.angles;
-
-               if(beamdir != wantdir)
-               {
-                       float angle = vlen(wantdir - beamdir) * RAD2DEG;
-
-                       // calculate how many segments are needed
-                       float max_allowed_segments;
-
-                       if(self.beam_distancepersegment)
-                       {
-                               max_allowed_segments = min(
-                                       ARC_MAX_SEGMENTS,
-                                       1 + (vlen(wantdir / self.beam_distancepersegment))
-                               );
-                       }
-                       else { max_allowed_segments = ARC_MAX_SEGMENTS; }
-
-                       if(self.beam_degreespersegment)
-                       {
-                               segments = bound(
-                                       1,
-                                       (
-                                               min(
-                                                       angle,
-                                                       self.beam_maxangle
-                                               )
-                                               /
-                                               self.beam_degreespersegment
-                                       ),
-                                       max_allowed_segments
-                               );
-                       }
-                       else { segments = 1; }
-               }
-               else { segments = 1; }
-       }
-
-       setorigin(self, start_pos);
-       self.beam_muzzleentity.angles_z = random() * 360; // WEAPONTODO: use avelocity instead?
-
-       vector beam_endpos = (start_pos + (beamdir * self.beam_range));
-       vector beam_controlpoint = start_pos + wantdir * (self.beam_range * (1 - self.beam_tightness));
-
-       Draw_ArcBeam_callback_entity = self;
-       Draw_ArcBeam_callback_last_thickness = 0;
-       Draw_ArcBeam_callback_last_top = start_pos;
-       Draw_ArcBeam_callback_last_bottom = start_pos;
-
-       vector last_origin = start_pos;
-       vector original_start_pos = start_pos;
-
-       float i;
-       for(i = 1; i <= segments; ++i)
-       {
-               // WEAPONTODO (client):
-               // In order to do nice fading and pointing on the starting segment, we must always
-               // have that drawn as a separate triangle... However, that is difficult to do when
-               // keeping in mind the above problems and also optimizing the amount of segments
-               // drawn on screen at any given time. (Automatic beam quality scaling, essentially)
-
-               vector new_origin = bezier_quadratic_getpoint(
-                       start_pos,
-                       beam_controlpoint,
-                       beam_endpos,
-                       i / segments);
-
-               WarpZone_TraceBox_ThroughZone(
-                       last_origin,
-                       '0 0 0',
-                       '0 0 0',
-                       new_origin,
-                       MOVE_NORMAL,
-                       world,
-                       world,
-                       Draw_ArcBeam_callback
-               );
-
-               // Do all the transforms for warpzones right now, as we already "are" in the post-trace
-               // system (if we hit a player, that's always BEHIND the last passed wz).
-               last_origin = trace_endpos;
-               start_pos = WarpZone_TransformOrigin(WarpZone_trace_transform, start_pos);
-               beam_controlpoint = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_controlpoint);
-               beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos);
-               beamdir = WarpZone_TransformVelocity(WarpZone_trace_transform, beamdir);
-               Draw_ArcBeam_callback_last_top = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_top);
-               Draw_ArcBeam_callback_last_bottom = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_bottom);
-
-               if(trace_fraction < 1) { break; }
-       }
-
-       // visual effects for startpoint and endpoint
-       if(self.beam_hiteffect)
-       {
-               // FIXME we really should do this on the server so it actually
-               // matches gameplay. What this client side stuff is doing is no
-               // more than guesswork.
-               if((trace_ent || trace_fraction < 1) && !(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT))
-               pointparticles(
-                       self.beam_hiteffect,
-                       last_origin,
-                       beamdir * -1,
-                       frametime * 2
-               );
-       }
-       if(self.beam_hitlight[0])
-       {
-               adddynamiclight(
-                       last_origin,
-                       self.beam_hitlight[0],
-                       vec3(
-                               self.beam_hitlight[1],
-                               self.beam_hitlight[2],
-                               self.beam_hitlight[3]
-                       )
-               );
-       }
-       if(self.beam_muzzleeffect)
-       {
-               pointparticles(
-                       self.beam_muzzleeffect,
-                       original_start_pos + wantdir * 20,
-                       wantdir * 1000,
-                       frametime * 0.1
-               );
-       }
-       if(self.beam_muzzlelight[0])
-       {
-               adddynamiclight(
-                       original_start_pos + wantdir * 20,
-                       self.beam_muzzlelight[0],
-                       vec3(
-                               self.beam_muzzlelight[1],
-                               self.beam_muzzlelight[2],
-                               self.beam_muzzlelight[3]
-                       )
-               );
-       }
-
-       // cleanup
-       Draw_ArcBeam_callback_entity = world;
-       Draw_ArcBeam_callback_last_thickness = 0;
-       Draw_ArcBeam_callback_last_top = '0 0 0';
-       Draw_ArcBeam_callback_last_bottom = '0 0 0';
-}
-
-void Remove_ArcBeam(void)
-{
-       remove(self.beam_muzzleentity);
-       sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM);
-}
-
-void Ent_ReadArcBeam(float isnew)
-{
-       int sf = ReadByte();
-       entity flash;
-
-       if(isnew)
-       {
-               // calculate shot origin offset from gun alignment
-               int gunalign = autocvar_cl_gunalign;
-               if(gunalign != 1 && gunalign != 2 && gunalign != 4)
-                       gunalign = 3; // default value
-               --gunalign;
-
-               self.beam_shotorigin = arc_shotorigin[gunalign];
-
-               // set other main attributes of the beam
-               self.draw = Draw_ArcBeam;
-               self.entremove = Remove_ArcBeam;
-               self.move_time = time;
-               loopsound(self, CH_SHOTS_SINGLE, W_Sound("arc_loop"), VOL_BASE, ATTEN_NORM);
-
-               flash = spawn();
-               flash.owner = self;
-               flash.effects = EF_ADDITIVE | EF_FULLBRIGHT;
-               flash.drawmask = MASK_NORMAL;
-               flash.solid = SOLID_NOT;
-               flash.avelocity_z = 5000;
-               setattachment(flash, self, "");
-               setorigin(flash, '0 0 0');
-
-               self.beam_muzzleentity = flash;
-       }
-       else
-       {
-               flash = self.beam_muzzleentity;
-       }
-
-       if(sf & ARC_SF_SETTINGS) // settings information
-       {
-               self.beam_degreespersegment = ReadShort();
-               self.beam_distancepersegment = ReadShort();
-               self.beam_maxangle = ReadShort();
-               self.beam_range = ReadCoord();
-               self.beam_returnspeed = ReadShort();
-               self.beam_tightness = (ReadByte() / 10);
-
-               if(ReadByte())
-               {
-                       if(autocvar_chase_active)
-                               { self.beam_usevieworigin = 1; }
-                       else // use view origin
-                               { self.beam_usevieworigin = 2; }
-               }
-               else
-               {
-                       self.beam_usevieworigin = 0;
-               }
-       }
-
-       if(!self.beam_usevieworigin)
-       {
-               // self.iflags = IFLAG_ORIGIN | IFLAG_ANGLES | IFLAG_V_ANGLE; // why doesn't this work?
-               self.iflags = IFLAG_ORIGIN;
-
-               InterpolateOrigin_Undo();
-       }
-
-       if(sf & ARC_SF_START) // starting location
-       {
-               self.origin_x = ReadCoord();
-               self.origin_y = ReadCoord();
-               self.origin_z = ReadCoord();
-       }
-       else if(self.beam_usevieworigin) // infer the location from player location
-       {
-               if(self.beam_usevieworigin == 2)
-               {
-                       // use view origin
-                       self.origin = view_origin;
-               }
-               else
-               {
-                       // use player origin so that third person display still works
-                       self.origin = getplayerorigin(player_localnum) + ('0 0 1' * getstati(STAT_VIEWHEIGHT));
-               }
-       }
-
-       setorigin(self, self.origin);
-
-       if(sf & ARC_SF_WANTDIR) // want/aim direction
-       {
-               self.v_angle_x = ReadCoord();
-               self.v_angle_y = ReadCoord();
-               self.v_angle_z = ReadCoord();
-       }
-
-       if(sf & ARC_SF_BEAMDIR) // beam direction
-       {
-               self.angles_x = ReadCoord();
-               self.angles_y = ReadCoord();
-               self.angles_z = ReadCoord();
-       }
-
-       if(sf & ARC_SF_BEAMTYPE) // beam type
-       {
-               self.beam_type = ReadByte();
-               switch(self.beam_type)
-               {
-                       case ARC_BT_MISS:
-                       {
-                               self.beam_color = '1 1 1';
-                               self.beam_alpha = 0.5;
-                               self.beam_thickness = 8;
-                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM);
-                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING);
-                               self.beam_hitlight[0] = 0;
-                               self.beam_hitlight[1] = 1;
-                               self.beam_hitlight[2] = 1;
-                               self.beam_hitlight[3] = 1;
-                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
-                               self.beam_muzzlelight[0] = 0;
-                               self.beam_muzzlelight[1] = 1;
-                               self.beam_muzzlelight[2] = 1;
-                               self.beam_muzzlelight[3] = 1;
-                               if(self.beam_muzzleeffect >= 0)
-                               {
-                                       setmodel(flash, "models/flash.md3");
-                                       flash.alpha = self.beam_alpha;
-                                       flash.colormod = self.beam_color;
-                                       flash.scale = 0.5;
-                               }
-                               break;
-                       }
-                       case ARC_BT_WALL: // grenadelauncher_muzzleflash healray_muzzleflash
-                       {
-                               self.beam_color = '1 1 1';
-                               self.beam_alpha = 0.5;
-                               self.beam_thickness = 8;
-                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM);
-                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING);
-                               self.beam_hitlight[0] = 0;
-                               self.beam_hitlight[1] = 1;
-                               self.beam_hitlight[2] = 1;
-                               self.beam_hitlight[3] = 1;
-                               self.beam_muzzleeffect = -1; // particleeffectnum(EFFECT_GRENADE_MUZZLEFLASH);
-                               self.beam_muzzlelight[0] = 0;
-                               self.beam_muzzlelight[1] = 1;
-                               self.beam_muzzlelight[2] = 1;
-                               self.beam_muzzlelight[3] = 1;
-                               self.beam_image = "particles/lgbeam";
-                               if(self.beam_muzzleeffect >= 0)
-                               {
-                                       setmodel(flash, "models/flash.md3");
-                                       flash.alpha = self.beam_alpha;
-                                       flash.colormod = self.beam_color;
-                                       flash.scale = 0.5;
-                               }
-                               break;
-                       }
-                       case ARC_BT_HEAL:
-                       {
-                               self.beam_color = '1 1 1';
-                               self.beam_alpha = 0.5;
-                               self.beam_thickness = 8;
-                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL);
-                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL_IMPACT);
-                               self.beam_hitlight[0] = 0;
-                               self.beam_hitlight[1] = 1;
-                               self.beam_hitlight[2] = 1;
-                               self.beam_hitlight[3] = 1;
-                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
-                               self.beam_muzzlelight[0] = 0;
-                               self.beam_muzzlelight[1] = 1;
-                               self.beam_muzzlelight[2] = 1;
-                               self.beam_muzzlelight[3] = 1;
-                               self.beam_image = "particles/lgbeam";
-                               if(self.beam_muzzleeffect >= 0)
-                               {
-                                       setmodel(flash, "models/flash.md3");
-                                       flash.alpha = self.beam_alpha;
-                                       flash.colormod = self.beam_color;
-                                       flash.scale = 0.5;
-                               }
-                               break;
-                       }
-                       case ARC_BT_HIT:
-                       {
-                               self.beam_color = '1 1 1';
-                               self.beam_alpha = 0.5;
-                               self.beam_thickness = 8;
-                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM);
-                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING);
-                               self.beam_hitlight[0] = 20;
-                               self.beam_hitlight[1] = 1;
-                               self.beam_hitlight[2] = 0;
-                               self.beam_hitlight[3] = 0;
-                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
-                               self.beam_muzzlelight[0] = 50;
-                               self.beam_muzzlelight[1] = 1;
-                               self.beam_muzzlelight[2] = 0;
-                               self.beam_muzzlelight[3] = 0;
-                               self.beam_image = "particles/lgbeam";
-                               if(self.beam_muzzleeffect >= 0)
-                               {
-                                       setmodel(flash, "models/flash.md3");
-                                       flash.alpha = self.beam_alpha;
-                                       flash.colormod = self.beam_color;
-                                       flash.scale = 0.5;
-                               }
-                               break;
-                       }
-                       case ARC_BT_BURST_MISS:
-                       {
-                               self.beam_color = '1 1 1';
-                               self.beam_alpha = 0.5;
-                               self.beam_thickness = 14;
-                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM);
-                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING);
-                               self.beam_hitlight[0] = 0;
-                               self.beam_hitlight[1] = 1;
-                               self.beam_hitlight[2] = 1;
-                               self.beam_hitlight[3] = 1;
-                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
-                               self.beam_muzzlelight[0] = 0;
-                               self.beam_muzzlelight[1] = 1;
-                               self.beam_muzzlelight[2] = 1;
-                               self.beam_muzzlelight[3] = 1;
-                               self.beam_image = "particles/lgbeam";
-                               if(self.beam_muzzleeffect >= 0)
-                               {
-                                       setmodel(flash, "models/flash.md3");
-                                       flash.alpha = self.beam_alpha;
-                                       flash.colormod = self.beam_color;
-                                       flash.scale = 0.5;
-                               }
-                               break;
-                       }
-                       case ARC_BT_BURST_WALL:
-                       {
-                               self.beam_color = '1 1 1';
-                               self.beam_alpha = 0.5;
-                               self.beam_thickness = 14;
-                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM);
-                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING);
-                               self.beam_hitlight[0] = 0;
-                               self.beam_hitlight[1] = 1;
-                               self.beam_hitlight[2] = 1;
-                               self.beam_hitlight[3] = 1;
-                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
-                               self.beam_muzzlelight[0] = 0;
-                               self.beam_muzzlelight[1] = 1;
-                               self.beam_muzzlelight[2] = 1;
-                               self.beam_muzzlelight[3] = 1;
-                               self.beam_image = "particles/lgbeam";
-                               if(self.beam_muzzleeffect >= 0)
-                               {
-                                       setmodel(flash, "models/flash.md3");
-                                       flash.alpha = self.beam_alpha;
-                                       flash.colormod = self.beam_color;
-                                       flash.scale = 0.5;
-                               }
-                               break;
-                       }
-                       case ARC_BT_BURST_HEAL:
-                       {
-                               self.beam_color = '1 1 1';
-                               self.beam_alpha = 0.5;
-                               self.beam_thickness = 14;
-                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL);
-                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL_IMPACT2);
-                               self.beam_hitlight[0] = 0;
-                               self.beam_hitlight[1] = 1;
-                               self.beam_hitlight[2] = 1;
-                               self.beam_hitlight[3] = 1;
-                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
-                               self.beam_muzzlelight[0] = 0;
-                               self.beam_muzzlelight[1] = 1;
-                               self.beam_muzzlelight[2] = 1;
-                               self.beam_muzzlelight[3] = 1;
-                               self.beam_image = "particles/lgbeam";
-                               if(self.beam_muzzleeffect >= 0)
-                               {
-                                       setmodel(flash, "models/flash.md3");
-                                       flash.alpha = self.beam_alpha;
-                                       flash.colormod = self.beam_color;
-                                       flash.scale = 0.5;
-                               }
-                               break;
-                       }
-                       case ARC_BT_BURST_HIT:
-                       {
-                               self.beam_color = '1 1 1';
-                               self.beam_alpha = 0.5;
-                               self.beam_thickness = 14;
-                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM);
-                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING);
-                               self.beam_hitlight[0] = 0;
-                               self.beam_hitlight[1] = 1;
-                               self.beam_hitlight[2] = 1;
-                               self.beam_hitlight[3] = 1;
-                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
-                               self.beam_muzzlelight[0] = 0;
-                               self.beam_muzzlelight[1] = 1;
-                               self.beam_muzzlelight[2] = 1;
-                               self.beam_muzzlelight[3] = 1;
-                               self.beam_image = "particles/lgbeam";
-                               if(self.beam_muzzleeffect >= 0)
-                               {
-                                       setmodel(flash, "models/flash.md3");
-                                       flash.alpha = self.beam_alpha;
-                                       flash.colormod = self.beam_color;
-                                       flash.scale = 0.5;
-                               }
-                               break;
-                       }
-
-                       // shouldn't be possible, but lets make it colorful if it does :D
-                       default:
-                       {
-                               self.beam_color = randomvec();
-                               self.beam_alpha = 1;
-                               self.beam_thickness = 8;
-                               self.beam_traileffect = false;
-                               self.beam_hiteffect = false;
-                               self.beam_hitlight[0] = 0;
-                               self.beam_hitlight[1] = 1;
-                               self.beam_hitlight[2] = 1;
-                               self.beam_hitlight[3] = 1;
-                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
-                               self.beam_muzzlelight[0] = 0;
-                               self.beam_muzzlelight[1] = 1;
-                               self.beam_muzzlelight[2] = 1;
-                               self.beam_muzzlelight[3] = 1;
-                               self.beam_image = "particles/lgbeam";
-                               if(self.beam_muzzleeffect >= 0)
-                               {
-                                       setmodel(flash, "models/flash.md3");
-                                       flash.alpha = self.beam_alpha;
-                                       flash.colormod = self.beam_color;
-                                       flash.scale = 0.5;
-                               }
-                               break;
-                       }
-               }
-       }
-
-       if(!self.beam_usevieworigin)
-       {
-               InterpolateOrigin_Note();
-       }
-}
-
-bool W_Arc(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       // todo
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("arc_loop"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_blaster.qc b/qcsrc/common/weapons/w_blaster.qc
deleted file mode 100644 (file)
index 6234f4a..0000000
+++ /dev/null
@@ -1,297 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ BLASTER,
-/* function  */ W_Blaster,
-/* ammotype  */ ammo_none,
-/* impulse   */ 1,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
-/* rating    */ 0,
-/* color     */ '1 0.5 0.5',
-/* modelname */ "laser",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairlaser 0.5",
-/* wepimg    */ "weaponlaser",
-/* refname   */ "blaster",
-/* wepname   */ _("Blaster")
-);
-
-#define BLASTER_SETTINGS(w_cvar,w_prop) BLASTER_SETTINGS_LIST(w_cvar, w_prop, BLASTER, blaster)
-#define BLASTER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, BOTH, animtime) \
-       w_cvar(id, sn, BOTH, damage) \
-       w_cvar(id, sn, BOTH, delay) \
-       w_cvar(id, sn, BOTH, edgedamage) \
-       w_cvar(id, sn, BOTH, force) \
-       w_cvar(id, sn, BOTH, force_zscale) \
-       w_cvar(id, sn, BOTH, lifetime) \
-       w_cvar(id, sn, BOTH, radius) \
-       w_cvar(id, sn, BOTH, refire) \
-       w_cvar(id, sn, BOTH, shotangle) \
-       w_cvar(id, sn, BOTH, speed) \
-       w_cvar(id, sn, BOTH, spread) \
-       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) \
-       w_prop(id, sn, float,  weaponstart, weaponstart) \
-       w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
-       w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
-
-#ifdef SVQC
-BLASTER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-.float blaster_damage;
-.float blaster_edgedamage;
-.float blaster_radius;
-.float blaster_force;
-.float blaster_lifetime;
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_blaster(void) { weapon_defaultspawnfunc(WEP_BLASTER.m_id); }
-void spawnfunc_weapon_laser(void) { spawnfunc_weapon_blaster(); }
-
-void W_Blaster_Touch(void)
-{
-       PROJECTILE_TOUCH;
-
-       self.event_damage = func_null;
-
-       RadiusDamage(
-               self,
-               self.realowner,
-               self.blaster_damage,
-               self.blaster_edgedamage,
-               self.blaster_radius,
-               world,
-               world,
-               self.blaster_force,
-               self.projectiledeathtype,
-               other
-       );
-
-       remove(self);
-}
-
-void W_Blaster_Think(void)
-{
-       self.movetype = MOVETYPE_FLY;
-       self.think = SUB_Remove;
-       self.nextthink = time + self.blaster_lifetime;
-       CSQCProjectile(self, true, PROJECTILE_BLASTER, true);
-}
-
-void W_Blaster_Attack(
-       float atk_deathtype,
-       float atk_shotangle,
-       float atk_damage,
-       float atk_edgedamage,
-       float atk_radius,
-       float atk_force,
-       float atk_speed,
-       float atk_spread,
-       float atk_delay,
-       float atk_lifetime)
-{
-       vector s_forward = v_forward * cos(atk_shotangle * DEG2RAD) + v_up * sin(atk_shotangle * DEG2RAD);
-
-       W_SetupShot_Dir(self, s_forward, false, 3, W_Sound("lasergun_fire"), CH_WEAPON_B, atk_damage);
-       Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       entity missile = spawn();
-       missile.owner = missile.realowner = self;
-       missile.classname = "blasterbolt";
-       missile.bot_dodge = true;
-       missile.bot_dodgerating = atk_damage;
-       PROJECTILE_MAKETRIGGER(missile);
-
-       missile.blaster_damage = atk_damage;
-       missile.blaster_edgedamage = atk_edgedamage;
-       missile.blaster_radius = atk_radius;
-       missile.blaster_force = atk_force;
-       missile.blaster_lifetime = atk_lifetime;
-
-       setorigin(missile, w_shotorg);
-       setsize(missile, '0 0 0', '0 0 0');
-
-       W_SetupProjVelocity_Explicit(
-               missile,
-               w_shotdir,
-               v_up,
-               atk_speed,
-               0,
-               0,
-               atk_spread,
-               false
-       );
-
-       missile.angles = vectoangles(missile.velocity);
-
-       //missile.glow_color = 250; // 244, 250
-       //missile.glow_size = 120;
-
-       missile.touch = W_Blaster_Touch;
-       missile.flags = FL_PROJECTILE;
-       missile.missile_flags = MIF_SPLASH;
-       missile.projectiledeathtype = atk_deathtype;
-       missile.think = W_Blaster_Think;
-       missile.nextthink = time + atk_delay;
-
-       MUTATOR_CALLHOOK(EditProjectile, self, missile);
-
-       if(time >= missile.nextthink)
-       {
-               entity oldself;
-               oldself = self;
-               self = missile;
-               self.think();
-               self = oldself;
-       }
-}
-bool W_Blaster(int request)
-{
-       switch(request)
-       {
-               case WR_AIM:
-               {
-                       if(WEP_CVAR(blaster, secondary))
-                       {
-                               if((random() * (WEP_CVAR_PRI(blaster, damage) + WEP_CVAR_SEC(blaster, damage))) > WEP_CVAR_PRI(blaster, damage))
-                                       { self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_SEC(blaster, speed), 0, WEP_CVAR_SEC(blaster, lifetime), false); }
-                               else
-                                       { self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(blaster, speed), 0, WEP_CVAR_PRI(blaster, lifetime), false); }
-                       }
-                       else
-                               { self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(blaster, speed), 0, WEP_CVAR_PRI(blaster, lifetime), false); }
-
-                       return true;
-               }
-
-               case WR_THINK:
-               {
-                       if(self.BUTTON_ATCK)
-                       {
-                               if(weapon_prepareattack(0, WEP_CVAR_PRI(blaster, refire)))
-                               {
-                                       W_Blaster_Attack(
-                                               WEP_BLASTER.m_id,
-                                               WEP_CVAR_PRI(blaster, shotangle),
-                                               WEP_CVAR_PRI(blaster, damage),
-                                               WEP_CVAR_PRI(blaster, edgedamage),
-                                               WEP_CVAR_PRI(blaster, radius),
-                                               WEP_CVAR_PRI(blaster, force),
-                                               WEP_CVAR_PRI(blaster, speed),
-                                               WEP_CVAR_PRI(blaster, spread),
-                                               WEP_CVAR_PRI(blaster, delay),
-                                               WEP_CVAR_PRI(blaster, lifetime)
-                                       );
-                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(blaster, animtime), w_ready);
-                               }
-                       }
-                       else if(self.BUTTON_ATCK2)
-                       {
-                               switch(WEP_CVAR(blaster, secondary))
-                               {
-                                       case 0: // switch to last used weapon
-                                       {
-                                               if(self.switchweapon == WEP_BLASTER.m_id) // don't do this if already switching
-                                                       W_LastWeapon();
-                                               break;
-                                       }
-
-                                       case 1: // normal projectile secondary
-                                       {
-                                               if(weapon_prepareattack(1, WEP_CVAR_SEC(blaster, refire)))
-                                               {
-                                                       W_Blaster_Attack(
-                                                               WEP_BLASTER.m_id | HITTYPE_SECONDARY,
-                                                               WEP_CVAR_SEC(blaster, shotangle),
-                                                               WEP_CVAR_SEC(blaster, damage),
-                                                               WEP_CVAR_SEC(blaster, edgedamage),
-                                                               WEP_CVAR_SEC(blaster, radius),
-                                                               WEP_CVAR_SEC(blaster, force),
-                                                               WEP_CVAR_SEC(blaster, speed),
-                                                               WEP_CVAR_SEC(blaster, spread),
-                                                               WEP_CVAR_SEC(blaster, delay),
-                                                               WEP_CVAR_SEC(blaster, lifetime)
-                                                       );
-                                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(blaster, animtime), w_ready);
-                                               }
-
-                                               break;
-                                       }
-                               }
-                       }
-                       return true;
-               }
-
-               case WR_INIT:
-               {
-                       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;
-               }
-
-               case WR_SETUP:
-               {
-                       self.ammo_field = ammo_none;
-                       return true;
-               }
-
-               case WR_CHECKAMMO1:
-               case WR_CHECKAMMO2:
-               {
-                       return true; // laser has infinite ammo
-               }
-
-               case WR_CONFIG:
-               {
-                       BLASTER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_BLASTER_SUICIDE;
-               }
-
-               case WR_KILLMESSAGE:
-               {
-                       return WEAPON_BLASTER_MURDER;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_Blaster(int request)
-{
-       switch(request)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 6;
-                       pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1);
-                       if(!w_issilent) { sound(self, CH_SHOTS, W_Sound("laserimpact"), VOL_BASE, ATTN_NORM); }
-                       return true;
-               }
-
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("laserimpact"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_crylink.qc b/qcsrc/common/weapons/w_crylink.qc
deleted file mode 100644 (file)
index 08deaf7..0000000
+++ /dev/null
@@ -1,731 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ CRYLINK,
-/* function  */ W_Crylink,
-/* ammotype  */ ammo_cells,
-/* impulse   */ 6,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* color     */ '1 0.5 1',
-/* modelname */ "crylink",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshaircrylink 0.5",
-/* wepimg    */ "weaponcrylink",
-/* refname   */ "crylink",
-/* wepname   */ _("Crylink")
-);
-
-#define CRYLINK_SETTINGS(w_cvar,w_prop) CRYLINK_SETTINGS_LIST(w_cvar, w_prop, CRYLINK, crylink)
-#define CRYLINK_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, BOTH, ammo) \
-       w_cvar(id, sn, BOTH, animtime) \
-       w_cvar(id, sn, BOTH, damage) \
-       w_cvar(id, sn, BOTH, edgedamage) \
-       w_cvar(id, sn, BOTH, radius) \
-       w_cvar(id, sn, BOTH, force) \
-       w_cvar(id, sn, BOTH, spread) \
-       w_cvar(id, sn, BOTH, refire) \
-       w_cvar(id, sn, BOTH, speed) \
-       w_cvar(id, sn, BOTH, shots) \
-       w_cvar(id, sn, BOTH, bounces) \
-       w_cvar(id, sn, BOTH, bouncedamagefactor) \
-       w_cvar(id, sn, BOTH, middle_lifetime) \
-       w_cvar(id, sn, BOTH, middle_fadetime) \
-       w_cvar(id, sn, BOTH, other_lifetime) \
-       w_cvar(id, sn, BOTH, other_fadetime) \
-       w_cvar(id, sn, BOTH, linkexplode) \
-       w_cvar(id, sn, BOTH, joindelay) \
-       w_cvar(id, sn, BOTH, joinspread) \
-       w_cvar(id, sn, BOTH, joinexplode) \
-       w_cvar(id, sn, BOTH, joinexplode_damage) \
-       w_cvar(id, sn, BOTH, joinexplode_edgedamage) \
-       w_cvar(id, sn, BOTH, joinexplode_radius) \
-       w_cvar(id, sn, BOTH, joinexplode_force) \
-       w_cvar(id, sn, SEC,  spreadtype) \
-       w_cvar(id, sn, NONE, secondary) \
-       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
-CRYLINK_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-.float gravity;
-.float crylink_waitrelease;
-.entity crylink_lastgroup;
-
-.entity queuenext;
-.entity queueprev;
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_crylink(void) { weapon_defaultspawnfunc(WEP_CRYLINK.m_id); }
-
-void W_Crylink_CheckLinks(entity e)
-{
-       float i;
-       entity p;
-
-       if(e == world)
-               error("W_Crylink_CheckLinks: entity is world");
-       if(e.classname != "spike" || wasfreed(e))
-               error(sprintf("W_Crylink_CheckLinks: entity is not a spike but a %s (freed: %d)", e.classname, wasfreed(e)));
-
-       p = e;
-       for(i = 0; i < 1000; ++i)
-       {
-               if(p.queuenext.queueprev != p || p.queueprev.queuenext != p)
-                       error("W_Crylink_CheckLinks: queue is inconsistent");
-               p = p.queuenext;
-               if(p == e)
-                       break;
-       }
-       if(i >= 1000)
-               error("W_Crylink_CheckLinks: infinite chain");
-}
-
-void W_Crylink_Dequeue_Raw(entity own, entity prev, entity me, entity next)
-{
-       W_Crylink_CheckLinks(next);
-       if(me == own.crylink_lastgroup)
-               own.crylink_lastgroup = ((me == next) ? world : next);
-       prev.queuenext = next;
-       next.queueprev = prev;
-       me.classname = "spike_oktoremove";
-       if(me != next)
-               W_Crylink_CheckLinks(next);
-}
-
-void W_Crylink_Dequeue(entity e)
-{
-       W_Crylink_Dequeue_Raw(e.realowner, e.queueprev, e, e.queuenext);
-}
-
-void W_Crylink_Reset(void)
-{
-       W_Crylink_Dequeue(self);
-       remove(self);
-}
-
-// force projectile to explode
-void W_Crylink_LinkExplode(entity e, entity e2)
-{
-       float a;
-
-       if(e == e2)
-               return;
-
-       a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1);
-
-       if(e == e.realowner.crylink_lastgroup)
-               e.realowner.crylink_lastgroup = world;
-
-       float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
-
-       RadiusDamage(e, e.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * a, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * a, WEP_CVAR_BOTH(crylink, isprimary, radius), world, world, WEP_CVAR_BOTH(crylink, isprimary, force) * a, e.projectiledeathtype, other);
-
-       W_Crylink_LinkExplode(e.queuenext, e2);
-
-       e.classname = "spike_oktoremove";
-       remove(e);
-}
-
-// adjust towards center
-// returns the origin where they will meet... and the time till the meeting is
-// stored in w_crylink_linkjoin_time.
-// could possibly network this origin and time, and display a special particle
-// effect when projectiles meet there :P
-// jspeed: joining speed (calculate this as join spread * initial speed)
-float w_crylink_linkjoin_time;
-vector W_Crylink_LinkJoin(entity e, float jspeed)
-{
-       vector avg_origin, avg_velocity;
-       vector targ_origin;
-       float avg_dist, n;
-       entity p;
-
-       // FIXME remove this debug code
-       W_Crylink_CheckLinks(e);
-
-       w_crylink_linkjoin_time = 0;
-
-       avg_origin = e.origin;
-       avg_velocity = e.velocity;
-       n = 1;
-       for(p = e; (p = p.queuenext) != e; )
-       {
-               avg_origin += WarpZone_RefSys_TransformOrigin(p, e, p.origin);
-               avg_velocity += WarpZone_RefSys_TransformVelocity(p, e, p.velocity);
-               ++n;
-       }
-       avg_origin *= (1.0 / n);
-       avg_velocity *= (1.0 / n);
-
-       if(n < 2)
-               return avg_origin; // nothing to do
-
-       // yes, mathematically we can do this in ONE step, but beware of 32bit floats...
-       avg_dist = pow(vlen(e.origin - avg_origin), 2);
-       for(p = e; (p = p.queuenext) != e; )
-               avg_dist += pow(vlen(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_origin), 2);
-       avg_dist *= (1.0 / n);
-       avg_dist = sqrt(avg_dist);
-
-       if(avg_dist == 0)
-               return avg_origin; // no change needed
-
-       if(jspeed == 0)
-       {
-               e.velocity = avg_velocity;
-               UpdateCSQCProjectile(e);
-               for(p = e; (p = p.queuenext) != e; )
-               {
-                       p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_velocity);
-                       UpdateCSQCProjectile(p);
-               }
-               targ_origin = avg_origin + 1000000000 * normalize(avg_velocity); // HUUUUUUGE
-       }
-       else
-       {
-               w_crylink_linkjoin_time = avg_dist / jspeed;
-               targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity;
-
-               e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time);
-               UpdateCSQCProjectile(e);
-               for(p = e; (p = p.queuenext) != e; )
-               {
-                       p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time));
-                       UpdateCSQCProjectile(p);
-               }
-
-               // analysis:
-               //   jspeed -> +infinity:
-               //      w_crylink_linkjoin_time -> +0
-               //      targ_origin -> avg_origin
-               //      p->velocity -> HUEG towards center
-               //   jspeed -> 0:
-               //      w_crylink_linkjoin_time -> +/- infinity
-               //      targ_origin -> avg_velocity * +/- infinity
-               //      p->velocity -> avg_velocity
-               //   jspeed -> -infinity:
-               //      w_crylink_linkjoin_time -> -0
-               //      targ_origin -> avg_origin
-               //      p->velocity -> HUEG away from center
-       }
-
-       W_Crylink_CheckLinks(e);
-
-       return targ_origin;
-}
-
-void W_Crylink_LinkJoinEffect_Think(void)
-{
-       // is there at least 2 projectiles very close?
-       entity e, p;
-       float n;
-       e = self.owner.crylink_lastgroup;
-       n = 0;
-       if(e)
-       {
-               if(vlen(e.origin - self.origin) < vlen(e.velocity) * frametime)
-                       ++n;
-               for(p = e; (p = p.queuenext) != e; )
-               {
-                       if(vlen(p.origin - self.origin) < vlen(p.velocity) * frametime)
-                               ++n;
-               }
-               if(n >= 2)
-               {
-                       float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
-
-                       if(WEP_CVAR_BOTH(crylink, isprimary, joinexplode))
-                       {
-                               n /= WEP_CVAR_BOTH(crylink, isprimary, shots);
-                               RadiusDamage(
-                                       e,
-                                       e.realowner,
-                                       WEP_CVAR_BOTH(crylink, isprimary, joinexplode_damage) * n,
-                                       WEP_CVAR_BOTH(crylink, isprimary, joinexplode_edgedamage) * n,
-                                       WEP_CVAR_BOTH(crylink, isprimary, joinexplode_radius) * n,
-                                       e.realowner,
-                                       world,
-                                       WEP_CVAR_BOTH(crylink, isprimary, joinexplode_force) * n,
-                                       e.projectiledeathtype,
-                                       other
-                               );
-                               Send_Effect(EFFECT_CRYLINK_JOINEXPLODE, self.origin, '0 0 0', n);
-                       }
-               }
-       }
-       remove(self);
-}
-
-float W_Crylink_Touch_WouldHitFriendly(entity projectile, float rad)
-{
-       entity head = WarpZone_FindRadius((projectile.origin + (projectile.mins + projectile.maxs) * 0.5), rad + MAX_DAMAGEEXTRARADIUS, false);
-       float hit_friendly = 0;
-       float hit_enemy = 0;
-
-       while(head)
-       {
-               if((head.takedamage != DAMAGE_NO) && (head.deadflag == DEAD_NO))
-               {
-                       if(SAME_TEAM(head, projectile.realowner))
-                               ++hit_friendly;
-                       else
-                               ++hit_enemy;
-               }
-
-               head = head.chain;
-       }
-
-       return (hit_enemy ? false : hit_friendly);
-}
-
-// NO bounce protection, as bounces are limited!
-void W_Crylink_Touch(void)
-{
-       float finalhit;
-       float f;
-       float isprimary = !(self.projectiledeathtype & HITTYPE_SECONDARY);
-       PROJECTILE_TOUCH;
-
-       float a;
-       a = bound(0, 1 - (time - self.fade_time) * self.fade_rate, 1);
-
-       finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO));
-       if(finalhit)
-               f = 1;
-       else
-               f = WEP_CVAR_BOTH(crylink, isprimary, bouncedamagefactor);
-       if(a)
-               f *= a;
-
-       float totaldamage = RadiusDamage(self, self.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * f, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * f, WEP_CVAR_BOTH(crylink, isprimary, radius), world, world, WEP_CVAR_BOTH(crylink, isprimary, force) * f, self.projectiledeathtype, other);
-
-       if(totaldamage && ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 2) || ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 1) && !W_Crylink_Touch_WouldHitFriendly(self, WEP_CVAR_BOTH(crylink, isprimary, radius)))))
-       {
-               if(self == self.realowner.crylink_lastgroup)
-                       self.realowner.crylink_lastgroup = world;
-               W_Crylink_LinkExplode(self.queuenext, self);
-               self.classname = "spike_oktoremove";
-               remove(self);
-               return;
-       }
-       else if(finalhit)
-       {
-               // just unlink
-               W_Crylink_Dequeue(self);
-               remove(self);
-               return;
-       }
-       self.cnt = self.cnt - 1;
-       self.angles = vectoangles(self.velocity);
-       self.owner = world;
-       self.projectiledeathtype |= HITTYPE_BOUNCE;
-       // commented out as it causes a little hitch...
-       //if(proj.cnt == 0)
-       //      CSQCProjectile(proj, true, PROJECTILE_CRYLINK, true);
-}
-
-void W_Crylink_Fadethink(void)
-{
-       W_Crylink_Dequeue(self);
-       remove(self);
-}
-
-void W_Crylink_Attack(void)
-{
-       float counter, shots;
-       entity proj, prevproj, firstproj;
-       vector s;
-       vector forward, right, up;
-       float maxdmg;
-
-       W_DecreaseAmmo(WEP_CVAR_PRI(crylink, ammo));
-
-       maxdmg = WEP_CVAR_PRI(crylink, damage) * WEP_CVAR_PRI(crylink, shots);
-       maxdmg *= 1 + WEP_CVAR_PRI(crylink, bouncedamagefactor) * WEP_CVAR_PRI(crylink, bounces);
-       if(WEP_CVAR_PRI(crylink, joinexplode))
-               maxdmg += WEP_CVAR_PRI(crylink, joinexplode_damage);
-
-       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);
-       Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots);
-       proj = prevproj = firstproj = world;
-       for(counter = 0; counter < shots; ++counter)
-       {
-               proj = spawn();
-               proj.reset = W_Crylink_Reset;
-               proj.realowner = proj.owner = self;
-               proj.classname = "spike";
-               proj.bot_dodge = true;
-               proj.bot_dodgerating = WEP_CVAR_PRI(crylink, damage);
-               if(shots == 1) {
-                       proj.queuenext = proj;
-                       proj.queueprev = proj;
-               }
-               else if(counter == 0) { // first projectile, store in firstproj for now
-                       firstproj = proj;
-               }
-               else if(counter == shots - 1) { // last projectile, link up with first projectile
-                       prevproj.queuenext = proj;
-                       firstproj.queueprev = proj;
-                       proj.queuenext = firstproj;
-                       proj.queueprev = prevproj;
-               }
-               else { // else link up with previous projectile
-                       prevproj.queuenext = proj;
-                       proj.queueprev = prevproj;
-               }
-
-               prevproj = proj;
-
-               proj.movetype = MOVETYPE_BOUNCEMISSILE;
-               PROJECTILE_MAKETRIGGER(proj);
-               proj.projectiledeathtype = WEP_CRYLINK.m_id;
-               //proj.gravity = 0.001;
-
-               setorigin(proj, w_shotorg);
-               setsize(proj, '0 0 0', '0 0 0');
-
-
-               s = '0 0 0';
-               if(counter == 0)
-                       s = '0 0 0';
-               else
-               {
-                       makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
-                       s.y = v_forward.x;
-                       s.z = v_forward.y;
-               }
-               s = s * WEP_CVAR_PRI(crylink, spread) * g_weaponspreadfactor;
-               W_SetupProjVelocity_Explicit(proj, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_PRI(crylink, speed), 0, 0, 0, false);
-               proj.touch = W_Crylink_Touch;
-
-               proj.think = W_Crylink_Fadethink;
-               if(counter == 0)
-               {
-                       proj.fade_time = time + WEP_CVAR_PRI(crylink, middle_lifetime);
-                       proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, middle_fadetime);
-                       proj.nextthink = time + WEP_CVAR_PRI(crylink, middle_lifetime) + WEP_CVAR_PRI(crylink, middle_fadetime);
-               }
-               else
-               {
-                       proj.fade_time = time + WEP_CVAR_PRI(crylink, other_lifetime);
-                       proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, other_fadetime);
-                       proj.nextthink = time + WEP_CVAR_PRI(crylink, other_lifetime) + WEP_CVAR_PRI(crylink, other_fadetime);
-               }
-               proj.teleport_time = time + WEP_CVAR_PRI(crylink, joindelay);
-               proj.cnt = WEP_CVAR_PRI(crylink, bounces);
-               //proj.scale = 1 + 1 * proj.cnt;
-
-               proj.angles = vectoangles(proj.velocity);
-
-               //proj.glow_size = 20;
-
-               proj.flags = FL_PROJECTILE;
-               proj.missile_flags = MIF_SPLASH;
-
-               CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
-
-               MUTATOR_CALLHOOK(EditProjectile, self, proj);
-       }
-       if(WEP_CVAR_PRI(crylink, joinspread) != 0)
-       {
-               self.crylink_lastgroup = proj;
-               W_Crylink_CheckLinks(proj);
-               self.crylink_waitrelease = 1;
-       }
-}
-
-void W_Crylink_Attack2(void)
-{
-       float counter, shots;
-       entity proj, prevproj, firstproj;
-       vector s;
-       vector forward, right, up;
-       float maxdmg;
-
-       W_DecreaseAmmo(WEP_CVAR_SEC(crylink, ammo));
-
-       maxdmg = WEP_CVAR_SEC(crylink, damage) * WEP_CVAR_SEC(crylink, shots);
-       maxdmg *= 1 + WEP_CVAR_SEC(crylink, bouncedamagefactor) * WEP_CVAR_SEC(crylink, bounces);
-       if(WEP_CVAR_SEC(crylink, joinexplode))
-               maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage);
-
-       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);
-       Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots);
-       proj = prevproj = firstproj = world;
-       for(counter = 0; counter < shots; ++counter)
-       {
-               proj = spawn();
-               proj.reset = W_Crylink_Reset;
-               proj.realowner = proj.owner = self;
-               proj.classname = "spike";
-               proj.bot_dodge = true;
-               proj.bot_dodgerating = WEP_CVAR_SEC(crylink, damage);
-               if(shots == 1) {
-                       proj.queuenext = proj;
-                       proj.queueprev = proj;
-               }
-               else if(counter == 0) { // first projectile, store in firstproj for now
-                       firstproj = proj;
-               }
-               else if(counter == shots - 1) { // last projectile, link up with first projectile
-                       prevproj.queuenext = proj;
-                       firstproj.queueprev = proj;
-                       proj.queuenext = firstproj;
-                       proj.queueprev = prevproj;
-               }
-               else { // else link up with previous projectile
-                       prevproj.queuenext = proj;
-                       proj.queueprev = prevproj;
-               }
-
-               prevproj = proj;
-
-               proj.movetype = MOVETYPE_BOUNCEMISSILE;
-               PROJECTILE_MAKETRIGGER(proj);
-               proj.projectiledeathtype = WEP_CRYLINK.m_id | HITTYPE_SECONDARY;
-               //proj.gravity = 0.001;
-
-               setorigin(proj, w_shotorg);
-               setsize(proj, '0 0 0', '0 0 0');
-
-               if(WEP_CVAR_SEC(crylink, spreadtype) == 1)
-               {
-                       s = '0 0 0';
-                       if(counter == 0)
-                               s = '0 0 0';
-                       else
-                       {
-                               makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
-                               s.y = v_forward.x;
-                               s.z = v_forward.y;
-                       }
-                       s = s * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor;
-                       s = w_shotdir + right * s.y + up * s.z;
-               }
-               else
-               {
-                       s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor);
-               }
-
-               W_SetupProjVelocity_Explicit(proj, s, v_up, WEP_CVAR_SEC(crylink, speed), 0, 0, 0, false);
-               proj.touch = W_Crylink_Touch;
-               proj.think = W_Crylink_Fadethink;
-               if(counter == (shots - 1) / 2)
-               {
-                       proj.fade_time = time + WEP_CVAR_SEC(crylink, middle_lifetime);
-                       proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, middle_fadetime);
-                       proj.nextthink = time + WEP_CVAR_SEC(crylink, middle_lifetime) + WEP_CVAR_SEC(crylink, middle_fadetime);
-               }
-               else
-               {
-                       proj.fade_time = time + WEP_CVAR_SEC(crylink, other_lifetime);
-                       proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, other_fadetime);
-                       proj.nextthink = time + WEP_CVAR_SEC(crylink, other_lifetime) + WEP_CVAR_SEC(crylink, other_fadetime);
-               }
-               proj.teleport_time = time + WEP_CVAR_SEC(crylink, joindelay);
-               proj.cnt = WEP_CVAR_SEC(crylink, bounces);
-               //proj.scale = 1 + 1 * proj.cnt;
-
-               proj.angles = vectoangles(proj.velocity);
-
-               //proj.glow_size = 20;
-
-               proj.flags = FL_PROJECTILE;
-        proj.missile_flags = MIF_SPLASH;
-
-               CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
-
-               MUTATOR_CALLHOOK(EditProjectile, self, proj);
-       }
-       if(WEP_CVAR_SEC(crylink, joinspread) != 0)
-       {
-               self.crylink_lastgroup = proj;
-               W_Crylink_CheckLinks(proj);
-               self.crylink_waitrelease = 2;
-       }
-}
-
-bool W_Crylink(int req)
-{
-       float ammo_amount;
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       if(random() < 0.10)
-                               self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false);
-                       else
-                               self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false);
-
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       if(autocvar_g_balance_crylink_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo))) // forced reload
-                               WEP_ACTION(self.weapon, WR_RELOAD);
-
-                       if(self.BUTTON_ATCK)
-                       {
-                               if(self.crylink_waitrelease != 1)
-                               if(weapon_prepareattack(0, WEP_CVAR_PRI(crylink, refire)))
-                               {
-                                       W_Crylink_Attack();
-                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(crylink, animtime), w_ready);
-                               }
-                       }
-
-                       if(self.BUTTON_ATCK2 && autocvar_g_balance_crylink_secondary)
-                       {
-                               if(self.crylink_waitrelease != 2)
-                               if(weapon_prepareattack(1, WEP_CVAR_SEC(crylink, refire)))
-                               {
-                                       W_Crylink_Attack2();
-                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(crylink, animtime), w_ready);
-                               }
-                       }
-
-                       if((self.crylink_waitrelease == 1 && !self.BUTTON_ATCK) || (self.crylink_waitrelease == 2 && !self.BUTTON_ATCK2))
-                       {
-                               if(!self.crylink_lastgroup || time > self.crylink_lastgroup.teleport_time)
-                               {
-                                       // fired and released now!
-                                       if(self.crylink_lastgroup)
-                                       {
-                                               vector pos;
-                                               entity linkjoineffect;
-                                               float isprimary = (self.crylink_waitrelease == 1);
-
-                                               pos = W_Crylink_LinkJoin(self.crylink_lastgroup, WEP_CVAR_BOTH(crylink, isprimary, joinspread) * WEP_CVAR_BOTH(crylink, isprimary, speed));
-
-                                               linkjoineffect = spawn();
-                                               linkjoineffect.think = W_Crylink_LinkJoinEffect_Think;
-                                               linkjoineffect.classname = "linkjoineffect";
-                                               linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
-                                               linkjoineffect.owner = self;
-                                               setorigin(linkjoineffect, pos);
-                                       }
-                                       self.crylink_waitrelease = 0;
-                                       if(!W_Crylink(WR_CHECKAMMO1) && !W_Crylink(WR_CHECKAMMO2))
-                                       if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
-                                       {
-                                               // ran out of ammo!
-                                               self.cnt = WEP_CRYLINK.m_id;
-                                               self.switchweapon = w_getbestweapon(self);
-                                       }
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       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;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       // don't "run out of ammo" and switch weapons while waiting for release
-                       if(self.crylink_lastgroup && self.crylink_waitrelease)
-                               return true;
-
-                       ammo_amount = self.WEP_AMMO(CRYLINK) >= WEP_CVAR_PRI(crylink, ammo);
-                       ammo_amount += self.(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
-                       return ammo_amount;
-               }
-               case WR_CHECKAMMO2:
-               {
-                       // don't "run out of ammo" and switch weapons while waiting for release
-                       if(self.crylink_lastgroup && self.crylink_waitrelease)
-                               return true;
-
-                       ammo_amount = self.WEP_AMMO(CRYLINK) >= WEP_CVAR_SEC(crylink, ammo);
-                       ammo_amount += self.(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
-                       return ammo_amount;
-               }
-               case WR_CONFIG:
-               {
-                       CRYLINK_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       W_Reload(min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo)), W_Sound("reload"));
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_CRYLINK_SUICIDE;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       return WEAPON_CRYLINK_MURDER;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_Crylink(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 2;
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                       {
-                               pointparticles(particleeffectnum(EFFECT_CRYLINK_IMPACT2), org2, '0 0 0', 1);
-                               if(!w_issilent)
-                                       sound(self, CH_SHOTS, W_Sound("crylink_impact2"), VOL_BASE, ATTN_NORM);
-                       }
-                       else
-                       {
-                               pointparticles(particleeffectnum(EFFECT_CRYLINK_IMPACT), org2, '0 0 0', 1);
-                               if(!w_issilent)
-                                       sound(self, CH_SHOTS, W_Sound("crylink_impact"), VOL_BASE, ATTN_NORM);
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("crylink_impact2"));
-                       precache_sound(W_Sound("crylink_impact"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_devastator.qc b/qcsrc/common/weapons/w_devastator.qc
deleted file mode 100644 (file)
index 9a235bf..0000000
+++ /dev/null
@@ -1,687 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ DEVASTATOR,
-/* function  */ W_Devastator,
-/* ammotype  */ ammo_rockets,
-/* impulse   */ 9,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_HIGH,
-/* color     */ '1 1 0',
-/* modelname */ "rl",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairrocketlauncher 0.7",
-/* wepimg    */ "weaponrocketlauncher",
-/* refname   */ "devastator",
-/* wepname   */ _("Devastator")
-);
-
-#define DEVASTATOR_SETTINGS(w_cvar,w_prop) DEVASTATOR_SETTINGS_LIST(w_cvar, w_prop, DEVASTATOR, devastator)
-#define DEVASTATOR_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, damage) \
-       w_cvar(id, sn, NONE, damageforcescale) \
-       w_cvar(id, sn, NONE, detonatedelay) \
-       w_cvar(id, sn, NONE, edgedamage) \
-       w_cvar(id, sn, NONE, force) \
-       w_cvar(id, sn, NONE, guidedelay) \
-       w_cvar(id, sn, NONE, guidegoal) \
-       w_cvar(id, sn, NONE, guiderate) \
-       w_cvar(id, sn, NONE, guideratedelay) \
-       w_cvar(id, sn, NONE, guidestop) \
-       w_cvar(id, sn, NONE, health) \
-       w_cvar(id, sn, NONE, lifetime) \
-       w_cvar(id, sn, NONE, radius) \
-       w_cvar(id, sn, NONE, refire) \
-       w_cvar(id, sn, NONE, remote_damage) \
-       w_cvar(id, sn, NONE, remote_edgedamage) \
-       w_cvar(id, sn, NONE, remote_force) \
-       w_cvar(id, sn, NONE, remote_jump_damage) \
-       w_cvar(id, sn, NONE, remote_jump_radius) \
-       w_cvar(id, sn, NONE, remote_jump_velocity_z_add) \
-       w_cvar(id, sn, NONE, remote_jump_velocity_z_max) \
-       w_cvar(id, sn, NONE, remote_jump_velocity_z_min) \
-       w_cvar(id, sn, NONE, remote_radius) \
-       w_cvar(id, sn, NONE, speed) \
-       w_cvar(id, sn, NONE, speedaccel) \
-       w_cvar(id, sn, NONE, speedstart) \
-       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
-DEVASTATOR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-.float rl_release;
-.float rl_detonate_later;
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_devastator(void) { weapon_defaultspawnfunc(WEP_DEVASTATOR.m_id); }
-void spawnfunc_weapon_rocketlauncher(void) { spawnfunc_weapon_devastator(); }
-
-void W_Devastator_Unregister(void)
-{
-       if(self.realowner && self.realowner.lastrocket == self)
-       {
-               self.realowner.lastrocket = world;
-               // self.realowner.rl_release = 1;
-       }
-}
-
-void W_Devastator_Explode(void)
-{
-       W_Devastator_Unregister();
-
-       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_AIRSHOT);
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       RadiusDamage(
-               self,
-               self.realowner,
-               WEP_CVAR(devastator, damage),
-               WEP_CVAR(devastator, edgedamage),
-               WEP_CVAR(devastator, radius),
-               world,
-               world,
-               WEP_CVAR(devastator, force),
-               self.projectiledeathtype,
-               other
-       );
-
-       if(self.realowner.weapon == WEP_DEVASTATOR.m_id)
-       {
-               if(self.realowner.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo))
-               if(!(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO))
-               {
-                       self.realowner.cnt = WEP_DEVASTATOR.m_id;
-                       ATTACK_FINISHED(self.realowner) = time;
-                       self.realowner.switchweapon = w_getbestweapon(self.realowner);
-               }
-       }
-       remove(self);
-}
-
-void W_Devastator_DoRemoteExplode(void)
-{
-       W_Devastator_Unregister();
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       float handled_as_rocketjump = false;
-
-       entity head = WarpZone_FindRadius(
-               self.origin,
-               WEP_CVAR(devastator, remote_jump_radius),
-               false
-       );
-
-       while(head)
-       {
-               if(head.takedamage && (head == self.realowner))
-               {
-                       float distance_to_head = vlen(self.origin - head.WarpZone_findradius_nearest);
-                       if(distance_to_head <= WEP_CVAR(devastator, remote_jump_radius))
-                       {
-                               // we handled this as a rocketjump :)
-                               handled_as_rocketjump = true;
-
-                               // modify velocity
-                               head.velocity_x *= 0.9;
-                               head.velocity_y *= 0.9;
-                               head.velocity_z = bound(
-                                       WEP_CVAR(devastator, remote_jump_velocity_z_min),
-                                       head.velocity.z + WEP_CVAR(devastator, remote_jump_velocity_z_add),
-                                       WEP_CVAR(devastator, remote_jump_velocity_z_max)
-                               );
-
-                               // now do the damage
-                               RadiusDamage(
-                                       self,
-                                       head,
-                                       WEP_CVAR(devastator, remote_jump_damage),
-                                       WEP_CVAR(devastator, remote_jump_damage),
-                                       WEP_CVAR(devastator, remote_jump_radius),
-                                       world,
-                                       head,
-                                       0,
-                                       self.projectiledeathtype | HITTYPE_BOUNCE,
-                                       world
-                               );
-                               break;
-                       }
-               }
-               head = head.chain;
-       }
-
-       RadiusDamage(
-               self,
-               self.realowner,
-               WEP_CVAR(devastator, remote_damage),
-               WEP_CVAR(devastator, remote_edgedamage),
-               WEP_CVAR(devastator, remote_radius),
-               (handled_as_rocketjump ? head : world),
-               world,
-               WEP_CVAR(devastator, remote_force),
-               self.projectiledeathtype | HITTYPE_BOUNCE,
-               world
-       );
-
-       if(self.realowner.weapon == WEP_DEVASTATOR.m_id)
-       {
-               if(self.realowner.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo))
-               if(!(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO))
-               {
-                       self.realowner.cnt = WEP_DEVASTATOR.m_id;
-                       ATTACK_FINISHED(self.realowner) = time;
-                       self.realowner.switchweapon = w_getbestweapon(self.realowner);
-               }
-       }
-       remove(self);
-}
-
-void W_Devastator_RemoteExplode(void)
-{
-       if(self.realowner.deadflag == DEAD_NO)
-       if(self.realowner.lastrocket)
-       {
-               if((self.spawnshieldtime >= 0)
-                       ? (time >= self.spawnshieldtime) // timer
-                       : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > WEP_CVAR(devastator, remote_radius)) // safety device
-               )
-               {
-                       W_Devastator_DoRemoteExplode();
-               }
-       }
-}
-
-vector W_Devastator_SteerTo(vector thisdir, vector goaldir, float maxturn_cos)
-{
-       if(thisdir * goaldir > maxturn_cos)
-               return goaldir;
-       if(thisdir * goaldir < -0.9998) // less than 1 degree and opposite
-               return thisdir; // refuse to guide (better than letting a numerical error happen)
-       float f, m2;
-       vector v;
-       // solve:
-       //   g = normalize(thisdir + goaldir * X)
-       //   thisdir * g = maxturn
-       //
-       //   gg = thisdir + goaldir * X
-       //   (thisdir * gg)^2 = maxturn^2 * (gg * gg)
-       //
-       //   (1 + (thisdir * goaldir) * X)^2 = maxturn^2 * (1 + X*X + 2 * X * thisdir * goaldir)
-       f = thisdir * goaldir;
-       //   (1 + f * X)^2 = maxturn^2 * (1 + X*X + 2 * X * f)
-       //   0 = (m^2 - f^2) * x^2 + (2 * f * (m^2 - 1)) * x + (m^2 - 1)
-       m2 = maxturn_cos * maxturn_cos;
-       v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1);
-       return normalize(thisdir + goaldir * v.y); // the larger solution!
-}
-// assume thisdir == -goaldir:
-//   f == -1
-//   v = solve_qadratic(m2 - 1, -2 * (m2 - 1), m2 - 1)
-//   (m2 - 1) x^2 - 2 * (m2 - 1) * x + (m2 - 1) = 0
-//   x^2 - 2 * x + 1 = 0
-//   (x - 1)^2 = 0
-//   x = 1
-//   normalize(thisdir + goaldir)
-//   normalize(0)
-
-void W_Devastator_Think(void)
-{
-       vector desireddir, olddir, newdir, desiredorigin, goal;
-       float velspeed, f;
-       self.nextthink = time;
-       if(time > self.cnt)
-       {
-               other = world;
-               self.projectiledeathtype |= HITTYPE_BOUNCE;
-               W_Devastator_Explode();
-               return;
-       }
-
-       // accelerate
-       makevectors(self.angles.x * '-1 0 0' + self.angles.y * '0 1 0');
-       velspeed = WEP_CVAR(devastator, speed) * W_WeaponSpeedFactor() - (self.velocity * v_forward);
-       if(velspeed > 0)
-               self.velocity = self.velocity + v_forward * min(WEP_CVAR(devastator, speedaccel) * W_WeaponSpeedFactor() * frametime, velspeed);
-
-       // laser guided, or remote detonation
-       if(self.realowner.weapon == WEP_DEVASTATOR.m_id)
-       {
-               if(self == self.realowner.lastrocket)
-               if(!self.realowner.rl_release)
-               if(!self.BUTTON_ATCK2)
-               if(WEP_CVAR(devastator, guiderate))
-               if(time > self.pushltime)
-               if(self.realowner.deadflag == DEAD_NO)
-               {
-                       f = WEP_CVAR(devastator, guideratedelay);
-                       if(f)
-                               f = bound(0, (time - self.pushltime) / f, 1);
-                       else
-                               f = 1;
-
-                       velspeed = vlen(self.velocity);
-
-                       makevectors(self.realowner.v_angle);
-                       desireddir = WarpZone_RefSys_TransformVelocity(self.realowner, self, v_forward);
-                       desiredorigin = WarpZone_RefSys_TransformOrigin(self.realowner, self, self.realowner.origin + self.realowner.view_ofs);
-                       olddir = normalize(self.velocity);
-
-                       // now it gets tricky... we want to move like some curve to approximate the target direction
-                       // but we are limiting the rate at which we can turn!
-                       goal = desiredorigin + ((self.origin - desiredorigin) * desireddir + WEP_CVAR(devastator, guidegoal)) * desireddir;
-                       newdir = W_Devastator_SteerTo(olddir, normalize(goal - self.origin), cos(WEP_CVAR(devastator, guiderate) * f * frametime * DEG2RAD));
-
-                       self.velocity = newdir * velspeed;
-                       self.angles = vectoangles(self.velocity);
-
-                       if(!self.count)
-                       {
-                               Send_Effect(EFFECT_ROCKET_GUIDE, self.origin, self.velocity, 1);
-                               // TODO add a better sound here
-                               sound(self.realowner, CH_WEAPON_B, W_Sound("rocket_mode"), VOL_BASE, ATTN_NORM);
-                               self.count = 1;
-                       }
-               }
-
-               if(self.rl_detonate_later)
-                       W_Devastator_RemoteExplode();
-       }
-
-       if(self.csqcprojectile_clientanimate == 0)
-               UpdateCSQCProjectile(self);
-}
-
-void W_Devastator_Touch(void)
-{
-       if(WarpZone_Projectile_Touch())
-       {
-               if(wasfreed(self))
-                       W_Devastator_Unregister();
-               return;
-       }
-       W_Devastator_Unregister();
-       W_Devastator_Explode();
-}
-
-void W_Devastator_Damage(entity inflictor, entity attacker, float damage, int 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_Devastator_Explode);
-}
-
-void W_Devastator_Attack(void)
-{
-       entity missile;
-       entity flash;
-
-       W_DecreaseAmmo(WEP_CVAR(devastator, ammo));
-
-       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;
-       self.lastrocket = missile;
-       if(WEP_CVAR(devastator, detonatedelay) >= 0)
-               missile.spawnshieldtime = time + WEP_CVAR(devastator, detonatedelay);
-       else
-               missile.spawnshieldtime = -1;
-       missile.pushltime = time + WEP_CVAR(devastator, guidedelay);
-       missile.classname = "rocket";
-       missile.bot_dodge = true;
-       missile.bot_dodgerating = WEP_CVAR(devastator, damage) * 2; // * 2 because it can be detonated inflight which makes it even more dangerous
-
-       missile.takedamage = DAMAGE_YES;
-       missile.damageforcescale = WEP_CVAR(devastator, damageforcescale);
-       missile.health = WEP_CVAR(devastator, health);
-       missile.event_damage = W_Devastator_Damage;
-       missile.damagedbycontents = true;
-
-       missile.movetype = MOVETYPE_FLY;
-       PROJECTILE_MAKETRIGGER(missile);
-       missile.projectiledeathtype = WEP_DEVASTATOR.m_id;
-       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, WEP_CVAR(devastator, speedstart), 0);
-       missile.angles = vectoangles(missile.velocity);
-
-       missile.touch = W_Devastator_Touch;
-       missile.think = W_Devastator_Think;
-       missile.nextthink = time;
-       missile.cnt = time + WEP_CVAR(devastator, lifetime);
-       missile.flags = FL_PROJECTILE;
-       missile.missile_flags = MIF_SPLASH;
-
-       CSQCProjectile(missile, WEP_CVAR(devastator, guiderate) == 0 && WEP_CVAR(devastator, speedaccel) == 0, PROJECTILE_ROCKET, false); // because of fly sound
-
-       // muzzle flash for 1st person view
-       flash = spawn();
-       setmodel(flash, "models/flash.md3"); // precision set below
-       SUB_SetFade(flash, time, 0.1);
-       flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
-       W_AttachToShotorg(flash, '5 0 0');
-
-       // common properties
-       MUTATOR_CALLHOOK(EditProjectile, self, missile);
-}
-
-bool W_Devastator(int req)
-{
-       entity rock;
-       float rockfound;
-       float ammo_amount;
-       switch(req)
-       {
-               #if 0
-               case WR_AIM:
-               {
-                       // 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!
-                       {
-                               // decide whether to detonate rockets
-                               entity missile, targetlist, targ;
-                               targetlist = findchainfloat(bot_attack, true);
-                               for(missile = world; (missile = find(missile, classname, "rocket")); ) if(missile.realowner == self)
-                               {
-                                       targ = targetlist;
-                                       while(targ)
-                                       {
-                                               if(targ != missile.realowner && vlen(targ.origin - missile.origin) < WEP_CVAR(devastator, radius))
-                                               {
-                                                       self.BUTTON_ATCK2 = true;
-                                                       break;
-                                               }
-                                               targ = targ.chain;
-                                       }
-                               }
-
-                               if(self.BUTTON_ATCK2) self.BUTTON_ATCK = false;
-                       }
-
-                       return true;
-               }
-               #else
-               case WR_AIM:
-               {
-                       // 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!
-                       {
-                               // decide whether to detonate rockets
-                               entity missile, targetlist, targ;
-                               float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
-                               float selfdamage, teamdamage, enemydamage;
-                               edgedamage = WEP_CVAR(devastator, edgedamage);
-                               coredamage = WEP_CVAR(devastator, damage);
-                               edgeradius = WEP_CVAR(devastator, radius);
-                               recipricoledgeradius = 1 / edgeradius;
-                               selfdamage = 0;
-                               teamdamage = 0;
-                               enemydamage = 0;
-                               targetlist = findchainfloat(bot_attack, true);
-                               missile = find(world, classname, "rocket");
-                               while(missile)
-                               {
-                                       if(missile.realowner != self)
-                                       {
-                                               missile = find(missile, classname, "rocket");
-                                               continue;
-                                       }
-                                       targ = targetlist;
-                                       while(targ)
-                                       {
-                                               d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - missile.origin);
-                                               d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
-                                               // count potential damage according to type of target
-                                               if(targ == self)
-                                                       selfdamage = selfdamage + d;
-                                               else if(targ.team == self.team && teamplay)
-                                                       teamdamage = teamdamage + d;
-                                               else if(bot_shouldattack(targ))
-                                                       enemydamage = enemydamage + d;
-                                               targ = targ.chain;
-                                       }
-                                       missile = find(missile, classname, "rocket");
-                               }
-                               float desirabledamage;
-                               desirabledamage = enemydamage;
-                               if(time > self.invincible_finished && time > self.spawnshieldtime)
-                                       desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent;
-                               if(teamplay && self.team)
-                                       desirabledamage = desirabledamage - teamdamage;
-
-                               missile = find(world, classname, "rocket");
-                               while(missile)
-                               {
-                                       if(missile.realowner != self)
-                                       {
-                                               missile = find(missile, classname, "rocket");
-                                               continue;
-                                       }
-                                       makevectors(missile.v_angle);
-                                       targ = targetlist;
-                                       if(skill > 9) // normal players only do this for the target they are tracking
-                                       {
-                                               targ = targetlist;
-                                               while(targ)
-                                               {
-                                                       if(
-                                                               (v_forward * normalize(missile.origin - targ.origin)< 0.1)
-                                                               && desirabledamage > 0.1*coredamage
-                                                       )self.BUTTON_ATCK2 = true;
-                                                       targ = targ.chain;
-                                               }
-                                       }else{
-                                               float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
-                                               //As the distance gets larger, a correct detonation gets near imposible
-                                               //Bots are assumed to use the rocket spawnfunc_light to see if the rocket gets near a player
-                                               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))
-                                                                               self.BUTTON_ATCK2 = true;
-                                       //      dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
-                                       }
-
-                                       missile = find(missile, classname, "rocket");
-                               }
-                               // if we would be doing at X percent of the core damage, detonate it
-                               // 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))
-                                       self.BUTTON_ATCK2 = false;
-                               //if(self.BUTTON_ATCK2 == true)
-                               //      dprint(ftos(desirabledamage),"\n");
-                               if(self.BUTTON_ATCK2 == true) self.BUTTON_ATCK = false;
-                       }
-
-                       return true;
-               }
-               #endif
-               case WR_THINK:
-               {
-                       if(WEP_CVAR(devastator, reload_ammo) && self.clip_load < WEP_CVAR(devastator, ammo)) // forced reload
-                               WEP_ACTION(self.weapon, WR_RELOAD);
-                       else
-                       {
-                               if(self.BUTTON_ATCK)
-                               {
-                                       if(self.rl_release || WEP_CVAR(devastator, guidestop))
-                                       if(weapon_prepareattack(0, WEP_CVAR(devastator, refire)))
-                                       {
-                                               W_Devastator_Attack();
-                                               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(devastator, animtime), w_ready);
-                                               self.rl_release = 0;
-                                       }
-                               }
-                               else
-                                       self.rl_release = 1;
-
-                               if(self.BUTTON_ATCK2)
-                               if(self.switchweapon == WEP_DEVASTATOR.m_id)
-                               {
-                                       rockfound = 0;
-                                       for(rock = world; (rock = find(rock, classname, "rocket")); ) if(rock.realowner == self)
-                                       {
-                                               if(!rock.rl_detonate_later)
-                                               {
-                                                       rock.rl_detonate_later = true;
-                                                       rockfound = 1;
-                                               }
-                                       }
-                                       if(rockfound)
-                                               sound(self, CH_WEAPON_B, W_Sound("rocket_det"), VOL_BASE, ATTN_NORM);
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       //if(autocvar_sv_precacheweapons)
-                       //{
-                               precache_model("models/flash.md3");
-                               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;
-               }
-               case WR_SETUP:
-               {
-                       self.rl_release = 1;
-                       return true;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       #if 0
-                       // don't switch while guiding a missile
-                       if(ATTACK_FINISHED(self) <= time || self.weapon != WEP_DEVASTATOR.m_id)
-                       {
-                               ammo_amount = false;
-                               if(WEP_CVAR(devastator, reload_ammo))
-                               {
-                                       if(self.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo) && self.(weapon_load[WEP_DEVASTATOR.m_id]) < WEP_CVAR(devastator, ammo))
-                                               ammo_amount = true;
-                               }
-                               else if(self.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo))
-                                       ammo_amount = true;
-                               return !ammo_amount;
-                       }
-                       #endif
-                       #if 0
-                       if(self.rl_release == 0)
-                       {
-                               LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: TRUE\n", self.rl_release, self.WEP_AMMO(DEVASTATOR), WEP_CVAR(devastator, ammo));
-                               return true;
-                       }
-                       else
-                       {
-                               ammo_amount = self.WEP_AMMO(DEVASTATOR) >= WEP_CVAR(devastator, ammo);
-                               ammo_amount += self.(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo);
-                               LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: %s\n", self.rl_release, self.WEP_AMMO(DEVASTATOR), WEP_CVAR(devastator, ammo), (ammo_amount ? "TRUE" : "FALSE"));
-                               return ammo_amount;
-                       }
-                       #else
-                       ammo_amount = self.WEP_AMMO(DEVASTATOR) >= WEP_CVAR(devastator, ammo);
-                       ammo_amount += self.(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo);
-                       return ammo_amount;
-                       #endif
-               }
-               case WR_CHECKAMMO2:
-               {
-                       return false;
-               }
-               case WR_CONFIG:
-               {
-                       DEVASTATOR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RESETPLAYER:
-               {
-                       self.rl_release = 0;
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       W_Reload(WEP_CVAR(devastator, ammo), W_Sound("reload"));
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_DEVASTATOR_SUICIDE;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH))
-                               return WEAPON_DEVASTATOR_MURDER_SPLASH;
-                       else
-                               return WEAPON_DEVASTATOR_MURDER_DIRECT;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_Devastator(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 12;
-                       pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                               sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTN_NORM);
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("rocket_impact"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_electro.qc b/qcsrc/common/weapons/w_electro.qc
deleted file mode 100644 (file)
index 0f60fd2..0000000
+++ /dev/null
@@ -1,621 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ ELECTRO,
-/* function  */ W_Electro,
-/* ammotype  */ ammo_cells,
-/* impulse   */ 5,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* color     */ '0 0.5 1',
-/* modelname */ "electro",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairelectro 0.6",
-/* wepimg    */ "weaponelectro",
-/* refname   */ "electro",
-/* wepname   */ _("Electro")
-);
-
-#define ELECTRO_SETTINGS(w_cvar,w_prop) ELECTRO_SETTINGS_LIST(w_cvar, w_prop, ELECTRO, electro)
-#define ELECTRO_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, BOTH, ammo) \
-       w_cvar(id, sn, BOTH, animtime) \
-       w_cvar(id, sn, BOTH, damage) \
-       w_cvar(id, sn, BOTH, edgedamage) \
-       w_cvar(id, sn, BOTH, force) \
-       w_cvar(id, sn, BOTH, radius) \
-       w_cvar(id, sn, BOTH, refire) \
-       w_cvar(id, sn, BOTH, speed) \
-       w_cvar(id, sn, BOTH, spread) \
-       w_cvar(id, sn, BOTH, lifetime) \
-       w_cvar(id, sn, PRI,  comboradius) \
-       w_cvar(id, sn, PRI,  midaircombo_explode) \
-       w_cvar(id, sn, PRI,  midaircombo_interval) \
-       w_cvar(id, sn, PRI,  midaircombo_radius) \
-       w_cvar(id, sn, SEC,  bouncefactor) \
-       w_cvar(id, sn, SEC,  bouncestop) \
-       w_cvar(id, sn, SEC,  count) \
-       w_cvar(id, sn, SEC,  damageforcescale) \
-       w_cvar(id, sn, SEC,  damagedbycontents) \
-       w_cvar(id, sn, SEC,  health) \
-       w_cvar(id, sn, SEC,  refire2) \
-       w_cvar(id, sn, SEC,  speed_up) \
-       w_cvar(id, sn, SEC,  speed_z) \
-       w_cvar(id, sn, SEC,  touchexplode) \
-       w_cvar(id, sn, NONE, combo_comboradius) \
-       w_cvar(id, sn, NONE, combo_comboradius_thruwall) \
-       w_cvar(id, sn, NONE, combo_damage) \
-       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_speed) \
-       w_cvar(id, sn, NONE, combo_safeammocheck) \
-       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
-ELECTRO_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-.float electro_count;
-.float electro_secondarytime;
-void W_Electro_ExplodeCombo(void);
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_electro(void) { weapon_defaultspawnfunc(WEP_ELECTRO.m_id); }
-
-void W_Electro_TriggerCombo(vector org, float rad, entity own)
-{
-       entity e = WarpZone_FindRadius(org, rad, !WEP_CVAR(electro, combo_comboradius_thruwall));
-       while(e)
-       {
-               if(e.classname == "electro_orb")
-               {
-                       // do we allow thruwall triggering?
-                       if(WEP_CVAR(electro, combo_comboradius_thruwall))
-                       {
-                               // if distance is greater than thruwall distance, check to make sure it's not through a wall
-                               if(vlen(e.WarpZone_findradius_dist) > WEP_CVAR(electro, combo_comboradius_thruwall))
-                               {
-                                       WarpZone_TraceLine(org, e.origin, MOVE_NOMONSTERS, e);
-                                       if(trace_fraction != 1)
-                                       {
-                                               // trigger is through a wall and outside of thruwall range, abort
-                                               e = e.chain;
-                                               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.WarpZone_findradius_dist) / WEP_CVAR(electro, combo_speed))
-                                               :
-                                               0
-                                       )
-                               );
-               }
-               e = e.chain;
-       }
-}
-
-void W_Electro_ExplodeCombo(void)
-{
-       W_Electro_TriggerCombo(self.origin, WEP_CVAR(electro, combo_comboradius), self.realowner);
-
-       self.event_damage = func_null;
-
-       RadiusDamage(
-               self,
-               self.realowner,
-               WEP_CVAR(electro, combo_damage),
-               WEP_CVAR(electro, combo_edgedamage),
-               WEP_CVAR(electro, combo_radius),
-               world,
-               world,
-               WEP_CVAR(electro, combo_force),
-               WEP_ELECTRO.m_id | HITTYPE_BOUNCE, // use THIS type for a combo because primary can't bounce
-               world
-       );
-
-       remove(self);
-}
-
-void W_Electro_Explode(void)
-{
-       if(other.takedamage == DAMAGE_AIM)
-               if(IS_PLAYER(other))
-                       if(DIFF_TEAM(self.realowner, other))
-                               if(other.deadflag == DEAD_NO)
-                                       if(IsFlying(other))
-                                               Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH);
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       if(self.movetype == MOVETYPE_BOUNCE)
-       {
-               RadiusDamage(
-                       self,
-                       self.realowner,
-                       WEP_CVAR_SEC(electro, damage),
-                       WEP_CVAR_SEC(electro, edgedamage),
-                       WEP_CVAR_SEC(electro, radius),
-                       world,
-                       world,
-                       WEP_CVAR_SEC(electro, force),
-                       self.projectiledeathtype,
-                       other
-               );
-       }
-       else
-       {
-               W_Electro_TriggerCombo(self.origin, WEP_CVAR_PRI(electro, comboradius), self.realowner);
-               RadiusDamage(
-                       self,
-                       self.realowner,
-                       WEP_CVAR_PRI(electro, damage),
-                       WEP_CVAR_PRI(electro, edgedamage),
-                       WEP_CVAR_PRI(electro, radius),
-                       world,
-                       world,
-                       WEP_CVAR_PRI(electro, force),
-                       self.projectiledeathtype,
-                       other
-               );
-       }
-
-       remove(self);
-}
-
-void W_Electro_TouchExplode(void)
-{
-       PROJECTILE_TOUCH;
-       W_Electro_Explode();
-}
-
-void W_Electro_Bolt_Think(void)
-{
-       if(time >= self.ltime)
-       {
-               self.use();
-               return;
-       }
-
-       if(WEP_CVAR_PRI(electro, midaircombo_radius))
-       {
-               float found = 0;
-               entity e = WarpZone_FindRadius(self.origin, WEP_CVAR_PRI(electro, midaircombo_radius), true);
-
-               // loop through nearby orbs and trigger them
-               while(e)
-               {
-                       if(e.classname == "electro_orb")
-                       {
-                               // change owner to whoever caused the combo explosion
-                               e.realowner = self.realowner;
-                               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.WarpZone_findradius_dist) / WEP_CVAR(electro, combo_speed))
-                                                       :
-                                                       0
-                                               )
-                                       );
-
-                               ++found;
-                       }
-                       e = e.chain;
-               }
-
-               // if we triggered an orb, should we explode? if not, lets try again next time
-               if(found && WEP_CVAR_PRI(electro, midaircombo_explode))
-                       { self.use(); }
-               else
-                       { self.nextthink = min(time + WEP_CVAR_PRI(electro, midaircombo_interval), self.ltime); }
-       }
-       else { self.nextthink = self.ltime; }
-}
-
-void W_Electro_Attack_Bolt(void)
-{
-       entity proj;
-
-       W_DecreaseAmmo(WEP_CVAR_PRI(electro, ammo));
-
-       W_SetupShot_ProjectileSize(
-               self,
-               '0 0 -3',
-               '0 0 -3',
-               false,
-               2,
-               W_Sound("electro_fire"),
-               CH_WEAPON_A,
-               WEP_CVAR_PRI(electro, damage)
-       );
-
-       Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       proj = spawn();
-       proj.classname = "electro_bolt";
-       proj.owner = proj.realowner = self;
-       proj.bot_dodge = true;
-       proj.bot_dodgerating = WEP_CVAR_PRI(electro, damage);
-       proj.use = W_Electro_Explode;
-       proj.think = W_Electro_Bolt_Think;
-       proj.nextthink = time;
-       proj.ltime = time + WEP_CVAR_PRI(electro, lifetime);
-       PROJECTILE_MAKETRIGGER(proj);
-       proj.projectiledeathtype = WEP_ELECTRO.m_id;
-       setorigin(proj, w_shotorg);
-
-       proj.movetype = MOVETYPE_FLY;
-       W_SetupProjVelocity_PRI(proj, electro);
-       proj.angles = vectoangles(proj.velocity);
-       proj.touch = W_Electro_TouchExplode;
-       setsize(proj, '0 0 -3', '0 0 -3');
-       proj.flags = FL_PROJECTILE;
-       proj.missile_flags = MIF_SPLASH;
-
-       CSQCProjectile(proj, true, PROJECTILE_ELECTRO_BEAM, true);
-
-       MUTATOR_CALLHOOK(EditProjectile, self, proj);
-}
-
-void W_Electro_Orb_Touch(void)
-{
-       PROJECTILE_TOUCH;
-       if(other.takedamage == DAMAGE_AIM)
-               { if(WEP_CVAR_SEC(electro, touchexplode)) { W_Electro_Explode(); } }
-       else
-       {
-               //UpdateCSQCProjectile(self);
-               spamsound(self, CH_SHOTS, W_Sound("electro_bounce"), VOL_BASE, ATTEN_NORM);
-               self.projectiledeathtype |= HITTYPE_BOUNCE;
-       }
-}
-
-void W_Electro_Orb_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{
-       if(self.health <= 0)
-               return;
-
-       // note: combos are usually triggered by W_Electro_TriggerCombo, not damage
-       float is_combo = (inflictor.classname == "electro_orb_chain" || inflictor.classname == "electro_bolt");
-
-       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_combo ? 1 : -1)))
-               return; // g_projectiles_damage says to halt
-
-       self.health = self.health - damage;
-       if(self.health <= 0)
-       {
-               self.takedamage = DAMAGE_NO;
-               self.nextthink = time;
-               if(is_combo)
-               {
-                       // change owner to whoever caused the combo explosion
-                       self.realowner = inflictor.realowner;
-                       self.classname = "electro_orb_chain";
-                       self.think = W_Electro_ExplodeCombo;
-                       self.nextthink = time +
-                               (
-                                       // bound the length, inflictor may be in a galaxy far far away (warpzones)
-                                       min(
-                                               WEP_CVAR(electro, combo_radius),
-                                               vlen(self.origin - inflictor.origin)
-                                       )
-                                       /
-                                       // delay combo chains, looks cooler
-                                       WEP_CVAR(electro, combo_speed)
-                               );
-               }
-               else
-               {
-                       self.use = W_Electro_Explode;
-                       self.think = adaptor_think2use; // not _hittype_splash, as this runs "immediately"
-               }
-       }
-}
-
-void W_Electro_Attack_Orb(void)
-{
-       W_DecreaseAmmo(WEP_CVAR_SEC(electro, ammo));
-
-       W_SetupShot_ProjectileSize(
-               self,
-               '0 0 -4',
-               '0 0 -4',
-               false,
-               2,
-               W_Sound("electro_fire2"),
-               CH_WEAPON_A,
-               WEP_CVAR_SEC(electro, damage)
-       );
-
-       w_shotdir = v_forward; // no TrueAim for grenades please
-
-       Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       entity proj = spawn();
-       proj.classname = "electro_orb";
-       proj.owner = proj.realowner = self;
-       proj.use = W_Electro_Explode;
-       proj.think = adaptor_think2use_hittype_splash;
-       proj.bot_dodge = true;
-       proj.bot_dodgerating = WEP_CVAR_SEC(electro, damage);
-       proj.nextthink = time + WEP_CVAR_SEC(electro, lifetime);
-       PROJECTILE_MAKETRIGGER(proj);
-       proj.projectiledeathtype = WEP_ELECTRO.m_id | HITTYPE_SECONDARY;
-       setorigin(proj, w_shotorg);
-
-       //proj.glow_size = 50;
-       //proj.glow_color = 45;
-       proj.movetype = MOVETYPE_BOUNCE;
-       W_SetupProjVelocity_UP_SEC(proj, electro);
-       proj.touch = W_Electro_Orb_Touch;
-       setsize(proj, '0 0 -4', '0 0 -4');
-       proj.takedamage = DAMAGE_YES;
-       proj.damageforcescale = WEP_CVAR_SEC(electro, damageforcescale);
-       proj.health = WEP_CVAR_SEC(electro, health);
-       proj.event_damage = W_Electro_Orb_Damage;
-       proj.flags = FL_PROJECTILE;
-       proj.damagedbycontents = (WEP_CVAR_SEC(electro, damagedbycontents));
-
-       proj.bouncefactor = WEP_CVAR_SEC(electro, bouncefactor);
-       proj.bouncestop = WEP_CVAR_SEC(electro, bouncestop);
-       proj.missile_flags = MIF_SPLASH | MIF_ARC;
-
-#if 0
-       entity p2;
-       p2 = spawn();
-       copyentity(proj, p2);
-       setmodel(p2, "models/ebomb.mdl");
-       setsize(p2, proj.mins, proj.maxs);
-#endif
-
-       CSQCProjectile(proj, true, PROJECTILE_ELECTRO, false); // no culling, it has sound
-
-       MUTATOR_CALLHOOK(EditProjectile, self, proj);
-}
-
-void W_Electro_CheckAttack(void)
-{
-       if(self.electro_count > 1)
-       if(self.BUTTON_ATCK2)
-       if(weapon_prepareattack(1, -1))
-       {
-               W_Electro_Attack_Orb();
-               self.electro_count -= 1;
-               weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack);
-               return;
-       }
-       // WEAPONTODO: when the player releases the button, cut down the length of refire2?
-       w_ready();
-}
-
-.float bot_secondary_electromooth;
-bool W_Electro(int req)
-{
-       float ammo_amount;
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       self.BUTTON_ATCK = self.BUTTON_ATCK2 = false;
-                       if(vlen(self.origin-self.enemy.origin) > 1000) { self.bot_secondary_electromooth = 0; }
-                       if(self.bot_secondary_electromooth == 0)
-                       {
-                               float shoot;
-
-                               if(WEP_CVAR_PRI(electro, speed))
-                                       shoot = bot_aim(WEP_CVAR_PRI(electro, speed), 0, WEP_CVAR_PRI(electro, lifetime), false);
-                               else
-                                       shoot = bot_aim(1000000, 0, 0.001, false);
-
-                               if(shoot)
-                               {
-                                       self.BUTTON_ATCK = true;
-                                       if(random() < 0.01) self.bot_secondary_electromooth = 1;
-                               }
-                       }
-                       else
-                       {
-                               if(bot_aim(WEP_CVAR_SEC(electro, speed), WEP_CVAR_SEC(electro, speed_up), WEP_CVAR_SEC(electro, lifetime), true))
-                               {
-                                       self.BUTTON_ATCK2 = true;
-                                       if(random() < 0.03) self.bot_secondary_electromooth = 0;
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       if(autocvar_g_balance_electro_reload_ammo) // forced reload // WEAPONTODO
-                       {
-                               ammo_amount = 0;
-                               if(self.clip_load >= WEP_CVAR_PRI(electro, ammo))
-                                       ammo_amount = 1;
-                               if(self.clip_load >= WEP_CVAR_SEC(electro, ammo))
-                                       ammo_amount += 1;
-
-                               if(!ammo_amount)
-                               {
-                                       WEP_ACTION(self.weapon, WR_RELOAD);
-                                       return false;
-                               }
-
-                               return true;
-                       }
-
-                       if(self.BUTTON_ATCK)
-                       {
-                               if(weapon_prepareattack(0, WEP_CVAR_PRI(electro, refire)))
-                               {
-                                               W_Electro_Attack_Bolt();
-                                               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(electro, animtime), w_ready);
-                               }
-                       }
-                       else if(self.BUTTON_ATCK2)
-                       {
-                               if(time >= self.electro_secondarytime)
-                               if(weapon_prepareattack(1, WEP_CVAR_SEC(electro, refire)))
-                               {
-                                       W_Electro_Attack_Orb();
-                                       self.electro_count = WEP_CVAR_SEC(electro, count);
-                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack);
-                                       self.electro_secondarytime = time + WEP_CVAR_SEC(electro, refire2) * W_WeaponRateFactor();
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       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;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       ammo_amount = self.WEP_AMMO(ELECTRO) >= WEP_CVAR_PRI(electro, ammo);
-                       ammo_amount += self.(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_PRI(electro, ammo);
-                       return ammo_amount;
-               }
-               case WR_CHECKAMMO2:
-               {
-                       if(WEP_CVAR(electro, combo_safeammocheck)) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false.
-                       {
-                               ammo_amount = self.WEP_AMMO(ELECTRO) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
-                               ammo_amount += self.(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
-                       }
-                       else
-                       {
-                               ammo_amount = self.WEP_AMMO(ELECTRO) >= WEP_CVAR_SEC(electro, ammo);
-                               ammo_amount += self.(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_SEC(electro, ammo);
-                       }
-                       return ammo_amount;
-               }
-               case WR_CONFIG:
-               {
-                       ELECTRO_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RESETPLAYER:
-               {
-                       self.electro_secondarytime = time;
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       W_Reload(min(WEP_CVAR_PRI(electro, ammo), WEP_CVAR_SEC(electro, ammo)), W_Sound("reload"));
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                               return WEAPON_ELECTRO_SUICIDE_ORBS;
-                       else
-                               return WEAPON_ELECTRO_SUICIDE_BOLT;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                       {
-                               return WEAPON_ELECTRO_MURDER_ORBS;
-                       }
-                       else
-                       {
-                               if(w_deathtype & HITTYPE_BOUNCE)
-                                       return WEAPON_ELECTRO_MURDER_COMBO;
-                               else
-                                       return WEAPON_ELECTRO_MURDER_BOLT;
-                       }
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_Electro(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 6;
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                       {
-                               pointparticles(particleeffectnum(EFFECT_ELECTRO_BALLEXPLODE), org2, '0 0 0', 1);
-                               if(!w_issilent)
-                                       sound(self, CH_SHOTS, W_Sound("electro_impact"), VOL_BASE, ATTEN_NORM);
-                       }
-                       else
-                       {
-                               if(w_deathtype & HITTYPE_BOUNCE)
-                               {
-                                       // this is sent as "primary (w_deathtype & HITTYPE_BOUNCE)" to distinguish it from (w_deathtype & HITTYPE_SECONDARY) bounced balls
-                                       pointparticles(particleeffectnum(EFFECT_ELECTRO_COMBO), org2, '0 0 0', 1);
-                                       if(!w_issilent)
-                                               sound(self, CH_SHOTS, W_Sound("electro_impact_combo"), VOL_BASE, ATTEN_NORM);
-                               }
-                               else
-                               {
-                                       pointparticles(particleeffectnum(EFFECT_ELECTRO_IMPACT), org2, '0 0 0', 1);
-                                       if(!w_issilent)
-                                               sound(self, CH_SHOTS, W_Sound("electro_impact"), VOL_BASE, ATTEN_NORM);
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("electro_impact"));
-                       precache_sound(W_Sound("electro_impact_combo"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_fireball.qc b/qcsrc/common/weapons/w_fireball.qc
deleted file mode 100644 (file)
index 4f8f378..0000000
+++ /dev/null
@@ -1,486 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ FIREBALL,
-/* function  */ W_Fireball,
-/* ammotype  */ ammo_none,
-/* impulse   */ 9,
-/* flags     */ WEP_FLAG_SUPERWEAPON | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* color     */ '1 0.5 0',
-/* modelname */ "fireball",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairfireball",
-/* wepimg    */ "weaponfireball",
-/* refname   */ "fireball",
-/* wepname   */ _("Fireball")
-);
-
-#define FIREBALL_SETTINGS(w_cvar,w_prop) FIREBALL_SETTINGS_LIST(w_cvar, w_prop, FIREBALL, fireball)
-#define FIREBALL_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, damageforcescale) \
-       w_cvar(id, sn, BOTH, speed) \
-       w_cvar(id, sn, BOTH, spread) \
-       w_cvar(id, sn, BOTH, lifetime) \
-       w_cvar(id, sn, BOTH, laserburntime) \
-       w_cvar(id, sn, BOTH, laserdamage) \
-       w_cvar(id, sn, BOTH, laseredgedamage) \
-       w_cvar(id, sn, BOTH, laserradius) \
-       w_cvar(id, sn, PRI,  edgedamage) \
-       w_cvar(id, sn, PRI,  force) \
-       w_cvar(id, sn, PRI,  radius) \
-       w_cvar(id, sn, PRI,  health) \
-       w_cvar(id, sn, PRI,  refire2) \
-       w_cvar(id, sn, PRI,  bfgdamage) \
-       w_cvar(id, sn, PRI,  bfgforce) \
-       w_cvar(id, sn, PRI,  bfgradius) \
-       w_cvar(id, sn, SEC,  damagetime) \
-       w_cvar(id, sn, SEC,  speed_up) \
-       w_cvar(id, sn, SEC,  speed_z) \
-       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
-FIREBALL_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-.float bot_primary_fireballmooth; // whatever a mooth is
-.vector fireball_impactvec;
-.float fireball_primarytime;
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_fireball(void) { weapon_defaultspawnfunc(WEP_FIREBALL.m_id); }
-
-void W_Fireball_Explode(void)
-{
-       entity e;
-       float dist;
-       float points;
-       vector dir;
-       float d;
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       // 1. dist damage
-       d = (self.realowner.health + self.realowner.armorvalue);
-       RadiusDamage(self, self.realowner, WEP_CVAR_PRI(fireball, damage), WEP_CVAR_PRI(fireball, edgedamage), WEP_CVAR_PRI(fireball, radius), world, world, WEP_CVAR_PRI(fireball, force), self.projectiledeathtype, other);
-       if(self.realowner.health + self.realowner.armorvalue >= d)
-       if(!self.cnt)
-       {
-               modeleffect_spawn("models/sphere/sphere.md3", 0, 0, self.origin, '0 0 0', '0 0 0', '0 0 0', 0, WEP_CVAR_PRI(fireball, bfgradius), 0.2, 0.05, 0.25);
-
-               // 2. bfg effect
-               // NOTE: this cannot be made warpzone aware by design. So, better intentionally ignore warpzones here.
-               for(e = findradius(self.origin, WEP_CVAR_PRI(fireball, bfgradius)); e; e = e.chain)
-               if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self))
-               {
-                       // can we see fireball?
-                       traceline(e.origin + e.view_ofs, self.origin, MOVE_NORMAL, e);
-                       if(/* trace_startsolid || */ trace_fraction != 1) // startsolid should be never happening anyway
-                               continue;
-                       // can we see player who shot fireball?
-                       traceline(e.origin + e.view_ofs, self.realowner.origin + self.realowner.view_ofs, MOVE_NORMAL, e);
-                       if(trace_ent != self.realowner)
-                       if(/* trace_startsolid || */ trace_fraction != 1)
-                               continue;
-                       dist = vlen(self.origin - e.origin - e.view_ofs);
-                       points = (1 - sqrt(dist / WEP_CVAR_PRI(fireball, bfgradius)));
-                       if(points <= 0)
-                               continue;
-                       dir = normalize(e.origin + e.view_ofs - self.origin);
-
-                       if(accuracy_isgooddamage(self.realowner, e))
-                               accuracy_add(self.realowner, WEP_FIREBALL.m_id, 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);
-                       Send_Effect(EFFECT_FIREBALL_BFGDAMAGE, e.origin, -1 * dir, 1);
-               }
-       }
-
-       remove(self);
-}
-
-void W_Fireball_TouchExplode(void)
-{
-       PROJECTILE_TOUCH;
-       W_Fireball_Explode();
-}
-
-void W_Fireball_LaserPlay(float dt, float dist, float damage, float edgedamage, float burntime)
-{
-       entity e;
-       float d;
-       vector p;
-
-       if(damage <= 0)
-               return;
-
-       RandomSelection_Init();
-       for(e = WarpZone_FindRadius(self.origin, dist, true); e; e = e.chain)
-       if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self))
-       {
-               p = e.origin;
-               p.x += e.mins.x + random() * (e.maxs.x - e.mins.x);
-               p.y += e.mins.y + random() * (e.maxs.y - e.mins.y);
-               p.z += e.mins.z + random() * (e.maxs.z - e.mins.z);
-               d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p);
-               if(d < dist)
-               {
-                       e.fireball_impactvec = p;
-                       RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e));
-               }
-       }
-       if(RandomSelection_chosen_ent)
-       {
-               d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec);
-               d = damage + (edgedamage - damage) * (d / dist);
-               Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE);
-               //trailparticles(self, particleeffectnum(EFFECT_FIREBALL_LASER), self.origin, RandomSelection_chosen_ent.fireball_impactvec);
-               Send_Effect(EFFECT_FIREBALL_LASER, self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1);
-       }
-}
-
-void W_Fireball_Think(void)
-{
-       if(time > self.pushltime)
-       {
-               self.cnt = 1;
-               self.projectiledeathtype |= HITTYPE_SPLASH;
-               W_Fireball_Explode();
-               return;
-       }
-
-       W_Fireball_LaserPlay(0.1, WEP_CVAR_PRI(fireball, laserradius), WEP_CVAR_PRI(fireball, laserdamage), WEP_CVAR_PRI(fireball, laseredgedamage), WEP_CVAR_PRI(fireball, laserburntime));
-
-       self.nextthink = time + 0.1;
-}
-
-void W_Fireball_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{
-       if(self.health <= 0)
-               return;
-
-       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
-               return; // g_projectiles_damage says to halt
-
-       self.health = self.health - damage;
-       if(self.health <= 0)
-       {
-               self.cnt = 1;
-               W_PrepareExplosionByDamage(attacker, W_Fireball_Explode);
-       }
-}
-
-void W_Fireball_Attack1(void)
-{
-       entity proj;
-
-       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));
-
-       Send_Effect(EFFECT_FIREBALL_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       proj = spawn();
-       proj.classname = "plasma_prim";
-       proj.owner = proj.realowner = self;
-       proj.bot_dodge = true;
-       proj.bot_dodgerating = WEP_CVAR_PRI(fireball, damage);
-       proj.pushltime = time + WEP_CVAR_PRI(fireball, lifetime);
-       proj.use = W_Fireball_Explode;
-       proj.think = W_Fireball_Think;
-       proj.nextthink = time;
-       proj.health = WEP_CVAR_PRI(fireball, health);
-       proj.team = self.team;
-       proj.event_damage = W_Fireball_Damage;
-       proj.takedamage = DAMAGE_YES;
-       proj.damageforcescale = WEP_CVAR_PRI(fireball, damageforcescale);
-       PROJECTILE_MAKETRIGGER(proj);
-       proj.projectiledeathtype = WEP_FIREBALL.m_id;
-       setorigin(proj, w_shotorg);
-
-       proj.movetype = MOVETYPE_FLY;
-       W_SetupProjVelocity_PRI(proj, fireball);
-       proj.angles = vectoangles(proj.velocity);
-       proj.touch = W_Fireball_TouchExplode;
-       setsize(proj, '-16 -16 -16', '16 16 16');
-       proj.flags = FL_PROJECTILE;
-    proj.missile_flags = MIF_SPLASH | MIF_PROXY;
-
-       CSQCProjectile(proj, true, PROJECTILE_FIREBALL, true);
-
-       MUTATOR_CALLHOOK(EditProjectile, self, proj);
-}
-
-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;
-       Send_Effect(EFFECT_FIREBALL_PRE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-}
-
-void W_Fireball_Attack1_Frame4(void)
-{
-       W_Fireball_Attack1();
-       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), w_ready);
-}
-
-void W_Fireball_Attack1_Frame3(void)
-{
-       W_Fireball_AttackEffect(0, '+1.25 +3.75 0');
-       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame4);
-}
-
-void W_Fireball_Attack1_Frame2(void)
-{
-       W_Fireball_AttackEffect(0, '-1.25 +3.75 0');
-       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame3);
-}
-
-void W_Fireball_Attack1_Frame1(void)
-{
-       W_Fireball_AttackEffect(1, '+1.25 -3.75 0');
-       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame2);
-}
-
-void W_Fireball_Attack1_Frame0(void)
-{
-       W_Fireball_AttackEffect(0, '-1.25 -3.75 0');
-       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);
-}
-
-void W_Fireball_Firemine_Think(void)
-{
-       if(time > self.pushltime)
-       {
-               remove(self);
-               return;
-       }
-
-       // make it "hot" once it leaves its owner
-       if(self.owner)
-       {
-               if(vlen(self.origin - self.owner.origin - self.owner.view_ofs) > WEP_CVAR_SEC(fireball, laserradius))
-               {
-                       self.cnt += 1;
-                       if(self.cnt == 3)
-                               self.owner = world;
-               }
-               else
-                       self.cnt = 0;
-       }
-
-       W_Fireball_LaserPlay(0.1, WEP_CVAR_SEC(fireball, laserradius), WEP_CVAR_SEC(fireball, laserdamage), WEP_CVAR_SEC(fireball, laseredgedamage), WEP_CVAR_SEC(fireball, laserburntime));
-
-       self.nextthink = time + 0.1;
-}
-
-void W_Fireball_Firemine_Touch(void)
-{
-       PROJECTILE_TOUCH;
-       if(other.takedamage == DAMAGE_AIM)
-       if(Fire_AddDamage(other, self.realowner, WEP_CVAR_SEC(fireball, damage), WEP_CVAR_SEC(fireball, damagetime), self.projectiledeathtype) >= 0)
-       {
-               remove(self);
-               return;
-       }
-       self.projectiledeathtype |= HITTYPE_BOUNCE;
-}
-
-void W_Fireball_Attack2(void)
-{
-       entity proj;
-       vector f_diff;
-       float c;
-
-       c = self.bulletcounter % 4;
-       switch(c)
-       {
-               case 0:
-                       f_diff = '-1.25 -3.75 0';
-                       break;
-               case 1:
-                       f_diff = '+1.25 -3.75 0';
-                       break;
-               case 2:
-                       f_diff = '-1.25 +3.75 0';
-                       break;
-               case 3:
-               default:
-                       f_diff = '+1.25 +3.75 0';
-                       break;
-       }
-       W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', false, 2, 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;
-
-       Send_Effect(EFFECT_FIREBALL_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       proj = spawn();
-       proj.owner = proj.realowner = self;
-       proj.classname = "grenade";
-       proj.bot_dodge = true;
-       proj.bot_dodgerating = WEP_CVAR_SEC(fireball, damage);
-       proj.movetype = MOVETYPE_BOUNCE;
-       proj.projectiledeathtype = WEP_FIREBALL.m_id | HITTYPE_SECONDARY;
-       proj.touch = W_Fireball_Firemine_Touch;
-       PROJECTILE_MAKETRIGGER(proj);
-       setsize(proj, '-4 -4 -4', '4 4 4');
-       setorigin(proj, w_shotorg);
-       proj.think = W_Fireball_Firemine_Think;
-       proj.nextthink = time;
-       proj.damageforcescale = WEP_CVAR_SEC(fireball, damageforcescale);
-       proj.pushltime = time + WEP_CVAR_SEC(fireball, lifetime);
-       W_SetupProjVelocity_UP_SEC(proj, fireball);
-
-       proj.angles = vectoangles(proj.velocity);
-       proj.flags = FL_PROJECTILE;
-    proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC;
-
-       CSQCProjectile(proj, true, PROJECTILE_FIREMINE, true);
-
-       MUTATOR_CALLHOOK(EditProjectile, self, proj);
-}
-
-bool W_Fireball(int req)
-{
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       self.BUTTON_ATCK = false;
-                       self.BUTTON_ATCK2 = false;
-                       if(self.bot_primary_fireballmooth == 0)
-                       {
-                               if(bot_aim(WEP_CVAR_PRI(fireball, speed), 0, WEP_CVAR_PRI(fireball, lifetime), false))
-                               {
-                                       self.BUTTON_ATCK = true;
-                                       if(random() < 0.02) self.bot_primary_fireballmooth = 0;
-                               }
-                       }
-                       else
-                       {
-                               if(bot_aim(WEP_CVAR_SEC(fireball, speed), WEP_CVAR_SEC(fireball, speed_up), WEP_CVAR_SEC(fireball, lifetime), true))
-                               {
-                                       self.BUTTON_ATCK2 = true;
-                                       if(random() < 0.01) self.bot_primary_fireballmooth = 1;
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       if(self.BUTTON_ATCK)
-                       {
-                               if(time >= self.fireball_primarytime)
-                               if(weapon_prepareattack(0, WEP_CVAR_PRI(fireball, refire)))
-                               {
-                                       W_Fireball_Attack1_Frame0();
-                                       self.fireball_primarytime = time + WEP_CVAR_PRI(fireball, refire2) * W_WeaponRateFactor();
-                               }
-                       }
-                       else if(self.BUTTON_ATCK2)
-                       {
-                               if(weapon_prepareattack(1, WEP_CVAR_SEC(fireball, refire)))
-                               {
-                                       W_Fireball_Attack2();
-                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(fireball, animtime), w_ready);
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_model(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(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;
-               }
-               case WR_SETUP:
-               {
-                       self.ammo_field = ammo_none;
-                       return true;
-               }
-               case WR_CHECKAMMO1:
-               case WR_CHECKAMMO2:
-               {
-                       return true; // fireball has infinite ammo
-               }
-               case WR_CONFIG:
-               {
-                       FIREBALL_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RESETPLAYER:
-               {
-                       self.fireball_primarytime = time;
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                               return WEAPON_FIREBALL_SUICIDE_FIREMINE;
-                       else
-                               return WEAPON_FIREBALL_SUICIDE_BLAST;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                               return WEAPON_FIREBALL_MURDER_FIREMINE;
-                       else
-                               return WEAPON_FIREBALL_MURDER_BLAST;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_Fireball(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                       {
-                               // firemine goes out silently
-                       }
-                       else
-                       {
-                               org2 = w_org + w_backoff * 16;
-                               pointparticles(particleeffectnum(EFFECT_FIREBALL_EXPLODE), org2, '0 0 0', 1);
-                               if(!w_issilent)
-                                       sound(self, CH_SHOTS, W_Sound("fireball_impact2"), VOL_BASE, ATTEN_NORM * 0.25); // long range boom
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("fireball_impact2"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_hagar.qc b/qcsrc/common/weapons/w_hagar.qc
deleted file mode 100644 (file)
index 4f1b905..0000000
+++ /dev/null
@@ -1,565 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ HAGAR,
-/* function  */ W_Hagar,
-/* ammotype  */ ammo_rockets,
-/* impulse   */ 8,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* color     */ '1 1 0.5',
-/* modelname */ "hagar",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairhagar 0.8",
-/* wepimg    */ "weaponhagar",
-/* refname   */ "hagar",
-/* wepname   */ _("Hagar")
-);
-
-#define HAGAR_SETTINGS(w_cvar,w_prop) HAGAR_SETTINGS_LIST(w_cvar, w_prop, HAGAR, hagar)
-#define HAGAR_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, BOTH, ammo) \
-       w_cvar(id, sn, BOTH, damage) \
-       w_cvar(id, sn, BOTH, edgedamage) \
-       w_cvar(id, sn, BOTH, force) \
-       w_cvar(id, sn, BOTH, radius) \
-       w_cvar(id, sn, BOTH, refire) \
-       w_cvar(id, sn, BOTH, speed) \
-       w_cvar(id, sn, BOTH, spread) \
-       w_cvar(id, sn, BOTH, damageforcescale) \
-       w_cvar(id, sn, BOTH, health) \
-       w_cvar(id, sn, PRI,  lifetime) \
-       w_cvar(id, sn, SEC,  load) \
-       w_cvar(id, sn, SEC,  load_max) \
-       w_cvar(id, sn, SEC,  load_abort) \
-       w_cvar(id, sn, SEC,  load_animtime) \
-       w_cvar(id, sn, SEC,  load_hold) \
-       w_cvar(id, sn, SEC,  load_speed) \
-       w_cvar(id, sn, SEC,  load_releasedeath) \
-       w_cvar(id, sn, SEC,  load_spread) \
-       w_cvar(id, sn, SEC,  load_spread_bias) \
-       w_cvar(id, sn, SEC,  load_linkexplode) \
-       w_cvar(id, sn, SEC,  lifetime_min) \
-       w_cvar(id, sn, SEC,  lifetime_rand) \
-       w_cvar(id, sn, NONE, secondary) \
-       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
-HAGAR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_hagar(void) { weapon_defaultspawnfunc(WEP_HAGAR.m_id); }
-
-// NO bounce protection, as bounces are limited!
-
-void W_Hagar_Explode(void)
-{
-       self.event_damage = func_null;
-       RadiusDamage(self, self.realowner, WEP_CVAR_PRI(hagar, damage), WEP_CVAR_PRI(hagar, edgedamage), WEP_CVAR_PRI(hagar, radius), world, world, WEP_CVAR_PRI(hagar, force), self.projectiledeathtype, other);
-
-       remove(self);
-}
-
-void W_Hagar_Explode2(void)
-{
-       self.event_damage = func_null;
-       RadiusDamage(self, self.realowner, WEP_CVAR_SEC(hagar, damage), WEP_CVAR_SEC(hagar, edgedamage), WEP_CVAR_SEC(hagar, radius), world, world, WEP_CVAR_SEC(hagar, force), self.projectiledeathtype, other);
-
-       remove(self);
-}
-
-void W_Hagar_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{
-       if(self.health <= 0)
-               return;
-
-       float is_linkexplode = ( ((inflictor.owner != world) ? (inflictor.owner == self.owner) : true)
-               && (inflictor.projectiledeathtype & HITTYPE_SECONDARY)
-               && (self.projectiledeathtype & HITTYPE_SECONDARY));
-
-       if(is_linkexplode)
-               is_linkexplode = (is_linkexplode && WEP_CVAR_SEC(hagar, load_linkexplode));
-       else
-               is_linkexplode = -1; // not secondary load, so continue as normal without exception.
-
-       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, is_linkexplode))
-               return; // g_projectiles_damage says to halt
-
-       self.health = self.health - damage;
-       self.angles = vectoangles(self.velocity);
-
-       if(self.health <= 0)
-               W_PrepareExplosionByDamage(attacker, self.think);
-}
-
-void W_Hagar_Touch(void)
-{
-       PROJECTILE_TOUCH;
-       self.use();
-}
-
-void W_Hagar_Touch2(void)
-{
-       PROJECTILE_TOUCH;
-
-       if(self.cnt > 0 || other.takedamage == DAMAGE_AIM) {
-               self.use();
-       } else {
-               self.cnt++;
-               Send_Effect(EFFECT_HAGAR_BOUNCE, self.origin, self.velocity, 1);
-               self.angles = vectoangles(self.velocity);
-               self.owner = world;
-               self.projectiledeathtype |= HITTYPE_BOUNCE;
-       }
-}
-
-void W_Hagar_Attack(void)
-{
-       entity missile;
-
-       W_DecreaseAmmo(WEP_CVAR_PRI(hagar, ammo));
-
-       W_SetupShot(self, false, 2, W_Sound("hagar_fire"), CH_WEAPON_A, WEP_CVAR_PRI(hagar, damage));
-
-       Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       missile = spawn();
-       missile.owner = missile.realowner = self;
-       missile.classname = "missile";
-       missile.bot_dodge = true;
-       missile.bot_dodgerating = WEP_CVAR_PRI(hagar, damage);
-
-       missile.takedamage = DAMAGE_YES;
-       missile.health = WEP_CVAR_PRI(hagar, health);
-       missile.damageforcescale = WEP_CVAR_PRI(hagar, damageforcescale);
-       missile.event_damage = W_Hagar_Damage;
-       missile.damagedbycontents = true;
-
-       missile.touch = W_Hagar_Touch;
-       missile.use = W_Hagar_Explode;
-       missile.think = adaptor_think2use_hittype_splash;
-       missile.nextthink = time + WEP_CVAR_PRI(hagar, lifetime);
-       PROJECTILE_MAKETRIGGER(missile);
-       missile.projectiledeathtype = WEP_HAGAR.m_id;
-       setorigin(missile, w_shotorg);
-       setsize(missile, '0 0 0', '0 0 0');
-
-       missile.movetype = MOVETYPE_FLY;
-       W_SetupProjVelocity_PRI(missile, hagar);
-
-       missile.angles = vectoangles(missile.velocity);
-       missile.flags = FL_PROJECTILE;
-       missile.missile_flags = MIF_SPLASH;
-
-       CSQCProjectile(missile, true, PROJECTILE_HAGAR, true);
-
-       MUTATOR_CALLHOOK(EditProjectile, self, missile);
-}
-
-void W_Hagar_Attack2(void)
-{
-       entity missile;
-
-       W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo));
-
-       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);
-
-       missile = spawn();
-       missile.owner = missile.realowner = self;
-       missile.classname = "missile";
-       missile.bot_dodge = true;
-       missile.bot_dodgerating = WEP_CVAR_SEC(hagar, damage);
-
-       missile.takedamage = DAMAGE_YES;
-       missile.health = WEP_CVAR_SEC(hagar, health);
-       missile.damageforcescale = WEP_CVAR_SEC(hagar, damageforcescale);
-       missile.event_damage = W_Hagar_Damage;
-       missile.damagedbycontents = true;
-
-       missile.touch = W_Hagar_Touch2;
-       missile.cnt = 0;
-       missile.use = W_Hagar_Explode2;
-       missile.think = adaptor_think2use_hittype_splash;
-       missile.nextthink = time + WEP_CVAR_SEC(hagar, lifetime_min) + random() * WEP_CVAR_SEC(hagar, lifetime_rand);
-       PROJECTILE_MAKETRIGGER(missile);
-       missile.projectiledeathtype = WEP_HAGAR.m_id | HITTYPE_SECONDARY;
-       setorigin(missile, w_shotorg);
-       setsize(missile, '0 0 0', '0 0 0');
-
-       missile.movetype = MOVETYPE_BOUNCEMISSILE;
-       W_SetupProjVelocity_SEC(missile, hagar);
-
-       missile.angles = vectoangles(missile.velocity);
-       missile.flags = FL_PROJECTILE;
-       missile.missile_flags = MIF_SPLASH;
-
-       CSQCProjectile(missile, true, PROJECTILE_HAGAR_BOUNCING, true);
-
-       MUTATOR_CALLHOOK(EditProjectile, self, missile);
-}
-
-.float hagar_loadstep, hagar_loadblock, hagar_loadbeep, hagar_warning;
-void W_Hagar_Attack2_Load_Release(void)
-{
-       // time to release the rockets we've loaded
-
-       entity missile;
-       float counter, shots, spread_pershot;
-       vector s;
-       vector forward, right, up;
-
-       if(!self.hagar_load)
-               return;
-
-       weapon_prepareattack_do(1, WEP_CVAR_SEC(hagar, refire));
-
-       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;
-       up = v_up;
-
-       shots = self.hagar_load;
-       missile = world;
-       for(counter = 0; counter < shots; ++counter)
-       {
-               missile = spawn();
-               missile.owner = missile.realowner = self;
-               missile.classname = "missile";
-               missile.bot_dodge = true;
-               missile.bot_dodgerating = WEP_CVAR_SEC(hagar, damage);
-
-               missile.takedamage = DAMAGE_YES;
-               missile.health = WEP_CVAR_SEC(hagar, health);
-               missile.damageforcescale = WEP_CVAR_SEC(hagar, damageforcescale);
-               missile.event_damage = W_Hagar_Damage;
-               missile.damagedbycontents = true;
-
-               missile.touch = W_Hagar_Touch; // not bouncy
-               missile.use = W_Hagar_Explode2;
-               missile.think = adaptor_think2use_hittype_splash;
-               missile.nextthink = time + WEP_CVAR_SEC(hagar, lifetime_min) + random() * WEP_CVAR_SEC(hagar, lifetime_rand);
-               PROJECTILE_MAKETRIGGER(missile);
-               missile.projectiledeathtype = WEP_HAGAR.m_id | HITTYPE_SECONDARY;
-               setorigin(missile, w_shotorg);
-               setsize(missile, '0 0 0', '0 0 0');
-               missile.movetype = MOVETYPE_FLY;
-               missile.missile_flags = MIF_SPLASH;
-
-               // per-shot spread calculation: the more shots there are, the less spread is applied (based on the bias cvar)
-               spread_pershot = ((shots - 1) / (WEP_CVAR_SEC(hagar, load_max) - 1));
-               spread_pershot = (1 - (spread_pershot * WEP_CVAR_SEC(hagar, load_spread_bias)));
-               spread_pershot = (WEP_CVAR_SEC(hagar, spread) * spread_pershot * g_weaponspreadfactor);
-
-               // pattern spread calculation
-               s = '0 0 0';
-               if(counter == 0)
-                       s = '0 0 0';
-               else
-               {
-                       makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
-                       s.y = v_forward.x;
-                       s.z = v_forward.y;
-               }
-               s = s * WEP_CVAR_SEC(hagar, load_spread) * g_weaponspreadfactor;
-
-               W_SetupProjVelocity_Explicit(missile, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_SEC(hagar, speed), 0, 0, spread_pershot, false);
-
-               missile.angles = vectoangles(missile.velocity);
-               missile.flags = FL_PROJECTILE;
-
-               CSQCProjectile(missile, true, PROJECTILE_HAGAR, true);
-
-               MUTATOR_CALLHOOK(EditProjectile, self, missile);
-       }
-
-       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hagar, load_animtime), w_ready);
-       self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, refire) * W_WeaponRateFactor();
-       self.hagar_load = 0;
-}
-
-void W_Hagar_Attack2_Load(void)
-{
-       // loadable hagar secondary attack, must always run each frame
-
-       if(time < game_starttime)
-               return;
-
-       bool loaded = self.hagar_load >= WEP_CVAR_SEC(hagar, load_max);
-
-       // this is different than WR_CHECKAMMO when it comes to reloading
-       bool enough_ammo;
-       if(self.items & IT_UNLIMITED_WEAPON_AMMO)
-               enough_ammo = true;
-       else if(autocvar_g_balance_hagar_reload_ammo)
-               enough_ammo = self.(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_SEC(hagar, ammo);
-       else
-               enough_ammo = self.WEP_AMMO(HAGAR) >= WEP_CVAR_SEC(hagar, ammo);
-
-       bool stopped = loaded || !enough_ammo;
-
-       if(self.BUTTON_ATCK2)
-       {
-               if(self.BUTTON_ATCK && WEP_CVAR_SEC(hagar, load_abort))
-               {
-                       if(self.hagar_load)
-                       {
-                               // if we pressed primary fire while loading, unload all rockets and abort
-                               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, 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();
-
-                               // require letting go of the alt fire button before we can load again
-                               self.hagar_loadblock = true;
-                       }
-               }
-               else
-               {
-                       // check if we can attempt to load another rocket
-                       if(!stopped)
-                       {
-                               if(!self.hagar_loadblock && self.hagar_loadstep < time)
-                               {
-                                       W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo));
-                                       self.weaponentity.state = WS_INUSE;
-                                       self.hagar_load += 1;
-                                       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))
-                                               stopped = true;
-                                       else
-                                               self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, load_speed) * W_WeaponRateFactor();
-                               }
-                       }
-                       if(stopped && !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, W_Sound("hagar_beep"), VOL_BASE, ATTN_NORM);
-                               self.hagar_loadbeep = true;
-                               self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, load_hold) * W_WeaponRateFactor();
-                       }
-               }
-       }
-       else if(self.hagar_loadblock)
-       {
-               // the alt fire button has been released, so re-enable loading if blocked
-               self.hagar_loadblock = false;
-       }
-
-       if(self.hagar_load)
-       {
-               // play warning sound if we're about to release
-               if(stopped && self.hagar_loadstep - 0.5 < time && WEP_CVAR_SEC(hagar, load_hold) >= 0)
-               {
-                       if(!self.hagar_warning) // 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, W_Sound("hagar_beep"), VOL_BASE, ATTN_NORM);
-                               self.hagar_warning = true;
-                       }
-               }
-
-               // release if player let go of button or if they've held it in too long
-               if(!self.BUTTON_ATCK2 || (stopped && self.hagar_loadstep < time && WEP_CVAR_SEC(hagar, load_hold) >= 0))
-               {
-                       self.weaponentity.state = WS_READY;
-                       W_Hagar_Attack2_Load_Release();
-               }
-       }
-       else
-       {
-               self.hagar_loadbeep = false;
-               self.hagar_warning = false;
-
-               // we aren't checking ammo during an attack, so we must do it here
-               if(!(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2)))
-               if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
-               {
-                       // note: this doesn't force the switch
-                       W_SwitchToOtherWeapon(self);
-                       return;
-               }
-       }
-}
-
-bool W_Hagar(int req)
-{
-       float ammo_amount;
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       if(random()>0.15)
-                               self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(hagar, speed), 0, WEP_CVAR_PRI(hagar, lifetime), false);
-                       else // not using secondary_speed since these are only 15% and should cause some ricochets without re-aiming
-                               self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_PRI(hagar, speed), 0, WEP_CVAR_PRI(hagar, lifetime), false);
-
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       float loadable_secondary;
-                       loadable_secondary = (WEP_CVAR_SEC(hagar, load) && WEP_CVAR(hagar, secondary));
-
-                       if(loadable_secondary)
-                               W_Hagar_Attack2_Load(); // must always run each frame
-                       if(autocvar_g_balance_hagar_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(hagar, ammo), WEP_CVAR_SEC(hagar, ammo))) // forced reload
-                               WEP_ACTION(self.weapon, WR_RELOAD);
-                       else if(self.BUTTON_ATCK && !self.hagar_load && !self.hagar_loadblock) // not while secondary is loaded or awaiting reset
-                       {
-                               if(weapon_prepareattack(0, WEP_CVAR_PRI(hagar, refire)))
-                               {
-                                       W_Hagar_Attack();
-                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hagar, refire), w_ready);
-                               }
-                       }
-                       else if(self.BUTTON_ATCK2 && !loadable_secondary && WEP_CVAR(hagar, secondary))
-                       {
-                               if(weapon_prepareattack(1, WEP_CVAR_SEC(hagar, refire)))
-                               {
-                                       W_Hagar_Attack2();
-                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hagar, refire), w_ready);
-                               }
-                       }
-                       return true;
-               }
-               case WR_GONETHINK:
-               {
-                       // we lost the weapon and want to prepare switching away
-                       if(self.hagar_load)
-                       {
-                               self.weaponentity.state = WS_READY;
-                               W_Hagar_Attack2_Load_Release();
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       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;
-               }
-               case WR_SETUP:
-               {
-                       self.hagar_loadblock = false;
-
-                       if(self.hagar_load)
-                       {
-                               W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo) * self.hagar_load * -1); // give back ammo if necessary
-                               self.hagar_load = 0;
-                       }
-
-                       return true;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       ammo_amount = self.WEP_AMMO(HAGAR) >= WEP_CVAR_PRI(hagar, ammo);
-                       ammo_amount += self.(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_PRI(hagar, ammo);
-                       return ammo_amount;
-               }
-               case WR_CHECKAMMO2:
-               {
-                       ammo_amount = self.WEP_AMMO(HAGAR) >= WEP_CVAR_SEC(hagar, ammo);
-                       ammo_amount += self.(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_SEC(hagar, ammo);
-                       return ammo_amount;
-               }
-               case WR_CONFIG:
-               {
-                       HAGAR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RESETPLAYER:
-               {
-                       self.hagar_load = 0;
-                       return true;
-               }
-               case WR_PLAYERDEATH:
-               {
-                       // if we have any rockets loaded when we die, release them
-                       if(self.hagar_load && WEP_CVAR_SEC(hagar, load_releasedeath))
-                               W_Hagar_Attack2_Load_Release();
-
-                       return true;
-               }
-               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)), W_Sound("reload"));
-
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_HAGAR_SUICIDE;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                               return WEAPON_HAGAR_MURDER_BURST;
-                       else
-                               return WEAPON_HAGAR_MURDER_SPRAY;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_Hagar(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 6;
-                       pointparticles(particleeffectnum(EFFECT_HAGAR_EXPLODE), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                       {
-                               if(w_random<0.15)
-                                       sound(self, CH_SHOTS, W_Sound("hagexp1"), VOL_BASE, ATTN_NORM);
-                               else if(w_random<0.7)
-                                       sound(self, CH_SHOTS, W_Sound("hagexp2"), VOL_BASE, ATTN_NORM);
-                               else
-                                       sound(self, CH_SHOTS, W_Sound("hagexp3"), VOL_BASE, ATTN_NORM);
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("hagexp1"));
-                       precache_sound(W_Sound("hagexp2"));
-                       precache_sound(W_Sound("hagexp3"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_hlac.qc b/qcsrc/common/weapons/w_hlac.qc
deleted file mode 100644 (file)
index 4968529..0000000
+++ /dev/null
@@ -1,314 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ HLAC,
-/* function  */ W_HLAC,
-/* ammotype  */ ammo_cells,
-/* impulse   */ 6,
-/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* color     */ '0 1 0',
-/* modelname */ "hlac",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairhlac 0.6",
-/* wepimg    */ "weaponhlac",
-/* refname   */ "hlac",
-/* wepname   */ _("Heavy Laser Assault Cannon")
-);
-
-#define HLAC_SETTINGS(w_cvar,w_prop) HLAC_SETTINGS_LIST(w_cvar, w_prop, HLAC, hlac)
-#define HLAC_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, BOTH, ammo) \
-       w_cvar(id, sn, BOTH, animtime) \
-       w_cvar(id, sn, BOTH, damage) \
-       w_cvar(id, sn, BOTH, edgedamage) \
-       w_cvar(id, sn, BOTH, force) \
-       w_cvar(id, sn, BOTH, lifetime) \
-       w_cvar(id, sn, BOTH, radius) \
-       w_cvar(id, sn, BOTH, refire) \
-       w_cvar(id, sn, BOTH, speed) \
-       w_cvar(id, sn, BOTH, spread_crouchmod) \
-       w_cvar(id, sn, PRI,  spread_add) \
-       w_cvar(id, sn, PRI,  spread_max) \
-       w_cvar(id, sn, PRI,  spread_min) \
-       w_cvar(id, sn, NONE, secondary) \
-       w_cvar(id, sn, SEC,  shots) \
-       w_cvar(id, sn, SEC,  spread) \
-       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
-HLAC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_hlac(void) { weapon_defaultspawnfunc(WEP_HLAC.m_id); }
-
-void W_HLAC_Touch(void)
-{
-       float isprimary;
-
-       PROJECTILE_TOUCH;
-
-       self.event_damage = func_null;
-
-       isprimary = !(self.projectiledeathtype & HITTYPE_SECONDARY);
-
-       RadiusDamage(self, self.realowner, WEP_CVAR_BOTH(hlac, isprimary, damage), WEP_CVAR_BOTH(hlac, isprimary, edgedamage), WEP_CVAR_BOTH(hlac, isprimary, radius), world, world, WEP_CVAR_BOTH(hlac, isprimary, force), self.projectiledeathtype, other);
-
-       remove(self);
-}
-
-void W_HLAC_Attack(void)
-{
-       entity missile;
-    float spread;
-
-       W_DecreaseAmmo(WEP_CVAR_PRI(hlac, ammo));
-
-    spread = WEP_CVAR_PRI(hlac, spread_min) + (WEP_CVAR_PRI(hlac, spread_add) * self.misc_bulletcounter);
-    spread = min(spread,WEP_CVAR_PRI(hlac, spread_max));
-    if(self.crouch)
-        spread = spread * WEP_CVAR_PRI(hlac, spread_crouchmod);
-
-       W_SetupShot(self, false, 3, W_Sound("lasergun_fire"), CH_WEAPON_A, WEP_CVAR_PRI(hlac, damage));
-       Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-       if(!autocvar_g_norecoil)
-       {
-               self.punchangle_x = random() - 0.5;
-               self.punchangle_y = random() - 0.5;
-       }
-
-       missile = spawn();
-       missile.owner = missile.realowner = self;
-       missile.classname = "hlacbolt";
-       missile.bot_dodge = true;
-
-    missile.bot_dodgerating = WEP_CVAR_PRI(hlac, damage);
-
-       missile.movetype = MOVETYPE_FLY;
-       PROJECTILE_MAKETRIGGER(missile);
-
-       setorigin(missile, w_shotorg);
-       setsize(missile, '0 0 0', '0 0 0');
-
-       W_SetupProjVelocity_Basic(missile, WEP_CVAR_PRI(hlac, speed), spread);
-       //missile.angles = vectoangles(missile.velocity); // csqc
-
-       missile.touch = W_HLAC_Touch;
-       missile.think = SUB_Remove;
-
-    missile.nextthink = time + WEP_CVAR_PRI(hlac, lifetime);
-
-       missile.flags = FL_PROJECTILE;
-       missile.projectiledeathtype = WEP_HLAC.m_id;
-
-       CSQCProjectile(missile, true, PROJECTILE_HLAC, true);
-
-       MUTATOR_CALLHOOK(EditProjectile, self, missile);
-}
-
-void W_HLAC_Attack2(void)
-{
-       entity missile;
-    float spread;
-
-    spread = WEP_CVAR_SEC(hlac, spread);
-
-
-    if(self.crouch)
-        spread = spread * WEP_CVAR_SEC(hlac, spread_crouchmod);
-
-       W_SetupShot(self, false, 3, W_Sound("lasergun_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hlac, damage));
-       Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       missile = spawn();
-       missile.owner = missile.realowner = self;
-       missile.classname = "hlacbolt";
-       missile.bot_dodge = true;
-
-    missile.bot_dodgerating = WEP_CVAR_SEC(hlac, damage);
-
-       missile.movetype = MOVETYPE_FLY;
-       PROJECTILE_MAKETRIGGER(missile);
-
-       setorigin(missile, w_shotorg);
-       setsize(missile, '0 0 0', '0 0 0');
-
-       W_SetupProjVelocity_Basic(missile, WEP_CVAR_SEC(hlac, speed), spread);
-       //missile.angles = vectoangles(missile.velocity); // csqc
-
-       missile.touch = W_HLAC_Touch;
-       missile.think = SUB_Remove;
-
-    missile.nextthink = time + WEP_CVAR_SEC(hlac, lifetime);
-
-       missile.flags = FL_PROJECTILE;
-       missile.missile_flags = MIF_SPLASH;
-       missile.projectiledeathtype = WEP_HLAC.m_id | HITTYPE_SECONDARY;
-
-       CSQCProjectile(missile, true, PROJECTILE_HLAC, true);
-
-       MUTATOR_CALLHOOK(EditProjectile, self, missile);
-}
-
-// weapon frames
-void W_HLAC_Attack_Frame(void)
-{
-       if(self.weapon != self.switchweapon) // abort immediately if switching
-       {
-               w_ready();
-               return;
-       }
-
-       if(self.BUTTON_ATCK)
-       {
-               if(!WEP_ACTION(self.weapon, WR_CHECKAMMO1))
-               if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
-               {
-                       W_SwitchWeapon_Force(self, w_getbestweapon(self));
-                       w_ready();
-                       return;
-               }
-
-               ATTACK_FINISHED(self) = time + WEP_CVAR_PRI(hlac, refire) * W_WeaponRateFactor();
-               W_HLAC_Attack();
-               self.misc_bulletcounter = self.misc_bulletcounter + 1;
-        weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hlac, refire), W_HLAC_Attack_Frame);
-       }
-       else
-       {
-               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hlac, animtime), w_ready);
-       }
-}
-
-void W_HLAC_Attack2_Frame(void)
-{
-    float i;
-
-       W_DecreaseAmmo(WEP_CVAR_SEC(hlac, ammo));
-
-    for(i=WEP_CVAR_SEC(hlac, shots);i>0;--i)
-        W_HLAC_Attack2();
-
-       if(!autocvar_g_norecoil)
-       {
-               self.punchangle_x = random() - 0.5;
-               self.punchangle_y = random() - 0.5;
-       }
-}
-
-bool W_HLAC(int req)
-{
-       float ammo_amount;
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(hlac, speed), 0, WEP_CVAR_PRI(hlac, lifetime), false);
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       if(autocvar_g_balance_hlac_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(hlac, ammo), WEP_CVAR_SEC(hlac, ammo))) // forced reload
-                               WEP_ACTION(self.weapon, WR_RELOAD);
-                       else if(self.BUTTON_ATCK)
-                       {
-                               if(weapon_prepareattack(0, WEP_CVAR_PRI(hlac, refire)))
-                               {
-                                       self.misc_bulletcounter = 0;
-                                       W_HLAC_Attack();
-                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hlac, refire), W_HLAC_Attack_Frame);
-                               }
-                       }
-
-                       else if(self.BUTTON_ATCK2 && WEP_CVAR(hlac, secondary))
-                       {
-                               if(weapon_prepareattack(1, WEP_CVAR_SEC(hlac, refire)))
-                               {
-                                       W_HLAC_Attack2_Frame();
-                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hlac, animtime), w_ready);
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       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;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       ammo_amount = self.WEP_AMMO(HLAC) >= WEP_CVAR_PRI(hlac, ammo);
-                       ammo_amount += self.(weapon_load[WEP_HLAC.m_id]) >= WEP_CVAR_PRI(hlac, ammo);
-                       return ammo_amount;
-               }
-               case WR_CHECKAMMO2:
-               {
-                       ammo_amount = self.WEP_AMMO(HLAC) >= WEP_CVAR_SEC(hlac, ammo);
-                       ammo_amount += self.(weapon_load[WEP_HLAC.m_id]) >= WEP_CVAR_SEC(hlac, ammo);
-                       return ammo_amount;
-               }
-               case WR_CONFIG:
-               {
-                       HLAC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       W_Reload(min(WEP_CVAR_PRI(hlac, ammo), WEP_CVAR_SEC(hlac, ammo)), W_Sound("reload"));
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_HLAC_SUICIDE;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       return WEAPON_HLAC_MURDER;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_HLAC(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 6;
-                       pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1);
-                       if(!w_issilent)
-                               sound(self, CH_SHOTS, W_Sound("laserimpact"), VOL_BASE, ATTN_NORM);
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("laserimpact"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_hmg.qc b/qcsrc/common/weapons/w_hmg.qc
deleted file mode 100644 (file)
index 52b60e8..0000000
+++ /dev/null
@@ -1,208 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id     */ HMG,
-/* function     */ W_HeavyMachineGun,
-/* ammotype     */ ammo_nails,
-/* impulse      */ 3,
-/* flags        */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_SUPERWEAPON,
-/* rating       */ BOT_PICKUP_RATING_HIGH,
-/* color     */ '0.5 0.5 0',
-/* modelname */ "ok_hmg",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairuzi 0.6",
-/* wepimg    */ "weaponhmg",
-/* refname   */ "hmg",
-/* wepname   */ _("Heavy Machine Gun")
-);
-
-#define HMG_SETTINGS(w_cvar,w_prop) HMG_SETTINGS_LIST(w_cvar, w_prop, HMG, hmg)
-#define HMG_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, NONE, spread_min) \
-       w_cvar(id, sn, NONE, spread_max) \
-       w_cvar(id, sn, NONE, spread_add) \
-       w_cvar(id, sn, NONE, solidpenetration) \
-       w_cvar(id, sn, NONE, damage) \
-       w_cvar(id, sn, NONE, force) \
-       w_cvar(id, sn, NONE, refire) \
-       w_cvar(id, sn, NONE, ammo) \
-       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
-HMG_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-
-void spawnfunc_weapon_hmg() { weapon_defaultspawnfunc(WEP_HMG.m_id); }
-
-void W_HeavyMachineGun_Attack_Auto()
-{
-       if (!self.BUTTON_ATCK)
-       {
-               w_ready();
-               return;
-       }
-
-       if(!WEP_ACTION(self.weapon, WR_CHECKAMMO1))
-       if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
-       {
-               W_SwitchWeapon_Force(self, w_getbestweapon(self));
-               w_ready();
-               return;
-       }
-
-       W_DecreaseAmmo(WEP_CVAR(hmg, ammo));
-
-       W_SetupShot (self, true, 0, W_Sound("uzi_fire"), CH_WEAPON_A, WEP_CVAR(hmg, damage));
-
-       if(!autocvar_g_norecoil)
-       {
-               self.punchangle_x = random () - 0.5;
-               self.punchangle_y = random () - 0.5;
-       }
-
-       float hmg_spread = bound(WEP_CVAR(hmg, spread_min), WEP_CVAR(hmg, spread_min) + (WEP_CVAR(hmg, spread_add) * self.misc_bulletcounter), WEP_CVAR(hmg, spread_max));
-       fireBullet(w_shotorg, w_shotdir, hmg_spread, WEP_CVAR(hmg, solidpenetration), WEP_CVAR(hmg, damage), WEP_CVAR(hmg, force), WEP_HMG.m_id, 0);
-
-       self.misc_bulletcounter = self.misc_bulletcounter + 1;
-
-       Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       W_MachineGun_MuzzleFlash();
-       W_AttachToShotorg(self.muzzle_flash, '5 0 0');
-
-       if (autocvar_g_casings >= 2) // casing code
-               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);
-
-       ATTACK_FINISHED(self) = time + WEP_CVAR(hmg, refire) * W_WeaponRateFactor();
-       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(hmg, refire), W_HeavyMachineGun_Attack_Auto);
-}
-
-bool W_HeavyMachineGun(int req)
-{
-       float ammo_amount;
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       if(vlen(self.origin-self.enemy.origin) < 3000 - bound(0, skill, 10) * 200)
-                               self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, false);
-                       else
-                               self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, false);
-
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       if(WEP_CVAR(hmg, reload_ammo) && self.clip_load < WEP_CVAR(hmg, ammo)) // forced reload
-                               WEP_ACTION(self.weapon, WR_RELOAD);
-                       else
-                       {
-                               if (self.BUTTON_ATCK)
-                               if (weapon_prepareattack(0, 0))
-                               {
-                                       self.misc_bulletcounter = 0;
-                                       W_HeavyMachineGun_Attack_Auto();
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_model ("models/uziflash.md3");
-                       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;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       ammo_amount = self.ammo_nails >= WEP_CVAR(hmg, ammo);
-
-                       if(autocvar_g_balance_hmg_reload_ammo)
-                               ammo_amount += self.(weapon_load[WEP_HMG.m_id]) >= WEP_CVAR(hmg, ammo);
-
-                       return ammo_amount;
-               }
-               case WR_CHECKAMMO2:
-               {
-                       ammo_amount = self.ammo_nails >= WEP_CVAR(hmg, ammo);
-
-                       if(autocvar_g_balance_hmg_reload_ammo)
-                               ammo_amount += self.(weapon_load[WEP_HMG.m_id]) >= WEP_CVAR(hmg, ammo);
-
-                       return ammo_amount;
-               }
-               case WR_CONFIG:
-               {
-                       HMG_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       W_Reload(WEP_CVAR(hmg, ammo), W_Sound("reload"));
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_THINKING_WITH_PORTALS;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                               return WEAPON_HMG_MURDER_SNIPE;
-                       else
-                               return WEAPON_HMG_MURDER_SPRAY;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_HeavyMachineGun(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 2;
-                       pointparticles(particleeffectnum(EFFECT_MACHINEGUN_IMPACT), org2, w_backoff * 1000, 1);
-                       if(!w_issilent)
-                               if(w_random < 0.05)
-                                       sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTEN_NORM);
-                               else if(w_random < 0.1)
-                                       sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTEN_NORM);
-                               else if(w_random < 0.2)
-                                       sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTEN_NORM);
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("ric1"));
-                       precache_sound(W_Sound("ric2"));
-                       precache_sound(W_Sound("ric3"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_hook.qc b/qcsrc/common/weapons/w_hook.qc
deleted file mode 100644 (file)
index 6c5519e..0000000
+++ /dev/null
@@ -1,368 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ HOOK,
-/* function  */ W_Hook,
-/* ammotype  */ ammo_fuel,
-/* impulse   */ 0,
-/* flags     */ WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
-/* rating    */ 0,
-/* color     */ '0 0.5 0',
-/* modelname */ "hookgun",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairhook 0.5",
-/* wepimg    */ "weaponhook",
-/* refname   */ "hook",
-/* wepname   */ _("Grappling Hook")
-);
-
-#define HOOK_SETTINGS(w_cvar,w_prop) HOOK_SETTINGS_LIST(w_cvar, w_prop, HOOK, hook)
-#define HOOK_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, BOTH, animtime) \
-       w_cvar(id, sn, BOTH, refire) \
-       w_cvar(id, sn, PRI,  ammo) \
-       w_cvar(id, sn, PRI,  hooked_ammo) \
-       w_cvar(id, sn, PRI,  hooked_time_free) \
-       w_cvar(id, sn, PRI,  hooked_time_max) \
-       w_cvar(id, sn, SEC,  damage) \
-       w_cvar(id, sn, SEC,  duration) \
-       w_cvar(id, sn, SEC,  edgedamage) \
-       w_cvar(id, sn, SEC,  force) \
-       w_cvar(id, sn, SEC,  gravity) \
-       w_cvar(id, sn, SEC,  lifetime) \
-       w_cvar(id, sn, SEC,  power) \
-       w_cvar(id, sn, SEC,  radius) \
-       w_cvar(id, sn, SEC,  speed) \
-       w_cvar(id, sn, SEC,  health) \
-       w_cvar(id, sn, SEC,  damageforcescale) \
-       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
-HOOK_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-
-.float dmg;
-.float dmg_edge;
-.float dmg_radius;
-.float dmg_force;
-.float dmg_power;
-.float dmg_duration;
-.float dmg_last;
-.float hook_refire;
-.float hook_time_hooked;
-.float hook_time_fueldecrease;
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-
-void spawnfunc_weapon_hook(void)
-{
-       if(g_grappling_hook) // offhand hook
-       {
-               startitem_failed = true;
-               remove(self);
-               return;
-       }
-       weapon_defaultspawnfunc(WEP_HOOK.m_id);
-}
-
-void W_Hook_ExplodeThink(void)
-{
-       float dt, dmg_remaining_next, f;
-
-       dt = time - self.teleport_time;
-       dmg_remaining_next = pow(bound(0, 1 - dt / self.dmg_duration, 1), self.dmg_power);
-
-       f = self.dmg_last - dmg_remaining_next;
-       self.dmg_last = dmg_remaining_next;
-
-       RadiusDamage(self, self.realowner, self.dmg * f, self.dmg_edge * f, self.dmg_radius, self.realowner, world, self.dmg_force * f, self.projectiledeathtype, world);
-       self.projectiledeathtype |= HITTYPE_BOUNCE;
-       //RadiusDamage(self, world, self.dmg * f, self.dmg_edge * f, self.dmg_radius, world, world, self.dmg_force * f, self.projectiledeathtype, world);
-
-       if(dt < self.dmg_duration)
-               self.nextthink = time + 0.05; // soon
-       else
-               remove(self);
-}
-
-void W_Hook_Explode2(void)
-{
-       self.event_damage = func_null;
-       self.touch = func_null;
-       self.effects |= EF_NODRAW;
-
-       self.think = W_Hook_ExplodeThink;
-       self.nextthink = time;
-       self.dmg = WEP_CVAR_SEC(hook, damage);
-       self.dmg_edge = WEP_CVAR_SEC(hook, edgedamage);
-       self.dmg_radius = WEP_CVAR_SEC(hook, radius);
-       self.dmg_force = WEP_CVAR_SEC(hook, force);
-       self.dmg_power = WEP_CVAR_SEC(hook, power);
-       self.dmg_duration = WEP_CVAR_SEC(hook, duration);
-       self.teleport_time = time;
-       self.dmg_last = 1;
-       self.movetype = MOVETYPE_NONE;
-}
-
-void W_Hook_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{
-       if(self.health <= 0)
-               return;
-
-       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
-               return; // g_projectiles_damage says to halt
-
-       self.health = self.health - damage;
-
-       if(self.health <= 0)
-               W_PrepareExplosionByDamage(self.realowner, W_Hook_Explode2);
-}
-
-void W_Hook_Touch2(void)
-{
-       PROJECTILE_TOUCH;
-       self.use();
-}
-
-void W_Hook_Attack2(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, W_Sound("hookbomb_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hook, damage));
-
-       gren = spawn();
-       gren.owner = gren.realowner = self;
-       gren.classname = "hookbomb";
-       gren.bot_dodge = true;
-       gren.bot_dodgerating = WEP_CVAR_SEC(hook, damage);
-       gren.movetype = MOVETYPE_TOSS;
-       PROJECTILE_MAKETRIGGER(gren);
-       gren.projectiledeathtype = WEP_HOOK.m_id | HITTYPE_SECONDARY;
-       setorigin(gren, w_shotorg);
-       setsize(gren, '0 0 0', '0 0 0');
-
-       gren.nextthink = time + WEP_CVAR_SEC(hook, lifetime);
-       gren.think = adaptor_think2use_hittype_splash;
-       gren.use = W_Hook_Explode2;
-       gren.touch = W_Hook_Touch2;
-
-       gren.takedamage = DAMAGE_YES;
-       gren.health = WEP_CVAR_SEC(hook, health);
-       gren.damageforcescale = WEP_CVAR_SEC(hook, damageforcescale);
-       gren.event_damage = W_Hook_Damage;
-       gren.damagedbycontents = true;
-       gren.missile_flags = MIF_SPLASH | MIF_ARC;
-
-       gren.velocity = '0 0 1' * WEP_CVAR_SEC(hook, speed);
-       if(autocvar_g_projectiles_newton_style)
-               gren.velocity = gren.velocity + self.velocity;
-
-       gren.gravity = WEP_CVAR_SEC(hook, gravity);
-       //W_SetupProjVelocity_Basic(gren); // just falling down!
-
-       gren.angles = '0 0 0';
-       gren.flags = FL_PROJECTILE;
-
-       CSQCProjectile(gren, true, PROJECTILE_HOOKBOMB, true);
-
-       MUTATOR_CALLHOOK(EditProjectile, self, gren);
-}
-
-bool W_Hook(int req)
-{
-       float hooked_time_max, hooked_fuel;
-
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       // no bot AI for hook (yet?)
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       if(self.BUTTON_ATCK || self.BUTTON_HOOK)
-                       {
-                               if(!self.hook)
-                               if(!(self.hook_state & HOOK_WAITING_FOR_RELEASE))
-                               if(!(self.hook_state & HOOK_FIRING))
-                               if(time > self.hook_refire)
-                               if(weapon_prepareattack(0, -1))
-                               {
-                                       W_DecreaseAmmo(WEP_CVAR_PRI(hook, ammo));
-                                       self.hook_state |= HOOK_FIRING;
-                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hook, animtime), w_ready);
-                               }
-                       }
-
-                       if(self.BUTTON_ATCK2)
-                       {
-                               if(weapon_prepareattack(1, WEP_CVAR_SEC(hook, refire)))
-                               {
-                                       W_Hook_Attack2();
-                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hook, animtime), w_ready);
-                               }
-                       }
-
-                       if(self.hook)
-                       {
-                               // if hooked, no bombs, and increase the timer
-                               self.hook_refire = max(self.hook_refire, time + WEP_CVAR_PRI(hook, refire) * W_WeaponRateFactor());
-
-                               // hook also inhibits health regeneration, but only for 1 second
-                               if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
-                                       self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
-                       }
-
-                       if(self.hook && self.hook.state == 1)
-                       {
-                               hooked_time_max = WEP_CVAR_PRI(hook, hooked_time_max);
-                               if(hooked_time_max > 0)
-                               {
-                                       if( time > self.hook_time_hooked + hooked_time_max )
-                                               self.hook_state |= HOOK_REMOVING;
-                               }
-
-                               hooked_fuel = WEP_CVAR_PRI(hook, hooked_ammo);
-                               if(hooked_fuel > 0)
-                               {
-                                       if( time > self.hook_time_fueldecrease )
-                                       {
-                                               if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
-                                               {
-                                                       if( self.ammo_fuel >= (time - self.hook_time_fueldecrease) * hooked_fuel )
-                                                       {
-                                                               W_DecreaseAmmo((time - self.hook_time_fueldecrease) * hooked_fuel);
-                                                               self.hook_time_fueldecrease = time;
-                                                               // decrease next frame again
-                                                       }
-                                                       else
-                                                       {
-                                                               self.ammo_fuel = 0;
-                                                               self.hook_state |= HOOK_REMOVING;
-                                                               W_SwitchWeapon_Force(self, w_getbestweapon(self));
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-                       else
-                       {
-                               self.hook_time_hooked = time;
-                               self.hook_time_fueldecrease = time + WEP_CVAR_PRI(hook, hooked_time_free);
-                       }
-
-                       if(self.BUTTON_CROUCH)
-                       {
-                               self.hook_state &= ~HOOK_PULLING;
-                               if(self.BUTTON_ATCK || self.BUTTON_HOOK)
-                                       self.hook_state &= ~HOOK_RELEASING;
-                               else
-                                       self.hook_state |= HOOK_RELEASING;
-                       }
-                       else
-                       {
-                               self.hook_state |= HOOK_PULLING;
-                               self.hook_state &= ~HOOK_RELEASING;
-
-                               if(self.BUTTON_ATCK || self.BUTTON_HOOK)
-                               {
-                                       // already fired
-                                       if(self.hook)
-                                               self.hook_state |= HOOK_WAITING_FOR_RELEASE;
-                               }
-                               else
-                               {
-                                       self.hook_state |= HOOK_REMOVING;
-                                       self.hook_state &= ~HOOK_WAITING_FOR_RELEASE;
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       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;
-               }
-               case WR_SETUP:
-               {
-                       self.hook_state &= ~HOOK_WAITING_FOR_RELEASE;
-                       return true;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       if(self.hook)
-                               return self.ammo_fuel > 0;
-                       else
-                               return self.ammo_fuel >= WEP_CVAR_PRI(hook, ammo);
-               }
-               case WR_CHECKAMMO2:
-               {
-                       // infinite ammo for now
-                       return true; // self.ammo_cells >= WEP_CVAR_SEC(hook, ammo); // WEAPONTODO: see above
-               }
-               case WR_CONFIG:
-               {
-                       HOOK_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RESETPLAYER:
-               {
-                       self.hook_refire = time;
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       return false;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       return WEAPON_HOOK_MURDER;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_Hook(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 2;
-                       pointparticles(particleeffectnum(EFFECT_HOOK_EXPLODE), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                               sound(self, CH_SHOTS, W_Sound("hookbomb_impact"), VOL_BASE, ATTN_NORM);
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("hookbomb_impact"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_machinegun.qc b/qcsrc/common/weapons/w_machinegun.qc
deleted file mode 100644 (file)
index da1eb33..0000000
+++ /dev/null
@@ -1,408 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ MACHINEGUN,
-/* function  */ W_MachineGun,
-/* ammotype  */ ammo_nails,
-/* impulse   */ 3,
-/* flags     */ WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* color     */ '1 1 0',
-/* modelname */ "uzi",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairuzi 0.6",
-/* wepimg    */ "weaponuzi",
-/* refname   */ "machinegun",
-/* wepname   */ _("Machine Gun")
-);
-
-#define MACHINEGUN_SETTINGS(w_cvar,w_prop) MACHINEGUN_SETTINGS_LIST(w_cvar, w_prop, MACHINEGUN, machinegun)
-#define MACHINEGUN_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, NONE, spread_min) \
-       w_cvar(id, sn, NONE, spread_max) \
-       w_cvar(id, sn, NONE, spread_add) \
-       w_cvar(id, sn, NONE, mode) \
-       w_cvar(id, sn, NONE, first) \
-       w_cvar(id, sn, NONE, first_damage) \
-       w_cvar(id, sn, NONE, first_force) \
-       w_cvar(id, sn, NONE, first_refire) \
-       w_cvar(id, sn, NONE, first_spread) \
-       w_cvar(id, sn, NONE, first_ammo) \
-       w_cvar(id, sn, NONE, solidpenetration) \
-       w_cvar(id, sn, NONE, sustained_damage) \
-       w_cvar(id, sn, NONE, sustained_force) \
-       w_cvar(id, sn, NONE, sustained_refire) \
-       w_cvar(id, sn, NONE, sustained_spread) \
-       w_cvar(id, sn, NONE, sustained_ammo) \
-       w_cvar(id, sn, NONE, burst) \
-       w_cvar(id, sn, NONE, burst_refire) \
-       w_cvar(id, sn, NONE, burst_refire2) \
-       w_cvar(id, sn, NONE, burst_animtime) \
-       w_cvar(id, sn, NONE, burst_speed) \
-       w_cvar(id, sn, NONE, burst_ammo) \
-       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
-MACHINEGUN_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-
-void spawnfunc_weapon_machinegun(void)
-{
-       if(autocvar_sv_q3acompat_machineshotgunswap)
-       if(self.classname != "droppedweapon")
-       {
-               weapon_defaultspawnfunc(WEP_SHOCKWAVE.m_id);
-               return;
-       }
-       weapon_defaultspawnfunc(WEP_MACHINEGUN.m_id);
-}
-void spawnfunc_weapon_uzi(void) { spawnfunc_weapon_machinegun(); }
-
-void W_MachineGun_MuzzleFlash_Think(void)
-{
-       self.frame = self.frame + 2;
-       self.scale = self.scale * 0.5;
-       self.alpha = self.alpha - 0.25;
-       self.nextthink = time + 0.05;
-
-       if(self.alpha <= 0)
-       {
-               self.think = SUB_Remove;
-               self.nextthink = time;
-               self.realowner.muzzle_flash = world;
-               return;
-       }
-
-}
-
-void W_MachineGun_MuzzleFlash(void)
-{
-       if(self.muzzle_flash == world)
-               self.muzzle_flash = spawn();
-
-       // muzzle flash for 1st person view
-       setmodel(self.muzzle_flash, "models/uziflash.md3"); // precision set below
-
-       self.muzzle_flash.scale = 0.75;
-       self.muzzle_flash.think = W_MachineGun_MuzzleFlash_Think;
-       self.muzzle_flash.nextthink = time + 0.02;
-       self.muzzle_flash.frame = 2;
-       self.muzzle_flash.alpha = 0.75;
-       self.muzzle_flash.angles_z = random() * 180;
-       self.muzzle_flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
-       self.muzzle_flash.owner = self.muzzle_flash.realowner = self;
-}
-
-void W_MachineGun_Attack(int deathtype)
-{
-       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;
-               self.punchangle_y = random() - 0.5;
-       }
-
-       // this attack_finished just enforces a cooldown at the end of a burst
-       ATTACK_FINISHED(self) = time + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor();
-
-       if(self.misc_bulletcounter == 1)
-               fireBullet(w_shotorg, w_shotdir, WEP_CVAR(machinegun, first_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, first_damage), WEP_CVAR(machinegun, first_force), deathtype, 0);
-       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);
-
-       Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       W_MachineGun_MuzzleFlash();
-       W_AttachToShotorg(self.muzzle_flash, '5 0 0');
-
-       // 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);
-
-       if(self.misc_bulletcounter == 1)
-               W_DecreaseAmmo(WEP_CVAR(machinegun, first_ammo));
-       else
-               W_DecreaseAmmo(WEP_CVAR(machinegun, sustained_ammo));
-}
-
-// weapon frames
-void W_MachineGun_Attack_Frame(void)
-{
-       if(self.weapon != self.switchweapon) // abort immediately if switching
-       {
-               w_ready();
-               return;
-       }
-       if(self.BUTTON_ATCK)
-       {
-               if(!WEP_ACTION(self.weapon, WR_CHECKAMMO2))
-               if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
-               {
-                       W_SwitchWeapon_Force(self, w_getbestweapon(self));
-                       w_ready();
-                       return;
-               }
-               self.misc_bulletcounter = self.misc_bulletcounter + 1;
-               W_MachineGun_Attack(WEP_MACHINEGUN.m_id);
-               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Frame);
-       }
-       else
-               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), w_ready);
-}
-
-
-void W_MachineGun_Attack_Auto(void)
-{
-       float machinegun_spread;
-
-       if(!self.BUTTON_ATCK)
-       {
-               w_ready();
-               return;
-       }
-
-       if(!WEP_ACTION(self.weapon, WR_CHECKAMMO1))
-       if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
-       {
-               W_SwitchWeapon_Force(self, w_getbestweapon(self));
-               w_ready();
-               return;
-       }
-
-       W_DecreaseAmmo(WEP_CVAR(machinegun, sustained_ammo));
-
-       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;
-               self.punchangle_y = random() - 0.5;
-       }
-
-       machinegun_spread = bound(WEP_CVAR(machinegun, spread_min), WEP_CVAR(machinegun, spread_min) + (WEP_CVAR(machinegun, spread_add) * self.misc_bulletcounter), WEP_CVAR(machinegun, spread_max));
-       fireBullet(w_shotorg, w_shotdir, machinegun_spread, WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), WEP_MACHINEGUN.m_id, 0);
-
-       self.misc_bulletcounter = self.misc_bulletcounter + 1;
-
-       Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       W_MachineGun_MuzzleFlash();
-       W_AttachToShotorg(self.muzzle_flash, '5 0 0');
-
-       if(autocvar_g_casings >= 2) // casing code
-               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);
-
-       ATTACK_FINISHED(self) = time + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor();
-       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Auto);
-}
-
-void W_MachineGun_Attack_Burst(void)
-{
-       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;
-               self.punchangle_y = random() - 0.5;
-       }
-
-       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.m_id, 0);
-
-       Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       W_MachineGun_MuzzleFlash();
-       W_AttachToShotorg(self.muzzle_flash, '5 0 0');
-
-       if(autocvar_g_casings >= 2) // casing code
-               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);
-
-       self.misc_bulletcounter = self.misc_bulletcounter + 1;
-       if(self.misc_bulletcounter == 0)
-       {
-               ATTACK_FINISHED(self) = time + WEP_CVAR(machinegun, burst_refire2) * W_WeaponRateFactor();
-               weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(machinegun, burst_animtime), w_ready);
-       }
-       else
-       {
-               weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(machinegun, burst_refire), W_MachineGun_Attack_Burst);
-       }
-
-}
-
-bool W_MachineGun(int req)
-{
-       float ammo_amount;
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       if(vlen(self.origin-self.enemy.origin) < 3000 - bound(0, skill, 10) * 200)
-                               self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, false);
-                       else
-                               self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, false);
-
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       if(WEP_CVAR(machinegun, reload_ammo) && self.clip_load < min(max(WEP_CVAR(machinegun, sustained_ammo), WEP_CVAR(machinegun, first_ammo)), WEP_CVAR(machinegun, burst_ammo))) // forced reload
-                               WEP_ACTION(self.weapon, WR_RELOAD);
-                       else if(WEP_CVAR(machinegun, mode) == 1)
-                       {
-                               if(self.BUTTON_ATCK)
-                               if(weapon_prepareattack(0, 0))
-                               {
-                                       self.misc_bulletcounter = 0;
-                                       W_MachineGun_Attack_Auto();
-                               }
-
-                               if(self.BUTTON_ATCK2)
-                               if(weapon_prepareattack(1, 0))
-                               {
-                                       if(!WEP_ACTION(self.weapon, WR_CHECKAMMO2))
-                                       if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
-                                       {
-                                               W_SwitchWeapon_Force(self, w_getbestweapon(self));
-                                               w_ready();
-                                               return false;
-                                       }
-
-                                       W_DecreaseAmmo(WEP_CVAR(machinegun, burst_ammo));
-
-                                       self.misc_bulletcounter = WEP_CVAR(machinegun, burst) * -1;
-                                       W_MachineGun_Attack_Burst();
-                               }
-                       }
-                       else
-                       {
-
-                               if(self.BUTTON_ATCK)
-                               if(weapon_prepareattack(0, 0))
-                               {
-                                       self.misc_bulletcounter = 1;
-                                       W_MachineGun_Attack(WEP_MACHINEGUN.m_id); // sets attack_finished
-                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Frame);
-                               }
-
-                               if(self.BUTTON_ATCK2 && WEP_CVAR(machinegun, first))
-                               if(weapon_prepareattack(1, 0))
-                               {
-                                       self.misc_bulletcounter = 1;
-                                       W_MachineGun_Attack(WEP_MACHINEGUN.m_id | HITTYPE_SECONDARY); // sets attack_finished
-                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(machinegun, first_refire), w_ready);
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_model("models/uziflash.md3");
-                       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;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       if(WEP_CVAR(machinegun, mode) == 1)
-                               ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, sustained_ammo);
-                       else
-                               ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, first_ammo);
-
-                       if(WEP_CVAR(machinegun, reload_ammo))
-                       {
-                               if(WEP_CVAR(machinegun, mode) == 1)
-                                       ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, sustained_ammo);
-                               else
-                                       ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, first_ammo);
-                       }
-                       return ammo_amount;
-               }
-               case WR_CHECKAMMO2:
-               {
-                       if(WEP_CVAR(machinegun, mode) == 1)
-                               ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, burst_ammo);
-                       else
-                               ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, first_ammo);
-
-                       if(WEP_CVAR(machinegun, reload_ammo))
-                       {
-                               if(WEP_CVAR(machinegun, mode) == 1)
-                                       ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, burst_ammo);
-                               else
-                                       ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, first_ammo);
-                       }
-                       return ammo_amount;
-               }
-               case WR_CONFIG:
-               {
-                       MACHINEGUN_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       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:
-               {
-                       return WEAPON_THINKING_WITH_PORTALS;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                               return WEAPON_MACHINEGUN_MURDER_SNIPE;
-                       else
-                               return WEAPON_MACHINEGUN_MURDER_SPRAY;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_MachineGun(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 2;
-                       pointparticles(particleeffectnum(EFFECT_MACHINEGUN_IMPACT), org2, w_backoff * 1000, 1);
-                       if(!w_issilent)
-                               if(w_random < 0.05)
-                                       sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTN_NORM);
-                               else if(w_random < 0.1)
-                                       sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTN_NORM);
-                               else if(w_random < 0.2)
-                                       sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTN_NORM);
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("ric1"));
-                       precache_sound(W_Sound("ric2"));
-                       precache_sound(W_Sound("ric3"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_minelayer.qc b/qcsrc/common/weapons/w_minelayer.qc
deleted file mode 100644 (file)
index 8048957..0000000
+++ /dev/null
@@ -1,623 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ MINE_LAYER,
-/* function  */ W_MineLayer,
-/* ammotype  */ ammo_rockets,
-/* impulse   */ 4,
-/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_HIGH,
-/* color     */ '0.75 1 0',
-/* modelname */ "minelayer",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairminelayer 0.9",
-/* wepimg    */ "weaponminelayer",
-/* refname   */ "minelayer",
-/* wepname   */ _("Mine Layer")
-);
-
-#define MINELAYER_SETTINGS(w_cvar,w_prop) MINELAYER_SETTINGS_LIST(w_cvar, w_prop, MINE_LAYER, minelayer)
-#define MINELAYER_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, damage) \
-       w_cvar(id, sn, NONE, damageforcescale) \
-       w_cvar(id, sn, NONE, detonatedelay) \
-       w_cvar(id, sn, NONE, edgedamage) \
-       w_cvar(id, sn, NONE, force) \
-       w_cvar(id, sn, NONE, health) \
-       w_cvar(id, sn, NONE, lifetime) \
-       w_cvar(id, sn, NONE, lifetime_countdown) \
-       w_cvar(id, sn, NONE, limit) \
-       w_cvar(id, sn, NONE, protection) \
-       w_cvar(id, sn, NONE, proximityradius) \
-       w_cvar(id, sn, NONE, radius) \
-       w_cvar(id, sn, NONE, refire) \
-       w_cvar(id, sn, NONE, remote_damage) \
-       w_cvar(id, sn, NONE, remote_edgedamage) \
-       w_cvar(id, sn, NONE, remote_force) \
-       w_cvar(id, sn, NONE, remote_radius) \
-       w_cvar(id, sn, NONE, speed) \
-       w_cvar(id, sn, NONE, time) \
-       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
-MINELAYER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-void W_MineLayer_Think(void);
-.float minelayer_detonate, mine_explodeanyway;
-.float mine_time;
-.vector mine_orientation;
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_minelayer(void) { weapon_defaultspawnfunc(WEP_MINE_LAYER.m_id); }
-
-void W_MineLayer_Stick(entity to)
-{
-       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
-
-       entity newmine;
-       newmine = spawn();
-       newmine.classname = self.classname;
-
-       newmine.bot_dodge = self.bot_dodge;
-       newmine.bot_dodgerating = self.bot_dodgerating;
-
-       newmine.owner = self.owner;
-       newmine.realowner = self.realowner;
-       setsize(newmine, '-4 -4 -4', '4 4 4');
-       setorigin(newmine, self.origin);
-       setmodel(newmine, "models/mine.md3");
-       newmine.angles = vectoangles(-trace_plane_normal); // face against the surface
-
-       newmine.mine_orientation = -trace_plane_normal;
-
-       newmine.takedamage = self.takedamage;
-       newmine.damageforcescale = self.damageforcescale;
-       newmine.health = self.health;
-       newmine.event_damage = self.event_damage;
-       newmine.spawnshieldtime = self.spawnshieldtime;
-       newmine.damagedbycontents = true;
-
-       newmine.movetype = MOVETYPE_NONE; // lock the mine in place
-       newmine.projectiledeathtype = self.projectiledeathtype;
-
-       newmine.mine_time = self.mine_time;
-
-       newmine.touch = func_null;
-       newmine.think = W_MineLayer_Think;
-       newmine.nextthink = time;
-       newmine.cnt = self.cnt;
-       newmine.flags = self.flags;
-
-       remove(self);
-       self = newmine;
-
-       if(to)
-               SetMovetypeFollow(self, to);
-}
-
-void W_MineLayer_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_AIRSHOT);
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       RadiusDamage(self, self.realowner, WEP_CVAR(minelayer, damage), WEP_CVAR(minelayer, edgedamage), WEP_CVAR(minelayer, radius), world, world, WEP_CVAR(minelayer, force), self.projectiledeathtype, other);
-
-       if(self.realowner.weapon == WEP_MINE_LAYER.m_id)
-       {
-               entity oldself;
-               oldself = self;
-               self = self.realowner;
-               if(!WEP_ACTION(WEP_MINE_LAYER.m_id, WR_CHECKAMMO1))
-               {
-                       self.cnt = WEP_MINE_LAYER.m_id;
-                       ATTACK_FINISHED(self) = time;
-                       self.switchweapon = w_getbestweapon(self);
-               }
-               self = oldself;
-       }
-       self.realowner.minelayer_mines -= 1;
-       remove(self);
-}
-
-void W_MineLayer_DoRemoteExplode(void)
-{
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW)
-               self.velocity = self.mine_orientation; // particle fx and decals need .velocity
-
-       RadiusDamage(self, self.realowner, WEP_CVAR(minelayer, remote_damage), WEP_CVAR(minelayer, remote_edgedamage), WEP_CVAR(minelayer, remote_radius), world, world, WEP_CVAR(minelayer, remote_force), self.projectiledeathtype | HITTYPE_BOUNCE, world);
-
-       if(self.realowner.weapon == WEP_MINE_LAYER.m_id)
-       {
-               entity oldself;
-               oldself = self;
-               self = self.realowner;
-               if(!WEP_ACTION(WEP_MINE_LAYER.m_id, WR_CHECKAMMO1))
-               {
-                       self.cnt = WEP_MINE_LAYER.m_id;
-                       ATTACK_FINISHED(self) = time;
-                       self.switchweapon = w_getbestweapon(self);
-               }
-               self = oldself;
-       }
-       self.realowner.minelayer_mines -= 1;
-       remove(self);
-}
-
-void W_MineLayer_RemoteExplode(void)
-{
-       if(self.realowner.deadflag == DEAD_NO)
-               if((self.spawnshieldtime >= 0)
-                       ? (time >= self.spawnshieldtime) // timer
-                       : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > WEP_CVAR(minelayer, remote_radius)) // safety device
-               )
-               {
-                       W_MineLayer_DoRemoteExplode();
-               }
-}
-
-void W_MineLayer_ProximityExplode(void)
-{
-       // make sure no friend is in the mine's radius. If there is any, explosion is delayed until he's at a safe distance
-       if(WEP_CVAR(minelayer, protection) && self.mine_explodeanyway == 0)
-       {
-               entity head;
-               head = findradius(self.origin, WEP_CVAR(minelayer, radius));
-               while(head)
-               {
-                       if(head == self.realowner || SAME_TEAM(head, self.realowner))
-                               return;
-                       head = head.chain;
-               }
-       }
-
-       self.mine_time = 0;
-       W_MineLayer_Explode();
-}
-
-int W_MineLayer_Count(entity e)
-{
-       int minecount = 0;
-       entity mine;
-       for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == e)
-               minecount += 1;
-
-       return minecount;
-}
-
-void W_MineLayer_Think(void)
-{
-       entity head;
-
-       self.nextthink = time;
-
-       if(self.movetype == MOVETYPE_FOLLOW)
-       {
-               if(LostMovetypeFollow(self))
-               {
-                       UnsetMovetypeFollow(self);
-                       self.movetype = MOVETYPE_NONE;
-               }
-       }
-
-       // our lifetime has expired, it's time to die - mine_time just allows us to play a sound for this
-       // TODO: replace this mine_trigger.wav sound with a real countdown
-       if((time > self.cnt) && (!self.mine_time) && (self.cnt > 0))
-       {
-               if(WEP_CVAR(minelayer, lifetime_countdown) > 0)
-                       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)
-       {
-               other = world;
-               self.projectiledeathtype |= HITTYPE_BOUNCE;
-               W_MineLayer_Explode();
-               return;
-       }
-
-       // set the mine for detonation when a foe gets close enough
-       head = findradius(self.origin, WEP_CVAR(minelayer, proximityradius));
-       while(head)
-       {
-               if(IS_PLAYER(head) && head.deadflag == DEAD_NO && !head.frozen)
-               if(head != self.realowner && DIFF_TEAM(head, self.realowner)) // don't trigger for team mates
-               if(!self.mine_time)
-               {
-                       spamsound(self, CH_SHOTS, W_Sound("mine_trigger"), VOL_BASE, ATTN_NORM);
-                       self.mine_time = time + WEP_CVAR(minelayer, time);
-               }
-               head = head.chain;
-       }
-
-       // explode if it's time to
-       if(self.mine_time && time >= self.mine_time)
-       {
-               W_MineLayer_ProximityExplode();
-               return;
-       }
-
-       // remote detonation
-       if(self.realowner.weapon == WEP_MINE_LAYER.m_id)
-       if(self.realowner.deadflag == DEAD_NO)
-       if(self.minelayer_detonate)
-               W_MineLayer_RemoteExplode();
-}
-
-void W_MineLayer_Touch(void)
-{
-       if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW)
-               return; // we're already a stuck mine, why do we get called? TODO does this even happen?
-
-       if(WarpZone_Projectile_Touch())
-       {
-               if(wasfreed(self))
-                       self.realowner.minelayer_mines -= 1;
-               return;
-       }
-
-       if(other && IS_PLAYER(other) && other.deadflag == DEAD_NO)
-       {
-               // hit a player
-               // don't stick
-       }
-       else
-       {
-               W_MineLayer_Stick(other);
-       }
-}
-
-void W_MineLayer_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{
-       if(self.health <= 0)
-               return;
-
-       float is_from_enemy = (inflictor.realowner != self.realowner);
-
-       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_from_enemy ? 1 : -1)))
-               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_MineLayer_Explode);
-}
-
-void W_MineLayer_Attack(void)
-{
-       entity mine;
-       entity flash;
-
-       // scan how many mines we placed, and return if we reached our limit
-       if(WEP_CVAR(minelayer, limit))
-       {
-               if(self.minelayer_mines >= WEP_CVAR(minelayer, limit))
-               {
-                       // 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, W_Sound("unavailable"));
-                       return;
-               }
-       }
-
-       W_DecreaseAmmo(WEP_CVAR(minelayer, ammo));
-
-       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;
-       if(WEP_CVAR(minelayer, detonatedelay) >= 0)
-               mine.spawnshieldtime = time + WEP_CVAR(minelayer, detonatedelay);
-       else
-               mine.spawnshieldtime = -1;
-       mine.classname = "mine";
-       mine.bot_dodge = true;
-       mine.bot_dodgerating = WEP_CVAR(minelayer, damage) * 2; // * 2 because it can detonate inflight which makes it even more dangerous
-
-       mine.takedamage = DAMAGE_YES;
-       mine.damageforcescale = WEP_CVAR(minelayer, damageforcescale);
-       mine.health = WEP_CVAR(minelayer, health);
-       mine.event_damage = W_MineLayer_Damage;
-       mine.damagedbycontents = true;
-
-       mine.movetype = MOVETYPE_TOSS;
-       PROJECTILE_MAKETRIGGER(mine);
-       mine.projectiledeathtype = WEP_MINE_LAYER.m_id;
-       setsize(mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot
-
-       setorigin(mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point
-       W_SetupProjVelocity_Basic(mine, WEP_CVAR(minelayer, speed), 0);
-       mine.angles = vectoangles(mine.velocity);
-
-       mine.touch = W_MineLayer_Touch;
-       mine.think = W_MineLayer_Think;
-       mine.nextthink = time;
-       mine.cnt = (WEP_CVAR(minelayer, lifetime) - WEP_CVAR(minelayer, lifetime_countdown));
-       mine.flags = FL_PROJECTILE;
-       mine.missile_flags = MIF_SPLASH | MIF_ARC | MIF_PROXY;
-
-       if(mine.cnt > 0) { mine.cnt += time; }
-
-       CSQCProjectile(mine, true, PROJECTILE_MINE, true);
-
-       // muzzle flash for 1st person view
-       flash = spawn();
-       setmodel(flash, "models/flash.md3"); // precision set below
-       SUB_SetFade(flash, time, 0.1);
-       flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
-       W_AttachToShotorg(flash, '5 0 0');
-
-       // common properties
-
-       MUTATOR_CALLHOOK(EditProjectile, self, mine);
-
-       self.minelayer_mines = W_MineLayer_Count(self);
-}
-
-float W_MineLayer_PlacedMines(float detonate)
-{
-       entity mine;
-       float minfound = 0;
-
-       for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == self)
-       {
-               if(detonate)
-               {
-                       if(!mine.minelayer_detonate)
-                       {
-                               mine.minelayer_detonate = true;
-                               minfound = 1;
-                       }
-               }
-               else
-                       minfound = 1;
-       }
-       return minfound;
-}
-
-bool W_MineLayer(int req)
-{
-       entity mine;
-       float ammo_amount;
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       // aim and decide to fire if appropriate
-                       if(self.minelayer_mines >= WEP_CVAR(minelayer, limit))
-                               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!
-                       {
-                               // decide whether to detonate mines
-                               entity targetlist, targ;
-                               float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
-                               float selfdamage, teamdamage, enemydamage;
-                               edgedamage = WEP_CVAR(minelayer, edgedamage);
-                               coredamage = WEP_CVAR(minelayer, damage);
-                               edgeradius = WEP_CVAR(minelayer, radius);
-                               recipricoledgeradius = 1 / edgeradius;
-                               selfdamage = 0;
-                               teamdamage = 0;
-                               enemydamage = 0;
-                               targetlist = findchainfloat(bot_attack, true);
-                               mine = find(world, classname, "mine");
-                               while(mine)
-                               {
-                                       if(mine.realowner != self)
-                                       {
-                                               mine = find(mine, classname, "mine");
-                                               continue;
-                                       }
-                                       targ = targetlist;
-                                       while(targ)
-                                       {
-                                               d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin);
-                                               d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
-                                               // count potential damage according to type of target
-                                               if(targ == self)
-                                                       selfdamage = selfdamage + d;
-                                               else if(targ.team == self.team && teamplay)
-                                                       teamdamage = teamdamage + d;
-                                               else if(bot_shouldattack(targ))
-                                                       enemydamage = enemydamage + d;
-                                               targ = targ.chain;
-                                       }
-                                       mine = find(mine, classname, "mine");
-                               }
-                               float desirabledamage;
-                               desirabledamage = enemydamage;
-                               if(time > self.invincible_finished && time > self.spawnshieldtime)
-                                       desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent;
-                               if(teamplay && self.team)
-                                       desirabledamage = desirabledamage - teamdamage;
-
-                               mine = find(world, classname, "mine");
-                               while(mine)
-                               {
-                                       if(mine.realowner != self)
-                                       {
-                                               mine = find(mine, classname, "mine");
-                                               continue;
-                                       }
-                                       makevectors(mine.v_angle);
-                                       targ = targetlist;
-                                       if(skill > 9) // normal players only do this for the target they are tracking
-                                       {
-                                               targ = targetlist;
-                                               while(targ)
-                                               {
-                                                       if(
-                                                               (v_forward * normalize(mine.origin - targ.origin)< 0.1)
-                                                               && desirabledamage > 0.1*coredamage
-                                                       )self.BUTTON_ATCK2 = true;
-                                                       targ = targ.chain;
-                                               }
-                                       }else{
-                                               float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
-                                               //As the distance gets larger, a correct detonation gets near imposible
-                                               //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player
-                                               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))
-                                                                               self.BUTTON_ATCK2 = true;
-                                       //      dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
-                                       }
-
-                                       mine = find(mine, classname, "mine");
-                               }
-                               // if we would be doing at X percent of the core damage, detonate it
-                               // 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))
-                                       self.BUTTON_ATCK2 = false;
-                               //if(self.BUTTON_ATCK2 == true)
-                               //      dprint(ftos(desirabledamage),"\n");
-                               if(self.BUTTON_ATCK2 == true) self.BUTTON_ATCK = false;
-                       }
-
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       if(autocvar_g_balance_minelayer_reload_ammo && self.clip_load < WEP_CVAR(minelayer, ammo)) // forced reload
-                       {
-                               // not if we're holding the minelayer without enough ammo, but can detonate existing mines
-                               if(!(W_MineLayer_PlacedMines(false) && self.WEP_AMMO(MINE_LAYER) < WEP_CVAR(minelayer, ammo)))
-                                       WEP_ACTION(self.weapon, WR_RELOAD);
-                       }
-                       else if(self.BUTTON_ATCK)
-                       {
-                               if(weapon_prepareattack(0, WEP_CVAR(minelayer, refire)))
-                               {
-                                       W_MineLayer_Attack();
-                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(minelayer, animtime), w_ready);
-                               }
-                       }
-
-                       if(self.BUTTON_ATCK2)
-                       {
-                               if(W_MineLayer_PlacedMines(true))
-                                       sound(self, CH_WEAPON_B, W_Sound("mine_det"), VOL_BASE, ATTN_NORM);
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_model("models/flash.md3");
-                       precache_model("models/mine.md3");
-                       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;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       // don't switch while placing a mine
-                       if(ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER.m_id)
-                       {
-                               ammo_amount = self.WEP_AMMO(MINE_LAYER) >= WEP_CVAR(minelayer, ammo);
-                               ammo_amount += self.(weapon_load[WEP_MINE_LAYER.m_id]) >= WEP_CVAR(minelayer, ammo);
-                               return ammo_amount;
-                       }
-                       return true;
-               }
-               case WR_CHECKAMMO2:
-               {
-                       if(W_MineLayer_PlacedMines(false))
-                               return true;
-                       else
-                               return false;
-               }
-               case WR_CONFIG:
-               {
-                       MINELAYER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RESETPLAYER:
-               {
-                       self.minelayer_mines = 0;
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       W_Reload(WEP_CVAR(minelayer, ammo), W_Sound("reload"));
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_MINELAYER_SUICIDE;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       return WEAPON_MINELAYER_MURDER;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_MineLayer(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 12;
-                       pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                               sound(self, CH_SHOTS, W_Sound("mine_exp"), VOL_BASE, ATTN_NORM);
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("mine_exp"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_mortar.qc b/qcsrc/common/weapons/w_mortar.qc
deleted file mode 100644 (file)
index 850a6c3..0000000
+++ /dev/null
@@ -1,491 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ MORTAR,
-/* function  */ W_Mortar,
-/* ammotype  */ ammo_rockets,
-/* impulse   */ 4,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* color     */ '1 0 0',
-/* modelname */ "gl",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairgrenadelauncher 0.7",
-/* wepimg    */ "weapongrenadelauncher",
-/* refname   */ "mortar",
-/* wepname   */ _("Mortar")
-);
-
-#define MORTAR_SETTINGS(w_cvar,w_prop) MORTAR_SETTINGS_LIST(w_cvar, w_prop, MORTAR, mortar)
-#define MORTAR_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, BOTH, ammo) \
-       w_cvar(id, sn, BOTH, animtime) \
-       w_cvar(id, sn, NONE, bouncefactor) \
-       w_cvar(id, sn, NONE, bouncestop) \
-       w_cvar(id, sn, BOTH, damage) \
-       w_cvar(id, sn, BOTH, damageforcescale) \
-       w_cvar(id, sn, BOTH, edgedamage) \
-       w_cvar(id, sn, BOTH, force) \
-       w_cvar(id, sn, BOTH, health) \
-       w_cvar(id, sn, BOTH, lifetime) \
-       w_cvar(id, sn, SEC,  lifetime_bounce) \
-       w_cvar(id, sn, BOTH, lifetime_stick) \
-       w_cvar(id, sn, BOTH, radius) \
-       w_cvar(id, sn, BOTH, refire) \
-       w_cvar(id, sn, SEC,  remote_detonateprimary) \
-       w_cvar(id, sn, PRI,  remote_minbouncecnt) \
-       w_cvar(id, sn, BOTH, speed) \
-       w_cvar(id, sn, BOTH, speed_up) \
-       w_cvar(id, sn, BOTH, speed_z) \
-       w_cvar(id, sn, BOTH, spread) \
-       w_cvar(id, sn, BOTH, type) \
-       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
-MORTAR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-.float gl_detonate_later;
-.float gl_bouncecnt;
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-
-void spawnfunc_weapon_mortar(void) { weapon_defaultspawnfunc(WEP_MORTAR.m_id); }
-void spawnfunc_weapon_grenadelauncher(void) { spawnfunc_weapon_mortar(); }
-
-void W_Mortar_Grenade_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_AIRSHOT);
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       if(self.movetype == MOVETYPE_NONE)
-               self.velocity = self.oldvelocity;
-
-       RadiusDamage(self, self.realowner, WEP_CVAR_PRI(mortar, damage), WEP_CVAR_PRI(mortar, edgedamage), WEP_CVAR_PRI(mortar, radius), world, world, WEP_CVAR_PRI(mortar, force), self.projectiledeathtype, other);
-
-       remove(self);
-}
-
-void W_Mortar_Grenade_Explode2(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_AIRSHOT);
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       if(self.movetype == MOVETYPE_NONE)
-               self.velocity = self.oldvelocity;
-
-       RadiusDamage(self, self.realowner, WEP_CVAR_SEC(mortar, damage), WEP_CVAR_SEC(mortar, edgedamage), WEP_CVAR_SEC(mortar, radius), world, world, WEP_CVAR_SEC(mortar, force), self.projectiledeathtype, other);
-
-       remove(self);
-}
-
-
-void W_Mortar_Grenade_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{
-       if(self.health <= 0)
-               return;
-
-       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
-               return; // g_projectiles_damage says to halt
-
-       self.health = self.health - damage;
-
-       if(self.health <= 0)
-               W_PrepareExplosionByDamage(attacker, self.use);
-}
-
-void W_Mortar_Grenade_Think1(void)
-{
-       self.nextthink = time;
-       if(time > self.cnt)
-       {
-               other = world;
-               self.projectiledeathtype |= HITTYPE_BOUNCE;
-               W_Mortar_Grenade_Explode();
-               return;
-       }
-       if(self.gl_detonate_later && self.gl_bouncecnt >= WEP_CVAR_PRI(mortar, remote_minbouncecnt))
-               W_Mortar_Grenade_Explode();
-}
-
-void W_Mortar_Grenade_Touch1(void)
-{
-       PROJECTILE_TOUCH;
-       if(other.takedamage == DAMAGE_AIM || WEP_CVAR_PRI(mortar, type) == 0) // always explode when hitting a player, or if normal mortar projectile
-       {
-               self.use();
-       }
-       else if(WEP_CVAR_PRI(mortar, type) == 1) // bounce
-       {
-               float r;
-               r = random() * 6;
-               if(r < 1)
-                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce1"), VOL_BASE, ATTN_NORM);
-               else if(r < 2)
-                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce2"), VOL_BASE, ATTN_NORM);
-               else if(r < 3)
-                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce3"), VOL_BASE, ATTN_NORM);
-               else if(r < 4)
-                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce4"), VOL_BASE, ATTN_NORM);
-               else if(r < 5)
-                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce5"), VOL_BASE, ATTN_NORM);
-               else
-                       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, W_Sound("grenade_stick"), VOL_BASE, ATTN_NORM);
-
-               // let it stick whereever it is
-               self.oldvelocity = self.velocity;
-               self.velocity = '0 0 0';
-               self.movetype = MOVETYPE_NONE; // also disables gravity
-               self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
-               UpdateCSQCProjectile(self);
-
-               // do not respond to any more touches
-               self.solid = SOLID_NOT;
-
-               self.nextthink = min(self.nextthink, time + WEP_CVAR_PRI(mortar, lifetime_stick));
-       }
-}
-
-void W_Mortar_Grenade_Touch2(void)
-{
-       PROJECTILE_TOUCH;
-       if(other.takedamage == DAMAGE_AIM || WEP_CVAR_SEC(mortar, type) == 0) // always explode when hitting a player, or if normal mortar projectile
-       {
-               self.use();
-       }
-       else if(WEP_CVAR_SEC(mortar, type) == 1) // bounce
-       {
-               float r;
-               r = random() * 6;
-               if(r < 1)
-                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce1"), VOL_BASE, ATTN_NORM);
-               else if(r < 2)
-                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce2"), VOL_BASE, ATTN_NORM);
-               else if(r < 3)
-                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce3"), VOL_BASE, ATTN_NORM);
-               else if(r < 4)
-                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce4"), VOL_BASE, ATTN_NORM);
-               else if(r < 5)
-                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce5"), VOL_BASE, ATTN_NORM);
-               else
-                       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;
-
-               if(WEP_CVAR_SEC(mortar, lifetime_bounce) && self.gl_bouncecnt == 1)
-                       self.nextthink = time + WEP_CVAR_SEC(mortar, lifetime_bounce);
-
-       }
-       else if(WEP_CVAR_SEC(mortar, type) == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick
-       {
-               spamsound(self, CH_SHOTS, W_Sound("grenade_stick"), VOL_BASE, ATTN_NORM);
-
-               // let it stick whereever it is
-               self.oldvelocity = self.velocity;
-               self.velocity = '0 0 0';
-               self.movetype = MOVETYPE_NONE; // also disables gravity
-               self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
-               UpdateCSQCProjectile(self);
-
-               // do not respond to any more touches
-               self.solid = SOLID_NOT;
-
-               self.nextthink = min(self.nextthink, time + WEP_CVAR_SEC(mortar, lifetime_stick));
-       }
-}
-
-void W_Mortar_Attack(void)
-{
-       entity gren;
-
-       W_DecreaseAmmo(WEP_CVAR_PRI(mortar, ammo));
-
-       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
-
-       Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       gren = spawn();
-       gren.owner = gren.realowner = self;
-       gren.classname = "grenade";
-       gren.bot_dodge = true;
-       gren.bot_dodgerating = WEP_CVAR_PRI(mortar, damage);
-       gren.movetype = MOVETYPE_BOUNCE;
-       gren.bouncefactor = WEP_CVAR(mortar, bouncefactor);
-       gren.bouncestop = WEP_CVAR(mortar, bouncestop);
-       PROJECTILE_MAKETRIGGER(gren);
-       gren.projectiledeathtype = WEP_MORTAR.m_id;
-       setorigin(gren, w_shotorg);
-       setsize(gren, '-3 -3 -3', '3 3 3');
-
-       gren.cnt = time + WEP_CVAR_PRI(mortar, lifetime);
-       gren.nextthink = time;
-       gren.think = W_Mortar_Grenade_Think1;
-       gren.use = W_Mortar_Grenade_Explode;
-       gren.touch = W_Mortar_Grenade_Touch1;
-
-       gren.takedamage = DAMAGE_YES;
-       gren.health = WEP_CVAR_PRI(mortar, health);
-       gren.damageforcescale = WEP_CVAR_PRI(mortar, damageforcescale);
-       gren.event_damage = W_Mortar_Grenade_Damage;
-       gren.damagedbycontents = true;
-       gren.missile_flags = MIF_SPLASH | MIF_ARC;
-       W_SetupProjVelocity_UP_PRI(gren, mortar);
-
-       gren.angles = vectoangles(gren.velocity);
-       gren.flags = FL_PROJECTILE;
-
-       if(WEP_CVAR_PRI(mortar, type) == 0 || WEP_CVAR_PRI(mortar, type) == 2)
-               CSQCProjectile(gren, true, PROJECTILE_GRENADE, true);
-       else
-               CSQCProjectile(gren, true, PROJECTILE_GRENADE_BOUNCING, true);
-
-       MUTATOR_CALLHOOK(EditProjectile, self, gren);
-}
-
-void W_Mortar_Attack2(void)
-{
-       entity gren;
-
-       W_DecreaseAmmo(WEP_CVAR_SEC(mortar, ammo));
-
-       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
-
-       Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       gren = spawn();
-       gren.owner = gren.realowner = self;
-       gren.classname = "grenade";
-       gren.bot_dodge = true;
-       gren.bot_dodgerating = WEP_CVAR_SEC(mortar, damage);
-       gren.movetype = MOVETYPE_BOUNCE;
-       gren.bouncefactor = WEP_CVAR(mortar, bouncefactor);
-       gren.bouncestop = WEP_CVAR(mortar, bouncestop);
-       PROJECTILE_MAKETRIGGER(gren);
-       gren.projectiledeathtype = WEP_MORTAR.m_id | HITTYPE_SECONDARY;
-       setorigin(gren, w_shotorg);
-       setsize(gren, '-3 -3 -3', '3 3 3');
-
-       gren.nextthink = time + WEP_CVAR_SEC(mortar, lifetime);
-       gren.think = adaptor_think2use_hittype_splash;
-       gren.use = W_Mortar_Grenade_Explode2;
-       gren.touch = W_Mortar_Grenade_Touch2;
-
-       gren.takedamage = DAMAGE_YES;
-       gren.health = WEP_CVAR_SEC(mortar, health);
-       gren.damageforcescale = WEP_CVAR_SEC(mortar, damageforcescale);
-       gren.event_damage = W_Mortar_Grenade_Damage;
-       gren.damagedbycontents = true;
-       gren.missile_flags = MIF_SPLASH | MIF_ARC;
-       W_SetupProjVelocity_UP_SEC(gren, mortar);
-
-       gren.angles = vectoangles(gren.velocity);
-       gren.flags = FL_PROJECTILE;
-
-       if(WEP_CVAR_SEC(mortar, type) == 0 || WEP_CVAR_SEC(mortar, type) == 2)
-               CSQCProjectile(gren, true, PROJECTILE_GRENADE, true);
-       else
-               CSQCProjectile(gren, true, PROJECTILE_GRENADE_BOUNCING, true);
-
-       MUTATOR_CALLHOOK(EditProjectile, self, gren);
-}
-
-.float bot_secondary_grenademooth;
-bool W_Mortar(int req)
-{
-       entity nade;
-       float nadefound;
-       float ammo_amount;
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       self.BUTTON_ATCK = false;
-                       self.BUTTON_ATCK2 = false;
-                       if(self.bot_secondary_grenademooth == 0) // WEAPONTODO: merge this into using WEP_CVAR_BOTH
-                       {
-                               if(bot_aim(WEP_CVAR_PRI(mortar, speed), WEP_CVAR_PRI(mortar, speed_up), WEP_CVAR_PRI(mortar, lifetime), true))
-                               {
-                                       self.BUTTON_ATCK = true;
-                                       if(random() < 0.01) self.bot_secondary_grenademooth = 1;
-                               }
-                       }
-                       else
-                       {
-                               if(bot_aim(WEP_CVAR_SEC(mortar, speed), WEP_CVAR_SEC(mortar, speed_up), WEP_CVAR_SEC(mortar, lifetime), true))
-                               {
-                                       self.BUTTON_ATCK2 = true;
-                                       if(random() < 0.02) self.bot_secondary_grenademooth = 0;
-                               }
-                       }
-
-                       return true;
-               }
-               /*case WR_CALCINFO:
-               {
-                       wepinfo_pri_refire = max3(sys_frametime, WEP_CVAR_PRI(mortar, refire), WEP_CVAR_PRI(mortar, animtime));
-                       wepinfo_pri_dps = (WEP_CVAR_PRI(mortar, damage) * (1 / wepinfo_pri_refire));
-                       wepinfo_pri_speed = (1 / max(1, (10000 / max(1, WEP_CVAR_PRI(mortar, speed)))));
-
-                       // for the range calculation, closer to 1 is better
-                       wepinfo_pri_range_max = 2000 * wepinfo_pri_speed;
-                       wepinfo_pri_range = wepinfo_pri_speed * WEP_CVAR_PRI(mortar,
-
-                       wepinfo_sec_refire = max3(sys_frametime, WEP_CVAR_SEC(mortar, refire), WEP_CVAR_SEC(mortar, animtime));
-                       wepinfo_sec_dps = (WEP_CVAR_SEC(mortar, damage) * (1 / wepinfo_sec_refire));
-
-                       wepinfo_sec_dps = (WEP_CVAR_SEC(mortar, damage) * (1 / max3(sys_frametime, WEP_CVAR_SEC(mortar, refire), WEP_CVAR_SEC(mortar, animtime))));
-                       wepinfo_ter_dps = 0;
-                       */
-               case WR_THINK:
-               {
-                       if(autocvar_g_balance_mortar_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo))) // forced reload
-                               WEP_ACTION(self.weapon, WR_RELOAD);
-                       else if(self.BUTTON_ATCK)
-                       {
-                               if(weapon_prepareattack(0, WEP_CVAR_PRI(mortar, refire)))
-                               {
-                                       W_Mortar_Attack();
-                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(mortar, animtime), w_ready);
-                               }
-                       }
-                       else if(self.BUTTON_ATCK2)
-                       {
-                               if(WEP_CVAR_SEC(mortar, remote_detonateprimary))
-                               {
-                                       nadefound = 0;
-                                       for(nade = world; (nade = find(nade, classname, "grenade")); ) if(nade.realowner == self)
-                                       {
-                                               if(!nade.gl_detonate_later)
-                                               {
-                                                       nade.gl_detonate_later = true;
-                                                       nadefound = 1;
-                                               }
-                                       }
-                                       if(nadefound)
-                                               sound(self, CH_WEAPON_B, W_Sound("rocket_det"), VOL_BASE, ATTN_NORM);
-                               }
-                               else if(weapon_prepareattack(1, WEP_CVAR_SEC(mortar, refire)))
-                               {
-                                       W_Mortar_Attack2();
-                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(mortar, animtime), w_ready);
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       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;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       ammo_amount = self.WEP_AMMO(MORTAR) >= WEP_CVAR_PRI(mortar, ammo);
-                       ammo_amount += self.(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_PRI(mortar, ammo);
-                       return ammo_amount;
-               }
-               case WR_CHECKAMMO2:
-               {
-                       ammo_amount = self.WEP_AMMO(MORTAR) >= WEP_CVAR_SEC(mortar, ammo);
-                       ammo_amount += self.(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_SEC(mortar, ammo);
-                       return ammo_amount;
-               }
-               case WR_CONFIG:
-               {
-                       MORTAR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       W_Reload(min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo)), W_Sound("reload")); // WEAPONTODO
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                               return WEAPON_MORTAR_SUICIDE_BOUNCE;
-                       else
-                               return WEAPON_MORTAR_SUICIDE_EXPLODE;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                               return WEAPON_MORTAR_MURDER_BOUNCE;
-                       else
-                               return WEAPON_MORTAR_MURDER_EXPLODE;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_Mortar(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 12;
-                       pointparticles(particleeffectnum(EFFECT_GRENADE_EXPLODE), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                               sound(self, CH_SHOTS, W_Sound("grenade_impact"), VOL_BASE, ATTN_NORM);
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("grenade_impact"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_porto.qc b/qcsrc/common/weapons/w_porto.qc
deleted file mode 100644 (file)
index 2d08c0f..0000000
+++ /dev/null
@@ -1,431 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ PORTO,
-/* function  */ W_Porto,
-/* ammotype  */ ammo_none,
-/* impulse   */ 0,
-/* flags     */ WEP_TYPE_OTHER | WEP_FLAG_SUPERWEAPON,
-/* rating    */ 0,
-/* color     */ '0.5 0.5 0.5',
-/* modelname */ "porto",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairporto 0.6",
-/* wepimg    */ "weaponporto",
-/* refname   */ "porto",
-/* wepname   */ _("Port-O-Launch")
-);
-
-#define PORTO_SETTINGS(w_cvar,w_prop) PORTO_SETTINGS_LIST(w_cvar, w_prop, PORTO, porto)
-#define PORTO_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, BOTH, animtime) \
-       w_cvar(id, sn, BOTH, lifetime) \
-       w_cvar(id, sn, BOTH, refire) \
-       w_cvar(id, sn, BOTH, speed) \
-       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) \
-       w_prop(id, sn, float,  weaponstart, weaponstart) \
-       w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
-       w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
-
-#ifdef SVQC
-PORTO_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-.entity porto_current;
-.vector porto_v_angle; // holds "held" view angles
-.float porto_v_angle_held;
-.vector right_vector;
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-#include "../triggers/trigger/jumppads.qh"
-
-void spawnfunc_weapon_porto(void) { weapon_defaultspawnfunc(WEP_PORTO.m_id); }
-
-void W_Porto_Success(void)
-{
-       if(self.realowner == world)
-       {
-               objerror("Cannot succeed successfully: no owner\n");
-               return;
-       }
-
-       self.realowner.porto_current = world;
-       remove(self);
-}
-
-string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo);
-void W_Porto_Fail(float failhard)
-{
-       if(self.realowner == world)
-       {
-               objerror("Cannot fail successfully: no owner\n");
-               return;
-       }
-
-       // no portals here!
-       if(self.cnt < 0)
-       {
-               Portal_ClearWithID(self.realowner, self.portal_id);
-       }
-
-       self.realowner.porto_current = world;
-
-       if(self.cnt < 0 && !failhard && self.realowner.playerid == self.playerid && self.realowner.deadflag == DEAD_NO && !(self.realowner.weapons & WEPSET_PORTO))
-       {
-               setsize(self, '-16 -16 0', '16 16 32');
-               setorigin(self, self.origin + trace_plane_normal);
-               if(move_out_of_solid(self))
-               {
-                       self.flags = FL_ITEM;
-                       self.velocity = trigger_push_calculatevelocity(self.origin, self.realowner, 128);
-                       tracetoss(self, self);
-                       if(vlen(trace_endpos - self.realowner.origin) < 128)
-                       {
-                               W_ThrowNewWeapon(self.realowner, WEP_PORTO.m_id, 0, self.origin, self.velocity);
-                               Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_FAILED);
-                       }
-               }
-       }
-       remove(self);
-}
-
-void W_Porto_Remove(entity p)
-{
-       if(p.porto_current.realowner == p && p.porto_current.classname == "porto")
-       {
-               entity oldself;
-               oldself = self;
-               self = p.porto_current;
-               W_Porto_Fail(1);
-               self = oldself;
-       }
-}
-
-void W_Porto_Think(void)
-{
-       trace_plane_normal = '0 0 0';
-       if(self.realowner.playerid != self.playerid)
-               remove(self);
-       else
-               W_Porto_Fail(0);
-}
-
-void W_Porto_Touch(void)
-{
-       vector norm;
-
-       // do not use PROJECTILE_TOUCH here
-       // FIXME but DO handle warpzones!
-
-       if(other.classname == "portal")
-               return; // handled by the portal
-
-       norm = trace_plane_normal;
-       if(trace_ent.iscreature)
-       {
-               traceline(trace_ent.origin, trace_ent.origin + '0 0 2' * PL_MIN.z, MOVE_WORLDONLY, self);
-               if(trace_fraction >= 1)
-                       return;
-               if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP)
-                       return;
-               if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
-                       return;
-       }
-
-       if(self.realowner.playerid != self.playerid)
-       {
-               sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM);
-               remove(self);
-       }
-       else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP)
-       {
-               spamsound(self, CH_SHOTS, "porto/bounce.wav", VOL_BASE, ATTEN_NORM);
-               // just reflect
-               self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * trace_plane_normal);
-               self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * trace_plane_normal));
-       }
-       else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
-       {
-               sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM);
-               W_Porto_Fail(0);
-               if(self.cnt < 0)
-                       Portal_ClearAll_PortalsOnly(self.realowner);
-       }
-       else if(self.cnt == 0)
-       {
-               // in-portal only
-               if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
-               {
-                       sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM);
-                       trace_plane_normal = norm;
-                       Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN);
-                       W_Porto_Success();
-               }
-               else
-               {
-                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM);
-                       trace_plane_normal = norm;
-                       W_Porto_Fail(0);
-               }
-       }
-       else if(self.cnt == 1)
-       {
-               // out-portal only
-               if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
-               {
-                       sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM);
-                       trace_plane_normal = norm;
-                       Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT);
-                       W_Porto_Success();
-               }
-               else
-               {
-                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM);
-                       trace_plane_normal = norm;
-                       W_Porto_Fail(0);
-               }
-       }
-       else if(self.effects & EF_RED)
-       {
-               self.effects += EF_BLUE - EF_RED;
-               if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
-               {
-                       sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM);
-                       trace_plane_normal = norm;
-                       Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN);
-                       self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * norm);
-                       self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * norm));
-                       CSQCProjectile(self, true, PROJECTILE_PORTO_BLUE, true); // change type
-               }
-               else
-               {
-                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM);
-                       trace_plane_normal = norm;
-                       Portal_ClearAll_PortalsOnly(self.realowner);
-                       W_Porto_Fail(0);
-               }
-       }
-       else
-       {
-               if(self.realowner.portal_in.portal_id == self.portal_id)
-               {
-                       if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
-                       {
-                               sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM);
-                               trace_plane_normal = norm;
-                               Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT);
-                               W_Porto_Success();
-                       }
-                       else
-                       {
-                               sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM);
-                               Portal_ClearAll_PortalsOnly(self.realowner);
-                               W_Porto_Fail(0);
-                       }
-               }
-               else
-               {
-                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM);
-                       Portal_ClearAll_PortalsOnly(self.realowner);
-                       W_Porto_Fail(0);
-               }
-       }
-}
-
-void W_Porto_Attack(float type)
-{
-       entity gren;
-
-       W_SetupShot(self, false, 4, "porto/fire.wav", CH_WEAPON_A, 0);
-       // always shoot from the eye
-       w_shotdir = v_forward;
-       w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward;
-
-       //Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       gren = spawn();
-       gren.cnt = type;
-       gren.owner = gren.realowner = self;
-       gren.playerid = self.playerid;
-       gren.classname = "porto";
-       gren.bot_dodge = true;
-       gren.bot_dodgerating = 200;
-       gren.movetype = MOVETYPE_BOUNCEMISSILE;
-       PROJECTILE_MAKETRIGGER(gren);
-       gren.effects = EF_RED;
-       gren.scale = 4;
-       setorigin(gren, w_shotorg);
-       setsize(gren, '0 0 0', '0 0 0');
-
-       gren.nextthink = time + WEP_CVAR_BOTH(porto, (type <= 0), lifetime);
-       gren.think = W_Porto_Think;
-       gren.touch = W_Porto_Touch;
-
-       if(self.items & ITEM_Strength.m_itemid)
-               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);
-
-       gren.angles = vectoangles(gren.velocity);
-       gren.flags = FL_PROJECTILE;
-
-       gren.portal_id = time;
-       self.porto_current = gren;
-       gren.playerid = self.playerid;
-       fixedmakevectors(fixedvectoangles(gren.velocity));
-       gren.right_vector = v_right;
-
-       gren.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
-
-       if(type > 0)
-               CSQCProjectile(gren, true, PROJECTILE_PORTO_BLUE, true);
-       else
-               CSQCProjectile(gren, true, PROJECTILE_PORTO_RED, true);
-
-       MUTATOR_CALLHOOK(EditProjectile, self, gren);
-}
-
-bool w_nexball_weapon(int req); // WEAPONTODO
-bool W_Porto(int req)
-{
-       //vector v_angle_save;
-
-       if(g_nexball) { return w_nexball_weapon(req); }
-
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       self.BUTTON_ATCK = false;
-                       self.BUTTON_ATCK2 = false;
-                       if(!WEP_CVAR(porto, secondary))
-                               if(bot_aim(WEP_CVAR_PRI(porto, speed), 0, WEP_CVAR_PRI(porto, lifetime), false))
-                                       self.BUTTON_ATCK = true;
-
-                       return true;
-               }
-               case WR_CONFIG:
-               {
-                       PORTO_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       if(WEP_CVAR(porto, secondary))
-                       {
-                               if(self.BUTTON_ATCK)
-                               if(!self.porto_current)
-                               if(!self.porto_forbidden)
-                               if(weapon_prepareattack(0, WEP_CVAR_PRI(porto, refire)))
-                               {
-                                       W_Porto_Attack(0);
-                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready);
-                               }
-
-                               if(self.BUTTON_ATCK2)
-                               if(!self.porto_current)
-                               if(!self.porto_forbidden)
-                               if(weapon_prepareattack(1, WEP_CVAR_SEC(porto, refire)))
-                               {
-                                       W_Porto_Attack(1);
-                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(porto, animtime), w_ready);
-                               }
-                       }
-                       else
-                       {
-                               if(self.porto_v_angle_held)
-                               {
-                                       if(!self.BUTTON_ATCK2)
-                                       {
-                                               self.porto_v_angle_held = 0;
-
-                                               ClientData_Touch(self);
-                                       }
-                               }
-                               else
-                               {
-                                       if(self.BUTTON_ATCK2)
-                                       {
-                                               self.porto_v_angle = self.v_angle;
-                                               self.porto_v_angle_held = 1;
-
-                                               ClientData_Touch(self);
-                                       }
-                               }
-                               if(self.porto_v_angle_held)
-                                       makevectors(self.porto_v_angle); // override the previously set angles
-
-                               if(self.BUTTON_ATCK)
-                               if(!self.porto_current)
-                               if(!self.porto_forbidden)
-                               if(weapon_prepareattack(0, WEP_CVAR_PRI(porto, refire)))
-                               {
-                                       W_Porto_Attack(-1);
-                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready);
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_CHECKAMMO1:
-               case WR_CHECKAMMO2:
-               {
-                       // always allow infinite ammo
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       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");
-                       precache_sound("porto/expire.wav");
-                       precache_sound("porto/explode.wav");
-                       precache_sound("porto/fire.wav");
-                       precache_sound("porto/unsupported.wav");
-                       PORTO_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP);
-                       return true;
-               }
-               case WR_SETUP:
-               {
-                       self.ammo_field = ammo_none;
-                       return true;
-               }
-               case WR_RESETPLAYER:
-               {
-                       self.porto_current = world;
-                       return true;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_Porto(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       LOG_INFO("Since when does Porto send DamageInfo?\n");
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       // nothing to do
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_rifle.qc b/qcsrc/common/weapons/w_rifle.qc
deleted file mode 100644 (file)
index ef3babd..0000000
+++ /dev/null
@@ -1,317 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ RIFLE,
-/* function  */ W_Rifle,
-/* ammotype  */ ammo_nails,
-/* impulse   */ 7,
-/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* color     */ '0.5 1 0',
-/* modelname */ "campingrifle",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairrifle 0.6",
-/* wepimg    */ "weaponrifle",
-/* refname   */ "rifle",
-/* wepname   */ _("Rifle")
-);
-
-#define RIFLE_SETTINGS(w_cvar,w_prop) RIFLE_SETTINGS_LIST(w_cvar, w_prop, RIFLE, rifle)
-#define RIFLE_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, BOTH, ammo) \
-       w_cvar(id, sn, BOTH, animtime) \
-       w_cvar(id, sn, BOTH, bullethail) \
-       w_cvar(id, sn, BOTH, burstcost) \
-       w_cvar(id, sn, BOTH, damage) \
-       w_cvar(id, sn, BOTH, force) \
-       w_cvar(id, sn, BOTH, refire) \
-       w_cvar(id, sn, BOTH, shots) \
-       w_cvar(id, sn, BOTH, solidpenetration) \
-       w_cvar(id, sn, BOTH, spread) \
-       w_cvar(id, sn, BOTH, tracer) \
-       w_cvar(id, sn, NONE, bursttime) \
-       w_cvar(id, sn, NONE, secondary) \
-       w_cvar(id, sn, SEC,  reload) \
-       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
-RIFLE_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-.float rifle_accumulator;
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_rifle(void) { weapon_defaultspawnfunc(WEP_RIFLE.m_id); }
-void spawnfunc_weapon_campingrifle(void) { spawnfunc_weapon_rifle(); }
-void spawnfunc_weapon_sniperrifle(void) { spawnfunc_weapon_rifle(); }
-
-void W_Rifle_FireBullet(float pSpread, float pDamage, float pForce, float pSolidPenetration, float pAmmo, int deathtype, float pTracer, float pShots, string pSound)
-{
-       float i;
-
-       W_DecreaseAmmo(pAmmo);
-
-       W_SetupShot(self, true, 2, pSound, CH_WEAPON_A, pDamage * pShots);
-
-       Send_Effect(EFFECT_RIFLE_MUZZLEFLASH, w_shotorg, w_shotdir * 2000, 1);
-
-       if(self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) // if zoomed, shoot from the eye
-       {
-               w_shotdir = v_forward;
-               w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward;
-       }
-
-       for(i = 0; i < pShots; ++i)
-               fireBullet(w_shotorg, w_shotdir, pSpread, pSolidPenetration, pDamage, pForce, deathtype, (pTracer ? EF_RED : EF_BLUE));
-
-       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 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.m_id, 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.m_id | HITTYPE_SECONDARY, WEP_CVAR_SEC(rifle, tracer), WEP_CVAR_SEC(rifle, shots), W_Sound("campingrifle_fire2"));
-}
-
-.void(void) rifle_bullethail_attackfunc;
-.float rifle_bullethail_frame;
-.float rifle_bullethail_animtime;
-.float rifle_bullethail_refire;
-void W_Rifle_BulletHail_Continue(void)
-{
-       float r, sw, af;
-
-       sw = self.switchweapon; // make it not detect weapon changes as reason to abort firing
-       af = ATTACK_FINISHED(self);
-       self.switchweapon = self.weapon;
-       ATTACK_FINISHED(self) = time;
-       LOG_INFO(ftos(self.WEP_AMMO(RIFLE)), "\n");
-       r = weapon_prepareattack(self.rifle_bullethail_frame == WFRAME_FIRE2, self.rifle_bullethail_refire);
-       if(self.switchweapon == self.weapon)
-               self.switchweapon = sw;
-       if(r)
-       {
-               self.rifle_bullethail_attackfunc();
-               weapon_thinkf(self.rifle_bullethail_frame, self.rifle_bullethail_animtime, W_Rifle_BulletHail_Continue);
-               LOG_INFO("thinkf set\n");
-       }
-       else
-       {
-               ATTACK_FINISHED(self) = af; // reset attack_finished if we didn't fire, so the last shot enforces the refire time
-               LOG_INFO("out of ammo... ", ftos(self.weaponentity.state), "\n");
-       }
-}
-
-void W_Rifle_BulletHail(float mode, void(void) AttackFunc, float fr, float animtime, float refire)
-{
-       // if we get here, we have at least one bullet to fire
-       AttackFunc();
-       if(mode)
-       {
-               // continue hail
-               self.rifle_bullethail_attackfunc = AttackFunc;
-               self.rifle_bullethail_frame = fr;
-               self.rifle_bullethail_animtime = animtime;
-               self.rifle_bullethail_refire = refire;
-               weapon_thinkf(fr, animtime, W_Rifle_BulletHail_Continue);
-       }
-       else
-       {
-               // just one shot
-               weapon_thinkf(fr, animtime, w_ready);
-       }
-}
-
-.float bot_secondary_riflemooth;
-bool W_Rifle(int req)
-{
-       float ammo_amount;
-
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       self.BUTTON_ATCK=false;
-                       self.BUTTON_ATCK2=false;
-                       if(vlen(self.origin-self.enemy.origin) > 1000)
-                               self.bot_secondary_riflemooth = 0;
-                       if(self.bot_secondary_riflemooth == 0)
-                       {
-                               if(bot_aim(1000000, 0, 0.001, false))
-                               {
-                                       self.BUTTON_ATCK = true;
-                                       if(random() < 0.01) self.bot_secondary_riflemooth = 1;
-                               }
-                       }
-                       else
-                       {
-                               if(bot_aim(1000000, 0, 0.001, false))
-                               {
-                                       self.BUTTON_ATCK2 = true;
-                                       if(random() < 0.03) self.bot_secondary_riflemooth = 0;
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       if(autocvar_g_balance_rifle_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(rifle, ammo), WEP_CVAR_SEC(rifle, ammo))) // forced reload
-                               WEP_ACTION(self.weapon, WR_RELOAD);
-                       else
-                       {
-                               self.rifle_accumulator = bound(time - WEP_CVAR(rifle, bursttime), self.rifle_accumulator, time);
-                               if(self.BUTTON_ATCK)
-                               if(weapon_prepareattack_check(0, WEP_CVAR_PRI(rifle, refire)))
-                               if(time >= self.rifle_accumulator + WEP_CVAR_PRI(rifle, burstcost))
-                               {
-                                       weapon_prepareattack_do(0, WEP_CVAR_PRI(rifle, refire));
-                                       W_Rifle_BulletHail(WEP_CVAR_PRI(rifle, bullethail), W_Rifle_Attack, WFRAME_FIRE1, WEP_CVAR_PRI(rifle, animtime), WEP_CVAR_PRI(rifle, refire));
-                                       self.rifle_accumulator += WEP_CVAR_PRI(rifle, burstcost);
-                               }
-                               if(self.BUTTON_ATCK2)
-                               {
-                                       if(WEP_CVAR(rifle, secondary))
-                                       {
-                                               if(WEP_CVAR_SEC(rifle, reload))
-                                                       WEP_ACTION(self.weapon, WR_RELOAD);
-                                               else
-                                               {
-                                                       if(weapon_prepareattack_check(1, WEP_CVAR_SEC(rifle, refire)))
-                                                       if(time >= self.rifle_accumulator + WEP_CVAR_SEC(rifle, burstcost))
-                                                       {
-                                                               weapon_prepareattack_do(1, WEP_CVAR_SEC(rifle, refire));
-                                                               W_Rifle_BulletHail(WEP_CVAR_SEC(rifle, bullethail), W_Rifle_Attack2, WFRAME_FIRE2, WEP_CVAR_SEC(rifle, animtime), WEP_CVAR_PRI(rifle, refire));
-                                                               self.rifle_accumulator += WEP_CVAR_SEC(rifle, burstcost);
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       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;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       ammo_amount = self.WEP_AMMO(RIFLE) >= WEP_CVAR_PRI(rifle, ammo);
-                       ammo_amount += self.(weapon_load[WEP_RIFLE.m_id]) >= WEP_CVAR_PRI(rifle, ammo);
-                       return ammo_amount;
-               }
-               case WR_CHECKAMMO2:
-               {
-                       ammo_amount = self.WEP_AMMO(RIFLE) >= WEP_CVAR_SEC(rifle, ammo);
-                       ammo_amount += self.(weapon_load[WEP_RIFLE.m_id]) >= WEP_CVAR_SEC(rifle, ammo);
-                       return ammo_amount;
-               }
-               case WR_CONFIG:
-               {
-                       RIFLE_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RESETPLAYER:
-               {
-                       self.rifle_accumulator = time - WEP_CVAR(rifle, bursttime);
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       W_Reload(min(WEP_CVAR_PRI(rifle, ammo), WEP_CVAR_SEC(rifle, ammo)), W_Sound("reload"));
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_THINKING_WITH_PORTALS;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                       {
-                               if(w_deathtype & HITTYPE_BOUNCE)
-                                       return WEAPON_RIFLE_MURDER_HAIL_PIERCING;
-                               else
-                                       return WEAPON_RIFLE_MURDER_HAIL;
-                       }
-                       else
-                       {
-                               if(w_deathtype & HITTYPE_BOUNCE)
-                                       return WEAPON_RIFLE_MURDER_PIERCING;
-                               else
-                                       return WEAPON_RIFLE_MURDER;
-                       }
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_Rifle(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 2;
-                       pointparticles(particleeffectnum(EFFECT_RIFLE_IMPACT), org2, w_backoff * 1000, 1);
-                       if(!w_issilent)
-                       {
-                               if(w_random < 0.2)
-                                       sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTN_NORM);
-                               else if(w_random < 0.4)
-                                       sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTN_NORM);
-                               else if(w_random < 0.5)
-                                       sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTN_NORM);
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("ric1"));
-                       precache_sound(W_Sound("ric2"));
-                       precache_sound(W_Sound("ric3"));
-                       if(autocvar_cl_reticle && autocvar_cl_reticle_weapon)
-                       {
-                               precache_pic("gfx/reticle_nex");
-                       }
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       if(button_zoom || zoomscript_caught)
-                       {
-                               reticle_image = "gfx/reticle_nex";
-                               return true;
-                       }
-                       else
-                       {
-                               // no weapon specific image for this weapon
-                               return false;
-                       }
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_rpc.qc b/qcsrc/common/weapons/w_rpc.qc
deleted file mode 100644 (file)
index 8a34490..0000000
+++ /dev/null
@@ -1,265 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id     */ RPC,
-/* function     */ W_RocketPropelledChainsaw,
-/* ammotype     */ ammo_rockets,
-/* impulse      */ 7,
-/* flags        */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH | WEP_FLAG_SUPERWEAPON,
-/* rating       */ BOT_PICKUP_RATING_HIGH,
-/* color     */ '0.5 0.5 0',
-/* modelname */ "ok_rl",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairrocketlauncher 0.7",
-/* wepimg    */ "weaponrpc",
-/* refname   */ "rpc",
-/* wepname      */ _("Rocket Propelled Chainsaw")
-);
-
-#define RPC_SETTINGS(w_cvar,w_prop) RPC_SETTINGS_LIST(w_cvar, w_prop, RPC, rpc)
-#define RPC_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, damage) \
-       w_cvar(id, sn, NONE, damage2) \
-       w_cvar(id, sn, NONE, damageforcescale) \
-       w_cvar(id, sn, NONE, edgedamage) \
-       w_cvar(id, sn, NONE, force) \
-       w_cvar(id, sn, NONE, health) \
-       w_cvar(id, sn, NONE, lifetime) \
-       w_cvar(id, sn, NONE, radius) \
-       w_cvar(id, sn, NONE, refire) \
-       w_cvar(id, sn, NONE, speed) \
-       w_cvar(id, sn, NONE, speedaccel) \
-       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
-RPC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_rpc() { weapon_defaultspawnfunc(WEP_RPC.m_id); }
-
-void W_RocketPropelledChainsaw_Explode()
-{
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       RadiusDamage (self, self.realowner, WEP_CVAR(rpc, damage), WEP_CVAR(rpc, edgedamage), WEP_CVAR(rpc, radius), world, world, WEP_CVAR(rpc, force), self.projectiledeathtype, other);
-
-       remove (self);
-}
-
-void W_RocketPropelledChainsaw_Touch (void)
-{
-       if(WarpZone_Projectile_Touch())
-               if(wasfreed(self))
-                       return;
-
-       W_RocketPropelledChainsaw_Explode();
-}
-
-void W_RocketPropelledChainsaw_Damage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{
-       if (self.health <= 0)
-               return;
-
-       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
-               return; // g_projectiles_damage says to halt
-
-       self.health = self.health - damage;
-
-       if (self.health <= 0)
-               W_PrepareExplosionByDamage(attacker, W_RocketPropelledChainsaw_Explode);
-}
-
-void W_RocketPropelledChainsaw_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, WEP_CVAR(rpc, damage2), self.projectiledeathtype, self.origin, normalize(self.origin - other.origin) * WEP_CVAR(rpc, force));
-
-       self.velocity = self.pos1 * (self.cnt + (WEP_CVAR(rpc, speedaccel) * sys_frametime));
-
-       UpdateCSQCProjectile(self);
-       self.nextthink = time;
-}
-
-void W_RocketPropelledChainsaw_Attack (void)
-{
-       entity missile = spawn(); //WarpZone_RefSys_SpawnSameRefSys(self);
-       entity flash = spawn ();
-
-       W_DecreaseAmmo(WEP_CVAR(rpc, ammo));
-       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;
-       missile.bot_dodge = true;
-       missile.bot_dodgerating = WEP_CVAR(rpc, damage) * 2;
-
-       missile.takedamage = DAMAGE_YES;
-       missile.damageforcescale = WEP_CVAR(rpc, damageforcescale);
-       missile.health = WEP_CVAR(rpc, health);
-       missile.event_damage = W_RocketPropelledChainsaw_Damage;
-       missile.damagedbycontents = true;
-       missile.movetype = MOVETYPE_FLY;
-
-       missile.projectiledeathtype = WEP_RPC.m_id;
-       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, WEP_CVAR(rpc, speed), 0);
-
-       missile.touch = W_RocketPropelledChainsaw_Touch;
-
-       missile.think = W_RocketPropelledChainsaw_Think;
-       missile.cnt = time + WEP_CVAR(rpc, lifetime);
-       missile.nextthink = time;
-       missile.flags = FL_PROJECTILE;
-
-       CSQCProjectile(missile, true, PROJECTILE_RPC, false);
-
-       setmodel(flash, "models/flash.md3"); // precision set below
-       SUB_SetFade (flash, time, 0.1);
-       flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
-       W_AttachToShotorg(flash, '5 0 0');
-       missile.pos1 = missile.velocity;
-
-       MUTATOR_CALLHOOK(EditProjectile, self, missile);
-}
-
-bool W_RocketPropelledChainsaw(int req)
-{
-       float ammo_amount = false;
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       self.BUTTON_ATCK = bot_aim(WEP_CVAR(rpc, speed), 0, WEP_CVAR(rpc, lifetime), false);
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       if(WEP_CVAR(rpc, reload_ammo) && self.clip_load < WEP_CVAR(rpc, ammo))
-                               WEP_ACTION(self.weapon, WR_RELOAD);
-                       else
-                       {
-                               if (self.BUTTON_ATCK)
-                               {
-                                       if(weapon_prepareattack(0, WEP_CVAR(rpc, refire)))
-                                       {
-                                               W_RocketPropelledChainsaw_Attack();
-                                               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(rpc, animtime), w_ready);
-                                       }
-                               }
-
-                               if (self.BUTTON_ATCK2)
-                               {
-                                       // to-do
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_model ("models/flash.md3");
-                       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;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       ammo_amount = self.WEP_AMMO(RPC) >= WEP_CVAR(rpc, ammo);
-                       ammo_amount += self.(weapon_load[WEP_RPC.m_id]) >= WEP_CVAR(rpc, ammo);
-                       return ammo_amount;
-               }
-               case WR_CHECKAMMO2:
-               {
-                       return false;
-               }
-               case WR_CONFIG:
-               {
-                       RPC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       W_Reload(WEP_CVAR(rpc, ammo), W_Sound("reload"));
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH))
-                               return WEAPON_RPC_SUICIDE_SPLASH;
-                       else
-                               return WEAPON_RPC_SUICIDE_DIRECT;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                               return WEAPON_BLASTER_MURDER;
-                       else if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH))
-                               return WEAPON_RPC_MURDER_SPLASH;
-                       else
-                               return WEAPON_RPC_MURDER_DIRECT;
-               }
-       }
-
-       return false;
-}
-#endif
-
-#ifdef CSQC
-bool W_RocketPropelledChainsaw(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 12;
-                       pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                               sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM);
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("rocket_impact"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_seeker.qc b/qcsrc/common/weapons/w_seeker.qc
deleted file mode 100644 (file)
index 69c4974..0000000
+++ /dev/null
@@ -1,793 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ SEEKER,
-/* function  */ W_Seeker,
-/* ammotype  */ ammo_rockets,
-/* impulse   */ 8,
-/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* color     */ '0.5 1 0',
-/* modelname */ "seeker",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairseeker 0.8",
-/* wepimg    */ "weaponseeker",
-/* refname   */ "seeker",
-/* wepname   */ _("T.A.G. Seeker")
-);
-
-#define SEEKER_SETTINGS(w_cvar,w_prop) SEEKER_SETTINGS_LIST(w_cvar, w_prop, SEEKER, seeker)
-#define SEEKER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, NONE, type) \
-       w_cvar(id, sn, NONE, flac_ammo) \
-       w_cvar(id, sn, NONE, flac_animtime) \
-       w_cvar(id, sn, NONE, flac_damage) \
-       w_cvar(id, sn, NONE, flac_edgedamage) \
-       w_cvar(id, sn, NONE, flac_force) \
-       w_cvar(id, sn, NONE, flac_lifetime) \
-       w_cvar(id, sn, NONE, flac_lifetime_rand) \
-       w_cvar(id, sn, NONE, flac_radius) \
-       w_cvar(id, sn, NONE, flac_refire) \
-       w_cvar(id, sn, NONE, flac_speed) \
-       w_cvar(id, sn, NONE, flac_speed_up) \
-       w_cvar(id, sn, NONE, flac_speed_z) \
-       w_cvar(id, sn, NONE, flac_spread) \
-       w_cvar(id, sn, NONE, missile_accel) \
-       w_cvar(id, sn, NONE, missile_ammo) \
-       w_cvar(id, sn, NONE, missile_animtime) \
-       w_cvar(id, sn, NONE, missile_count) \
-       w_cvar(id, sn, NONE, missile_damage) \
-       w_cvar(id, sn, NONE, missile_damageforcescale) \
-       w_cvar(id, sn, NONE, missile_decel) \
-       w_cvar(id, sn, NONE, missile_delay) \
-       w_cvar(id, sn, NONE, missile_edgedamage) \
-       w_cvar(id, sn, NONE, missile_force) \
-       w_cvar(id, sn, NONE, missile_health) \
-       w_cvar(id, sn, NONE, missile_lifetime) \
-       w_cvar(id, sn, NONE, missile_proxy) \
-       w_cvar(id, sn, NONE, missile_proxy_delay) \
-       w_cvar(id, sn, NONE, missile_proxy_maxrange) \
-       w_cvar(id, sn, NONE, missile_radius) \
-       w_cvar(id, sn, NONE, missile_refire) \
-       w_cvar(id, sn, NONE, missile_smart) \
-       w_cvar(id, sn, NONE, missile_smart_mindist) \
-       w_cvar(id, sn, NONE, missile_smart_trace_max) \
-       w_cvar(id, sn, NONE, missile_smart_trace_min) \
-       w_cvar(id, sn, NONE, missile_speed) \
-       w_cvar(id, sn, NONE, missile_speed_max) \
-       w_cvar(id, sn, NONE, missile_speed_up) \
-       w_cvar(id, sn, NONE, missile_speed_z) \
-       w_cvar(id, sn, NONE, missile_spread) \
-       w_cvar(id, sn, NONE, missile_turnrate) \
-       w_cvar(id, sn, NONE, tag_ammo) \
-       w_cvar(id, sn, NONE, tag_animtime) \
-       w_cvar(id, sn, NONE, tag_damageforcescale) \
-       w_cvar(id, sn, NONE, tag_health) \
-       w_cvar(id, sn, NONE, tag_lifetime) \
-       w_cvar(id, sn, NONE, tag_refire) \
-       w_cvar(id, sn, NONE, tag_speed) \
-       w_cvar(id, sn, NONE, tag_spread) \
-       w_cvar(id, sn, NONE, tag_tracker_lifetime) \
-       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
-SEEKER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-.entity tag_target, wps_tag_tracker;
-.float tag_time;
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_seeker(void) { weapon_defaultspawnfunc(WEP_SEEKER.m_id); }
-
-// ============================
-// Begin: Missile functions, these are general functions to be manipulated by other code
-// ============================
-void W_Seeker_Missile_Explode(void)
-{
-       self.event_damage = func_null;
-       RadiusDamage(self, self.realowner, WEP_CVAR(seeker, missile_damage), WEP_CVAR(seeker, missile_edgedamage), WEP_CVAR(seeker, missile_radius), world, world, WEP_CVAR(seeker, missile_force), self.projectiledeathtype, other);
-
-       remove(self);
-}
-
-void W_Seeker_Missile_Touch(void)
-{
-       PROJECTILE_TOUCH;
-
-       W_Seeker_Missile_Explode();
-}
-
-void W_Seeker_Missile_Think(void)
-{
-       entity e;
-       vector desireddir, olddir, newdir, eorg;
-       float turnrate;
-       float dist;
-       float spd;
-
-       if(time > self.cnt)
-       {
-               self.projectiledeathtype |= HITTYPE_SPLASH;
-               W_Seeker_Missile_Explode();
-       }
-
-       spd = vlen(self.velocity);
-       spd = bound(
-               spd - WEP_CVAR(seeker, missile_decel) * frametime,
-               WEP_CVAR(seeker, missile_speed_max),
-               spd + WEP_CVAR(seeker, missile_accel) * frametime
-       );
-
-       if(self.enemy != world)
-               if(self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
-                       self.enemy = world;
-
-       if(self.enemy != world)
-       {
-               e               = self.enemy;
-               eorg            = 0.5 * (e.absmin + e.absmax);
-               turnrate        = WEP_CVAR(seeker, missile_turnrate); // how fast to turn
-               desireddir      = normalize(eorg - self.origin);
-               olddir          = normalize(self.velocity); // get my current direction
-               dist            = vlen(eorg - self.origin);
-
-               // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P )
-               if(WEP_CVAR(seeker, missile_smart) && (dist > WEP_CVAR(seeker, missile_smart_mindist)))
-               {
-                       // Is it a better idea (shorter distance) to trace to the target itself?
-                       if( vlen(self.origin + olddir * self.wait) < dist)
-                               traceline(self.origin, self.origin + olddir * self.wait, false, self);
-                       else
-                               traceline(self.origin, eorg, false, self);
-
-                       // Setup adaptive tracelength
-                       self.wait = bound(WEP_CVAR(seeker, missile_smart_trace_min), vlen(self.origin - trace_endpos), self.wait = WEP_CVAR(seeker, missile_smart_trace_max));
-
-                       // Calc how important it is that we turn and add this to the desierd (enemy) dir.
-                       desireddir  = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5);
-               }
-
-               newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy
-               self.velocity = newdir * spd; // make me fly in the new direction at my flight speed
-       }
-       else
-               dist = 0;
-
-       // Proxy
-       if(WEP_CVAR(seeker, missile_proxy))
-       {
-               if(dist <= WEP_CVAR(seeker, missile_proxy_maxrange))
-               {
-                       if(self.autoswitch == 0)
-                       {
-                               self.autoswitch = time + WEP_CVAR(seeker, missile_proxy_delay);
-                       }
-                       else
-                       {
-                               if(self.autoswitch <= time)
-                               {
-                                       W_Seeker_Missile_Explode();
-                                       self.autoswitch = 0;
-                               }
-                       }
-               }
-               else
-               {
-                       if(self.autoswitch != 0)
-                               self.autoswitch = 0;
-               }
-       }
-       ///////////////
-
-       if(self.enemy.deadflag != DEAD_NO)
-       {
-               self.enemy = world;
-               self.cnt = time + 1 + (random() * 4);
-               self.nextthink = self.cnt;
-               return;
-       }
-
-       //self.angles = vectoangles(self.velocity);                     // turn model in the new flight direction
-       self.nextthink = time;// + 0.05; // csqc projectiles
-       UpdateCSQCProjectile(self);
-}
-
-
-
-void W_Seeker_Missile_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{
-       if(self.health <= 0)
-               return;
-
-       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
-               return; // g_projectiles_damage says to halt
-
-       if(self.realowner == attacker)
-               self.health = self.health - (damage * 0.25);
-       else
-               self.health = self.health - damage;
-
-       if(self.health <= 0)
-               W_PrepareExplosionByDamage(attacker, W_Seeker_Missile_Explode);
-}
-
-/*
-void W_Seeker_Missile_Animate(void)
-{
-       self.frame = self.frame +1;
-       self.nextthink = time + 0.05;
-
-       if(self.enemy != world)
-               if(self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
-                       self.enemy = world;
-
-       if(self.frame == 5)
-       {
-               self.think           = W_Seeker_Missile_Think;
-               self.nextthink       = time;// + cvar("g_balance_seeker_missile_activate_delay"); // cant dealy with csqc projectiles
-
-               if(autocvar_g_balance_seeker_missile_proxy)
-                       self.movetype    = MOVETYPE_BOUNCEMISSILE;
-               else
-                       self.movetype    = MOVETYPE_FLYMISSILE;
-       }
-
-       UpdateCSQCProjectile(self);
-}
-*/
-
-void W_Seeker_Fire_Missile(vector f_diff, entity m_target)
-{
-       entity missile;
-
-       W_DecreaseAmmo(WEP_CVAR(seeker, missile_ammo));
-
-       makevectors(self.v_angle);
-       W_SetupShot_ProjectileSize(self, '-2 -2 -2', '2 2 2', false, 2, W_Sound("seeker_fire"), CH_WEAPON_A, 0);
-       w_shotorg += f_diff;
-       Send_Effect(EFFECT_SEEKER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       //self.detornator         = false;
-
-       missile                 = spawn();
-       missile.owner           = missile.realowner = self;
-       missile.classname       = "seeker_missile";
-       missile.bot_dodge       = true;
-       missile.bot_dodgerating = WEP_CVAR(seeker, missile_damage);
-
-       missile.think           = W_Seeker_Missile_Think;
-       missile.touch           = W_Seeker_Missile_Touch;
-       missile.event_damage    = W_Seeker_Missile_Damage;
-       missile.nextthink       = time;// + 0.2;// + cvar("g_balance_seeker_missile_activate_delay");
-       missile.cnt             = time + WEP_CVAR(seeker, missile_lifetime);
-       missile.enemy           = m_target;
-       missile.solid           = SOLID_BBOX;
-       missile.scale           = 2;
-       missile.takedamage      = DAMAGE_YES;
-       missile.health          = WEP_CVAR(seeker, missile_health);
-       missile.damageforcescale = WEP_CVAR(seeker, missile_damageforcescale);
-       missile.damagedbycontents = true;
-       //missile.think           = W_Seeker_Missile_Animate; // csqc projectiles.
-
-       if(missile.enemy != world)
-               missile.projectiledeathtype = WEP_SEEKER.m_id | HITTYPE_SECONDARY;
-       else
-               missile.projectiledeathtype = WEP_SEEKER.m_id;
-
-
-       setorigin(missile, w_shotorg);
-       setsize(missile, '-4 -4 -4', '4 4 4');
-       missile.movetype    = MOVETYPE_FLYMISSILE;
-       missile.flags       = FL_PROJECTILE;
-       missile.missile_flags = MIF_SPLASH | MIF_GUIDED_TAG;
-
-       W_SetupProjVelocity_UP_PRE(missile, seeker, missile_);
-
-       missile.angles = vectoangles(missile.velocity);
-
-       CSQCProjectile(missile, false, PROJECTILE_SEEKER, true);
-
-       MUTATOR_CALLHOOK(EditProjectile, self, missile);
-}
-
-// ============================
-// Begin: FLAC, close range attack meant for defeating rockets which are coming at you.
-// ============================
-void W_Seeker_Flac_Explode(void)
-{
-       self.event_damage = func_null;
-
-       RadiusDamage(self, self.realowner, WEP_CVAR(seeker, flac_damage), WEP_CVAR(seeker, flac_edgedamage), WEP_CVAR(seeker, flac_radius), world, world, WEP_CVAR(seeker, flac_force), self.projectiledeathtype, other);
-
-       remove(self);
-}
-
-void W_Seeker_Flac_Touch(void)
-{
-       PROJECTILE_TOUCH;
-
-       W_Seeker_Flac_Explode();
-}
-
-void W_Seeker_Fire_Flac(void)
-{
-       entity missile;
-       vector f_diff;
-       float c;
-
-       W_DecreaseAmmo(WEP_CVAR(seeker, flac_ammo));
-
-       c = self.bulletcounter % 4;
-       switch(c)
-       {
-               case 0:
-                       f_diff = '-1.25 -3.75 0';
-                       break;
-               case 1:
-                       f_diff = '+1.25 -3.75 0';
-                       break;
-               case 2:
-                       f_diff = '-1.25 +3.75 0';
-                       break;
-               case 3:
-               default:
-                       f_diff = '+1.25 +3.75 0';
-                       break;
-       }
-       W_SetupShot_ProjectileSize(self, '-2 -2 -2', '2 2 2', false, 2, W_Sound("flac_fire"), CH_WEAPON_A, WEP_CVAR(seeker, flac_damage));
-       w_shotorg += f_diff;
-
-       Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
-
-       missile                                 = spawn();
-       missile.owner                   = missile.realowner = self;
-       missile.classname               = "missile";
-       missile.bot_dodge               = true;
-       missile.bot_dodgerating = WEP_CVAR(seeker, flac_damage);
-       missile.touch                   = W_Seeker_Flac_Explode;
-       missile.use                     = W_Seeker_Flac_Explode;
-       missile.think                   = adaptor_think2use_hittype_splash;
-       missile.nextthink               = time + WEP_CVAR(seeker, flac_lifetime) + WEP_CVAR(seeker, flac_lifetime_rand);
-       missile.solid                   = SOLID_BBOX;
-       missile.movetype                = MOVETYPE_FLY;
-       missile.projectiledeathtype = WEP_SEEKER.m_id;
-       missile.projectiledeathtype = WEP_SEEKER.m_id | HITTYPE_SECONDARY;
-       missile.flags                           = FL_PROJECTILE;
-       missile.missile_flags       = MIF_SPLASH;
-
-       // csqc projectiles
-       //missile.angles                                = vectoangles(missile.velocity);
-       //missile.scale = 0.4; // BUG: the model is too big
-
-       setorigin(missile, w_shotorg);
-       setsize(missile, '-2 -2 -2', '2 2 2');
-
-       W_SetupProjVelocity_UP_PRE(missile, seeker, flac_);
-       CSQCProjectile(missile, true, PROJECTILE_FLAC, true);
-
-       MUTATOR_CALLHOOK(EditProjectile, self, missile);
-}
-
-// ============================
-// Begin: Tag and rocket controllers
-// ============================
-entity W_Seeker_Tagged_Info(entity isowner, entity istarget)
-{
-       entity tag;
-       for(tag = world; (tag = find(tag, classname, "tag_tracker")); )
-               if((tag.realowner == isowner) && (tag.tag_target == istarget))
-                       return tag;
-
-       return world;
-}
-
-void W_Seeker_Attack(void)
-{
-       entity tracker, closest_target;
-
-       closest_target = world;
-       for(tracker = world; (tracker = find(tracker, classname, "tag_tracker")); ) if (tracker.realowner == self)
-       {
-               if(closest_target)
-               {
-                       if(vlen(self.origin - tracker.tag_target.origin) < vlen(self.origin - closest_target.origin))
-                               closest_target = tracker.tag_target;
-               }
-               else
-                       closest_target = tracker.tag_target;
-       }
-
-       traceline(self.origin + self.view_ofs, closest_target.origin, MOVE_NOMONSTERS, self);
-       if((!closest_target) || ((trace_fraction < 1) && (trace_ent != closest_target)))
-               closest_target = world;
-
-       W_Seeker_Fire_Missile('0 0 0', closest_target);
-}
-
-void W_Seeker_Vollycontroller_Think(void) // TODO: Merge this with W_Seeker_Attack
-{
-       float c;
-       entity oldself,oldenemy;
-       self.cnt = self.cnt - 1;
-
-       if((!(self.realowner.items & IT_UNLIMITED_AMMO) && self.realowner.WEP_AMMO(SEEKER) < WEP_CVAR(seeker, missile_ammo)) || (self.cnt <= -1) || (self.realowner.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER.m_id))
-       {
-               remove(self);
-               return;
-       }
-
-       self.nextthink = time + WEP_CVAR(seeker, missile_delay) * W_WeaponRateFactor();
-
-       oldself = self;
-       self = self.realowner;
-
-       oldenemy = self.enemy;
-       self.enemy = oldself.enemy;
-
-       c = self.cnt % 4;
-       switch(c)
-       {
-               case 0:
-                       W_Seeker_Fire_Missile('-1.25 -3.75 0', self.enemy);
-                       break;
-               case 1:
-                       W_Seeker_Fire_Missile('+1.25 -3.75 0', self.enemy);
-                       break;
-               case 2:
-                       W_Seeker_Fire_Missile('-1.25 +3.75 0', self.enemy);
-                       break;
-               case 3:
-               default:
-                       W_Seeker_Fire_Missile('+1.25 +3.75 0', self.enemy);
-                       break;
-       }
-
-       self.enemy = oldenemy;
-       self = oldself;
-}
-
-void W_Seeker_Tracker_Think(void)
-{
-       // commit suicide if: You die OR target dies OR you switch away from the seeker OR commit suicide if lifetime is up
-       if((self.realowner.deadflag != DEAD_NO) || (self.tag_target.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER.m_id)
-       || (time > self.tag_time + WEP_CVAR(seeker, tag_tracker_lifetime)))
-       {
-               if(self)
-               {
-                       WaypointSprite_Kill(self.tag_target.wps_tag_tracker);
-                       remove(self);
-               }
-               return;
-       }
-
-       // Update the think method information
-       self.nextthink = time;
-}
-
-// ============================
-// Begin: Tag projectile
-// ============================
-void W_Seeker_Tag_Explode(void)
-{
-       //if(other==self.realowner)
-       //    return;
-       Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER.m_id | HITTYPE_BOUNCE, other.species, self);
-
-       remove(self);
-}
-
-void W_Seeker_Tag_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{
-       if(self.health <= 0)
-               return;
-       self.health = self.health - damage;
-       if(self.health <= 0)
-               W_Seeker_Tag_Explode();
-}
-
-void W_Seeker_Tag_Touch(void)
-{
-       vector dir;
-       vector org2;
-       entity e;
-
-       PROJECTILE_TOUCH;
-
-       dir     = normalize(self.realowner.origin - self.origin);
-       org2    = findbetterlocation(self.origin, 8);
-
-       te_knightspike(org2);
-
-       self.event_damage = func_null;
-       Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER.m_id | HITTYPE_BOUNCE | HITTYPE_SECONDARY, other.species, self);
-
-       if(other.takedamage == DAMAGE_AIM && other.deadflag == DEAD_NO)
-       {
-               // check to see if this person is already tagged by me
-               entity tag = W_Seeker_Tagged_Info(self.realowner, other);
-
-               if(tag != world)
-               {
-                       if(other.wps_tag_tracker && (WEP_CVAR(seeker, type) == 1)) // don't attach another waypointsprite without killing the old one first
-                               WaypointSprite_Kill(other.wps_tag_tracker);
-
-                       tag.tag_time = time;
-               }
-               else
-               {
-                       //sprint(self.realowner, strcat("You just tagged ^2", other.netname, "^7 with a tracking device!\n"));
-                       e             = spawn();
-                       e.cnt         = WEP_CVAR(seeker, missile_count);
-                       e.classname   = "tag_tracker";
-                       e.owner       = self.owner;
-                       e.realowner   = self.realowner;
-
-                       if(WEP_CVAR(seeker, type) == 1)
-                       {
-                               e.tag_target  = other;
-                               e.tag_time    = time;
-                               e.think       = W_Seeker_Tracker_Think;
-                       }
-                       else
-                       {
-                               e.enemy     = other;
-                               e.think     = W_Seeker_Vollycontroller_Think;
-                       }
-
-                       e.nextthink   = time;
-               }
-
-               if(WEP_CVAR(seeker, type) == 1)
-               {
-                       WaypointSprite_Spawn(WP_Seeker, WEP_CVAR(seeker, tag_tracker_lifetime), 0, other, '0 0 64', self.realowner, 0, other, wps_tag_tracker, true, RADARICON_TAGGED);
-                       WaypointSprite_UpdateRule(other.wps_tag_tracker, 0, SPRITERULE_DEFAULT);
-               }
-       }
-
-       remove(self);
-       return;
-}
-
-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, W_Sound("tag_fire"), CH_WEAPON_A, WEP_CVAR(seeker, missile_damage) * WEP_CVAR(seeker, missile_count));
-
-       missile                 = spawn();
-       missile.owner           = missile.realowner = self;
-       missile.classname       = "seeker_tag";
-       missile.bot_dodge       = true;
-       missile.bot_dodgerating = 50;
-       missile.touch           = W_Seeker_Tag_Touch;
-       missile.think           = SUB_Remove;
-       missile.nextthink       = time + WEP_CVAR(seeker, tag_lifetime);
-       missile.movetype        = MOVETYPE_FLY;
-       missile.solid           = SOLID_BBOX;
-
-       missile.takedamage       = DAMAGE_YES;
-       missile.event_damage     = W_Seeker_Tag_Damage;
-       missile.health           = WEP_CVAR(seeker, tag_health);
-       missile.damageforcescale = WEP_CVAR(seeker, tag_damageforcescale);
-
-       setorigin(missile, w_shotorg);
-       setsize(missile, '-2 -2 -2', '2 2 2');
-
-       missile.flags       = FL_PROJECTILE;
-       //missile.missile_flags = MIF_..?;
-
-       missile.movetype    = MOVETYPE_FLY;
-       W_SetupProjVelocity_PRE(missile, seeker, tag_);
-       missile.angles = vectoangles(missile.velocity);
-
-       CSQCProjectile(missile, true, PROJECTILE_TAG, false); // has sound
-
-       MUTATOR_CALLHOOK(EditProjectile, self, missile);
-}
-
-// ============================
-// Begin: Genereal weapon functions
-// ============================
-
-bool W_Seeker(int req)
-{
-       float ammo_amount;
-
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       if(WEP_CVAR(seeker, type) == 1)
-                               if(W_Seeker_Tagged_Info(self, self.enemy) != world)
-                                       self.BUTTON_ATCK = bot_aim(WEP_CVAR(seeker, missile_speed_max), 0, WEP_CVAR(seeker, missile_lifetime), false);
-                               else
-                                       self.BUTTON_ATCK2 = bot_aim(WEP_CVAR(seeker, tag_speed), 0, WEP_CVAR(seeker, tag_lifetime), false);
-                       else
-                               self.BUTTON_ATCK = bot_aim(WEP_CVAR(seeker, tag_speed), 0, WEP_CVAR(seeker, tag_lifetime), false);
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       if(autocvar_g_balance_seeker_reload_ammo && self.clip_load < min(WEP_CVAR(seeker, missile_ammo), WEP_CVAR(seeker, tag_ammo))) // forced reload
-                               WEP_ACTION(self.weapon, WR_RELOAD);
-
-                       else if(self.BUTTON_ATCK)
-                       {
-                               if(WEP_CVAR(seeker, type) == 1)
-                               {
-                                       if(weapon_prepareattack(0, WEP_CVAR(seeker, missile_refire)))
-                                       {
-                                               W_Seeker_Attack();
-                                               weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, missile_animtime), w_ready);
-                                       }
-                               }
-                               else
-                               {
-                                       if(weapon_prepareattack(0, WEP_CVAR(seeker, tag_refire)))
-                                       {
-                                               W_Seeker_Fire_Tag();
-                                               weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, tag_animtime), w_ready);
-                                       }
-                               }
-                       }
-
-                       else if(self.BUTTON_ATCK2)
-                       {
-                               if(WEP_CVAR(seeker, type) == 1)
-                               {
-                                       if(weapon_prepareattack(0, WEP_CVAR(seeker, tag_refire)))
-                                       {
-                                               W_Seeker_Fire_Tag();
-                                               weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, tag_animtime), w_ready);
-                                       }
-                               }
-                               else
-                               {
-                                       if(weapon_prepareattack(0, WEP_CVAR(seeker, flac_refire)))
-                                       {
-                                               W_Seeker_Fire_Flac();
-                                               weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, flac_animtime), w_ready);
-                                       }
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       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;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       if(WEP_CVAR(seeker, type) == 1)
-                       {
-                               ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, missile_ammo);
-                               ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, missile_ammo);
-                       }
-                       else
-                       {
-                               ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, tag_ammo);
-                               ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, tag_ammo);
-                       }
-                       return ammo_amount;
-               }
-               case WR_CHECKAMMO2:
-               {
-                       if(WEP_CVAR(seeker, type) == 1)
-                       {
-                               ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, tag_ammo);
-                               ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, tag_ammo);
-                       }
-                       else
-                       {
-                               ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, flac_ammo);
-                               ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, flac_ammo);
-                       }
-                       return ammo_amount;
-               }
-               case WR_CONFIG:
-               {
-                       SEEKER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       W_Reload(min(WEP_CVAR(seeker, missile_ammo), WEP_CVAR(seeker, tag_ammo)), W_Sound("reload"));
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_SEEKER_SUICIDE;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                               return WEAPON_SEEKER_MURDER_TAG;
-                       else
-                               return WEAPON_SEEKER_MURDER_SPRAY;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_Seeker(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 6;
-                       if(w_deathtype & HITTYPE_BOUNCE)
-                       {
-                               if(w_deathtype & HITTYPE_SECONDARY)
-                               {
-                                       if(!w_issilent)
-                                               sound(self, CH_SHOTS, W_Sound("tag_impact"), 1, ATTEN_NORM);
-                               }
-                               else
-                               {
-                                       pointparticles(particleeffectnum(EFFECT_HAGAR_EXPLODE), org2, '0 0 0', 1);
-                                       if(!w_issilent)
-                                       {
-                                               if(w_random<0.15)
-                                                       sound(self, CH_SHOTS, W_Sound("tagexp1"), 1, ATTEN_NORM);
-                                               else if(w_random<0.7)
-                                                       sound(self, CH_SHOTS, W_Sound("tagexp2"), 1, ATTEN_NORM);
-                                               else
-                                                       sound(self, CH_SHOTS, W_Sound("tagexp3"), 1, ATTEN_NORM);
-                                       }
-                               }
-                       }
-                       else
-                       {
-                               pointparticles(particleeffectnum(EFFECT_HAGAR_EXPLODE), org2, '0 0 0', 1);
-                               if(!w_issilent)
-                               {
-                                       if(w_random<0.15)
-                                               sound(self, CH_SHOTS, W_Sound("seekerexp1"), 1, ATTEN_NORM);
-                                       else if(w_random<0.7)
-                                               sound(self, CH_SHOTS, W_Sound("seekerexp2"), 1, ATTEN_NORM);
-                                       else
-                                               sound(self, CH_SHOTS, W_Sound("seekerexp3"), 1, ATTEN_NORM);
-                               }
-                       }
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("seekerexp1"));
-                       precache_sound(W_Sound("seekerexp2"));
-                       precache_sound(W_Sound("seekerexp3"));
-                       precache_sound(W_Sound("tagexp1"));
-                       precache_sound(W_Sound("tagexp2"));
-                       precache_sound(W_Sound("tagexp3"));
-                       precache_sound(W_Sound("tag_impact"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_shockwave.qc b/qcsrc/common/weapons/w_shockwave.qc
deleted file mode 100644 (file)
index 3287ea3..0000000
+++ /dev/null
@@ -1,896 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ SHOCKWAVE,
-/* function  */ W_Shockwave,
-/* ammotype  */ ammo_none,
-/* impulse   */ 2,
-/* flags     */ WEP_FLAG_NORMAL | WEP_TYPE_HITSCAN | WEP_FLAG_CANCLIMB | WEP_FLAG_MUTATORBLOCKED,
-/* rating    */ BOT_PICKUP_RATING_LOW,
-/* color     */ '0.5 0.25 0',
-/* modelname */ "shotgun",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairshotgun 0.7",
-/* wepimg    */ "weaponshotgun",
-/* refname   */ "shockwave",
-/* wepname   */ _("Shockwave")
-);
-
-#define SHOCKWAVE_SETTINGS(w_cvar,w_prop) SHOCKWAVE_SETTINGS_LIST(w_cvar, w_prop, SHOCKWAVE, shockwave)
-#define SHOCKWAVE_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, NONE, blast_animtime) \
-       w_cvar(id, sn, NONE, blast_damage) \
-       w_cvar(id, sn, NONE, blast_distance) \
-       w_cvar(id, sn, NONE, blast_edgedamage) \
-       w_cvar(id, sn, NONE, blast_force) \
-       w_cvar(id, sn, NONE, blast_force_forwardbias) \
-       w_cvar(id, sn, NONE, blast_force_zscale) \
-       w_cvar(id, sn, NONE, blast_jump_damage) \
-       w_cvar(id, sn, NONE, blast_jump_edgedamage) \
-       w_cvar(id, sn, NONE, blast_jump_force) \
-       w_cvar(id, sn, NONE, blast_jump_force_velocitybias) \
-       w_cvar(id, sn, NONE, blast_jump_force_zscale) \
-       w_cvar(id, sn, NONE, blast_jump_multiplier_accuracy) \
-       w_cvar(id, sn, NONE, blast_jump_multiplier_distance) \
-       w_cvar(id, sn, NONE, blast_jump_multiplier_min) \
-       w_cvar(id, sn, NONE, blast_jump_radius) \
-       w_cvar(id, sn, NONE, blast_multiplier_accuracy) \
-       w_cvar(id, sn, NONE, blast_multiplier_distance) \
-       w_cvar(id, sn, NONE, blast_multiplier_min) \
-       w_cvar(id, sn, NONE, blast_refire) \
-       w_cvar(id, sn, NONE, blast_splash_damage) \
-       w_cvar(id, sn, NONE, blast_splash_edgedamage) \
-       w_cvar(id, sn, NONE, blast_splash_force) \
-       w_cvar(id, sn, NONE, blast_splash_force_forwardbias) \
-       w_cvar(id, sn, NONE, blast_splash_multiplier_accuracy) \
-       w_cvar(id, sn, NONE, blast_splash_multiplier_distance) \
-       w_cvar(id, sn, NONE, blast_splash_multiplier_min) \
-       w_cvar(id, sn, NONE, blast_splash_radius) \
-       w_cvar(id, sn, NONE, blast_spread_max) \
-       w_cvar(id, sn, NONE, blast_spread_min) \
-       w_cvar(id, sn, NONE, melee_animtime) \
-       w_cvar(id, sn, NONE, melee_damage) \
-       w_cvar(id, sn, NONE, melee_delay) \
-       w_cvar(id, sn, NONE, melee_force) \
-       w_cvar(id, sn, NONE, melee_multihit) \
-       w_cvar(id, sn, NONE, melee_no_doubleslap) \
-       w_cvar(id, sn, NONE, melee_nonplayerdamage) \
-       w_cvar(id, sn, NONE, melee_range) \
-       w_cvar(id, sn, NONE, melee_refire) \
-       w_cvar(id, sn, NONE, melee_swing_side) \
-       w_cvar(id, sn, NONE, melee_swing_up) \
-       w_cvar(id, sn, NONE, melee_time) \
-       w_cvar(id, sn, NONE, melee_traces) \
-       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
-SHOCKWAVE_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-#endif
-#ifdef CSQC
-void Net_ReadShockwaveParticle(void);
-.vector sw_shotorg;
-.vector sw_shotdir;
-.float sw_distance;
-.float sw_spread_max;
-.float sw_spread_min;
-.float sw_time;
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_shockwave(void)
-{
-       //if(autocvar_sv_q3acompat_machineshockwaveswap) // WEAPONTODO
-       if(autocvar_sv_q3acompat_machineshotgunswap)
-       if(self.classname != "droppedweapon")
-       {
-               weapon_defaultspawnfunc(WEP_MACHINEGUN.m_id);
-               return;
-       }
-       weapon_defaultspawnfunc(WEP_SHOCKWAVE.m_id);
-}
-
-const float MAX_SHOCKWAVE_HITS = 10;
-//#define DEBUG_SHOCKWAVE
-
-.float swing_prev;
-.entity swing_alreadyhit;
-.float shockwave_blasttime;
-entity shockwave_hit[MAX_SHOCKWAVE_HITS];
-float shockwave_hit_damage[MAX_SHOCKWAVE_HITS];
-vector shockwave_hit_force[MAX_SHOCKWAVE_HITS];
-
-// MELEE ATTACK MODE
-void W_Shockwave_Melee_Think(void)
-{
-       // declarations
-       float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
-       entity target_victim;
-       vector targpos;
-
-       // check to see if we can still continue, otherwise give up now
-       if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR(shockwave, melee_no_doubleslap))
-       {
-               remove(self);
-               return;
-       }
-
-       // set start time of melee
-       if(!self.cnt)
-       {
-               self.cnt = time;
-               W_PlayStrengthSound(self.realowner);
-       }
-
-       // update values for v_* vectors
-       makevectors(self.realowner.v_angle);
-
-       // calculate swing percentage based on time
-       meleetime = WEP_CVAR(shockwave, melee_time) * W_WeaponRateFactor();
-       swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10);
-       f = ((1 - swing) * WEP_CVAR(shockwave, melee_traces));
-
-       // perform the traces needed for this frame
-       for(i=self.swing_prev; i < f; ++i)
-       {
-               swing_factor = ((1 - (i / WEP_CVAR(shockwave, melee_traces))) * 2 - 1);
-
-               targpos = (self.realowner.origin + self.realowner.view_ofs
-                       + (v_forward * WEP_CVAR(shockwave, melee_range))
-                       + (v_up * swing_factor * WEP_CVAR(shockwave, melee_swing_up))
-                       + (v_right * swing_factor * WEP_CVAR(shockwave, melee_swing_side)));
-
-               WarpZone_traceline_antilag(
-                       self.realowner,
-                       (self.realowner.origin + self.realowner.view_ofs),
-                       targpos,
-                       false,
-                       self.realowner,
-                       ANTILAG_LATENCY(self.realowner)
-               );
-
-               // draw lightning beams for debugging
-#ifdef DEBUG_SHOCKWAVE
-               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');
-#endif
-
-               is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body" || IS_MONSTER(trace_ent));
-
-               if((trace_fraction < 1) // if trace is good, apply the damage and remove self if necessary
-                       && (trace_ent.takedamage == DAMAGE_AIM)
-                       && (trace_ent != self.swing_alreadyhit)
-                       && (is_player || WEP_CVAR(shockwave, melee_nonplayerdamage)))
-               {
-                       target_victim = trace_ent; // so it persists through other calls
-
-                       if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught
-                               swing_damage = (WEP_CVAR(shockwave, melee_damage) * min(1, swing_factor + 1));
-                       else
-                               swing_damage = (WEP_CVAR(shockwave, melee_nonplayerdamage) * min(1, swing_factor + 1));
-
-                       // trigger damage with this calculated info
-                       Damage(
-                               target_victim,
-                               self.realowner,
-                               self.realowner,
-                               swing_damage,
-                               (WEP_SHOCKWAVE.m_id | HITTYPE_SECONDARY),
-                               (self.realowner.origin + self.realowner.view_ofs),
-                               (v_forward * WEP_CVAR(shockwave, melee_force))
-                       );
-
-                       // handle accuracy
-                       if(accuracy_isgooddamage(self.realowner, target_victim))
-                               { accuracy_add(self.realowner, WEP_SHOCKWAVE.m_id, 0, swing_damage); }
-
-                       #ifdef DEBUG_SHOCKWAVE
-                       LOG_INFO(sprintf(
-                               "MELEE: %s hitting %s with %f damage (factor: %f) at %f time.\n",
-                               self.realowner.netname,
-                               target_victim.netname,
-                               swing_damage,
-                               swing_factor,
-                               time
-                       ));
-                       #endif
-
-                       // allow multiple hits with one swing, but not against the same player twice
-                       if(WEP_CVAR(shockwave, melee_multihit))
-                       {
-                               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_Shockwave_Melee(void)
-{
-       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;
-       meleetemp = spawn();
-       meleetemp.owner = meleetemp.realowner = self;
-       meleetemp.think = W_Shockwave_Melee_Think;
-       meleetemp.nextthink = time + WEP_CVAR(shockwave, melee_delay) * W_WeaponRateFactor();
-       W_SetupShot_Range(self, true, 0, "", 0, WEP_CVAR(shockwave, melee_damage), WEP_CVAR(shockwave, melee_range));
-}
-
-// SHOCKWAVE ATTACK MODE
-float W_Shockwave_Attack_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(shockwave, blast_spread_min) * (1 - spreadlimit))
-                       +
-                       (WEP_CVAR(shockwave, blast_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_Shockwave_Attack_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_Shockwave_Attack_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_Shockwave_Attack_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_Shockwave_Attack_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;
-}
-
-float W_Shockwave_Attack_CheckHit(
-       float queue,
-       entity head,
-       vector final_force,
-       float final_damage)
-{
-       if(!head) { return false; }
-       float i;
-
-       for(i = 0; 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_Shockwave_Send(void)
-{
-       WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
-       WriteByte(MSG_BROADCAST, TE_CSQC_SHOCKWAVEPARTICLE);
-       WriteCoord(MSG_BROADCAST, w_shotorg.x);
-       WriteCoord(MSG_BROADCAST, w_shotorg.y);
-       WriteCoord(MSG_BROADCAST, w_shotorg.z);
-       WriteCoord(MSG_BROADCAST, w_shotdir.x);
-       WriteCoord(MSG_BROADCAST, w_shotdir.y);
-       WriteCoord(MSG_BROADCAST, w_shotdir.z);
-       WriteShort(MSG_BROADCAST, WEP_CVAR(shockwave, blast_distance));
-       WriteByte(MSG_BROADCAST, bound(0, WEP_CVAR(shockwave, blast_spread_max), 255));
-       WriteByte(MSG_BROADCAST, bound(0, WEP_CVAR(shockwave, blast_spread_min), 255));
-       WriteByte(MSG_BROADCAST, num_for_edict(self));
-}
-
-void W_Shockwave_Attack(void)
-{
-       // declarations
-       float multiplier, multiplier_from_accuracy, multiplier_from_distance;
-       float final_damage;
-       vector final_force, center, vel;
-       entity head;
-
-       float i, queue = 0;
-
-       // set up the shot direction
-       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;
-       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
-       W_Shockwave_Send();
-       Damage_DamageInfo(
-               attack_hitpos,
-               WEP_CVAR(shockwave, blast_splash_damage),
-               WEP_CVAR(shockwave, blast_splash_edgedamage),
-               WEP_CVAR(shockwave, blast_splash_radius),
-               w_shotdir * WEP_CVAR(shockwave, blast_splash_force),
-               WEP_SHOCKWAVE.m_id,
-               0,
-               self
-       );
-
-       // splash damage/jumping trace
-       head = WarpZone_FindRadius(
-               attack_hitpos,
-               max(
-                       WEP_CVAR(shockwave, blast_splash_radius),
-                       WEP_CVAR(shockwave, blast_jump_radius)
-               ),
-               false
-       );
-
-       while(head)
-       {
-               if(head.takedamage)
-               {
-                       float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest);
-
-                       if((head == self) && (distance_to_head <= WEP_CVAR(shockwave, blast_jump_radius)))
-                       {
-                               // ========================
-                               //  BLAST JUMP CALCULATION
-                               // ========================
-
-                               // calculate importance of distance and accuracy for this attack
-                               multiplier_from_accuracy = (1 -
-                                       (distance_to_head ?
-                                               min(1, (distance_to_head / WEP_CVAR(shockwave, blast_jump_radius)))
-                                               :
-                                               0
-                                       )
-                               );
-                               multiplier_from_distance = (1 -
-                                       (distance_to_hit ?
-                                               min(1, (distance_to_hit / distance_to_end))
-                                               :
-                                               0
-                                       )
-                               );
-                               multiplier =
-                                       max(
-                                               WEP_CVAR(shockwave, blast_jump_multiplier_min),
-                                               (
-                                                       (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_jump_multiplier_accuracy))
-                                                       +
-                                                       (multiplier_from_distance * WEP_CVAR(shockwave, blast_jump_multiplier_distance))
-                                               )
-                                       );
-
-                               // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
-                               final_damage =
-                                       (
-                                               (WEP_CVAR(shockwave, blast_jump_damage) * multiplier)
-                                               +
-                                               (WEP_CVAR(shockwave, blast_jump_edgedamage) * (1 - multiplier))
-                                       );
-
-                               // figure out the direction of force
-                               vel = normalize(combine_to_vector(head.velocity.x, head.velocity.y, 0));
-                               vel *=
-                                       (
-                                               bound(0, (vlen(vel) / autocvar_sv_maxspeed), 1)
-                                               *
-                                               WEP_CVAR(shockwave, blast_jump_force_velocitybias)
-                                       );
-                               final_force = normalize((CENTER_OR_VIEWOFS(head) - attack_hitpos) + vel);
-
-                               // now multiply the direction by force units
-                               final_force *= (WEP_CVAR(shockwave, blast_jump_force) * multiplier);
-                               final_force.z *= WEP_CVAR(shockwave, blast_jump_force_zscale);
-
-                               // trigger damage with this calculated info
-                               Damage(
-                                       head,
-                                       self,
-                                       self,
-                                       final_damage,
-                                       WEP_SHOCKWAVE.m_id,
-                                       head.origin,
-                                       final_force
-                               );
-
-                               #ifdef DEBUG_SHOCKWAVE
-                               LOG_INFO(sprintf(
-                                       "SELF HIT: multiplier = %f, damage = %f, force = %f... "
-                                       "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n",
-                                       multiplier,
-                                       final_damage,
-                                       vlen(final_force),
-                                       multiplier_from_accuracy,
-                                       multiplier_from_distance
-                               ));
-                               #endif
-                       }
-                       else if(distance_to_head <= WEP_CVAR(shockwave, blast_splash_radius))
-                       {
-                               // ==========================
-                               //  BLAST SPLASH CALCULATION
-                               // ==========================
-
-                               // calculate importance of distance and accuracy for this attack
-                               multiplier_from_accuracy = (1 -
-                                       (distance_to_head ?
-                                               min(1, (distance_to_head / WEP_CVAR(shockwave, blast_splash_radius)))
-                                               :
-                                               0
-                                       )
-                               );
-                               multiplier_from_distance = (1 -
-                                       (distance_to_hit ?
-                                               min(1, (distance_to_hit / distance_to_end))
-                                               :
-                                               0
-                                       )
-                               );
-                               multiplier =
-                                       max(
-                                               WEP_CVAR(shockwave, blast_splash_multiplier_min),
-                                               (
-                                                       (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_splash_multiplier_accuracy))
-                                                       +
-                                                       (multiplier_from_distance * WEP_CVAR(shockwave, blast_splash_multiplier_distance))
-                                               )
-                                       );
-
-                               // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
-                               final_damage =
-                                       (
-                                               (WEP_CVAR(shockwave, blast_splash_damage) * multiplier)
-                                               +
-                                               (WEP_CVAR(shockwave, blast_splash_edgedamage) * (1 - multiplier))
-                                       );
-
-                               // figure out the direction of force
-                               final_force = (w_shotdir * WEP_CVAR(shockwave, blast_splash_force_forwardbias));
-                               final_force = normalize(CENTER_OR_VIEWOFS(head) - (attack_hitpos - final_force));
-                               //te_lightning2(world, attack_hitpos, (attack_hitpos + (final_force * 200)));
-
-                               // now multiply the direction by force units
-                               final_force *= (WEP_CVAR(shockwave, blast_splash_force) * multiplier);
-                               final_force.z *= WEP_CVAR(shockwave, blast_force_zscale);
-
-                               // queue damage with this calculated info
-                               if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { queue = min(queue + 1, MAX_SHOCKWAVE_HITS); }
-
-                               #ifdef DEBUG_SHOCKWAVE
-                               LOG_INFO(sprintf(
-                                       "SPLASH HIT: multiplier = %f, damage = %f, force = %f... "
-                                       "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n",
-                                       multiplier,
-                                       final_damage,
-                                       vlen(final_force),
-                                       multiplier_from_accuracy,
-                                       multiplier_from_distance
-                               ));
-                               #endif
-                       }
-               }
-               head = head.chain;
-       }
-
-       // cone damage trace
-       head = WarpZone_FindRadius(w_shotorg, WEP_CVAR(shockwave, blast_distance), false);
-       while(head)
-       {
-               if((head != self) && head.takedamage)
-               {
-                       // ========================
-                       //  BLAST CONE CALCULATION
-                       // ========================
-
-                       // 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 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);
-                       a = h * (normalize(center - self.origin) * w_shotdir);
-                       // WEAPONTODO: replace with simpler method
-
-                       vector nearest_on_line = (w_shotorg + a * w_shotdir);
-                       vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line);
-
-                       if((vlen(head.WarpZone_findradius_dist) <= WEP_CVAR(shockwave, blast_distance))
-                               && (W_Shockwave_Attack_IsVisible(head, nearest_on_line, w_shotorg, attack_endpos)))
-                       {
-                               // calculate importance of distance and accuracy for this attack
-                               multiplier_from_accuracy = (1 -
-                                       W_Shockwave_Attack_CheckSpread(
-                                               nearest_to_attacker,
-                                               nearest_on_line,
-                                               w_shotorg,
-                                               attack_endpos
-                                       )
-                               );
-                               multiplier_from_distance = (1 -
-                                       (distance_to_hit ?
-                                               min(1, (vlen(head.WarpZone_findradius_dist) / distance_to_end))
-                                               :
-                                               0
-                                       )
-                               );
-                               multiplier =
-                                       max(
-                                               WEP_CVAR(shockwave, blast_multiplier_min),
-                                               (
-                                                       (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_multiplier_accuracy))
-                                                       +
-                                                       (multiplier_from_distance * WEP_CVAR(shockwave, blast_multiplier_distance))
-                                               )
-                                       );
-
-                               // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
-                               final_damage =
-                                       (
-                                               (WEP_CVAR(shockwave, blast_damage) * multiplier)
-                                               +
-                                               (WEP_CVAR(shockwave, blast_edgedamage) * (1 - multiplier))
-                                       );
-
-                               // figure out the direction of force
-                               final_force = (w_shotdir * WEP_CVAR(shockwave, blast_force_forwardbias));
-                               final_force = normalize(center - (nearest_on_line - final_force));
-                               //te_lightning2(world, nearest_on_line, (attack_hitpos + (final_force * 200)));
-
-                               // now multiply the direction by force units
-                               final_force *= (WEP_CVAR(shockwave, blast_force) * multiplier);
-                               final_force.z *= WEP_CVAR(shockwave, blast_force_zscale);
-
-                               // queue damage with this calculated info
-                               if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { queue = min(queue + 1, MAX_SHOCKWAVE_HITS); }
-
-                               #ifdef DEBUG_SHOCKWAVE
-                               LOG_INFO(sprintf(
-                                       "BLAST HIT: multiplier = %f, damage = %f, force = %f... "
-                                       "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n",
-                                       multiplier,
-                                       final_damage,
-                                       vlen(final_force),
-                                       multiplier_from_accuracy,
-                                       multiplier_from_distance
-                               ));
-                               #endif
-                       }
-               }
-               head = head.chain;
-       }
-
-       for(i = 1; i <= queue; ++i)
-       {
-               head = shockwave_hit[i-1];
-               final_force = shockwave_hit_force[i-1];
-               final_damage = shockwave_hit_damage[i-1];
-
-               Damage(
-                       head,
-                       self,
-                       self,
-                       final_damage,
-                       WEP_SHOCKWAVE.m_id,
-                       head.origin,
-                       final_force
-               );
-
-               if(accuracy_isgooddamage(self.realowner, head))
-               {
-                       LOG_INFO("wtf\n");
-                       accuracy_add(self.realowner, WEP_SHOCKWAVE.m_id, 0, final_damage);
-               }
-
-               #ifdef DEBUG_SHOCKWAVE
-               LOG_INFO(sprintf(
-                       "SHOCKWAVE by %s: damage = %f, force = %f.\n",
-                       self.netname,
-                       final_damage,
-                       vlen(final_force)
-               ));
-               #endif
-
-               shockwave_hit[i-1] = world;
-               shockwave_hit_force[i-1] = '0 0 0';
-               shockwave_hit_damage[i-1] = 0;
-       }
-}
-
-bool W_Shockwave(int req)
-{
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       if(vlen(self.origin - self.enemy.origin) <= WEP_CVAR(shockwave, melee_range))
-                               { 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)
-                       {
-                               if(time >= self.shockwave_blasttime) // handle refire separately so the secondary can be fired straight after a primary
-                               {
-                                       if(weapon_prepareattack(0, WEP_CVAR(shockwave, blast_animtime)))
-                                       {
-                                               W_Shockwave_Attack();
-                                               self.shockwave_blasttime = time + WEP_CVAR(shockwave, blast_refire) * W_WeaponRateFactor();
-                                               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(shockwave, blast_animtime), w_ready);
-                                       }
-                               }
-                       }
-                       else if(self.BUTTON_ATCK2)
-                       {
-                               //if(self.clip_load >= 0) // we are not currently reloading
-                               if(!self.crouch) // no crouchmelee please
-                               if(weapon_prepareattack(1, WEP_CVAR(shockwave, melee_refire)))
-                               {
-                                       // attempt forcing playback of the anim by switching to another anim (that we never play) here...
-                                       weapon_thinkf(WFRAME_FIRE1, 0, W_Shockwave_Melee);
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_model("models/uziflash.md3");
-                       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(W_Sound("lasergun_fire"));
-                       precache_sound(W_Sound("shotgun_melee"));
-                       SHOCKWAVE_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP);
-                       return true;
-               }
-               case WR_CHECKAMMO1:
-               case WR_CHECKAMMO2:
-               {
-                       // shockwave has infinite ammo
-                       return true;
-               }
-               case WR_CONFIG:
-               {
-                       SHOCKWAVE_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_THINKING_WITH_PORTALS;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                               return WEAPON_SHOCKWAVE_MURDER_SLAP;
-                       else
-                               return WEAPON_SHOCKWAVE_MURDER;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-// WEAPONTODO: add client side settings for these
-const float SW_MAXALPHA = 0.5;
-const float SW_FADETIME = 0.4;
-const float SW_DISTTOMIN = 200;
-void Draw_Shockwave()
-{
-       // fading/removal control
-       float a = bound(0, (SW_MAXALPHA - ((time - self.sw_time) / SW_FADETIME)), SW_MAXALPHA);
-       if(a < ALPHA_MIN_VISIBLE) { remove(self); }
-
-       // WEAPONTODO: save this only once when creating the entity
-       vector sw_color = getcsqcplayercolor(self.sv_entnum); // GetTeamRGB(GetPlayerColor(self.sv_entnum));
-
-       // WEAPONTODO: trace to find what we actually hit
-       vector endpos = (self.sw_shotorg + (self.sw_shotdir * self.sw_distance));
-
-       vectorvectors(self.sw_shotdir);
-       vector right = v_right; // save this for when we do makevectors later
-       vector up = v_up; // save this for when we do makevectors later
-
-       // WEAPONTODO: combine and simplify these calculations
-       vector min_end = ((self.sw_shotorg + (self.sw_shotdir * SW_DISTTOMIN)) + (up * self.sw_spread_min));
-       vector max_end = (endpos + (up * self.sw_spread_max));
-       float spread_to_min = vlen(normalize(min_end - self.sw_shotorg) - self.sw_shotdir);
-       float spread_to_max = vlen(normalize(max_end - min_end) - self.sw_shotdir);
-
-       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 deviation, angle = '0 0 0';
-       float counter, divisions = 20;
-       for(counter = 0; counter < divisions; ++counter)
-       {
-               // perfect circle effect lines
-               makevectors('0 360 0' * (0.75 + (counter - 0.5) / divisions));
-               angle.y = v_forward.x;
-               angle.z = v_forward.y;
-
-               // first do the spread_to_min effect
-               deviation = angle * spread_to_min;
-               deviation = ((self.sw_shotdir + (right * deviation.y) + (up * deviation.z)));
-               new_min_dist = SW_DISTTOMIN;
-               new_min_end = (self.sw_shotorg + (deviation * new_min_dist));
-               //te_lightning2(world, new_min_end, self.sw_shotorg);
-
-               // then calculate spread_to_max effect
-               deviation = angle * spread_to_max;
-               deviation = ((self.sw_shotdir + (right * deviation.y) + (up * deviation.z)));
-               new_max_dist = vlen(new_min_end - 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)
-               {
-                       // draw from shot origin to min spread radius
-                       R_BeginPolygon("", DRAWFLAG_NORMAL);
-                       R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a);
-                       R_PolygonVertex(new_min_end, '0 0 0', sw_color, a);
-                       R_PolygonVertex(self.sw_shotorg, '0 0 0', sw_color, a);
-                       R_EndPolygon();
-
-                       // draw from min spread radius to max spread radius
-                       R_BeginPolygon("", DRAWFLAG_NORMAL);
-                       R_PolygonVertex(new_min_end, '0 0 0', sw_color, a);
-                       R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a);
-                       R_PolygonVertex(prev_max_end, '0 0 0', sw_color, a);
-                       R_PolygonVertex(new_max_end, '0 0 0', sw_color, a);
-                       R_EndPolygon();
-               }
-
-               prev_min_end = new_min_end;
-               prev_max_end = new_max_end;
-
-               // last division only
-               if((counter + 1) == divisions)
-               {
-                       // draw from shot origin to min spread radius
-                       R_BeginPolygon("", DRAWFLAG_NORMAL);
-                       R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a);
-                       R_PolygonVertex(first_min_end, '0 0 0', sw_color, a);
-                       R_PolygonVertex(self.sw_shotorg, '0 0 0', sw_color, a);
-                       R_EndPolygon();
-
-                       // draw from min spread radius to max spread radius
-                       R_BeginPolygon("", DRAWFLAG_NORMAL);
-                       R_PolygonVertex(first_min_end, '0 0 0', sw_color, a);
-                       R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a);
-                       R_PolygonVertex(prev_max_end, '0 0 0', sw_color, a);
-                       R_PolygonVertex(first_max_end, '0 0 0', sw_color, a);
-                       R_EndPolygon();
-               }
-       }
-}
-
-void Net_ReadShockwaveParticle(void)
-{
-       entity shockwave;
-       shockwave = spawn();
-       shockwave.draw = Draw_Shockwave;
-
-       shockwave.sw_shotorg_x = ReadCoord(); shockwave.sw_shotorg_y = ReadCoord(); shockwave.sw_shotorg_z = ReadCoord();
-       shockwave.sw_shotdir_x = ReadCoord(); shockwave.sw_shotdir_y = ReadCoord(); shockwave.sw_shotdir_z = ReadCoord();
-
-       shockwave.sw_distance = ReadShort();
-       shockwave.sw_spread_max = ReadByte();
-       shockwave.sw_spread_min = ReadByte();
-
-       shockwave.sv_entnum = ReadByte();
-
-       shockwave.sw_time = time;
-}
-
-bool W_Shockwave(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       // handled by Net_ReadShockwaveParticle
-                       //vector org2;
-                       //org2 = w_org + w_backoff * 2;
-                       //pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1);
-                       return false;
-               }
-               case WR_INIT:
-               {
-                       //precache_sound(W_Sound("ric1"));
-                       //precache_sound(W_Sound("ric2"));
-                       //precache_sound(W_Sound("ric3"));
-                       return false;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_shotgun.qc b/qcsrc/common/weapons/w_shotgun.qc
deleted file mode 100644 (file)
index 9024bbf..0000000
+++ /dev/null
@@ -1,392 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ SHOTGUN,
-/* function  */ W_Shotgun,
-/* ammotype  */ ammo_shells,
-/* impulse   */ 2,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
-/* rating    */ BOT_PICKUP_RATING_LOW,
-/* color     */ '0.5 0.25 0',
-/* modelname */ "shotgun",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairshotgun 0.65",
-/* wepimg    */ "weaponshotgun",
-/* refname   */ "shotgun",
-/* wepname   */ _("Shotgun")
-);
-
-#define SHOTGUN_SETTINGS(w_cvar,w_prop) SHOTGUN_SETTINGS_LIST(w_cvar, w_prop, SHOTGUN, shotgun)
-#define SHOTGUN_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, PRI,  ammo) \
-       w_cvar(id, sn, BOTH, animtime) \
-       w_cvar(id, sn, BOTH, refire) \
-       w_cvar(id, sn, PRI,  bullets) \
-       w_cvar(id, sn, BOTH, damage) \
-       w_cvar(id, sn, BOTH, force) \
-       w_cvar(id, sn, PRI,  solidpenetration) \
-       w_cvar(id, sn, PRI,  spread) \
-       w_cvar(id, sn, NONE, secondary) \
-       w_cvar(id, sn, SEC,  melee_time) \
-       w_cvar(id, sn, SEC,  melee_no_doubleslap) \
-       w_cvar(id, sn, SEC,  melee_traces) \
-       w_cvar(id, sn, SEC,  melee_swing_up) \
-       w_cvar(id, sn, SEC,  melee_swing_side) \
-       w_cvar(id, sn, SEC,  melee_nonplayerdamage) \
-       w_cvar(id, sn, SEC,  melee_multihit) \
-       w_cvar(id, sn, SEC,  melee_delay) \
-       w_cvar(id, sn, SEC,  melee_range) \
-       w_cvar(id, sn, SEC,  alt_animtime) \
-       w_cvar(id, sn, SEC,  alt_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
-SHOTGUN_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_shotgun(void) { weapon_defaultspawnfunc(WEP_SHOTGUN.m_id); }
-
-void W_Shotgun_Attack(float isprimary)
-{
-       float   sc;
-       entity flash;
-
-       W_DecreaseAmmo(WEP_CVAR_PRI(shotgun, ammo));
-
-       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.m_id, 0);
-
-       Send_Effect(EFFECT_SHOTGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, WEP_CVAR_PRI(shotgun, ammo));
-
-       // casing code
-       if(autocvar_g_casings >= 1)
-               for(sc = 0;sc < WEP_CVAR_PRI(shotgun, ammo);sc = sc + 1)
-                       SpawnCasing(((random() * 50 + 50) * v_right) - (v_forward * (random() * 25 + 25)) - ((random() * 5 - 30) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 1, self);
-
-       // muzzle flash for 1st person view
-       flash = spawn();
-       setmodel(flash, "models/uziflash.md3"); // precision set below
-       flash.think = SUB_Remove;
-       flash.nextthink = time + 0.06;
-       flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
-       W_AttachToShotorg(flash, '5 0 0');
-}
-
-.float swing_prev;
-.entity swing_alreadyhit;
-void W_Shotgun_Melee_Think(void)
-{
-       // declarations
-       float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
-       entity target_victim;
-       vector targpos;
-
-       if(!self.cnt) // set start time of melee
-       {
-               self.cnt = time;
-               W_PlayStrengthSound(self.realowner);
-       }
-
-       makevectors(self.realowner.v_angle); // update values for v_* vectors
-
-       // calculate swing percentage based on time
-       meleetime = WEP_CVAR_SEC(shotgun, melee_time) * W_WeaponRateFactor();
-       swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10);
-       f = ((1 - swing) * WEP_CVAR_SEC(shotgun, melee_traces));
-
-       // check to see if we can still continue, otherwise give up now
-       if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR_SEC(shotgun, 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_SEC(shotgun, melee_traces))) * 2 - 1);
-
-               targpos = (self.realowner.origin + self.realowner.view_ofs
-                       + (v_forward * WEP_CVAR_SEC(shotgun, melee_range))
-                       + (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));
-
-               // 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(trace_ent));
-
-               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_SEC(shotgun, melee_nonplayerdamage)))
-               {
-                       target_victim = trace_ent; // so it persists through other calls
-
-                       if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught.
-                               swing_damage = (WEP_CVAR_SEC(shotgun, damage) * min(1, swing_factor + 1));
-                       else
-                               swing_damage = (WEP_CVAR_SEC(shotgun, melee_nonplayerdamage) * min(1, swing_factor + 1));
-
-                       //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n"));
-
-                       Damage(target_victim, self.realowner, self.realowner,
-                               swing_damage, WEP_SHOTGUN.m_id | HITTYPE_SECONDARY,
-                               self.realowner.origin + self.realowner.view_ofs,
-                               v_forward * WEP_CVAR_SEC(shotgun, force));
-
-                       if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_SHOTGUN.m_id, 0, swing_damage); }
-
-                       // draw large red flash for debugging
-                       //te_customflash(targpos, 200, 2, '15 0 0');
-
-                       if(WEP_CVAR_SEC(shotgun, melee_multihit)) // allow multiple hits with one swing, but not against the same player twice.
-                       {
-                               self.swing_alreadyhit = target_victim;
-                               continue; // move along to next trace
-                       }
-                       else
-                       {
-                               remove(self);
-                               return;
-                       }
-               }
-       }
-
-       if(time >= self.cnt + meleetime)
-       {
-               // melee is finished
-               remove(self);
-               return;
-       }
-       else
-       {
-               // set up next frame
-               self.swing_prev = i;
-               self.nextthink = time;
-       }
-}
-
-void W_Shotgun_Attack2(void)
-{
-       sound(self, CH_WEAPON_A, W_Sound("shotgun_melee"), VOL_BASE, ATTEN_NORM);
-       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(shotgun, animtime), w_ready);
-
-       entity meleetemp;
-       meleetemp = spawn();
-       meleetemp.realowner = self;
-       meleetemp.think = W_Shotgun_Melee_Think;
-       meleetemp.nextthink = time + WEP_CVAR_SEC(shotgun, melee_delay) * W_WeaponRateFactor();
-       W_SetupShot_Range(self, true, 0, "", 0, WEP_CVAR_SEC(shotgun, damage), WEP_CVAR_SEC(shotgun, melee_range));
-}
-
-// alternate secondary weapon frames
-void W_Shotgun_Attack3_Frame2()
-{
-       if (!WEP_ACTION(self.weapon, WR_CHECKAMMO2))
-       if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
-       {
-               W_SwitchWeapon_Force(self, w_getbestweapon(self));
-               w_ready();
-               return;
-       }
-
-       sound(self, CH_WEAPON_SINGLE, "misc/null.wav", VOL_BASE, ATTN_NORM); // kill previous sound
-       W_Shotgun_Attack(true); // actually is secondary, but we trick the last shot into playing full reload sound
-       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), w_ready);
-}
-void W_Shotgun_Attack3_Frame1()
-{
-       if (!WEP_ACTION(self.weapon, WR_CHECKAMMO2))
-       if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
-       {
-               W_SwitchWeapon_Force(self, w_getbestweapon(self));
-               w_ready();
-               return;
-       }
-
-       W_Shotgun_Attack(false);
-       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame2);
-}
-
-.float shotgun_primarytime;
-
-float W_Shotgun(float req)
-{
-       float ammo_amount;
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       if(vlen(self.origin-self.enemy.origin) <= WEP_CVAR_SEC(shotgun, melee_range))
-                               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(WEP_CVAR(shotgun, reload_ammo) && self.clip_load < WEP_CVAR_PRI(shotgun, ammo)) // forced reload
-                       {
-                               // don't force reload an empty shotgun if its melee attack is active
-                               if(WEP_CVAR(shotgun, secondary) < 2)
-                                       WEP_ACTION(self.weapon, WR_RELOAD);
-                       }
-                       else
-                       {
-                               if(self.BUTTON_ATCK)
-                               {
-                                       if(time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary
-                                       {
-                                               if(weapon_prepareattack(0, WEP_CVAR_PRI(shotgun, animtime)))
-                                               {
-                                                       W_Shotgun_Attack(true);
-                                                       self.shotgun_primarytime = time + WEP_CVAR_PRI(shotgun, refire) * W_WeaponRateFactor();
-                                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(shotgun, animtime), w_ready);
-                                               }
-                                       }
-                               }
-                               else if(self.BUTTON_ATCK2 && WEP_CVAR(shotgun, secondary) == 2)
-                               {
-                                       if(time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary
-                                       {
-                                               if(weapon_prepareattack(0, WEP_CVAR_SEC(shotgun, alt_animtime)))
-                                               {
-                                                       W_Shotgun_Attack(false);
-                                                       self.shotgun_primarytime = time + WEP_CVAR_SEC(shotgun, alt_refire) * W_WeaponRateFactor();
-                                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame1);
-                                               }
-                                       }
-                               }
-                       }
-                       if(self.clip_load >= 0) // we are not currently reloading
-                       if(!self.crouch) // no crouchmelee please
-                       if(WEP_CVAR(shotgun, secondary) == 1)
-                       if((self.BUTTON_ATCK && self.WEP_AMMO(SHOTGUN) <= 0 && !(self.items & IT_UNLIMITED_WEAPON_AMMO)) || self.BUTTON_ATCK2)
-                       if(weapon_prepareattack(1, WEP_CVAR_SEC(shotgun, refire)))
-                       {
-                               // attempt forcing playback of the anim by switching to another anim (that we never play) here...
-                               weapon_thinkf(WFRAME_FIRE1, 0, W_Shotgun_Attack2);
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_model("models/uziflash.md3");
-                       precache_model(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(W_Sound("shotgun_fire"));
-                       precache_sound(W_Sound("shotgun_melee"));
-                       SHOTGUN_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP);
-                       return true;
-               }
-               case WR_SETUP:
-               {
-                       self.ammo_field = ammo_none;
-                       return true;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       ammo_amount = self.WEP_AMMO(SHOTGUN) >= WEP_CVAR_PRI(shotgun, ammo);
-                       ammo_amount += self.(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
-                       return ammo_amount;
-               }
-               case WR_CHECKAMMO2:
-               {
-                       if(IS_BOT_CLIENT(self))
-                       if(vlen(self.origin-self.enemy.origin) > WEP_CVAR_SEC(shotgun, melee_range))
-                               return false; // bots cannot use secondary out of range (fixes constant melee when out of ammo)
-                       switch(WEP_CVAR(shotgun, secondary))
-                       {
-                               case 1: return true; // melee does not use ammo
-                               case 2: // secondary triple shot
-                               {
-                                       ammo_amount = self.WEP_AMMO(SHOTGUN) >= WEP_CVAR_PRI(shotgun, ammo);
-                                       ammo_amount += self.(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
-                                       return ammo_amount;
-                               }
-                               default: return false; // secondary unavailable
-                       }
-               }
-               case WR_CONFIG:
-               {
-                       SHOTGUN_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       W_Reload(WEP_CVAR_PRI(shotgun, ammo), W_Sound("reload")); // WEAPONTODO
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_THINKING_WITH_PORTALS;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                               return WEAPON_SHOTGUN_MURDER_SLAP;
-                       else
-                               return WEAPON_SHOTGUN_MURDER;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-.float prevric;
-float W_Shotgun(float req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 2;
-                       pointparticles(particleeffectnum(EFFECT_SHOTGUN_IMPACT), org2, w_backoff * 1000, 1);
-                       if(!w_issilent && time - self.prevric > 0.25)
-                       {
-                               if(w_random < 0.0165)
-                                       sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTEN_NORM);
-                               else if(w_random < 0.033)
-                                       sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTEN_NORM);
-                               else if(w_random < 0.05)
-                                       sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTEN_NORM);
-                               self.prevric = time;
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("ric1"));
-                       precache_sound(W_Sound("ric2"));
-                       precache_sound(W_Sound("ric3"));
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_tuba.qc b/qcsrc/common/weapons/w_tuba.qc
deleted file mode 100644 (file)
index 686c89e..0000000
+++ /dev/null
@@ -1,514 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ TUBA,
-/* function  */ W_Tuba,
-/* ammotype  */ ammo_none,
-/* impulse   */ 1,
-/* flags     */ WEP_FLAG_HIDDEN | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* color     */ '0 1 0',
-/* modelname */ "tuba",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairtuba",
-/* wepimg    */ "weapontuba",
-/* refname   */ "tuba",
-/* xgettext:no-c-format */
-/* wepname   */ _("@!#%'n Tuba")
-);
-
-#define TUBA_SETTINGS(w_cvar,w_prop) TUBA_SETTINGS_LIST(w_cvar, w_prop, TUBA, tuba)
-#define TUBA_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, NONE, animtime) \
-       w_cvar(id, sn, NONE, attenuation) \
-       w_cvar(id, sn, NONE, damage) \
-       w_cvar(id, sn, NONE, edgedamage) \
-       w_cvar(id, sn, NONE, fadetime) \
-       w_cvar(id, sn, NONE, force) \
-       w_cvar(id, sn, NONE, pitchstep) \
-       w_cvar(id, sn, NONE, radius) \
-       w_cvar(id, sn, NONE, refire) \
-       w_cvar(id, sn, NONE, volume) \
-       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
-TUBA_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-float W_Tuba_MarkClientOnlyFieldsAsUsed() {
-       // These variables are only used by client/tuba.qc. TODO: move client/tuba.qc code here.
-       return WEP_CVAR(tuba, fadetime) + WEP_CVAR(tuba, pitchstep) + WEP_CVAR(tuba, volume);
-}
-
-.entity tuba_note;
-.float tuba_smoketime;
-.float tuba_instrument;
-
-#define MAX_TUBANOTES 32
-.float tuba_lastnotes_last;
-.float tuba_lastnotes_cnt; // over
-.vector tuba_lastnotes[MAX_TUBANOTES];
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_tuba(void) { weapon_defaultspawnfunc(WEP_TUBA.m_id); }
-
-bool W_Tuba_HasPlayed(entity pl, string melody, int instrument, bool ignorepitch, float mintempo, float maxtempo)
-{
-       float i, j, mmin, mmax, nolength;
-       float n = tokenize_console(melody);
-       if(n > pl.tuba_lastnotes_cnt)
-               return false;
-       float pitchshift = 0;
-
-       if(instrument >= 0)
-               if(pl.tuba_instrument != instrument)
-                       return false;
-
-       // verify notes...
-       nolength = false;
-       for(i = 0; i < n; ++i)
-       {
-               vector v = pl.(tuba_lastnotes[(pl.tuba_lastnotes_last - i + MAX_TUBANOTES) % MAX_TUBANOTES]);
-               float ai = stof(argv(n - i - 1));
-               float np = floor(ai);
-               if(ai == np)
-                       nolength = true;
-               // n counts the last played notes BACKWARDS
-               // _x is start
-               // _y is end
-               // _z is note pitch
-               if(ignorepitch && i == 0)
-               {
-                       pitchshift = np - v.z;
-               }
-               else
-               {
-                       if(v.z + pitchshift != np)
-                               return false;
-               }
-       }
-
-       // now we know the right NOTES were played
-       if(!nolength)
-       {
-               // verify rhythm...
-               float ti = 0;
-               if(maxtempo > 0)
-                       mmin = 240 / maxtempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec
-               else
-                       mmin = 0;
-               if(mintempo > 0)
-                       mmax = 240 / mintempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec
-               else
-                       mmax = 240; // you won't try THAT hard... (tempo 1)
-               //printf("initial tempo rules: %f %f\n", mmin, mmax);
-
-               for(i = 0; i < n; ++i)
-               {
-                       vector vi = pl.(tuba_lastnotes[(pl.tuba_lastnotes_last - i + MAX_TUBANOTES) % MAX_TUBANOTES]);
-                       float ai = stof(argv(n - i - 1));
-                       ti -= 1 / (ai - floor(ai));
-                       float tj = ti;
-                       for(j = i+1; j < n; ++j)
-                       {
-                               vector vj = pl.(tuba_lastnotes[(pl.tuba_lastnotes_last - j + MAX_TUBANOTES) % MAX_TUBANOTES]);
-                               float aj = stof(argv(n - j - 1));
-                               tj -= (aj - floor(aj));
-
-                               // note i should be at m*ti+b
-                               // note j should be at m*tj+b
-                               // so:
-                               // we have a LINE l, so that
-                               // vi_x <= l(ti) <= vi_y
-                               // vj_x <= l(tj) <= vj_y
-                               // what is m?
-
-                               // vi_x <= vi_y <= vj_x <= vj_y
-                               // ti <= tj
-                               //printf("first note: %f to %f, should be %f\n", vi_x, vi_y, ti);
-                               //printf("second note: %f to %f, should be %f\n", vj_x, vj_y, tj);
-                               //printf("m1 = %f\n", (vi_x - vj_y) / (ti - tj));
-                               //printf("m2 = %f\n", (vi_y - vj_x) / (ti - tj));
-                               mmin = max(mmin, (vi.x - vj.y) / (ti - tj)); // lower bound
-                               mmax = min(mmax, (vi.y - vj.x) / (ti - tj)); // upper bound
-                       }
-               }
-
-               if(mmin > mmax) // rhythm fail
-                       return false;
-       }
-
-       pl.tuba_lastnotes_cnt = 0;
-
-       return true;
-}
-
-void W_Tuba_NoteOff(void)
-{
-       // we have a note:
-       //   on: self.spawnshieldtime
-       //   off: time
-       //   note: self.cnt
-       if(self.owner.tuba_note == self)
-       {
-               self.owner.tuba_lastnotes_last = (self.owner.tuba_lastnotes_last + 1) % MAX_TUBANOTES;
-               self.owner.(tuba_lastnotes[self.owner.tuba_lastnotes_last]) = eX * self.spawnshieldtime + eY * time + eZ * self.cnt;
-               self.owner.tuba_note = world;
-               self.owner.tuba_lastnotes_cnt = bound(0, self.owner.tuba_lastnotes_cnt + 1, MAX_TUBANOTES);
-
-               string s;
-               s = trigger_magicear_processmessage_forallears(self.owner, 0, world, string_null);
-               if(s != "")
-               {
-                       // simulate a server message
-                       switch(self.tuba_instrument)
-                       {
-                               default:
-                               case 0: // Tuba
-                                       bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Tuba: ^7", s, "\n"));
-                                       break;
-                               case 1:
-                                       bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Accordeon: ^7", s, "\n"));
-                                       break;
-                               case 2:
-                                       bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Klein Bottle: ^7", s, "\n"));
-                                       break;
-                       }
-               }
-       }
-       remove(self);
-}
-
-int W_Tuba_GetNote(entity pl, int hittype)
-{
-       float movestate = 5;
-       if (pl.movement.x < 0)          movestate -= 3;
-       else if (pl.movement.x > 0)     movestate += 3;
-       if (pl.movement.y < 0)          movestate -= 1;
-       else if (pl.movement.y > 0)     movestate += 1;
-
-       int note = 0;
-       switch(movestate)
-       {
-       // layout: originally I wanted
-       //   eb e  e#=f
-       //   B  c  d
-       //   Gb G  G#
-       // but then you only use forward and right key. So to make things more
-       // interesting, I swapped B with e#. Har har har...
-       //   eb e  B
-       // f=e# c  d
-       //   Gb G  G#
-               case 1: note = -6; break; // Gb
-               case 2: note = -5; break; // G
-               case 3: note = -4; break; // G#
-               case 4: note = +5; break; // e#
-               default:
-               case 5: note =  0; break; // c
-               case 6: note = +2; break; // d
-               case 7: note = +3; break; // eb
-               case 8: note = +4; break; // e
-               case 9: note = -1; break; // B
-       }
-       if(pl.BUTTON_CROUCH)
-               note -= 12;
-       if(pl.BUTTON_JUMP)
-               note += 12;
-       if(hittype & HITTYPE_SECONDARY)
-               note += 7;
-
-       // we support two kinds of tubas, those tuned in Eb and those tuned in C
-       // kind of tuba currently is player slot number, or team number if in
-       // teamplay
-       // that way, holes in the range of notes are "plugged"
-       if(teamplay)
-       {
-               if(pl.team == NUM_TEAM_2 || pl.team == NUM_TEAM_4)
-                       note += 3;
-       }
-       else
-       {
-               if(pl.clientcolors & 1)
-                       note += 3;
-       }
-
-       // total range of notes:
-       //                       0
-       //                 ***  ** ****
-       //                        ***  ** ****
-       //     ***  ** ****
-       //            ***  ** ****
-       //     ***  ********************* ****
-       //     -18.........................+12
-       //        ***  ********************* ****
-       //     -18............................+15
-       //     with jump: ... +24
-       //     ... +27
-       return note;
-}
-
-bool W_Tuba_NoteSendEntity(entity to, int sf)
-{
-       int f;
-
-       msg_entity = to;
-       if(!sound_allowed(MSG_ONE, self.realowner))
-               return false;
-
-       WriteByte(MSG_ENTITY, ENT_CLIENT_TUBANOTE);
-       WriteByte(MSG_ENTITY, sf);
-       if(sf & 1)
-       {
-               WriteChar(MSG_ENTITY, self.cnt);
-               f = 0;
-               if(self.realowner != to)
-                       f |= 1;
-               f |= 2 * self.tuba_instrument;
-               WriteByte(MSG_ENTITY, f);
-       }
-       if(sf & 2)
-       {
-               WriteCoord(MSG_ENTITY, self.origin.x);
-               WriteCoord(MSG_ENTITY, self.origin.y);
-               WriteCoord(MSG_ENTITY, self.origin.z);
-       }
-       return true;
-}
-
-void W_Tuba_NoteThink(void)
-{
-       float dist_mult;
-       float vol0, vol1;
-       vector dir0, dir1;
-       vector v;
-       entity e;
-       if(time > self.teleport_time)
-       {
-               W_Tuba_NoteOff();
-               return;
-       }
-       self.nextthink = time;
-       dist_mult = WEP_CVAR(tuba, attenuation) / autocvar_snd_soundradius;
-       FOR_EACH_REALCLIENT(e)
-       if(e != self.realowner)
-       {
-               v = self.origin - (e.origin + e.view_ofs);
-               vol0 = max(0, 1 - vlen(v) * dist_mult);
-               dir0 = normalize(v);
-               v = self.realowner.origin - (e.origin + e.view_ofs);
-               vol1 = max(0, 1 - vlen(v) * dist_mult);
-               dir1 = normalize(v);
-               if(fabs(vol0 - vol1) > 0.005) // 0.5 percent change in volume
-               {
-                       setorigin(self, self.realowner.origin);
-                       self.SendFlags |= 2;
-                       break;
-               }
-               if(dir0 * dir1 < 0.9994) // 2 degrees change in angle
-               {
-                       setorigin(self, self.realowner.origin);
-                       self.SendFlags |= 2;
-                       break;
-               }
-       }
-}
-
-void W_Tuba_NoteOn(float hittype)
-{
-       vector o;
-       float n;
-
-       W_SetupShot(self, false, 2, "", 0, WEP_CVAR(tuba, damage));
-
-       n = W_Tuba_GetNote(self, hittype);
-
-       hittype = 0;
-       if(self.tuba_instrument & 1)
-               hittype |= HITTYPE_SECONDARY;
-       if(self.tuba_instrument & 2)
-               hittype |= HITTYPE_BOUNCE;
-
-       if(self.tuba_note)
-       {
-               if(self.tuba_note.cnt != n || self.tuba_note.tuba_instrument != self.tuba_instrument)
-               {
-                       entity oldself = self;
-                       self = self.tuba_note;
-                       W_Tuba_NoteOff();
-                       self = oldself;
-               }
-       }
-
-       if(!self.tuba_note)
-       {
-               self.tuba_note = spawn();
-               self.tuba_note.owner = self.tuba_note.realowner = self;
-               self.tuba_note.cnt = n;
-               self.tuba_note.tuba_instrument = self.tuba_instrument;
-               self.tuba_note.think = W_Tuba_NoteThink;
-               self.tuba_note.nextthink = time;
-               self.tuba_note.spawnshieldtime = time;
-               Net_LinkEntity(self.tuba_note, false, 0, W_Tuba_NoteSendEntity);
-       }
-
-       self.tuba_note.teleport_time = time + WEP_CVAR(tuba, refire) * 2 * W_WeaponRateFactor(); // so it can get prolonged safely
-
-       //sound(self, c, TUBA_NOTE(n), bound(0, VOL_BASE * cvar("g_balance_tuba_volume"), 1), autocvar_g_balance_tuba_attenuation);
-       RadiusDamage(self, self, WEP_CVAR(tuba, damage), WEP_CVAR(tuba, edgedamage), WEP_CVAR(tuba, radius), world, world, WEP_CVAR(tuba, force), hittype | WEP_TUBA.m_id, world);
-
-       o = gettaginfo(self.exteriorweaponentity, 0);
-       if(time > self.tuba_smoketime)
-       {
-               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;
-       }
-}
-
-bool W_Tuba(int req)
-{
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       // bots cannot play the Tuba well yet
-                       // I think they should start with the recorder first
-                       if(vlen(self.origin - self.enemy.origin) < WEP_CVAR(tuba, radius))
-                       {
-                               if(random() > 0.5)
-                                       self.BUTTON_ATCK = 1;
-                               else
-                                       self.BUTTON_ATCK2 = 1;
-                       }
-
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       if(self.BUTTON_ATCK)
-                       if(weapon_prepareattack(0, WEP_CVAR(tuba, refire)))
-                       {
-                               W_Tuba_NoteOn(0);
-                               //weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_tuba_animtime, w_ready);
-                               weapon_thinkf(WFRAME_IDLE, WEP_CVAR(tuba, animtime), w_ready);
-                       }
-                       if(self.BUTTON_ATCK2)
-                       if(weapon_prepareattack(1, WEP_CVAR(tuba, refire)))
-                       {
-                               W_Tuba_NoteOn(HITTYPE_SECONDARY);
-                               //weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_tuba_animtime, w_ready);
-                               weapon_thinkf(WFRAME_IDLE, WEP_CVAR(tuba, animtime), w_ready);
-                       }
-                       if(self.tuba_note)
-                       {
-                               if(!self.BUTTON_ATCK && !self.BUTTON_ATCK2)
-                               {
-                                       entity oldself = self;
-                                       self = self.tuba_note;
-                                       W_Tuba_NoteOff();
-                                       self = oldself;
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_model(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;
-               }
-               case WR_SETUP:
-               {
-                       self.ammo_field = ammo_none;
-                       self.tuba_instrument = 0;
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       // switch to alternate instruments :)
-                       if(self.weaponentity.state == WS_READY)
-                       {
-                               switch(self.tuba_instrument)
-                               {
-                                       case 0:
-                                               self.tuba_instrument = 1;
-                                               self.weaponname = "akordeon";
-                                               break;
-                                       case 1:
-                                               self.tuba_instrument = 2;
-                                               self.weaponname = "kleinbottle";
-                                               break;
-                                       case 2:
-                                               self.tuba_instrument = 0;
-                                               self.weaponname = "tuba";
-                                               break;
-                               }
-                               W_SetupShot(self, false, 0, "", 0, 0);
-                               Send_Effect(EFFECT_TELEPORT, w_shotorg, '0 0 0', 1);
-                               self.weaponentity.state = WS_INUSE;
-                               weapon_thinkf(WFRAME_RELOAD, 0.5, w_ready);
-                       }
-
-                       return true;
-               }
-               case WR_CHECKAMMO1:
-               case WR_CHECKAMMO2:
-               {
-                       return true; // tuba has infinite ammo
-               }
-               case WR_CONFIG:
-               {
-                       TUBA_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_BOUNCE)
-                               return WEAPON_KLEINBOTTLE_SUICIDE;
-                       else if(w_deathtype & HITTYPE_SECONDARY)
-                               return WEAPON_ACCORDEON_SUICIDE;
-                       else
-                               return WEAPON_TUBA_SUICIDE;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       if(w_deathtype & HITTYPE_BOUNCE)
-                               return WEAPON_KLEINBOTTLE_MURDER;
-                       else if(w_deathtype & HITTYPE_SECONDARY)
-                               return WEAPON_ACCORDEON_MURDER;
-                       else
-                               return WEAPON_TUBA_MURDER;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-bool W_Tuba(int req)
-{
-       // nothing to do here; particles of tuba are handled differently
-       // WEAPONTODO
-
-       switch(req)
-       {
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_vaporizer.qc b/qcsrc/common/weapons/w_vaporizer.qc
deleted file mode 100644 (file)
index 4ed2dc2..0000000
+++ /dev/null
@@ -1,476 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ VAPORIZER,
-/* function  */ W_Vaporizer,
-/* ammotype  */ ammo_cells,
-/* impulse   */ 7,
-/* flags     */ WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_FLAG_SUPERWEAPON | WEP_TYPE_HITSCAN,
-/* rating    */ BOT_PICKUP_RATING_HIGH,
-/* color     */ '0.5 1 1',
-/* modelname */ "minstanex",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairminstanex 0.6",
-/* wepimg    */ "weaponminstanex",
-/* refname   */ "vaporizer",
-/* wepname   */ _("Vaporizer")
-);
-
-#define VAPORIZER_SETTINGS(w_cvar,w_prop) VAPORIZER_SETTINGS_LIST(w_cvar, w_prop, VAPORIZER, vaporizer)
-#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, refire) \
-       w_cvar(id, sn, SEC, ammo) \
-       w_cvar(id, sn, SEC, animtime) \
-       w_cvar(id, sn, SEC, damage) \
-       w_cvar(id, sn, SEC, delay) \
-       w_cvar(id, sn, SEC, edgedamage) \
-       w_cvar(id, sn, SEC, force) \
-       w_cvar(id, sn, SEC, lifetime) \
-       w_cvar(id, sn, SEC, radius) \
-       w_cvar(id, sn, SEC, refire) \
-       w_cvar(id, sn, SEC, shotangle) \
-       w_cvar(id, sn, SEC, speed) \
-       w_cvar(id, sn, SEC, spread) \
-       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
-VAPORIZER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-.float vaporizer_lasthit;
-.float jump_interval;
-.float jump_interval2;
-.bool held_down;
-.float rm_force;
-.float rm_damage;
-.float rm_edmg;
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_vaporizer(void) { weapon_defaultspawnfunc(WEP_VAPORIZER.m_id); }
-void spawnfunc_weapon_minstanex(void) { spawnfunc_weapon_vaporizer(); }
-
-void W_RocketMinsta_Explosion(vector loc)
-{
-       if(accuracy_canbegooddamage(self))
-               accuracy_add(self, WEP_DEVASTATOR.m_id, autocvar_g_rm_damage, 0);
-       entity dmgent = spawn();
-       dmgent.owner = dmgent.realowner = self;
-       setorigin(dmgent, loc);
-       RadiusDamage (dmgent, self, autocvar_g_rm_damage, autocvar_g_rm_edgedamage, autocvar_g_rm_radius, world, world, autocvar_g_rm_force, WEP_DEVASTATOR.m_id | HITTYPE_SPLASH, other);
-       remove(dmgent);
-}
-
-void W_Vaporizer_Attack(void)
-{
-       float flying;
-       flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last
-
-       W_SetupShot(self, true, 0, "", CH_WEAPON_A, 10000);
-       // 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, 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.m_id);
-
-       if(yoda && flying)
-               Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA);
-       if(damage_goodhits && self.vaporizer_lasthit)
-       {
-               Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_IMPRESSIVE);
-               damage_goodhits = 0; // only every second time
-       }
-
-       self.vaporizer_lasthit = damage_goodhits;
-
-       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);
-       switch(self.team)
-       {
-               case NUM_TEAM_1:   // Red
-                       if(damage_goodhits)
-                               Send_Effect(EFFECT_VAPORIZER_RED_HIT, w_shotorg, v, 1);
-                       else
-                               Send_Effect(EFFECT_VAPORIZER_RED, w_shotorg, v, 1);
-                       break;
-               case NUM_TEAM_2:   // Blue
-                       if(damage_goodhits)
-                               Send_Effect(EFFECT_VAPORIZER_BLUE_HIT, w_shotorg, v, 1);
-                       else
-                               Send_Effect(EFFECT_VAPORIZER_BLUE, w_shotorg, v, 1);
-                       break;
-               case NUM_TEAM_3:   // Yellow
-                       if(damage_goodhits)
-                               Send_Effect(EFFECT_VAPORIZER_YELLOW_HIT, w_shotorg, v, 1);
-                       else
-                               Send_Effect(EFFECT_VAPORIZER_YELLOW, w_shotorg, v, 1);
-                       break;
-               case NUM_TEAM_4:   // Pink
-                       if(damage_goodhits)
-                               Send_Effect(EFFECT_VAPORIZER_PINK_HIT, w_shotorg, v, 1);
-                       else
-                               Send_Effect(EFFECT_VAPORIZER_PINK, w_shotorg, v, 1);
-                       break;
-               default:
-                       if(damage_goodhits)
-                               Send_Effect_("TE_TEI_G3_HIT", w_shotorg, v, 1);
-                       else
-                               Send_Effect_("TE_TEI_G3", w_shotorg, v, 1);
-                       break;
-       }
-
-       if(autocvar_g_rm)
-       if(!(trace_dphitq3surfaceflags & (Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT)))
-               W_RocketMinsta_Explosion(trace_endpos);
-
-       W_DecreaseAmmo(((g_instagib) ? 1 : WEP_CVAR_PRI(vaporizer, ammo)));
-}
-
-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.m_id;
-       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.m_id;
-        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);
-
-        MUTATOR_CALLHOOK(EditProjectile, self, proj);
-        counter++;
-    }
-}
-
-void W_RocketMinsta_Attack3 (void)
-{
-       makevectors(self.v_angle);
-       
-       entity proj;
-       float counter = 0;
-       float total = 1;
-
-       int w = self.weapon;
-       self.weapon = WEP_ELECTRO.m_id;
-       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.m_id;
-        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);
-
-        MUTATOR_CALLHOOK(EditProjectile, self, proj);
-        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));
-
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       if(self.WEP_AMMO(VAPORIZER) > 0)
-                               self.BUTTON_ATCK = bot_aim(1000000, 0, 1, false);
-                       else
-                               self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_SEC(vaporizer, speed), 0, WEP_CVAR_SEC(vaporizer, lifetime), false); // WEAPONTODO: replace with proper vaporizer cvars
-
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       // if the laser uses load, we also consider its ammo for reloading
-                       if(WEP_CVAR(vaporizer, reload_ammo) && WEP_CVAR_SEC(vaporizer, ammo) && self.clip_load < min(vaporizer_ammo, WEP_CVAR_SEC(vaporizer, ammo))) // forced reload
-                               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);
-                       if(self.BUTTON_ATCK && (self.ammo_cells || !autocvar_g_rm) && !forbidWeaponUse(self))
-                       {
-                               if(weapon_prepareattack(0, WEP_CVAR_PRI(vaporizer, refire)))
-                               {
-                                       W_Vaporizer_Attack();
-                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(vaporizer, animtime), w_ready);
-                               }
-                       }
-                       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();
-
-                                       // decrease ammo for the laser?
-                                       if(WEP_CVAR_SEC(vaporizer, ammo))
-                                               W_DecreaseAmmo(WEP_CVAR_SEC(vaporizer, ammo));
-
-                                       // ugly instagib hack to reuse the fire mode of the laser
-                                       makevectors(self.v_angle);
-                                       int oldwep = self.weapon; // we can't avoid this hack
-                                       self.weapon = WEP_BLASTER.m_id;
-                                       W_Blaster_Attack(
-                                               WEP_BLASTER.m_id | HITTYPE_SECONDARY,
-                                               WEP_CVAR_SEC(vaporizer, shotangle),
-                                               WEP_CVAR_SEC(vaporizer, damage),
-                                               WEP_CVAR_SEC(vaporizer, edgedamage),
-                                               WEP_CVAR_SEC(vaporizer, radius),
-                                               WEP_CVAR_SEC(vaporizer, force),
-                                               WEP_CVAR_SEC(vaporizer, speed),
-                                               WEP_CVAR_SEC(vaporizer, spread),
-                                               WEP_CVAR_SEC(vaporizer, delay),
-                                               WEP_CVAR_SEC(vaporizer, lifetime)
-                                       );
-                                       self.weapon = oldwep;
-
-                                       // now do normal refire
-                                       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(W_Model("g_minstanex.md3"));
-                       precache_model(W_Model("v_minstanex.md3"));
-                       precache_model(W_Model("h_minstanex.iqm"));
-                       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;
-               }
-               case WR_SETUP:
-               {
-                       self.ammo_field = WEP_AMMO(VAPORIZER);
-                       self.vaporizer_lasthit = 0;
-                       return true;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       ammo_amount = self.WEP_AMMO(VAPORIZER) >= vaporizer_ammo;
-                       ammo_amount += self.(weapon_load[WEP_VAPORIZER.m_id]) >= vaporizer_ammo;
-                       return ammo_amount;
-               }
-               case WR_CHECKAMMO2:
-               {
-                       if(!WEP_CVAR_SEC(vaporizer, ammo))
-                               return true;
-                       ammo_amount = self.WEP_AMMO(VAPORIZER) >= WEP_CVAR_SEC(vaporizer, ammo);
-                       ammo_amount += self.(weapon_load[WEP_VAPORIZER.m_id]) >= WEP_CVAR_SEC(vaporizer, ammo);
-                       return ammo_amount;
-               }
-               case WR_CONFIG:
-               {
-                       VAPORIZER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RESETPLAYER:
-               {
-                       self.vaporizer_lasthit = 0;
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       float used_ammo;
-                       if(WEP_CVAR_SEC(vaporizer, ammo))
-                               used_ammo = min(vaporizer_ammo, WEP_CVAR_SEC(vaporizer, ammo));
-                       else
-                               used_ammo = vaporizer_ammo;
-
-                       W_Reload(used_ammo, W_Sound("reload"));
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_THINKING_WITH_PORTALS;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       return WEAPON_VAPORIZER_MURDER;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-float W_Vaporizer(float req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2 = w_org + w_backoff * 6;
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                       {
-                               pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1);
-                               if(!w_issilent) { sound(self, CH_SHOTS, W_Sound("laserimpact"), VOL_BASE, ATTN_NORM); }
-                       }
-                       else
-                       {
-                               pointparticles(particleeffectnum(EFFECT_VORTEX_IMPACT), org2, '0 0 0', 1);
-                               if(!w_issilent) { sound(self, CH_SHOTS, W_Sound("neximpact"), VOL_BASE, ATTN_NORM); }
-                       }
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("laserimpact"));
-                       precache_sound(W_Sound("neximpact"));
-                       if(autocvar_cl_reticle && autocvar_cl_reticle_weapon)
-                       {
-                               precache_pic("gfx/reticle_nex");
-                       }
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       if(button_zoom || zoomscript_caught)
-                       {
-                               reticle_image = "gfx/reticle_nex";
-                               return true;
-                       }
-                       else
-                       {
-                               // no weapon specific image for this weapon
-                               return false;
-                       }
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_vortex.qc b/qcsrc/common/weapons/w_vortex.qc
deleted file mode 100644 (file)
index 3aa17e5..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-#ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ VORTEX,
-/* function  */ W_Vortex,
-/* ammotype  */ ammo_cells,
-/* impulse   */ 7,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
-/* rating    */ BOT_PICKUP_RATING_HIGH,
-/* color     */ '0.5 1 1',
-/* modelname */ "nex",
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairnex 0.65",
-/* wepimg    */ "weaponnex",
-/* refname   */ "vortex",
-/* wepname   */ _("Vortex")
-);
-
-#define VORTEX_SETTINGS(w_cvar,w_prop) VORTEX_SETTINGS_LIST(w_cvar, w_prop, VORTEX, vortex)
-#define VORTEX_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, BOTH, ammo) \
-       w_cvar(id, sn, BOTH, animtime) \
-       w_cvar(id, sn, BOTH, damage) \
-       w_cvar(id, sn, BOTH, force) \
-       w_cvar(id, sn, BOTH, damagefalloff_mindist) \
-       w_cvar(id, sn, BOTH, damagefalloff_maxdist) \
-       w_cvar(id, sn, BOTH, damagefalloff_halflife) \
-       w_cvar(id, sn, BOTH, damagefalloff_forcehalflife) \
-       w_cvar(id, sn, BOTH, refire) \
-       w_cvar(id, sn, NONE, charge) \
-       w_cvar(id, sn, NONE, charge_mindmg) \
-       w_cvar(id, sn, NONE, charge_shot_multiplier) \
-       w_cvar(id, sn, NONE, charge_animlimit) \
-       w_cvar(id, sn, NONE, charge_limit) \
-       w_cvar(id, sn, NONE, charge_rate) \
-       w_cvar(id, sn, NONE, charge_rot_rate) \
-       w_cvar(id, sn, NONE, charge_rot_pause) \
-       w_cvar(id, sn, NONE, charge_start) \
-       w_cvar(id, sn, NONE, charge_minspeed) \
-       w_cvar(id, sn, NONE, charge_maxspeed) \
-       w_cvar(id, sn, NONE, charge_velocity_rate) \
-       w_cvar(id, sn, NONE, secondary) \
-       w_cvar(id, sn, SEC,  chargepool) \
-       w_cvar(id, sn, SEC,  chargepool_regen) \
-       w_cvar(id, sn, SEC,  chargepool_pause_regen) \
-       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
-VORTEX_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
-
-.float vortex_lasthit;
-#endif
-#endif
-#ifdef IMPLEMENTATION
-#ifdef SVQC
-void spawnfunc_weapon_vortex(void) { weapon_defaultspawnfunc(WEP_VORTEX.m_id); }
-void spawnfunc_weapon_nex(void) { spawnfunc_weapon_vortex(); }
-
-void SendCSQCVortexBeamParticle(float charge) {
-       vector v;
-       v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
-       WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
-       WriteByte(MSG_BROADCAST, TE_CSQC_VORTEXBEAMPARTICLE);
-       WriteCoord(MSG_BROADCAST, w_shotorg.x);
-       WriteCoord(MSG_BROADCAST, w_shotorg.y);
-       WriteCoord(MSG_BROADCAST, w_shotorg.z);
-       WriteCoord(MSG_BROADCAST, v.x);
-       WriteCoord(MSG_BROADCAST, v.y);
-       WriteCoord(MSG_BROADCAST, v.z);
-       WriteByte(MSG_BROADCAST, bound(0, 255 * charge, 255));
-}
-
-void W_Vortex_Attack(float issecondary)
-{
-       float mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, myammo, charge;
-
-       mydmg = WEP_CVAR_BOTH(vortex, !issecondary, damage);
-       myforce = WEP_CVAR_BOTH(vortex, !issecondary, force);
-       mymindist = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_mindist);
-       mymaxdist = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_maxdist);
-       myhalflife = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_halflife);
-       myforcehalflife = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_forcehalflife);
-       myammo = WEP_CVAR_BOTH(vortex, !issecondary, ammo);
-
-       float flying;
-       flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last
-
-       if(WEP_CVAR(vortex, charge))
-       {
-               charge = WEP_CVAR(vortex, charge_mindmg) / mydmg + (1 - WEP_CVAR(vortex, charge_mindmg) / mydmg) * self.vortex_charge;
-               self.vortex_charge *= WEP_CVAR(vortex, charge_shot_multiplier); // do this AFTER setting mydmg/myforce
-               // O RLY? -- divVerent
-               // YA RLY -- FruitieX
-       }
-       else
-               charge = 1;
-       mydmg *= charge;
-       myforce *= charge;
-
-       W_SetupShot(self, true, 5, W_Sound("nexfire"), 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
-       {
-               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;
-       damage_goodhits = 0;
-       FireRailgunBullet(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, WEP_VORTEX.m_id);
-
-       if(yoda && flying)
-               Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA);
-       if(damage_goodhits && self.vortex_lasthit)
-       {
-               Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_IMPRESSIVE);
-               damage_goodhits = 0; // only every second time
-       }
-
-       self.vortex_lasthit = damage_goodhits;
-
-       //beam and muzzle flash done on client
-       SendCSQCVortexBeamParticle(charge);
-
-       W_DecreaseAmmo(myammo);
-}
-
-void spawnfunc_weapon_vortex(void); // defined in t_items.qc
-
-.float vortex_chargepool_pauseregen_finished;
-bool W_Vortex(int req)
-{
-       float dt;
-       float ammo_amount;
-       switch(req)
-       {
-               case WR_AIM:
-               {
-                       if(bot_aim(1000000, 0, 1, false))
-                               self.BUTTON_ATCK = true;
-                       else
-                       {
-                               if(WEP_CVAR(vortex, charge))
-                                       self.BUTTON_ATCK2 = true;
-                       }
-                       return true;
-               }
-               case WR_THINK:
-               {
-                       if(WEP_CVAR(vortex, charge) && self.vortex_charge < WEP_CVAR(vortex, charge_limit))
-                               self.vortex_charge = min(1, self.vortex_charge + WEP_CVAR(vortex, charge_rate) * frametime / W_TICSPERFRAME);
-
-                       if(WEP_CVAR_SEC(vortex, chargepool))
-                               if(self.vortex_chargepool_ammo < 1)
-                               {
-                                       if(self.vortex_chargepool_pauseregen_finished < time)
-                                               self.vortex_chargepool_ammo = min(1, self.vortex_chargepool_ammo + WEP_CVAR_SEC(vortex, chargepool_regen) * frametime / W_TICSPERFRAME);
-                                       self.pauseregen_finished = max(self.pauseregen_finished, time + WEP_CVAR_SEC(vortex, chargepool_pause_regen));
-                               }
-
-                       if(autocvar_g_balance_vortex_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(vortex, ammo), WEP_CVAR_SEC(vortex, ammo))) // forced reload
-                               WEP_ACTION(self.weapon, WR_RELOAD);
-                       else
-                       {
-                               if(self.BUTTON_ATCK)
-                               {
-                                       if(weapon_prepareattack(0, WEP_CVAR_PRI(vortex, refire)))
-                                       {
-                                               W_Vortex_Attack(0);
-                                               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(vortex, animtime), w_ready);
-                                       }
-                               }
-                               if((WEP_CVAR(vortex, charge) && !WEP_CVAR(vortex, secondary)) ? (self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) : self.BUTTON_ATCK2)
-                               {
-                                       if(WEP_CVAR(vortex, charge))
-                                       {
-                                               self.vortex_charge_rottime = time + WEP_CVAR(vortex, charge_rot_pause);
-                                               dt = frametime / W_TICSPERFRAME;
-
-                                               if(self.vortex_charge < 1)
-                                               {
-                                                       if(WEP_CVAR_SEC(vortex, chargepool))
-                                                       {
-                                                               if(WEP_CVAR_SEC(vortex, ammo))
-                                                               {
-                                                                       // always deplete if secondary is held
-                                                                       self.vortex_chargepool_ammo = max(0, self.vortex_chargepool_ammo - WEP_CVAR_SEC(vortex, ammo) * dt);
-
-                                                                       dt = min(dt, (1 - self.vortex_charge) / WEP_CVAR(vortex, charge_rate));
-                                                                       self.vortex_chargepool_pauseregen_finished = time + WEP_CVAR_SEC(vortex, chargepool_pause_regen);
-                                                                       dt = min(dt, self.vortex_chargepool_ammo);
-                                                                       dt = max(0, dt);
-
-                                                                       self.vortex_charge += dt * WEP_CVAR(vortex, charge_rate);
-                                                               }
-                                                       }
-
-                                                       else if(WEP_CVAR_SEC(vortex, ammo))
-                                                       {
-                                                               if(self.BUTTON_ATCK2) // only eat ammo when the button is pressed
-                                                               {
-                                                                       dt = min(dt, (1 - self.vortex_charge) / WEP_CVAR(vortex, charge_rate));
-                                                                       if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
-                                                                       {
-                                                                               // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
-                                                                               if(autocvar_g_balance_vortex_reload_ammo)
-                                                                               {
-                                                                                       dt = min(dt, (self.clip_load - WEP_CVAR_PRI(vortex, ammo)) / WEP_CVAR_SEC(vortex, ammo));
-                                                                                       dt = max(0, dt);
-                                                                                       if(dt > 0)
-                                                                                       {
-                                                                                               self.clip_load = max(WEP_CVAR_SEC(vortex, ammo), self.clip_load - WEP_CVAR_SEC(vortex, ammo) * dt);
-                                                                                       }
-                                                                                       self.(weapon_load[WEP_VORTEX.m_id]) = self.clip_load;
-                                                                               }
-                                                                               else
-                                                                               {
-                                                                                       dt = min(dt, (self.WEP_AMMO(VORTEX) - WEP_CVAR_PRI(vortex, ammo)) / WEP_CVAR_SEC(vortex, ammo));
-                                                                                       dt = max(0, dt);
-                                                                                       if(dt > 0)
-                                                                                       {
-                                                                                               self.WEP_AMMO(VORTEX) = max(WEP_CVAR_SEC(vortex, ammo), self.WEP_AMMO(VORTEX) - WEP_CVAR_SEC(vortex, ammo) * dt);
-                                                                                       }
-                                                                               }
-                                                                       }
-                                                                       self.vortex_charge += dt * WEP_CVAR(vortex, charge_rate);
-                                                               }
-                                                       }
-
-                                                       else
-                                                       {
-                                                               dt = min(dt, (1 - self.vortex_charge) / WEP_CVAR(vortex, charge_rate));
-                                                               self.vortex_charge += dt * WEP_CVAR(vortex, charge_rate);
-                                                       }
-                                               }
-                                       }
-                                       else if(WEP_CVAR(vortex, secondary))
-                                       {
-                                               if(weapon_prepareattack(0, WEP_CVAR_SEC(vortex, refire)))
-                                               {
-                                                       W_Vortex_Attack(1);
-                                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(vortex, animtime), w_ready);
-                                               }
-                                       }
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_model("models/nexflash.md3");
-                       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;
-               }
-               case WR_SETUP:
-               {
-                       self.vortex_lasthit = 0;
-                       return true;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       ammo_amount = self.WEP_AMMO(VORTEX) >= WEP_CVAR_PRI(vortex, ammo);
-                       ammo_amount += (autocvar_g_balance_vortex_reload_ammo && self.(weapon_load[WEP_VORTEX.m_id]) >= WEP_CVAR_PRI(vortex, ammo));
-                       return ammo_amount;
-               }
-               case WR_CHECKAMMO2:
-               {
-                       if(WEP_CVAR(vortex, secondary))
-                       {
-                               // don't allow charging if we don't have enough ammo
-                               ammo_amount = self.WEP_AMMO(VORTEX) >= WEP_CVAR_SEC(vortex, ammo);
-                               ammo_amount += self.(weapon_load[WEP_VORTEX.m_id]) >= WEP_CVAR_SEC(vortex, ammo);
-                               return ammo_amount;
-                       }
-                       else
-                       {
-                               return false; // zoom is not a fire mode
-                       }
-               }
-               case WR_CONFIG:
-               {
-                       VORTEX_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RESETPLAYER:
-               {
-                       self.vortex_lasthit = 0;
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       W_Reload(min(WEP_CVAR_PRI(vortex, ammo), WEP_CVAR_SEC(vortex, ammo)), W_Sound("reload"));
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_THINKING_WITH_PORTALS;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       return WEAPON_VORTEX_MURDER;
-               }
-       }
-       return false;
-}
-#endif
-#ifdef CSQC
-float autocvar_g_balance_vortex_secondary = 0; // WEAPONTODO
-bool W_Vortex(int req)
-{
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 6;
-                       pointparticles(particleeffectnum(EFFECT_VORTEX_IMPACT), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                               sound(self, CH_SHOTS, W_Sound("neximpact"), VOL_BASE, ATTN_NORM);
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       precache_sound(W_Sound("neximpact"));
-                       if(autocvar_cl_reticle && autocvar_cl_reticle_weapon)
-                       {
-                               precache_pic("gfx/reticle_nex");
-                       }
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       if(button_zoom || zoomscript_caught || (!WEP_CVAR(vortex, secondary) && button_attack2))
-                       {
-                               reticle_image = "gfx/reticle_nex";
-                               return true;
-                       }
-                       else
-                       {
-                               // no weapon specific image for this weapon
-                               return false;
-                       }
-               }
-       }
-       return false;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/weapon/arc.qc b/qcsrc/common/weapons/weapon/arc.qc
new file mode 100644 (file)
index 0000000..c101a8c
--- /dev/null
@@ -0,0 +1,1546 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ ARC,
+/* function  */ W_Arc,
+/* ammotype  */ ammo_cells,
+/* impulse   */ 3,
+/* flags     */ WEP_FLAG_NORMAL,
+/* rating    */ BOT_PICKUP_RATING_HIGH,
+/* color     */ '1 1 1',
+/* modelname */ "arc",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairhlac 0.7",
+/* wepimg    */ "weaponarc",
+/* refname   */ "arc",
+/* wepname   */ _("Arc")
+);
+
+#define ARC_SETTINGS(w_cvar,w_prop) ARC_SETTINGS_LIST(w_cvar, w_prop, ARC, arc)
+#define ARC_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, NONE, beam_ammo) \
+       w_cvar(id, sn, NONE, beam_animtime) \
+       w_cvar(id, sn, NONE, beam_botaimspeed) \
+       w_cvar(id, sn, NONE, beam_botaimlifetime) \
+       w_cvar(id, sn, NONE, beam_damage) \
+       w_cvar(id, sn, NONE, beam_degreespersegment) \
+       w_cvar(id, sn, NONE, beam_distancepersegment) \
+       w_cvar(id, sn, NONE, beam_falloff_halflifedist) \
+       w_cvar(id, sn, NONE, beam_falloff_maxdist) \
+       w_cvar(id, sn, NONE, beam_falloff_mindist) \
+       w_cvar(id, sn, NONE, beam_force) \
+       w_cvar(id, sn, NONE, beam_healing_amax) \
+       w_cvar(id, sn, NONE, beam_healing_aps) \
+       w_cvar(id, sn, NONE, beam_healing_hmax) \
+       w_cvar(id, sn, NONE, beam_healing_hps) \
+       w_cvar(id, sn, NONE, beam_maxangle) \
+       w_cvar(id, sn, NONE, beam_nonplayerdamage) \
+       w_cvar(id, sn, NONE, beam_range) \
+       w_cvar(id, sn, NONE, beam_refire) \
+       w_cvar(id, sn, NONE, beam_returnspeed) \
+       w_cvar(id, sn, NONE, beam_tightness) \
+       w_cvar(id, sn, NONE, burst_ammo) \
+       w_cvar(id, sn, NONE, burst_damage) \
+       w_cvar(id, sn, NONE, burst_healing_aps) \
+       w_cvar(id, sn, NONE, burst_healing_hps) \
+       w_cvar(id, sn, NONE, overheat_max)/* maximum heat before jamming */ \
+       w_cvar(id, sn, NONE, overheat_min)/* minimum heat to wait for cooldown */ \
+       w_cvar(id, sn, NONE, beam_heat)   /* heat increase per second (primary) */ \
+       w_cvar(id, sn, NONE, burst_heat)  /* heat increase per second (secondary) */ \
+       w_cvar(id, sn, NONE, cooldown)    /* heat decrease per second when resting */ \
+       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)
+
+#ifndef MENUQC
+const float ARC_MAX_SEGMENTS = 20;
+vector arc_shotorigin[4];
+.vector beam_start;
+.vector beam_dir;
+.vector beam_wantdir;
+.int beam_type;
+
+const int ARC_BT_MISS =        0x00;
+const int ARC_BT_WALL =        0x01;
+const int ARC_BT_HEAL =        0x02;
+const int ARC_BT_HIT =         0x03;
+const int ARC_BT_BURST_MISS =  0x10;
+const int ARC_BT_BURST_WALL =  0x11;
+const int ARC_BT_BURST_HEAL =  0x12;
+const int ARC_BT_BURST_HIT =   0x13;
+const int ARC_BT_BURSTMASK =   0x10;
+
+const int ARC_SF_SETTINGS =    1;
+const int ARC_SF_START =       2;
+const int ARC_SF_WANTDIR =     4;
+const int ARC_SF_BEAMDIR =     8;
+const int ARC_SF_BEAMTYPE =    16;
+const int ARC_SF_LOCALMASK =   14;
+#endif
+#ifdef SVQC
+ARC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+.entity arc_beam;
+.float arc_BUTTON_ATCK_prev; // for better animation control
+.float beam_prev;
+.float beam_initialized;
+.float beam_bursting;
+.float beam_teleporttime;
+.float beam_heat; // (beam) amount of heat produced
+.float arc_overheat; // (dropped arc/player) time during which it's too hot
+.float arc_cooldown; // (dropped arc/player) cooling speed
+.float arc_heat_percent; // (player) arc heat in [0,1] (stat)
+.float arc_smoke_sound;
+#endif
+#ifdef CSQC
+void Ent_ReadArcBeam(float isnew);
+
+.vector beam_color;
+.float beam_alpha;
+.float beam_thickness;
+.float beam_traileffect;
+.float beam_hiteffect;
+.float beam_hitlight[4]; // 0: radius, 123: rgb
+.float beam_muzzleeffect;
+.float beam_muzzlelight[4]; // 0: radius, 123: rgb
+.string beam_image;
+
+.entity beam_muzzleentity;
+
+.float beam_degreespersegment;
+.float beam_distancepersegment;
+.float beam_usevieworigin;
+.float beam_initialized;
+.float beam_maxangle;
+.float beam_range;
+.float beam_returnspeed;
+.float beam_tightness;
+.vector beam_shotorigin;
+
+entity Draw_ArcBeam_callback_entity;
+float Draw_ArcBeam_callback_last_thickness;
+vector Draw_ArcBeam_callback_last_top; // NOTE: in same coordinate system as player.
+vector Draw_ArcBeam_callback_last_bottom; // NOTE: in same coordinate system as player.
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_arc(void) { weapon_defaultspawnfunc(WEP_ARC.m_id); }
+
+float W_Arc_Beam_Send(entity to, int sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_ARC_BEAM);
+
+       // Truncate information when this beam is displayed to the owner client
+       // - The owner client has no use for beam start position or directions,
+       //    it always figures this information out for itself with csqc code.
+       // - Spectating the owner also truncates this information.
+       float drawlocal = ((to == self.owner) || ((to.enemy == self.owner) && IS_SPEC(to)));
+       if(drawlocal) { sf &= ~ARC_SF_LOCALMASK; }
+
+       WriteByte(MSG_ENTITY, sf);
+
+       if(sf & ARC_SF_SETTINGS) // settings information
+       {
+               WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_degreespersegment));
+               WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_distancepersegment));
+               WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_maxangle));
+               WriteCoord(MSG_ENTITY, WEP_CVAR(arc, beam_range));
+               WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_returnspeed));
+               WriteByte(MSG_ENTITY, WEP_CVAR(arc, beam_tightness) * 10);
+
+               WriteByte(MSG_ENTITY, drawlocal);
+       }
+       if(sf & ARC_SF_START) // starting location
+       {
+               WriteCoord(MSG_ENTITY, self.beam_start.x);
+               WriteCoord(MSG_ENTITY, self.beam_start.y);
+               WriteCoord(MSG_ENTITY, self.beam_start.z);
+       }
+       if(sf & ARC_SF_WANTDIR) // want/aim direction
+       {
+               WriteCoord(MSG_ENTITY, self.beam_wantdir.x);
+               WriteCoord(MSG_ENTITY, self.beam_wantdir.y);
+               WriteCoord(MSG_ENTITY, self.beam_wantdir.z);
+       }
+       if(sf & ARC_SF_BEAMDIR) // beam direction
+       {
+               WriteCoord(MSG_ENTITY, self.beam_dir.x);
+               WriteCoord(MSG_ENTITY, self.beam_dir.y);
+               WriteCoord(MSG_ENTITY, self.beam_dir.z);
+       }
+       if(sf & ARC_SF_BEAMTYPE) // beam type
+       {
+               WriteByte(MSG_ENTITY, self.beam_type);
+       }
+
+       return true;
+}
+
+void Reset_ArcBeam(entity player, vector forward)
+{
+       if (!player.arc_beam) {
+               return;
+       }
+       player.arc_beam.beam_dir = forward;
+       player.arc_beam.beam_teleporttime = time;
+}
+
+float Arc_GetHeat_Percent(entity player)
+{
+       if ( WEP_CVAR(arc, overheat_max) <= 0 ||  WEP_CVAR(arc, overheat_max) <= 0 )
+       {
+               player.arc_overheat = 0;
+               return 0;
+       }
+
+       if ( player.arc_beam )
+               return player.arc_beam.beam_heat/WEP_CVAR(arc, overheat_max);
+
+       if ( player.arc_overheat > time )
+       {
+               return (player.arc_overheat-time) / WEP_CVAR(arc, overheat_max)
+                       * player.arc_cooldown;
+       }
+
+       return 0;
+}
+void Arc_Player_SetHeat(entity player)
+{
+       player.arc_heat_percent = Arc_GetHeat_Percent(player);
+       //dprint("Heat: ",ftos(player.arc_heat_percent*100),"%\n");
+}
+
+void W_Arc_Beam_Think(void)
+{
+       if(self != self.owner.arc_beam)
+       {
+               remove(self);
+               return;
+       }
+
+
+       float burst = 0;
+       if( self.owner.BUTTON_ATCK2 || self.beam_bursting)
+       {
+               if(!self.beam_bursting)
+                       self.beam_bursting = true;
+               burst = ARC_BT_BURSTMASK;
+       }
+
+       if(
+               !IS_PLAYER(self.owner)
+               ||
+               (self.owner.WEP_AMMO(ARC) <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO))
+               ||
+               self.owner.deadflag != DEAD_NO
+               ||
+               (!self.owner.BUTTON_ATCK && !burst )
+               ||
+               self.owner.frozen
+               ||
+               self.owner.vehicle
+               ||
+               (WEP_CVAR(arc, overheat_max) > 0 && self.beam_heat >= WEP_CVAR(arc, overheat_max))
+       )
+       {
+               if ( WEP_CVAR(arc, cooldown) > 0 )
+               {
+                       float cooldown_speed = 0;
+                       if ( self.beam_heat > WEP_CVAR(arc, overheat_min) && WEP_CVAR(arc, cooldown) > 0 )
+                       {
+                               cooldown_speed = WEP_CVAR(arc, cooldown);
+                       }
+                       else if ( !burst )
+                       {
+                               cooldown_speed = self.beam_heat / WEP_CVAR(arc, beam_refire);
+                       }
+
+                       if ( cooldown_speed )
+                       {
+                               self.owner.arc_overheat = time + self.beam_heat / cooldown_speed;
+                               self.owner.arc_cooldown = cooldown_speed;
+                       }
+
+                       if ( WEP_CVAR(arc, overheat_max) > 0 && self.beam_heat >= WEP_CVAR(arc, overheat_max) )
+                       {
+                               Send_Effect_("arc_overheat",
+                                       self.beam_start, self.beam_wantdir, 1 );
+                               sound(self, CH_WEAPON_A, W_Sound("arc_stop"), VOL_BASE, ATTN_NORM);
+                       }
+               }
+
+               if(self == self.owner.arc_beam) { self.owner.arc_beam = world; }
+               entity oldself = self;
+               self = self.owner;
+               if(!WEP_ACTION(WEP_ARC.m_id, WR_CHECKAMMO1) && !WEP_ACTION(WEP_ARC.m_id, WR_CHECKAMMO2))
+               if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+               {
+                       // note: this doesn't force the switch
+                       W_SwitchToOtherWeapon(self);
+               }
+               self = oldself;
+               remove(self);
+               return;
+       }
+
+       // decrease ammo
+       float coefficient = frametime;
+       if(!(self.owner.items & IT_UNLIMITED_WEAPON_AMMO))
+       {
+               float rootammo;
+               if(burst)
+                       { rootammo = WEP_CVAR(arc, burst_ammo); }
+               else
+                       { rootammo = WEP_CVAR(arc, beam_ammo); }
+
+               if(rootammo)
+               {
+                       coefficient = min(coefficient, self.owner.WEP_AMMO(ARC) / rootammo);
+                       self.owner.WEP_AMMO(ARC) = max(0, self.owner.WEP_AMMO(ARC) - (rootammo * frametime));
+               }
+       }
+       float heat_speed = burst ? WEP_CVAR(arc, burst_heat) : WEP_CVAR(arc, beam_heat);
+       self.beam_heat = min( WEP_CVAR(arc, overheat_max), self.beam_heat + heat_speed*frametime );
+
+       makevectors(self.owner.v_angle);
+
+       W_SetupShot_Range(
+               self.owner,
+               true,
+               0,
+               "",
+               0,
+               WEP_CVAR(arc, beam_damage) * coefficient,
+               WEP_CVAR(arc, beam_range)
+       );
+
+       // After teleport, "lock" the beam until the teleport is confirmed.
+       if (time < self.beam_teleporttime + ANTILAG_LATENCY(self.owner)) {
+               w_shotdir = self.beam_dir;
+       }
+
+       // network information: shot origin and want/aim direction
+       if(self.beam_start != w_shotorg)
+       {
+               self.SendFlags |= ARC_SF_START;
+               self.beam_start = w_shotorg;
+       }
+       if(self.beam_wantdir != w_shotdir)
+       {
+               self.SendFlags |= ARC_SF_WANTDIR;
+               self.beam_wantdir = w_shotdir;
+       }
+
+       if(!self.beam_initialized)
+       {
+               self.beam_dir = w_shotdir;
+               self.beam_initialized = true;
+       }
+
+       // WEAPONTODO: Detect player velocity so that the beam curves when moving too
+       // idea: blend together self.beam_dir with the inverted direction the player is moving in
+       // might have to make some special accomodation so that it only uses view_right and view_up
+
+       // note that if we do this, it'll always be corrected to a maximum angle by beam_maxangle handling
+
+       float segments;
+       if(self.beam_dir != w_shotdir)
+       {
+               // calculate how much we're going to move the end of the beam to the want position
+               // WEAPONTODO (server and client):
+               // blendfactor never actually becomes 0 in this situation, which is a problem
+               // regarding precision... this means that self.beam_dir and w_shotdir approach
+               // eachother, however they never actually become the same value with this method.
+               // Perhaps we should do some form of rounding/snapping?
+               float angle = vlen(w_shotdir - self.beam_dir) * RAD2DEG;
+               if(angle && (angle > WEP_CVAR(arc, beam_maxangle)))
+               {
+                       // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor
+                       float blendfactor = bound(
+                               0,
+                               (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)),
+                               min(WEP_CVAR(arc, beam_maxangle) / angle, 1)
+                       );
+                       self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
+               }
+               else
+               {
+                       // the radius is not too far yet, no worries :D
+                       float blendfactor = bound(
+                               0,
+                               (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)),
+                               1
+                       );
+                       self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
+               }
+
+               // network information: beam direction
+               self.SendFlags |= ARC_SF_BEAMDIR;
+
+               // calculate how many segments are needed
+               float max_allowed_segments;
+
+               if(WEP_CVAR(arc, beam_distancepersegment))
+               {
+                       max_allowed_segments = min(
+                               ARC_MAX_SEGMENTS,
+                               1 + (vlen(w_shotdir / WEP_CVAR(arc, beam_distancepersegment)))
+                       );
+               }
+               else { max_allowed_segments = ARC_MAX_SEGMENTS; }
+
+               if(WEP_CVAR(arc, beam_degreespersegment))
+               {
+                       segments = bound(
+                               1,
+                               (
+                                       min(
+                                               angle,
+                                               WEP_CVAR(arc, beam_maxangle)
+                                       )
+                                       /
+                                       WEP_CVAR(arc, beam_degreespersegment)
+                               ),
+                               max_allowed_segments
+                       );
+               }
+               else { segments = 1; }
+       }
+       else { segments = 1; }
+
+       vector beam_endpos = (w_shotorg + (self.beam_dir * WEP_CVAR(arc, beam_range)));
+       vector beam_controlpoint = w_shotorg + w_shotdir * (WEP_CVAR(arc, beam_range) * (1 - WEP_CVAR(arc, beam_tightness)));
+
+       float i;
+       float new_beam_type = 0;
+       vector last_origin = w_shotorg;
+       for(i = 1; i <= segments; ++i)
+       {
+               // WEAPONTODO (client):
+               // In order to do nice fading and pointing on the starting segment, we must always
+               // have that drawn as a separate triangle... However, that is difficult to do when
+               // keeping in mind the above problems and also optimizing the amount of segments
+               // drawn on screen at any given time. (Automatic beam quality scaling, essentially)
+
+               vector new_origin = bezier_quadratic_getpoint(
+                       w_shotorg,
+                       beam_controlpoint,
+                       beam_endpos,
+                       i / segments);
+               vector new_dir = normalize(new_origin - last_origin);
+
+               WarpZone_traceline_antilag(
+                       self.owner,
+                       last_origin,
+                       new_origin,
+                       MOVE_NORMAL,
+                       self.owner,
+                       ANTILAG_LATENCY(self.owner)
+               );
+
+               // Do all the transforms for warpzones right now, as we already
+               // "are" in the post-trace system (if we hit a player, that's
+               // always BEHIND the last passed wz).
+               last_origin = trace_endpos;
+               w_shotorg = WarpZone_TransformOrigin(WarpZone_trace_transform, w_shotorg);
+               beam_controlpoint = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_controlpoint);
+               beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos);
+               new_dir = WarpZone_TransformVelocity(WarpZone_trace_transform, new_dir);
+
+               float is_player = (
+                       IS_PLAYER(trace_ent)
+                       ||
+                       trace_ent.classname == "body"
+                       ||
+                       IS_MONSTER(trace_ent)
+               );
+
+               if(trace_ent && trace_ent.takedamage && (is_player || WEP_CVAR(arc, beam_nonplayerdamage)))
+               {
+                       // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
+                       // NO. trace_endpos should be just fine. If not,
+                       // that's an engine bug that needs proper debugging.
+                       vector hitorigin = trace_endpos;
+
+                       float falloff = ExponentialFalloff(
+                               WEP_CVAR(arc, beam_falloff_mindist),
+                               WEP_CVAR(arc, beam_falloff_maxdist),
+                               WEP_CVAR(arc, beam_falloff_halflifedist),
+                               vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg)
+                       );
+
+                       if(is_player && SAME_TEAM(self.owner, trace_ent))
+                       {
+                               float roothealth, rootarmor;
+                               if(burst)
+                               {
+                                       roothealth = WEP_CVAR(arc, burst_healing_hps);
+                                       rootarmor = WEP_CVAR(arc, burst_healing_aps);
+                               }
+                               else
+                               {
+                                       roothealth = WEP_CVAR(arc, beam_healing_hps);
+                                       rootarmor = WEP_CVAR(arc, beam_healing_aps);
+                               }
+
+                               if(trace_ent.health <= WEP_CVAR(arc, beam_healing_hmax) && roothealth)
+                               {
+                                       trace_ent.health = min(
+                                               trace_ent.health + (roothealth * coefficient),
+                                               WEP_CVAR(arc, beam_healing_hmax)
+                                       );
+                               }
+                               if(trace_ent.armorvalue <= WEP_CVAR(arc, beam_healing_amax) && rootarmor)
+                               {
+                                       trace_ent.armorvalue = min(
+                                               trace_ent.armorvalue + (rootarmor * coefficient),
+                                               WEP_CVAR(arc, beam_healing_amax)
+                                       );
+                               }
+
+                               // stop rot, set visual effect
+                               if(roothealth || rootarmor)
+                               {
+                                       trace_ent.pauserothealth_finished = max(
+                                               trace_ent.pauserothealth_finished,
+                                               time + autocvar_g_balance_pause_health_rot
+                                       );
+                                       trace_ent.pauserotarmor_finished = max(
+                                               trace_ent.pauserotarmor_finished,
+                                               time + autocvar_g_balance_pause_armor_rot
+                                       );
+                                       new_beam_type = ARC_BT_HEAL;
+                               }
+                       }
+                       else
+                       {
+                               float rootdamage;
+                               if(is_player)
+                               {
+                                       if(burst)
+                                               { rootdamage = WEP_CVAR(arc, burst_damage); }
+                                       else
+                                               { rootdamage = WEP_CVAR(arc, beam_damage); }
+                               }
+                               else
+                                       { rootdamage = WEP_CVAR(arc, beam_nonplayerdamage); }
+
+                               if(accuracy_isgooddamage(self.owner, trace_ent))
+                               {
+                                       accuracy_add(
+                                               self.owner,
+                                               WEP_ARC.m_id,
+                                               0,
+                                               rootdamage * coefficient * falloff
+                                       );
+                               }
+
+                               Damage(
+                                       trace_ent,
+                                       self.owner,
+                                       self.owner,
+                                       rootdamage * coefficient * falloff,
+                                       WEP_ARC.m_id,
+                                       hitorigin,
+                                       WEP_CVAR(arc, beam_force) * new_dir * coefficient * falloff
+                               );
+
+                               new_beam_type = ARC_BT_HIT;
+                       }
+                       break;
+               }
+               else if(trace_fraction != 1)
+               {
+                       // we collided with geometry
+                       new_beam_type = ARC_BT_WALL;
+                       break;
+               }
+       }
+
+       // te_explosion(trace_endpos);
+
+       // if we're bursting, use burst visual effects
+       new_beam_type |= burst;
+
+       // network information: beam type
+       if(new_beam_type != self.beam_type)
+       {
+               self.SendFlags |= ARC_SF_BEAMTYPE;
+               self.beam_type = new_beam_type;
+       }
+
+       self.owner.beam_prev = time;
+       self.nextthink = time;
+}
+
+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, W_Sound("arc_fire"), VOL_BASE, ATTN_NORM);
+
+       entity beam = self.arc_beam = spawn();
+       beam.classname = "W_Arc_Beam";
+       beam.solid = SOLID_NOT;
+       beam.think = W_Arc_Beam_Think;
+       beam.owner = self;
+       beam.movetype = MOVETYPE_NONE;
+       beam.bot_dodge = true;
+       beam.bot_dodgerating = WEP_CVAR(arc, beam_damage);
+       beam.beam_bursting = burst;
+       Net_LinkEntity(beam, false, 0, W_Arc_Beam_Send);
+
+       entity oldself = self;
+       self = beam;
+       self.think();
+       self = oldself;
+}
+
+void Arc_Smoke()
+{
+       makevectors(self.v_angle);
+       W_SetupShot_Range(self,true,0,"",0,0,0);
+
+       vector smoke_origin = w_shotorg + self.velocity*frametime;
+       if ( self.arc_overheat > time )
+       {
+               if ( random() < self.arc_heat_percent )
+                       Send_Effect_("arc_smoke", smoke_origin, '0 0 0', 1 );
+               if ( self.BUTTON_ATCK || self.BUTTON_ATCK2 )
+               {
+                       Send_Effect_("arc_overheat_fire", smoke_origin, w_shotdir, 1 );
+                       if ( !self.arc_smoke_sound )
+                       {
+                               self.arc_smoke_sound = 1;
+                               sound(self, CH_SHOTS_SINGLE, W_Sound("arc_loop_overheat"), VOL_BASE, ATTN_NORM);
+                       }
+               }
+       }
+       else if ( self.arc_beam && WEP_CVAR(arc, overheat_max) > 0 &&
+                       self.arc_beam.beam_heat > WEP_CVAR(arc, overheat_min) )
+       {
+               if ( random() < (self.arc_beam.beam_heat-WEP_CVAR(arc, overheat_min)) /
+                               ( WEP_CVAR(arc, overheat_max)-WEP_CVAR(arc, overheat_min) ) )
+                       Send_Effect_("arc_smoke", smoke_origin, '0 0 0', 1 );
+       }
+
+       if (  self.arc_smoke_sound && ( self.arc_overheat <= time ||
+               !( self.BUTTON_ATCK || self.BUTTON_ATCK2 ) ) || self.switchweapon != WEP_ARC.m_id )
+       {
+               self.arc_smoke_sound = 0;
+               sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM);
+       }
+}
+
+bool W_Arc(int req)
+{
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       if(WEP_CVAR(arc, beam_botaimspeed))
+                       {
+                               self.BUTTON_ATCK = bot_aim(
+                                       WEP_CVAR(arc, beam_botaimspeed),
+                                       0,
+                                       WEP_CVAR(arc, beam_botaimlifetime),
+                                       false
+                               );
+                       }
+                       else
+                       {
+                               self.BUTTON_ATCK = bot_aim(
+                                       1000000,
+                                       0,
+                                       0.001,
+                                       false
+                               );
+                       }
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       Arc_Player_SetHeat(self);
+                       Arc_Smoke();
+
+                       if ( self.arc_overheat <= time )
+                       if(self.BUTTON_ATCK || self.BUTTON_ATCK2 || self.arc_beam.beam_bursting )
+                       {
+
+                               if(self.arc_BUTTON_ATCK_prev)
+                               {
+                                       #if 0
+                                       if(self.animstate_startframe == self.anim_shoot.x && self.animstate_numframes == self.anim_shoot.y)
+                                               weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_arc_primary_animtime, w_ready);
+                                       else
+                                       #endif
+                                               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
+                               }
+
+                               if((!self.arc_beam) || wasfreed(self.arc_beam))
+                               {
+                                       if(weapon_prepareattack(!!self.BUTTON_ATCK2, 0))
+                                       {
+                                               W_Arc_Beam(!!self.BUTTON_ATCK2);
+
+                                               if(!self.arc_BUTTON_ATCK_prev)
+                                               {
+                                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
+                                                       self.arc_BUTTON_ATCK_prev = 1;
+                                               }
+                                       }
+                               }
+
+                               return true;
+                       }
+
+                       if(self.arc_BUTTON_ATCK_prev != 0)
+                       {
+                               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();
+                       }
+                       self.arc_BUTTON_ATCK_prev = 0;
+
+                       #if 0
+                       if(self.BUTTON_ATCK2)
+                       if(weapon_prepareattack(1, autocvar_g_balance_arc_secondary_refire))
+                       {
+                               W_Arc_Attack2();
+                               self.arc_count = autocvar_g_balance_arc_secondary_count;
+                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_arc_secondary_animtime, w_arc_checkattack);
+                               self.arc_secondarytime = time + autocvar_g_balance_arc_secondary_refire2 * W_WeaponRateFactor();
+                       }
+                       #endif
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       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.m_id), false, false, 1);
+                               arc_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 2);
+                               arc_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 3);
+                               arc_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 4);
+                       }
+                       ARC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP);
+                       return true;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       return ((!WEP_CVAR(arc, beam_ammo)) || (self.WEP_AMMO(ARC) > 0));
+               }
+               case WR_CHECKAMMO2:
+               {
+                       return WEP_CVAR(arc, overheat_max) > 0 &&
+                               ((!WEP_CVAR(arc, burst_ammo)) || (self.WEP_AMMO(ARC) > 0));
+               }
+               case WR_CONFIG:
+               {
+                       ARC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       return WEAPON_ARC_MURDER;
+               }
+               case WR_DROP:
+               {
+                       weapon_dropevent_item.arc_overheat = self.arc_overheat;
+                       weapon_dropevent_item.arc_cooldown = self.arc_cooldown;
+                       self.arc_overheat = 0;
+                       self.arc_cooldown = 0;
+                       return true;
+               }
+               case WR_PICKUP:
+               {
+                       if ( !client_hasweapon(self, WEP_ARC.m_id, false, false) &&
+                               weapon_dropevent_item.arc_overheat > time )
+                       {
+                               self.arc_overheat = weapon_dropevent_item.arc_overheat;
+                               self.arc_cooldown = weapon_dropevent_item.arc_cooldown;
+                       }
+                       return true;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+void Draw_ArcBeam_callback(vector start, vector hit, vector end)
+{
+       entity beam = Draw_ArcBeam_callback_entity;
+       vector transformed_view_org;
+       transformed_view_org = WarpZone_TransformOrigin(WarpZone_trace_transform, view_origin);
+
+       // Thickdir shall be perpendicular to the beam and to the view-to-beam direction (WEAPONTODO: WHY)
+       // WEAPONTODO: Wouldn't it be better to be perpendicular to the beam and to the view FORWARD direction?
+       vector thickdir = normalize(cross(normalize(start - hit), transformed_view_org - start));
+
+       vector hitorigin;
+
+       // draw segment
+       #if 0
+       if(trace_fraction != 1)
+       {
+               // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
+               hitorigin = start + (Draw_ArcBeam_callback_new_dir * Draw_ArcBeam_callback_segmentdist * trace_fraction);
+               hitorigin = WarpZone_TransformOrigin(WarpZone_trace_transform, hitorigin);
+       }
+       else
+       {
+               hitorigin = hit;
+       }
+       #else
+       hitorigin = hit;
+       #endif
+
+       // decide upon thickness
+       float thickness = beam.beam_thickness;
+
+       // draw primary beam render
+       vector top    = hitorigin + (thickdir * thickness);
+       vector bottom = hitorigin - (thickdir * thickness);
+
+       vector last_top = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_top);
+       vector last_bottom = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_bottom);
+
+       R_BeginPolygon(beam.beam_image, DRAWFLAG_NORMAL); // DRAWFLAG_ADDITIVE
+       R_PolygonVertex(
+               top,
+               '0 0.5 0' + ('0 0.5 0' * (thickness / beam.beam_thickness)),
+               beam.beam_color,
+               beam.beam_alpha
+       );
+       R_PolygonVertex(
+               last_top,
+               '0 0.5 0' + ('0 0.5 0' * (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)),
+               beam.beam_color,
+               beam.beam_alpha
+       );
+       R_PolygonVertex(
+               last_bottom,
+               '0 0.5 0' * (1 - (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)),
+               beam.beam_color,
+               beam.beam_alpha
+       );
+       R_PolygonVertex(
+               bottom,
+               '0 0.5 0' * (1 - (thickness / beam.beam_thickness)),
+               beam.beam_color,
+               beam.beam_alpha
+       );
+       R_EndPolygon();
+
+       // draw trailing particles
+       // NOTES:
+       //  - Don't use spammy particle counts here, use a FEW small particles around the beam
+       //  - We're not using WarpZone_TrailParticles here because we will handle warpzones ourselves.
+       if(beam.beam_traileffect)
+       {
+               trailparticles(beam, beam.beam_traileffect, start, hitorigin);
+       }
+
+       // set up for the next
+       Draw_ArcBeam_callback_last_thickness = thickness;
+       Draw_ArcBeam_callback_last_top = WarpZone_UnTransformOrigin(WarpZone_trace_transform, top);
+       Draw_ArcBeam_callback_last_bottom = WarpZone_UnTransformOrigin(WarpZone_trace_transform, bottom);
+}
+
+void Reset_ArcBeam(void)
+{
+       entity e;
+       for (e = world; (e = findfloat(e, beam_usevieworigin, 1)); ) {
+               e.beam_initialized = false;
+       }
+       for (e = world; (e = findfloat(e, beam_usevieworigin, 2)); ) {
+               e.beam_initialized = false;
+       }
+}
+
+void Draw_ArcBeam(void)
+{
+       float dt = time - self.move_time;
+       self.move_time = time;
+       if(dt <= 0) { return; }
+
+       if(!self.beam_usevieworigin)
+       {
+               InterpolateOrigin_Do();
+       }
+
+       // origin = beam starting origin
+       // v_angle = wanted/aim direction
+       // angles = current direction of beam
+
+       vector start_pos;
+       vector wantdir; //= view_forward;
+       vector beamdir; //= self.beam_dir;
+
+       float segments;
+       if(self.beam_usevieworigin)
+       {
+               // WEAPONTODO:
+               // Currently we have to replicate nearly the same method of figuring
+               // out the shotdir that the server does... Ideally in the future we
+               // should be able to acquire this from a generalized function built
+               // into a weapon system for client code.
+
+               // find where we are aiming
+               makevectors(warpzone_save_view_angles);
+               vector forward = v_forward;
+               vector right = v_right;
+               vector up = v_up;
+
+               // decide upon start position
+               if(self.beam_usevieworigin == 2)
+                       { start_pos = warpzone_save_view_origin; }
+               else
+                       { start_pos = self.origin; }
+
+               // trace forward with an estimation
+               WarpZone_TraceLine(
+                       start_pos,
+                       start_pos + forward * self.beam_range,
+                       MOVE_NOMONSTERS,
+                       self
+               );
+
+               // untransform in case our trace went through a warpzone
+               vector end_pos = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
+
+               // un-adjust trueaim if shotend is too close
+               if(vlen(end_pos - start_pos) < g_trueaim_minrange)
+                       end_pos = start_pos + (forward * g_trueaim_minrange);
+
+               // move shot origin to the actual gun muzzle origin
+               vector origin_offset =
+                         right * -self.beam_shotorigin.y
+                       + up * self.beam_shotorigin.z;
+
+               start_pos = start_pos + origin_offset;
+
+               // Move it also forward, but only as far as possible without hitting anything. Don't poke into walls!
+               traceline(start_pos, start_pos + forward * self.beam_shotorigin.x, MOVE_NORMAL, self);
+               start_pos = trace_endpos;
+
+               // calculate the aim direction now
+               wantdir = normalize(end_pos - start_pos);
+
+               if(!self.beam_initialized)
+               {
+                       self.beam_dir = wantdir;
+                       self.beam_initialized = true;
+               }
+
+               if(self.beam_dir != wantdir)
+               {
+                       // calculate how much we're going to move the end of the beam to the want position
+                       // WEAPONTODO (server and client):
+                       // blendfactor never actually becomes 0 in this situation, which is a problem
+                       // regarding precision... this means that self.beam_dir and w_shotdir approach
+                       // eachother, however they never actually become the same value with this method.
+                       // Perhaps we should do some form of rounding/snapping?
+                       float angle = vlen(wantdir - self.beam_dir) * RAD2DEG;
+                       if(angle && (angle > self.beam_maxangle))
+                       {
+                               // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor
+                               float blendfactor = bound(
+                                       0,
+                                       (1 - (self.beam_returnspeed * frametime)),
+                                       min(self.beam_maxangle / angle, 1)
+                               );
+                               self.beam_dir = normalize((wantdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
+                       }
+                       else
+                       {
+                               // the radius is not too far yet, no worries :D
+                               float blendfactor = bound(
+                                       0,
+                                       (1 - (self.beam_returnspeed * frametime)),
+                                       1
+                               );
+                               self.beam_dir = normalize((wantdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
+                       }
+
+                       // calculate how many segments are needed
+                       float max_allowed_segments;
+
+                       if(self.beam_distancepersegment)
+                       {
+                               max_allowed_segments = min(
+                                       ARC_MAX_SEGMENTS,
+                                       1 + (vlen(wantdir / self.beam_distancepersegment))
+                               );
+                       }
+                       else { max_allowed_segments = ARC_MAX_SEGMENTS; }
+
+                       if(self.beam_degreespersegment)
+                       {
+                               segments = bound(
+                                       1,
+                                       (
+                                               min(
+                                                       angle,
+                                                       self.beam_maxangle
+                                               )
+                                               /
+                                               self.beam_degreespersegment
+                                       ),
+                                       max_allowed_segments
+                               );
+                       }
+                       else { segments = 1; }
+               }
+               else { segments = 1; }
+
+               // set the beam direction which the rest of the code will refer to
+               beamdir = self.beam_dir;
+
+               // finally, set self.angles to the proper direction so that muzzle attachment points in proper direction
+               self.angles = fixedvectoangles2(forward, up); // TODO(Samual): is this == warpzone_save_view_angles?
+       }
+       else
+       {
+               // set the values from the provided info from the networked entity
+               start_pos = self.origin;
+               wantdir = self.v_angle;
+               beamdir = self.angles;
+
+               if(beamdir != wantdir)
+               {
+                       float angle = vlen(wantdir - beamdir) * RAD2DEG;
+
+                       // calculate how many segments are needed
+                       float max_allowed_segments;
+
+                       if(self.beam_distancepersegment)
+                       {
+                               max_allowed_segments = min(
+                                       ARC_MAX_SEGMENTS,
+                                       1 + (vlen(wantdir / self.beam_distancepersegment))
+                               );
+                       }
+                       else { max_allowed_segments = ARC_MAX_SEGMENTS; }
+
+                       if(self.beam_degreespersegment)
+                       {
+                               segments = bound(
+                                       1,
+                                       (
+                                               min(
+                                                       angle,
+                                                       self.beam_maxangle
+                                               )
+                                               /
+                                               self.beam_degreespersegment
+                                       ),
+                                       max_allowed_segments
+                               );
+                       }
+                       else { segments = 1; }
+               }
+               else { segments = 1; }
+       }
+
+       setorigin(self, start_pos);
+       self.beam_muzzleentity.angles_z = random() * 360; // WEAPONTODO: use avelocity instead?
+
+       vector beam_endpos = (start_pos + (beamdir * self.beam_range));
+       vector beam_controlpoint = start_pos + wantdir * (self.beam_range * (1 - self.beam_tightness));
+
+       Draw_ArcBeam_callback_entity = self;
+       Draw_ArcBeam_callback_last_thickness = 0;
+       Draw_ArcBeam_callback_last_top = start_pos;
+       Draw_ArcBeam_callback_last_bottom = start_pos;
+
+       vector last_origin = start_pos;
+       vector original_start_pos = start_pos;
+
+       float i;
+       for(i = 1; i <= segments; ++i)
+       {
+               // WEAPONTODO (client):
+               // In order to do nice fading and pointing on the starting segment, we must always
+               // have that drawn as a separate triangle... However, that is difficult to do when
+               // keeping in mind the above problems and also optimizing the amount of segments
+               // drawn on screen at any given time. (Automatic beam quality scaling, essentially)
+
+               vector new_origin = bezier_quadratic_getpoint(
+                       start_pos,
+                       beam_controlpoint,
+                       beam_endpos,
+                       i / segments);
+
+               WarpZone_TraceBox_ThroughZone(
+                       last_origin,
+                       '0 0 0',
+                       '0 0 0',
+                       new_origin,
+                       MOVE_NORMAL,
+                       world,
+                       world,
+                       Draw_ArcBeam_callback
+               );
+
+               // Do all the transforms for warpzones right now, as we already "are" in the post-trace
+               // system (if we hit a player, that's always BEHIND the last passed wz).
+               last_origin = trace_endpos;
+               start_pos = WarpZone_TransformOrigin(WarpZone_trace_transform, start_pos);
+               beam_controlpoint = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_controlpoint);
+               beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos);
+               beamdir = WarpZone_TransformVelocity(WarpZone_trace_transform, beamdir);
+               Draw_ArcBeam_callback_last_top = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_top);
+               Draw_ArcBeam_callback_last_bottom = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_bottom);
+
+               if(trace_fraction < 1) { break; }
+       }
+
+       // visual effects for startpoint and endpoint
+       if(self.beam_hiteffect)
+       {
+               // FIXME we really should do this on the server so it actually
+               // matches gameplay. What this client side stuff is doing is no
+               // more than guesswork.
+               if((trace_ent || trace_fraction < 1) && !(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT))
+               pointparticles(
+                       self.beam_hiteffect,
+                       last_origin,
+                       beamdir * -1,
+                       frametime * 2
+               );
+       }
+       if(self.beam_hitlight[0])
+       {
+               adddynamiclight(
+                       last_origin,
+                       self.beam_hitlight[0],
+                       vec3(
+                               self.beam_hitlight[1],
+                               self.beam_hitlight[2],
+                               self.beam_hitlight[3]
+                       )
+               );
+       }
+       if(self.beam_muzzleeffect)
+       {
+               pointparticles(
+                       self.beam_muzzleeffect,
+                       original_start_pos + wantdir * 20,
+                       wantdir * 1000,
+                       frametime * 0.1
+               );
+       }
+       if(self.beam_muzzlelight[0])
+       {
+               adddynamiclight(
+                       original_start_pos + wantdir * 20,
+                       self.beam_muzzlelight[0],
+                       vec3(
+                               self.beam_muzzlelight[1],
+                               self.beam_muzzlelight[2],
+                               self.beam_muzzlelight[3]
+                       )
+               );
+       }
+
+       // cleanup
+       Draw_ArcBeam_callback_entity = world;
+       Draw_ArcBeam_callback_last_thickness = 0;
+       Draw_ArcBeam_callback_last_top = '0 0 0';
+       Draw_ArcBeam_callback_last_bottom = '0 0 0';
+}
+
+void Remove_ArcBeam(void)
+{
+       remove(self.beam_muzzleentity);
+       sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM);
+}
+
+void Ent_ReadArcBeam(float isnew)
+{
+       int sf = ReadByte();
+       entity flash;
+
+       if(isnew)
+       {
+               // calculate shot origin offset from gun alignment
+               int gunalign = autocvar_cl_gunalign;
+               if(gunalign != 1 && gunalign != 2 && gunalign != 4)
+                       gunalign = 3; // default value
+               --gunalign;
+
+               self.beam_shotorigin = arc_shotorigin[gunalign];
+
+               // set other main attributes of the beam
+               self.draw = Draw_ArcBeam;
+               self.entremove = Remove_ArcBeam;
+               self.move_time = time;
+               loopsound(self, CH_SHOTS_SINGLE, W_Sound("arc_loop"), VOL_BASE, ATTEN_NORM);
+
+               flash = spawn();
+               flash.owner = self;
+               flash.effects = EF_ADDITIVE | EF_FULLBRIGHT;
+               flash.drawmask = MASK_NORMAL;
+               flash.solid = SOLID_NOT;
+               flash.avelocity_z = 5000;
+               setattachment(flash, self, "");
+               setorigin(flash, '0 0 0');
+
+               self.beam_muzzleentity = flash;
+       }
+       else
+       {
+               flash = self.beam_muzzleentity;
+       }
+
+       if(sf & ARC_SF_SETTINGS) // settings information
+       {
+               self.beam_degreespersegment = ReadShort();
+               self.beam_distancepersegment = ReadShort();
+               self.beam_maxangle = ReadShort();
+               self.beam_range = ReadCoord();
+               self.beam_returnspeed = ReadShort();
+               self.beam_tightness = (ReadByte() / 10);
+
+               if(ReadByte())
+               {
+                       if(autocvar_chase_active)
+                               { self.beam_usevieworigin = 1; }
+                       else // use view origin
+                               { self.beam_usevieworigin = 2; }
+               }
+               else
+               {
+                       self.beam_usevieworigin = 0;
+               }
+       }
+
+       if(!self.beam_usevieworigin)
+       {
+               // self.iflags = IFLAG_ORIGIN | IFLAG_ANGLES | IFLAG_V_ANGLE; // why doesn't this work?
+               self.iflags = IFLAG_ORIGIN;
+
+               InterpolateOrigin_Undo();
+       }
+
+       if(sf & ARC_SF_START) // starting location
+       {
+               self.origin_x = ReadCoord();
+               self.origin_y = ReadCoord();
+               self.origin_z = ReadCoord();
+       }
+       else if(self.beam_usevieworigin) // infer the location from player location
+       {
+               if(self.beam_usevieworigin == 2)
+               {
+                       // use view origin
+                       self.origin = view_origin;
+               }
+               else
+               {
+                       // use player origin so that third person display still works
+                       self.origin = getplayerorigin(player_localnum) + ('0 0 1' * getstati(STAT_VIEWHEIGHT));
+               }
+       }
+
+       setorigin(self, self.origin);
+
+       if(sf & ARC_SF_WANTDIR) // want/aim direction
+       {
+               self.v_angle_x = ReadCoord();
+               self.v_angle_y = ReadCoord();
+               self.v_angle_z = ReadCoord();
+       }
+
+       if(sf & ARC_SF_BEAMDIR) // beam direction
+       {
+               self.angles_x = ReadCoord();
+               self.angles_y = ReadCoord();
+               self.angles_z = ReadCoord();
+       }
+
+       if(sf & ARC_SF_BEAMTYPE) // beam type
+       {
+               self.beam_type = ReadByte();
+               switch(self.beam_type)
+               {
+                       case ARC_BT_MISS:
+                       {
+                               self.beam_color = '1 1 1';
+                               self.beam_alpha = 0.5;
+                               self.beam_thickness = 8;
+                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM);
+                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING);
+                               self.beam_hitlight[0] = 0;
+                               self.beam_hitlight[1] = 1;
+                               self.beam_hitlight[2] = 1;
+                               self.beam_hitlight[3] = 1;
+                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
+                               self.beam_muzzlelight[0] = 0;
+                               self.beam_muzzlelight[1] = 1;
+                               self.beam_muzzlelight[2] = 1;
+                               self.beam_muzzlelight[3] = 1;
+                               if(self.beam_muzzleeffect >= 0)
+                               {
+                                       setmodel(flash, "models/flash.md3");
+                                       flash.alpha = self.beam_alpha;
+                                       flash.colormod = self.beam_color;
+                                       flash.scale = 0.5;
+                               }
+                               break;
+                       }
+                       case ARC_BT_WALL: // grenadelauncher_muzzleflash healray_muzzleflash
+                       {
+                               self.beam_color = '1 1 1';
+                               self.beam_alpha = 0.5;
+                               self.beam_thickness = 8;
+                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM);
+                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING);
+                               self.beam_hitlight[0] = 0;
+                               self.beam_hitlight[1] = 1;
+                               self.beam_hitlight[2] = 1;
+                               self.beam_hitlight[3] = 1;
+                               self.beam_muzzleeffect = -1; // particleeffectnum(EFFECT_GRENADE_MUZZLEFLASH);
+                               self.beam_muzzlelight[0] = 0;
+                               self.beam_muzzlelight[1] = 1;
+                               self.beam_muzzlelight[2] = 1;
+                               self.beam_muzzlelight[3] = 1;
+                               self.beam_image = "particles/lgbeam";
+                               if(self.beam_muzzleeffect >= 0)
+                               {
+                                       setmodel(flash, "models/flash.md3");
+                                       flash.alpha = self.beam_alpha;
+                                       flash.colormod = self.beam_color;
+                                       flash.scale = 0.5;
+                               }
+                               break;
+                       }
+                       case ARC_BT_HEAL:
+                       {
+                               self.beam_color = '1 1 1';
+                               self.beam_alpha = 0.5;
+                               self.beam_thickness = 8;
+                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL);
+                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL_IMPACT);
+                               self.beam_hitlight[0] = 0;
+                               self.beam_hitlight[1] = 1;
+                               self.beam_hitlight[2] = 1;
+                               self.beam_hitlight[3] = 1;
+                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
+                               self.beam_muzzlelight[0] = 0;
+                               self.beam_muzzlelight[1] = 1;
+                               self.beam_muzzlelight[2] = 1;
+                               self.beam_muzzlelight[3] = 1;
+                               self.beam_image = "particles/lgbeam";
+                               if(self.beam_muzzleeffect >= 0)
+                               {
+                                       setmodel(flash, "models/flash.md3");
+                                       flash.alpha = self.beam_alpha;
+                                       flash.colormod = self.beam_color;
+                                       flash.scale = 0.5;
+                               }
+                               break;
+                       }
+                       case ARC_BT_HIT:
+                       {
+                               self.beam_color = '1 1 1';
+                               self.beam_alpha = 0.5;
+                               self.beam_thickness = 8;
+                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM);
+                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING);
+                               self.beam_hitlight[0] = 20;
+                               self.beam_hitlight[1] = 1;
+                               self.beam_hitlight[2] = 0;
+                               self.beam_hitlight[3] = 0;
+                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
+                               self.beam_muzzlelight[0] = 50;
+                               self.beam_muzzlelight[1] = 1;
+                               self.beam_muzzlelight[2] = 0;
+                               self.beam_muzzlelight[3] = 0;
+                               self.beam_image = "particles/lgbeam";
+                               if(self.beam_muzzleeffect >= 0)
+                               {
+                                       setmodel(flash, "models/flash.md3");
+                                       flash.alpha = self.beam_alpha;
+                                       flash.colormod = self.beam_color;
+                                       flash.scale = 0.5;
+                               }
+                               break;
+                       }
+                       case ARC_BT_BURST_MISS:
+                       {
+                               self.beam_color = '1 1 1';
+                               self.beam_alpha = 0.5;
+                               self.beam_thickness = 14;
+                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM);
+                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING);
+                               self.beam_hitlight[0] = 0;
+                               self.beam_hitlight[1] = 1;
+                               self.beam_hitlight[2] = 1;
+                               self.beam_hitlight[3] = 1;
+                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
+                               self.beam_muzzlelight[0] = 0;
+                               self.beam_muzzlelight[1] = 1;
+                               self.beam_muzzlelight[2] = 1;
+                               self.beam_muzzlelight[3] = 1;
+                               self.beam_image = "particles/lgbeam";
+                               if(self.beam_muzzleeffect >= 0)
+                               {
+                                       setmodel(flash, "models/flash.md3");
+                                       flash.alpha = self.beam_alpha;
+                                       flash.colormod = self.beam_color;
+                                       flash.scale = 0.5;
+                               }
+                               break;
+                       }
+                       case ARC_BT_BURST_WALL:
+                       {
+                               self.beam_color = '1 1 1';
+                               self.beam_alpha = 0.5;
+                               self.beam_thickness = 14;
+                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM);
+                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING);
+                               self.beam_hitlight[0] = 0;
+                               self.beam_hitlight[1] = 1;
+                               self.beam_hitlight[2] = 1;
+                               self.beam_hitlight[3] = 1;
+                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
+                               self.beam_muzzlelight[0] = 0;
+                               self.beam_muzzlelight[1] = 1;
+                               self.beam_muzzlelight[2] = 1;
+                               self.beam_muzzlelight[3] = 1;
+                               self.beam_image = "particles/lgbeam";
+                               if(self.beam_muzzleeffect >= 0)
+                               {
+                                       setmodel(flash, "models/flash.md3");
+                                       flash.alpha = self.beam_alpha;
+                                       flash.colormod = self.beam_color;
+                                       flash.scale = 0.5;
+                               }
+                               break;
+                       }
+                       case ARC_BT_BURST_HEAL:
+                       {
+                               self.beam_color = '1 1 1';
+                               self.beam_alpha = 0.5;
+                               self.beam_thickness = 14;
+                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL);
+                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL_IMPACT2);
+                               self.beam_hitlight[0] = 0;
+                               self.beam_hitlight[1] = 1;
+                               self.beam_hitlight[2] = 1;
+                               self.beam_hitlight[3] = 1;
+                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
+                               self.beam_muzzlelight[0] = 0;
+                               self.beam_muzzlelight[1] = 1;
+                               self.beam_muzzlelight[2] = 1;
+                               self.beam_muzzlelight[3] = 1;
+                               self.beam_image = "particles/lgbeam";
+                               if(self.beam_muzzleeffect >= 0)
+                               {
+                                       setmodel(flash, "models/flash.md3");
+                                       flash.alpha = self.beam_alpha;
+                                       flash.colormod = self.beam_color;
+                                       flash.scale = 0.5;
+                               }
+                               break;
+                       }
+                       case ARC_BT_BURST_HIT:
+                       {
+                               self.beam_color = '1 1 1';
+                               self.beam_alpha = 0.5;
+                               self.beam_thickness = 14;
+                               self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM);
+                               self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING);
+                               self.beam_hitlight[0] = 0;
+                               self.beam_hitlight[1] = 1;
+                               self.beam_hitlight[2] = 1;
+                               self.beam_hitlight[3] = 1;
+                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
+                               self.beam_muzzlelight[0] = 0;
+                               self.beam_muzzlelight[1] = 1;
+                               self.beam_muzzlelight[2] = 1;
+                               self.beam_muzzlelight[3] = 1;
+                               self.beam_image = "particles/lgbeam";
+                               if(self.beam_muzzleeffect >= 0)
+                               {
+                                       setmodel(flash, "models/flash.md3");
+                                       flash.alpha = self.beam_alpha;
+                                       flash.colormod = self.beam_color;
+                                       flash.scale = 0.5;
+                               }
+                               break;
+                       }
+
+                       // shouldn't be possible, but lets make it colorful if it does :D
+                       default:
+                       {
+                               self.beam_color = randomvec();
+                               self.beam_alpha = 1;
+                               self.beam_thickness = 8;
+                               self.beam_traileffect = false;
+                               self.beam_hiteffect = false;
+                               self.beam_hitlight[0] = 0;
+                               self.beam_hitlight[1] = 1;
+                               self.beam_hitlight[2] = 1;
+                               self.beam_hitlight[3] = 1;
+                               self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH);
+                               self.beam_muzzlelight[0] = 0;
+                               self.beam_muzzlelight[1] = 1;
+                               self.beam_muzzlelight[2] = 1;
+                               self.beam_muzzlelight[3] = 1;
+                               self.beam_image = "particles/lgbeam";
+                               if(self.beam_muzzleeffect >= 0)
+                               {
+                                       setmodel(flash, "models/flash.md3");
+                                       flash.alpha = self.beam_alpha;
+                                       flash.colormod = self.beam_color;
+                                       flash.scale = 0.5;
+                               }
+                               break;
+                       }
+               }
+       }
+
+       if(!self.beam_usevieworigin)
+       {
+               InterpolateOrigin_Note();
+       }
+}
+
+bool W_Arc(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       // todo
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("arc_loop"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/blaster.qc b/qcsrc/common/weapons/weapon/blaster.qc
new file mode 100644 (file)
index 0000000..6234f4a
--- /dev/null
@@ -0,0 +1,297 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ BLASTER,
+/* function  */ W_Blaster,
+/* ammotype  */ ammo_none,
+/* impulse   */ 1,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
+/* rating    */ 0,
+/* color     */ '1 0.5 0.5',
+/* modelname */ "laser",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairlaser 0.5",
+/* wepimg    */ "weaponlaser",
+/* refname   */ "blaster",
+/* wepname   */ _("Blaster")
+);
+
+#define BLASTER_SETTINGS(w_cvar,w_prop) BLASTER_SETTINGS_LIST(w_cvar, w_prop, BLASTER, blaster)
+#define BLASTER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, BOTH, animtime) \
+       w_cvar(id, sn, BOTH, damage) \
+       w_cvar(id, sn, BOTH, delay) \
+       w_cvar(id, sn, BOTH, edgedamage) \
+       w_cvar(id, sn, BOTH, force) \
+       w_cvar(id, sn, BOTH, force_zscale) \
+       w_cvar(id, sn, BOTH, lifetime) \
+       w_cvar(id, sn, BOTH, radius) \
+       w_cvar(id, sn, BOTH, refire) \
+       w_cvar(id, sn, BOTH, shotangle) \
+       w_cvar(id, sn, BOTH, speed) \
+       w_cvar(id, sn, BOTH, spread) \
+       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) \
+       w_prop(id, sn, float,  weaponstart, weaponstart) \
+       w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
+       w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
+
+#ifdef SVQC
+BLASTER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+.float blaster_damage;
+.float blaster_edgedamage;
+.float blaster_radius;
+.float blaster_force;
+.float blaster_lifetime;
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_blaster(void) { weapon_defaultspawnfunc(WEP_BLASTER.m_id); }
+void spawnfunc_weapon_laser(void) { spawnfunc_weapon_blaster(); }
+
+void W_Blaster_Touch(void)
+{
+       PROJECTILE_TOUCH;
+
+       self.event_damage = func_null;
+
+       RadiusDamage(
+               self,
+               self.realowner,
+               self.blaster_damage,
+               self.blaster_edgedamage,
+               self.blaster_radius,
+               world,
+               world,
+               self.blaster_force,
+               self.projectiledeathtype,
+               other
+       );
+
+       remove(self);
+}
+
+void W_Blaster_Think(void)
+{
+       self.movetype = MOVETYPE_FLY;
+       self.think = SUB_Remove;
+       self.nextthink = time + self.blaster_lifetime;
+       CSQCProjectile(self, true, PROJECTILE_BLASTER, true);
+}
+
+void W_Blaster_Attack(
+       float atk_deathtype,
+       float atk_shotangle,
+       float atk_damage,
+       float atk_edgedamage,
+       float atk_radius,
+       float atk_force,
+       float atk_speed,
+       float atk_spread,
+       float atk_delay,
+       float atk_lifetime)
+{
+       vector s_forward = v_forward * cos(atk_shotangle * DEG2RAD) + v_up * sin(atk_shotangle * DEG2RAD);
+
+       W_SetupShot_Dir(self, s_forward, false, 3, W_Sound("lasergun_fire"), CH_WEAPON_B, atk_damage);
+       Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       entity missile = spawn();
+       missile.owner = missile.realowner = self;
+       missile.classname = "blasterbolt";
+       missile.bot_dodge = true;
+       missile.bot_dodgerating = atk_damage;
+       PROJECTILE_MAKETRIGGER(missile);
+
+       missile.blaster_damage = atk_damage;
+       missile.blaster_edgedamage = atk_edgedamage;
+       missile.blaster_radius = atk_radius;
+       missile.blaster_force = atk_force;
+       missile.blaster_lifetime = atk_lifetime;
+
+       setorigin(missile, w_shotorg);
+       setsize(missile, '0 0 0', '0 0 0');
+
+       W_SetupProjVelocity_Explicit(
+               missile,
+               w_shotdir,
+               v_up,
+               atk_speed,
+               0,
+               0,
+               atk_spread,
+               false
+       );
+
+       missile.angles = vectoangles(missile.velocity);
+
+       //missile.glow_color = 250; // 244, 250
+       //missile.glow_size = 120;
+
+       missile.touch = W_Blaster_Touch;
+       missile.flags = FL_PROJECTILE;
+       missile.missile_flags = MIF_SPLASH;
+       missile.projectiledeathtype = atk_deathtype;
+       missile.think = W_Blaster_Think;
+       missile.nextthink = time + atk_delay;
+
+       MUTATOR_CALLHOOK(EditProjectile, self, missile);
+
+       if(time >= missile.nextthink)
+       {
+               entity oldself;
+               oldself = self;
+               self = missile;
+               self.think();
+               self = oldself;
+       }
+}
+bool W_Blaster(int request)
+{
+       switch(request)
+       {
+               case WR_AIM:
+               {
+                       if(WEP_CVAR(blaster, secondary))
+                       {
+                               if((random() * (WEP_CVAR_PRI(blaster, damage) + WEP_CVAR_SEC(blaster, damage))) > WEP_CVAR_PRI(blaster, damage))
+                                       { self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_SEC(blaster, speed), 0, WEP_CVAR_SEC(blaster, lifetime), false); }
+                               else
+                                       { self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(blaster, speed), 0, WEP_CVAR_PRI(blaster, lifetime), false); }
+                       }
+                       else
+                               { self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(blaster, speed), 0, WEP_CVAR_PRI(blaster, lifetime), false); }
+
+                       return true;
+               }
+
+               case WR_THINK:
+               {
+                       if(self.BUTTON_ATCK)
+                       {
+                               if(weapon_prepareattack(0, WEP_CVAR_PRI(blaster, refire)))
+                               {
+                                       W_Blaster_Attack(
+                                               WEP_BLASTER.m_id,
+                                               WEP_CVAR_PRI(blaster, shotangle),
+                                               WEP_CVAR_PRI(blaster, damage),
+                                               WEP_CVAR_PRI(blaster, edgedamage),
+                                               WEP_CVAR_PRI(blaster, radius),
+                                               WEP_CVAR_PRI(blaster, force),
+                                               WEP_CVAR_PRI(blaster, speed),
+                                               WEP_CVAR_PRI(blaster, spread),
+                                               WEP_CVAR_PRI(blaster, delay),
+                                               WEP_CVAR_PRI(blaster, lifetime)
+                                       );
+                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(blaster, animtime), w_ready);
+                               }
+                       }
+                       else if(self.BUTTON_ATCK2)
+                       {
+                               switch(WEP_CVAR(blaster, secondary))
+                               {
+                                       case 0: // switch to last used weapon
+                                       {
+                                               if(self.switchweapon == WEP_BLASTER.m_id) // don't do this if already switching
+                                                       W_LastWeapon();
+                                               break;
+                                       }
+
+                                       case 1: // normal projectile secondary
+                                       {
+                                               if(weapon_prepareattack(1, WEP_CVAR_SEC(blaster, refire)))
+                                               {
+                                                       W_Blaster_Attack(
+                                                               WEP_BLASTER.m_id | HITTYPE_SECONDARY,
+                                                               WEP_CVAR_SEC(blaster, shotangle),
+                                                               WEP_CVAR_SEC(blaster, damage),
+                                                               WEP_CVAR_SEC(blaster, edgedamage),
+                                                               WEP_CVAR_SEC(blaster, radius),
+                                                               WEP_CVAR_SEC(blaster, force),
+                                                               WEP_CVAR_SEC(blaster, speed),
+                                                               WEP_CVAR_SEC(blaster, spread),
+                                                               WEP_CVAR_SEC(blaster, delay),
+                                                               WEP_CVAR_SEC(blaster, lifetime)
+                                                       );
+                                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(blaster, animtime), w_ready);
+                                               }
+
+                                               break;
+                                       }
+                               }
+                       }
+                       return true;
+               }
+
+               case WR_INIT:
+               {
+                       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;
+               }
+
+               case WR_SETUP:
+               {
+                       self.ammo_field = ammo_none;
+                       return true;
+               }
+
+               case WR_CHECKAMMO1:
+               case WR_CHECKAMMO2:
+               {
+                       return true; // laser has infinite ammo
+               }
+
+               case WR_CONFIG:
+               {
+                       BLASTER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+
+               case WR_SUICIDEMESSAGE:
+               {
+                       return WEAPON_BLASTER_SUICIDE;
+               }
+
+               case WR_KILLMESSAGE:
+               {
+                       return WEAPON_BLASTER_MURDER;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_Blaster(int request)
+{
+       switch(request)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 6;
+                       pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1);
+                       if(!w_issilent) { sound(self, CH_SHOTS, W_Sound("laserimpact"), VOL_BASE, ATTN_NORM); }
+                       return true;
+               }
+
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("laserimpact"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/crylink.qc b/qcsrc/common/weapons/weapon/crylink.qc
new file mode 100644 (file)
index 0000000..08deaf7
--- /dev/null
@@ -0,0 +1,731 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ CRYLINK,
+/* function  */ W_Crylink,
+/* ammotype  */ ammo_cells,
+/* impulse   */ 6,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* color     */ '1 0.5 1',
+/* modelname */ "crylink",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshaircrylink 0.5",
+/* wepimg    */ "weaponcrylink",
+/* refname   */ "crylink",
+/* wepname   */ _("Crylink")
+);
+
+#define CRYLINK_SETTINGS(w_cvar,w_prop) CRYLINK_SETTINGS_LIST(w_cvar, w_prop, CRYLINK, crylink)
+#define CRYLINK_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, BOTH, ammo) \
+       w_cvar(id, sn, BOTH, animtime) \
+       w_cvar(id, sn, BOTH, damage) \
+       w_cvar(id, sn, BOTH, edgedamage) \
+       w_cvar(id, sn, BOTH, radius) \
+       w_cvar(id, sn, BOTH, force) \
+       w_cvar(id, sn, BOTH, spread) \
+       w_cvar(id, sn, BOTH, refire) \
+       w_cvar(id, sn, BOTH, speed) \
+       w_cvar(id, sn, BOTH, shots) \
+       w_cvar(id, sn, BOTH, bounces) \
+       w_cvar(id, sn, BOTH, bouncedamagefactor) \
+       w_cvar(id, sn, BOTH, middle_lifetime) \
+       w_cvar(id, sn, BOTH, middle_fadetime) \
+       w_cvar(id, sn, BOTH, other_lifetime) \
+       w_cvar(id, sn, BOTH, other_fadetime) \
+       w_cvar(id, sn, BOTH, linkexplode) \
+       w_cvar(id, sn, BOTH, joindelay) \
+       w_cvar(id, sn, BOTH, joinspread) \
+       w_cvar(id, sn, BOTH, joinexplode) \
+       w_cvar(id, sn, BOTH, joinexplode_damage) \
+       w_cvar(id, sn, BOTH, joinexplode_edgedamage) \
+       w_cvar(id, sn, BOTH, joinexplode_radius) \
+       w_cvar(id, sn, BOTH, joinexplode_force) \
+       w_cvar(id, sn, SEC,  spreadtype) \
+       w_cvar(id, sn, NONE, secondary) \
+       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
+CRYLINK_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+.float gravity;
+.float crylink_waitrelease;
+.entity crylink_lastgroup;
+
+.entity queuenext;
+.entity queueprev;
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_crylink(void) { weapon_defaultspawnfunc(WEP_CRYLINK.m_id); }
+
+void W_Crylink_CheckLinks(entity e)
+{
+       float i;
+       entity p;
+
+       if(e == world)
+               error("W_Crylink_CheckLinks: entity is world");
+       if(e.classname != "spike" || wasfreed(e))
+               error(sprintf("W_Crylink_CheckLinks: entity is not a spike but a %s (freed: %d)", e.classname, wasfreed(e)));
+
+       p = e;
+       for(i = 0; i < 1000; ++i)
+       {
+               if(p.queuenext.queueprev != p || p.queueprev.queuenext != p)
+                       error("W_Crylink_CheckLinks: queue is inconsistent");
+               p = p.queuenext;
+               if(p == e)
+                       break;
+       }
+       if(i >= 1000)
+               error("W_Crylink_CheckLinks: infinite chain");
+}
+
+void W_Crylink_Dequeue_Raw(entity own, entity prev, entity me, entity next)
+{
+       W_Crylink_CheckLinks(next);
+       if(me == own.crylink_lastgroup)
+               own.crylink_lastgroup = ((me == next) ? world : next);
+       prev.queuenext = next;
+       next.queueprev = prev;
+       me.classname = "spike_oktoremove";
+       if(me != next)
+               W_Crylink_CheckLinks(next);
+}
+
+void W_Crylink_Dequeue(entity e)
+{
+       W_Crylink_Dequeue_Raw(e.realowner, e.queueprev, e, e.queuenext);
+}
+
+void W_Crylink_Reset(void)
+{
+       W_Crylink_Dequeue(self);
+       remove(self);
+}
+
+// force projectile to explode
+void W_Crylink_LinkExplode(entity e, entity e2)
+{
+       float a;
+
+       if(e == e2)
+               return;
+
+       a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1);
+
+       if(e == e.realowner.crylink_lastgroup)
+               e.realowner.crylink_lastgroup = world;
+
+       float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
+
+       RadiusDamage(e, e.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * a, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * a, WEP_CVAR_BOTH(crylink, isprimary, radius), world, world, WEP_CVAR_BOTH(crylink, isprimary, force) * a, e.projectiledeathtype, other);
+
+       W_Crylink_LinkExplode(e.queuenext, e2);
+
+       e.classname = "spike_oktoremove";
+       remove(e);
+}
+
+// adjust towards center
+// returns the origin where they will meet... and the time till the meeting is
+// stored in w_crylink_linkjoin_time.
+// could possibly network this origin and time, and display a special particle
+// effect when projectiles meet there :P
+// jspeed: joining speed (calculate this as join spread * initial speed)
+float w_crylink_linkjoin_time;
+vector W_Crylink_LinkJoin(entity e, float jspeed)
+{
+       vector avg_origin, avg_velocity;
+       vector targ_origin;
+       float avg_dist, n;
+       entity p;
+
+       // FIXME remove this debug code
+       W_Crylink_CheckLinks(e);
+
+       w_crylink_linkjoin_time = 0;
+
+       avg_origin = e.origin;
+       avg_velocity = e.velocity;
+       n = 1;
+       for(p = e; (p = p.queuenext) != e; )
+       {
+               avg_origin += WarpZone_RefSys_TransformOrigin(p, e, p.origin);
+               avg_velocity += WarpZone_RefSys_TransformVelocity(p, e, p.velocity);
+               ++n;
+       }
+       avg_origin *= (1.0 / n);
+       avg_velocity *= (1.0 / n);
+
+       if(n < 2)
+               return avg_origin; // nothing to do
+
+       // yes, mathematically we can do this in ONE step, but beware of 32bit floats...
+       avg_dist = pow(vlen(e.origin - avg_origin), 2);
+       for(p = e; (p = p.queuenext) != e; )
+               avg_dist += pow(vlen(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_origin), 2);
+       avg_dist *= (1.0 / n);
+       avg_dist = sqrt(avg_dist);
+
+       if(avg_dist == 0)
+               return avg_origin; // no change needed
+
+       if(jspeed == 0)
+       {
+               e.velocity = avg_velocity;
+               UpdateCSQCProjectile(e);
+               for(p = e; (p = p.queuenext) != e; )
+               {
+                       p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_velocity);
+                       UpdateCSQCProjectile(p);
+               }
+               targ_origin = avg_origin + 1000000000 * normalize(avg_velocity); // HUUUUUUGE
+       }
+       else
+       {
+               w_crylink_linkjoin_time = avg_dist / jspeed;
+               targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity;
+
+               e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time);
+               UpdateCSQCProjectile(e);
+               for(p = e; (p = p.queuenext) != e; )
+               {
+                       p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time));
+                       UpdateCSQCProjectile(p);
+               }
+
+               // analysis:
+               //   jspeed -> +infinity:
+               //      w_crylink_linkjoin_time -> +0
+               //      targ_origin -> avg_origin
+               //      p->velocity -> HUEG towards center
+               //   jspeed -> 0:
+               //      w_crylink_linkjoin_time -> +/- infinity
+               //      targ_origin -> avg_velocity * +/- infinity
+               //      p->velocity -> avg_velocity
+               //   jspeed -> -infinity:
+               //      w_crylink_linkjoin_time -> -0
+               //      targ_origin -> avg_origin
+               //      p->velocity -> HUEG away from center
+       }
+
+       W_Crylink_CheckLinks(e);
+
+       return targ_origin;
+}
+
+void W_Crylink_LinkJoinEffect_Think(void)
+{
+       // is there at least 2 projectiles very close?
+       entity e, p;
+       float n;
+       e = self.owner.crylink_lastgroup;
+       n = 0;
+       if(e)
+       {
+               if(vlen(e.origin - self.origin) < vlen(e.velocity) * frametime)
+                       ++n;
+               for(p = e; (p = p.queuenext) != e; )
+               {
+                       if(vlen(p.origin - self.origin) < vlen(p.velocity) * frametime)
+                               ++n;
+               }
+               if(n >= 2)
+               {
+                       float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
+
+                       if(WEP_CVAR_BOTH(crylink, isprimary, joinexplode))
+                       {
+                               n /= WEP_CVAR_BOTH(crylink, isprimary, shots);
+                               RadiusDamage(
+                                       e,
+                                       e.realowner,
+                                       WEP_CVAR_BOTH(crylink, isprimary, joinexplode_damage) * n,
+                                       WEP_CVAR_BOTH(crylink, isprimary, joinexplode_edgedamage) * n,
+                                       WEP_CVAR_BOTH(crylink, isprimary, joinexplode_radius) * n,
+                                       e.realowner,
+                                       world,
+                                       WEP_CVAR_BOTH(crylink, isprimary, joinexplode_force) * n,
+                                       e.projectiledeathtype,
+                                       other
+                               );
+                               Send_Effect(EFFECT_CRYLINK_JOINEXPLODE, self.origin, '0 0 0', n);
+                       }
+               }
+       }
+       remove(self);
+}
+
+float W_Crylink_Touch_WouldHitFriendly(entity projectile, float rad)
+{
+       entity head = WarpZone_FindRadius((projectile.origin + (projectile.mins + projectile.maxs) * 0.5), rad + MAX_DAMAGEEXTRARADIUS, false);
+       float hit_friendly = 0;
+       float hit_enemy = 0;
+
+       while(head)
+       {
+               if((head.takedamage != DAMAGE_NO) && (head.deadflag == DEAD_NO))
+               {
+                       if(SAME_TEAM(head, projectile.realowner))
+                               ++hit_friendly;
+                       else
+                               ++hit_enemy;
+               }
+
+               head = head.chain;
+       }
+
+       return (hit_enemy ? false : hit_friendly);
+}
+
+// NO bounce protection, as bounces are limited!
+void W_Crylink_Touch(void)
+{
+       float finalhit;
+       float f;
+       float isprimary = !(self.projectiledeathtype & HITTYPE_SECONDARY);
+       PROJECTILE_TOUCH;
+
+       float a;
+       a = bound(0, 1 - (time - self.fade_time) * self.fade_rate, 1);
+
+       finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO));
+       if(finalhit)
+               f = 1;
+       else
+               f = WEP_CVAR_BOTH(crylink, isprimary, bouncedamagefactor);
+       if(a)
+               f *= a;
+
+       float totaldamage = RadiusDamage(self, self.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * f, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * f, WEP_CVAR_BOTH(crylink, isprimary, radius), world, world, WEP_CVAR_BOTH(crylink, isprimary, force) * f, self.projectiledeathtype, other);
+
+       if(totaldamage && ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 2) || ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 1) && !W_Crylink_Touch_WouldHitFriendly(self, WEP_CVAR_BOTH(crylink, isprimary, radius)))))
+       {
+               if(self == self.realowner.crylink_lastgroup)
+                       self.realowner.crylink_lastgroup = world;
+               W_Crylink_LinkExplode(self.queuenext, self);
+               self.classname = "spike_oktoremove";
+               remove(self);
+               return;
+       }
+       else if(finalhit)
+       {
+               // just unlink
+               W_Crylink_Dequeue(self);
+               remove(self);
+               return;
+       }
+       self.cnt = self.cnt - 1;
+       self.angles = vectoangles(self.velocity);
+       self.owner = world;
+       self.projectiledeathtype |= HITTYPE_BOUNCE;
+       // commented out as it causes a little hitch...
+       //if(proj.cnt == 0)
+       //      CSQCProjectile(proj, true, PROJECTILE_CRYLINK, true);
+}
+
+void W_Crylink_Fadethink(void)
+{
+       W_Crylink_Dequeue(self);
+       remove(self);
+}
+
+void W_Crylink_Attack(void)
+{
+       float counter, shots;
+       entity proj, prevproj, firstproj;
+       vector s;
+       vector forward, right, up;
+       float maxdmg;
+
+       W_DecreaseAmmo(WEP_CVAR_PRI(crylink, ammo));
+
+       maxdmg = WEP_CVAR_PRI(crylink, damage) * WEP_CVAR_PRI(crylink, shots);
+       maxdmg *= 1 + WEP_CVAR_PRI(crylink, bouncedamagefactor) * WEP_CVAR_PRI(crylink, bounces);
+       if(WEP_CVAR_PRI(crylink, joinexplode))
+               maxdmg += WEP_CVAR_PRI(crylink, joinexplode_damage);
+
+       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);
+       Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots);
+       proj = prevproj = firstproj = world;
+       for(counter = 0; counter < shots; ++counter)
+       {
+               proj = spawn();
+               proj.reset = W_Crylink_Reset;
+               proj.realowner = proj.owner = self;
+               proj.classname = "spike";
+               proj.bot_dodge = true;
+               proj.bot_dodgerating = WEP_CVAR_PRI(crylink, damage);
+               if(shots == 1) {
+                       proj.queuenext = proj;
+                       proj.queueprev = proj;
+               }
+               else if(counter == 0) { // first projectile, store in firstproj for now
+                       firstproj = proj;
+               }
+               else if(counter == shots - 1) { // last projectile, link up with first projectile
+                       prevproj.queuenext = proj;
+                       firstproj.queueprev = proj;
+                       proj.queuenext = firstproj;
+                       proj.queueprev = prevproj;
+               }
+               else { // else link up with previous projectile
+                       prevproj.queuenext = proj;
+                       proj.queueprev = prevproj;
+               }
+
+               prevproj = proj;
+
+               proj.movetype = MOVETYPE_BOUNCEMISSILE;
+               PROJECTILE_MAKETRIGGER(proj);
+               proj.projectiledeathtype = WEP_CRYLINK.m_id;
+               //proj.gravity = 0.001;
+
+               setorigin(proj, w_shotorg);
+               setsize(proj, '0 0 0', '0 0 0');
+
+
+               s = '0 0 0';
+               if(counter == 0)
+                       s = '0 0 0';
+               else
+               {
+                       makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
+                       s.y = v_forward.x;
+                       s.z = v_forward.y;
+               }
+               s = s * WEP_CVAR_PRI(crylink, spread) * g_weaponspreadfactor;
+               W_SetupProjVelocity_Explicit(proj, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_PRI(crylink, speed), 0, 0, 0, false);
+               proj.touch = W_Crylink_Touch;
+
+               proj.think = W_Crylink_Fadethink;
+               if(counter == 0)
+               {
+                       proj.fade_time = time + WEP_CVAR_PRI(crylink, middle_lifetime);
+                       proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, middle_fadetime);
+                       proj.nextthink = time + WEP_CVAR_PRI(crylink, middle_lifetime) + WEP_CVAR_PRI(crylink, middle_fadetime);
+               }
+               else
+               {
+                       proj.fade_time = time + WEP_CVAR_PRI(crylink, other_lifetime);
+                       proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, other_fadetime);
+                       proj.nextthink = time + WEP_CVAR_PRI(crylink, other_lifetime) + WEP_CVAR_PRI(crylink, other_fadetime);
+               }
+               proj.teleport_time = time + WEP_CVAR_PRI(crylink, joindelay);
+               proj.cnt = WEP_CVAR_PRI(crylink, bounces);
+               //proj.scale = 1 + 1 * proj.cnt;
+
+               proj.angles = vectoangles(proj.velocity);
+
+               //proj.glow_size = 20;
+
+               proj.flags = FL_PROJECTILE;
+               proj.missile_flags = MIF_SPLASH;
+
+               CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
+
+               MUTATOR_CALLHOOK(EditProjectile, self, proj);
+       }
+       if(WEP_CVAR_PRI(crylink, joinspread) != 0)
+       {
+               self.crylink_lastgroup = proj;
+               W_Crylink_CheckLinks(proj);
+               self.crylink_waitrelease = 1;
+       }
+}
+
+void W_Crylink_Attack2(void)
+{
+       float counter, shots;
+       entity proj, prevproj, firstproj;
+       vector s;
+       vector forward, right, up;
+       float maxdmg;
+
+       W_DecreaseAmmo(WEP_CVAR_SEC(crylink, ammo));
+
+       maxdmg = WEP_CVAR_SEC(crylink, damage) * WEP_CVAR_SEC(crylink, shots);
+       maxdmg *= 1 + WEP_CVAR_SEC(crylink, bouncedamagefactor) * WEP_CVAR_SEC(crylink, bounces);
+       if(WEP_CVAR_SEC(crylink, joinexplode))
+               maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage);
+
+       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);
+       Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots);
+       proj = prevproj = firstproj = world;
+       for(counter = 0; counter < shots; ++counter)
+       {
+               proj = spawn();
+               proj.reset = W_Crylink_Reset;
+               proj.realowner = proj.owner = self;
+               proj.classname = "spike";
+               proj.bot_dodge = true;
+               proj.bot_dodgerating = WEP_CVAR_SEC(crylink, damage);
+               if(shots == 1) {
+                       proj.queuenext = proj;
+                       proj.queueprev = proj;
+               }
+               else if(counter == 0) { // first projectile, store in firstproj for now
+                       firstproj = proj;
+               }
+               else if(counter == shots - 1) { // last projectile, link up with first projectile
+                       prevproj.queuenext = proj;
+                       firstproj.queueprev = proj;
+                       proj.queuenext = firstproj;
+                       proj.queueprev = prevproj;
+               }
+               else { // else link up with previous projectile
+                       prevproj.queuenext = proj;
+                       proj.queueprev = prevproj;
+               }
+
+               prevproj = proj;
+
+               proj.movetype = MOVETYPE_BOUNCEMISSILE;
+               PROJECTILE_MAKETRIGGER(proj);
+               proj.projectiledeathtype = WEP_CRYLINK.m_id | HITTYPE_SECONDARY;
+               //proj.gravity = 0.001;
+
+               setorigin(proj, w_shotorg);
+               setsize(proj, '0 0 0', '0 0 0');
+
+               if(WEP_CVAR_SEC(crylink, spreadtype) == 1)
+               {
+                       s = '0 0 0';
+                       if(counter == 0)
+                               s = '0 0 0';
+                       else
+                       {
+                               makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
+                               s.y = v_forward.x;
+                               s.z = v_forward.y;
+                       }
+                       s = s * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor;
+                       s = w_shotdir + right * s.y + up * s.z;
+               }
+               else
+               {
+                       s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor);
+               }
+
+               W_SetupProjVelocity_Explicit(proj, s, v_up, WEP_CVAR_SEC(crylink, speed), 0, 0, 0, false);
+               proj.touch = W_Crylink_Touch;
+               proj.think = W_Crylink_Fadethink;
+               if(counter == (shots - 1) / 2)
+               {
+                       proj.fade_time = time + WEP_CVAR_SEC(crylink, middle_lifetime);
+                       proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, middle_fadetime);
+                       proj.nextthink = time + WEP_CVAR_SEC(crylink, middle_lifetime) + WEP_CVAR_SEC(crylink, middle_fadetime);
+               }
+               else
+               {
+                       proj.fade_time = time + WEP_CVAR_SEC(crylink, other_lifetime);
+                       proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, other_fadetime);
+                       proj.nextthink = time + WEP_CVAR_SEC(crylink, other_lifetime) + WEP_CVAR_SEC(crylink, other_fadetime);
+               }
+               proj.teleport_time = time + WEP_CVAR_SEC(crylink, joindelay);
+               proj.cnt = WEP_CVAR_SEC(crylink, bounces);
+               //proj.scale = 1 + 1 * proj.cnt;
+
+               proj.angles = vectoangles(proj.velocity);
+
+               //proj.glow_size = 20;
+
+               proj.flags = FL_PROJECTILE;
+        proj.missile_flags = MIF_SPLASH;
+
+               CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
+
+               MUTATOR_CALLHOOK(EditProjectile, self, proj);
+       }
+       if(WEP_CVAR_SEC(crylink, joinspread) != 0)
+       {
+               self.crylink_lastgroup = proj;
+               W_Crylink_CheckLinks(proj);
+               self.crylink_waitrelease = 2;
+       }
+}
+
+bool W_Crylink(int req)
+{
+       float ammo_amount;
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       if(random() < 0.10)
+                               self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false);
+                       else
+                               self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false);
+
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       if(autocvar_g_balance_crylink_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo))) // forced reload
+                               WEP_ACTION(self.weapon, WR_RELOAD);
+
+                       if(self.BUTTON_ATCK)
+                       {
+                               if(self.crylink_waitrelease != 1)
+                               if(weapon_prepareattack(0, WEP_CVAR_PRI(crylink, refire)))
+                               {
+                                       W_Crylink_Attack();
+                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(crylink, animtime), w_ready);
+                               }
+                       }
+
+                       if(self.BUTTON_ATCK2 && autocvar_g_balance_crylink_secondary)
+                       {
+                               if(self.crylink_waitrelease != 2)
+                               if(weapon_prepareattack(1, WEP_CVAR_SEC(crylink, refire)))
+                               {
+                                       W_Crylink_Attack2();
+                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(crylink, animtime), w_ready);
+                               }
+                       }
+
+                       if((self.crylink_waitrelease == 1 && !self.BUTTON_ATCK) || (self.crylink_waitrelease == 2 && !self.BUTTON_ATCK2))
+                       {
+                               if(!self.crylink_lastgroup || time > self.crylink_lastgroup.teleport_time)
+                               {
+                                       // fired and released now!
+                                       if(self.crylink_lastgroup)
+                                       {
+                                               vector pos;
+                                               entity linkjoineffect;
+                                               float isprimary = (self.crylink_waitrelease == 1);
+
+                                               pos = W_Crylink_LinkJoin(self.crylink_lastgroup, WEP_CVAR_BOTH(crylink, isprimary, joinspread) * WEP_CVAR_BOTH(crylink, isprimary, speed));
+
+                                               linkjoineffect = spawn();
+                                               linkjoineffect.think = W_Crylink_LinkJoinEffect_Think;
+                                               linkjoineffect.classname = "linkjoineffect";
+                                               linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
+                                               linkjoineffect.owner = self;
+                                               setorigin(linkjoineffect, pos);
+                                       }
+                                       self.crylink_waitrelease = 0;
+                                       if(!W_Crylink(WR_CHECKAMMO1) && !W_Crylink(WR_CHECKAMMO2))
+                                       if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+                                       {
+                                               // ran out of ammo!
+                                               self.cnt = WEP_CRYLINK.m_id;
+                                               self.switchweapon = w_getbestweapon(self);
+                                       }
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       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;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       // don't "run out of ammo" and switch weapons while waiting for release
+                       if(self.crylink_lastgroup && self.crylink_waitrelease)
+                               return true;
+
+                       ammo_amount = self.WEP_AMMO(CRYLINK) >= WEP_CVAR_PRI(crylink, ammo);
+                       ammo_amount += self.(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
+                       return ammo_amount;
+               }
+               case WR_CHECKAMMO2:
+               {
+                       // don't "run out of ammo" and switch weapons while waiting for release
+                       if(self.crylink_lastgroup && self.crylink_waitrelease)
+                               return true;
+
+                       ammo_amount = self.WEP_AMMO(CRYLINK) >= WEP_CVAR_SEC(crylink, ammo);
+                       ammo_amount += self.(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
+                       return ammo_amount;
+               }
+               case WR_CONFIG:
+               {
+                       CRYLINK_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       W_Reload(min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo)), W_Sound("reload"));
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       return WEAPON_CRYLINK_SUICIDE;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       return WEAPON_CRYLINK_MURDER;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_Crylink(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 2;
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                       {
+                               pointparticles(particleeffectnum(EFFECT_CRYLINK_IMPACT2), org2, '0 0 0', 1);
+                               if(!w_issilent)
+                                       sound(self, CH_SHOTS, W_Sound("crylink_impact2"), VOL_BASE, ATTN_NORM);
+                       }
+                       else
+                       {
+                               pointparticles(particleeffectnum(EFFECT_CRYLINK_IMPACT), org2, '0 0 0', 1);
+                               if(!w_issilent)
+                                       sound(self, CH_SHOTS, W_Sound("crylink_impact"), VOL_BASE, ATTN_NORM);
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("crylink_impact2"));
+                       precache_sound(W_Sound("crylink_impact"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/devastator.qc b/qcsrc/common/weapons/weapon/devastator.qc
new file mode 100644 (file)
index 0000000..9a235bf
--- /dev/null
@@ -0,0 +1,687 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ DEVASTATOR,
+/* function  */ W_Devastator,
+/* ammotype  */ ammo_rockets,
+/* impulse   */ 9,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_HIGH,
+/* color     */ '1 1 0',
+/* modelname */ "rl",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairrocketlauncher 0.7",
+/* wepimg    */ "weaponrocketlauncher",
+/* refname   */ "devastator",
+/* wepname   */ _("Devastator")
+);
+
+#define DEVASTATOR_SETTINGS(w_cvar,w_prop) DEVASTATOR_SETTINGS_LIST(w_cvar, w_prop, DEVASTATOR, devastator)
+#define DEVASTATOR_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, damage) \
+       w_cvar(id, sn, NONE, damageforcescale) \
+       w_cvar(id, sn, NONE, detonatedelay) \
+       w_cvar(id, sn, NONE, edgedamage) \
+       w_cvar(id, sn, NONE, force) \
+       w_cvar(id, sn, NONE, guidedelay) \
+       w_cvar(id, sn, NONE, guidegoal) \
+       w_cvar(id, sn, NONE, guiderate) \
+       w_cvar(id, sn, NONE, guideratedelay) \
+       w_cvar(id, sn, NONE, guidestop) \
+       w_cvar(id, sn, NONE, health) \
+       w_cvar(id, sn, NONE, lifetime) \
+       w_cvar(id, sn, NONE, radius) \
+       w_cvar(id, sn, NONE, refire) \
+       w_cvar(id, sn, NONE, remote_damage) \
+       w_cvar(id, sn, NONE, remote_edgedamage) \
+       w_cvar(id, sn, NONE, remote_force) \
+       w_cvar(id, sn, NONE, remote_jump_damage) \
+       w_cvar(id, sn, NONE, remote_jump_radius) \
+       w_cvar(id, sn, NONE, remote_jump_velocity_z_add) \
+       w_cvar(id, sn, NONE, remote_jump_velocity_z_max) \
+       w_cvar(id, sn, NONE, remote_jump_velocity_z_min) \
+       w_cvar(id, sn, NONE, remote_radius) \
+       w_cvar(id, sn, NONE, speed) \
+       w_cvar(id, sn, NONE, speedaccel) \
+       w_cvar(id, sn, NONE, speedstart) \
+       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
+DEVASTATOR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+.float rl_release;
+.float rl_detonate_later;
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_devastator(void) { weapon_defaultspawnfunc(WEP_DEVASTATOR.m_id); }
+void spawnfunc_weapon_rocketlauncher(void) { spawnfunc_weapon_devastator(); }
+
+void W_Devastator_Unregister(void)
+{
+       if(self.realowner && self.realowner.lastrocket == self)
+       {
+               self.realowner.lastrocket = world;
+               // self.realowner.rl_release = 1;
+       }
+}
+
+void W_Devastator_Explode(void)
+{
+       W_Devastator_Unregister();
+
+       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_AIRSHOT);
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       RadiusDamage(
+               self,
+               self.realowner,
+               WEP_CVAR(devastator, damage),
+               WEP_CVAR(devastator, edgedamage),
+               WEP_CVAR(devastator, radius),
+               world,
+               world,
+               WEP_CVAR(devastator, force),
+               self.projectiledeathtype,
+               other
+       );
+
+       if(self.realowner.weapon == WEP_DEVASTATOR.m_id)
+       {
+               if(self.realowner.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo))
+               if(!(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO))
+               {
+                       self.realowner.cnt = WEP_DEVASTATOR.m_id;
+                       ATTACK_FINISHED(self.realowner) = time;
+                       self.realowner.switchweapon = w_getbestweapon(self.realowner);
+               }
+       }
+       remove(self);
+}
+
+void W_Devastator_DoRemoteExplode(void)
+{
+       W_Devastator_Unregister();
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       float handled_as_rocketjump = false;
+
+       entity head = WarpZone_FindRadius(
+               self.origin,
+               WEP_CVAR(devastator, remote_jump_radius),
+               false
+       );
+
+       while(head)
+       {
+               if(head.takedamage && (head == self.realowner))
+               {
+                       float distance_to_head = vlen(self.origin - head.WarpZone_findradius_nearest);
+                       if(distance_to_head <= WEP_CVAR(devastator, remote_jump_radius))
+                       {
+                               // we handled this as a rocketjump :)
+                               handled_as_rocketjump = true;
+
+                               // modify velocity
+                               head.velocity_x *= 0.9;
+                               head.velocity_y *= 0.9;
+                               head.velocity_z = bound(
+                                       WEP_CVAR(devastator, remote_jump_velocity_z_min),
+                                       head.velocity.z + WEP_CVAR(devastator, remote_jump_velocity_z_add),
+                                       WEP_CVAR(devastator, remote_jump_velocity_z_max)
+                               );
+
+                               // now do the damage
+                               RadiusDamage(
+                                       self,
+                                       head,
+                                       WEP_CVAR(devastator, remote_jump_damage),
+                                       WEP_CVAR(devastator, remote_jump_damage),
+                                       WEP_CVAR(devastator, remote_jump_radius),
+                                       world,
+                                       head,
+                                       0,
+                                       self.projectiledeathtype | HITTYPE_BOUNCE,
+                                       world
+                               );
+                               break;
+                       }
+               }
+               head = head.chain;
+       }
+
+       RadiusDamage(
+               self,
+               self.realowner,
+               WEP_CVAR(devastator, remote_damage),
+               WEP_CVAR(devastator, remote_edgedamage),
+               WEP_CVAR(devastator, remote_radius),
+               (handled_as_rocketjump ? head : world),
+               world,
+               WEP_CVAR(devastator, remote_force),
+               self.projectiledeathtype | HITTYPE_BOUNCE,
+               world
+       );
+
+       if(self.realowner.weapon == WEP_DEVASTATOR.m_id)
+       {
+               if(self.realowner.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo))
+               if(!(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO))
+               {
+                       self.realowner.cnt = WEP_DEVASTATOR.m_id;
+                       ATTACK_FINISHED(self.realowner) = time;
+                       self.realowner.switchweapon = w_getbestweapon(self.realowner);
+               }
+       }
+       remove(self);
+}
+
+void W_Devastator_RemoteExplode(void)
+{
+       if(self.realowner.deadflag == DEAD_NO)
+       if(self.realowner.lastrocket)
+       {
+               if((self.spawnshieldtime >= 0)
+                       ? (time >= self.spawnshieldtime) // timer
+                       : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > WEP_CVAR(devastator, remote_radius)) // safety device
+               )
+               {
+                       W_Devastator_DoRemoteExplode();
+               }
+       }
+}
+
+vector W_Devastator_SteerTo(vector thisdir, vector goaldir, float maxturn_cos)
+{
+       if(thisdir * goaldir > maxturn_cos)
+               return goaldir;
+       if(thisdir * goaldir < -0.9998) // less than 1 degree and opposite
+               return thisdir; // refuse to guide (better than letting a numerical error happen)
+       float f, m2;
+       vector v;
+       // solve:
+       //   g = normalize(thisdir + goaldir * X)
+       //   thisdir * g = maxturn
+       //
+       //   gg = thisdir + goaldir * X
+       //   (thisdir * gg)^2 = maxturn^2 * (gg * gg)
+       //
+       //   (1 + (thisdir * goaldir) * X)^2 = maxturn^2 * (1 + X*X + 2 * X * thisdir * goaldir)
+       f = thisdir * goaldir;
+       //   (1 + f * X)^2 = maxturn^2 * (1 + X*X + 2 * X * f)
+       //   0 = (m^2 - f^2) * x^2 + (2 * f * (m^2 - 1)) * x + (m^2 - 1)
+       m2 = maxturn_cos * maxturn_cos;
+       v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1);
+       return normalize(thisdir + goaldir * v.y); // the larger solution!
+}
+// assume thisdir == -goaldir:
+//   f == -1
+//   v = solve_qadratic(m2 - 1, -2 * (m2 - 1), m2 - 1)
+//   (m2 - 1) x^2 - 2 * (m2 - 1) * x + (m2 - 1) = 0
+//   x^2 - 2 * x + 1 = 0
+//   (x - 1)^2 = 0
+//   x = 1
+//   normalize(thisdir + goaldir)
+//   normalize(0)
+
+void W_Devastator_Think(void)
+{
+       vector desireddir, olddir, newdir, desiredorigin, goal;
+       float velspeed, f;
+       self.nextthink = time;
+       if(time > self.cnt)
+       {
+               other = world;
+               self.projectiledeathtype |= HITTYPE_BOUNCE;
+               W_Devastator_Explode();
+               return;
+       }
+
+       // accelerate
+       makevectors(self.angles.x * '-1 0 0' + self.angles.y * '0 1 0');
+       velspeed = WEP_CVAR(devastator, speed) * W_WeaponSpeedFactor() - (self.velocity * v_forward);
+       if(velspeed > 0)
+               self.velocity = self.velocity + v_forward * min(WEP_CVAR(devastator, speedaccel) * W_WeaponSpeedFactor() * frametime, velspeed);
+
+       // laser guided, or remote detonation
+       if(self.realowner.weapon == WEP_DEVASTATOR.m_id)
+       {
+               if(self == self.realowner.lastrocket)
+               if(!self.realowner.rl_release)
+               if(!self.BUTTON_ATCK2)
+               if(WEP_CVAR(devastator, guiderate))
+               if(time > self.pushltime)
+               if(self.realowner.deadflag == DEAD_NO)
+               {
+                       f = WEP_CVAR(devastator, guideratedelay);
+                       if(f)
+                               f = bound(0, (time - self.pushltime) / f, 1);
+                       else
+                               f = 1;
+
+                       velspeed = vlen(self.velocity);
+
+                       makevectors(self.realowner.v_angle);
+                       desireddir = WarpZone_RefSys_TransformVelocity(self.realowner, self, v_forward);
+                       desiredorigin = WarpZone_RefSys_TransformOrigin(self.realowner, self, self.realowner.origin + self.realowner.view_ofs);
+                       olddir = normalize(self.velocity);
+
+                       // now it gets tricky... we want to move like some curve to approximate the target direction
+                       // but we are limiting the rate at which we can turn!
+                       goal = desiredorigin + ((self.origin - desiredorigin) * desireddir + WEP_CVAR(devastator, guidegoal)) * desireddir;
+                       newdir = W_Devastator_SteerTo(olddir, normalize(goal - self.origin), cos(WEP_CVAR(devastator, guiderate) * f * frametime * DEG2RAD));
+
+                       self.velocity = newdir * velspeed;
+                       self.angles = vectoangles(self.velocity);
+
+                       if(!self.count)
+                       {
+                               Send_Effect(EFFECT_ROCKET_GUIDE, self.origin, self.velocity, 1);
+                               // TODO add a better sound here
+                               sound(self.realowner, CH_WEAPON_B, W_Sound("rocket_mode"), VOL_BASE, ATTN_NORM);
+                               self.count = 1;
+                       }
+               }
+
+               if(self.rl_detonate_later)
+                       W_Devastator_RemoteExplode();
+       }
+
+       if(self.csqcprojectile_clientanimate == 0)
+               UpdateCSQCProjectile(self);
+}
+
+void W_Devastator_Touch(void)
+{
+       if(WarpZone_Projectile_Touch())
+       {
+               if(wasfreed(self))
+                       W_Devastator_Unregister();
+               return;
+       }
+       W_Devastator_Unregister();
+       W_Devastator_Explode();
+}
+
+void W_Devastator_Damage(entity inflictor, entity attacker, float damage, int 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_Devastator_Explode);
+}
+
+void W_Devastator_Attack(void)
+{
+       entity missile;
+       entity flash;
+
+       W_DecreaseAmmo(WEP_CVAR(devastator, ammo));
+
+       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;
+       self.lastrocket = missile;
+       if(WEP_CVAR(devastator, detonatedelay) >= 0)
+               missile.spawnshieldtime = time + WEP_CVAR(devastator, detonatedelay);
+       else
+               missile.spawnshieldtime = -1;
+       missile.pushltime = time + WEP_CVAR(devastator, guidedelay);
+       missile.classname = "rocket";
+       missile.bot_dodge = true;
+       missile.bot_dodgerating = WEP_CVAR(devastator, damage) * 2; // * 2 because it can be detonated inflight which makes it even more dangerous
+
+       missile.takedamage = DAMAGE_YES;
+       missile.damageforcescale = WEP_CVAR(devastator, damageforcescale);
+       missile.health = WEP_CVAR(devastator, health);
+       missile.event_damage = W_Devastator_Damage;
+       missile.damagedbycontents = true;
+
+       missile.movetype = MOVETYPE_FLY;
+       PROJECTILE_MAKETRIGGER(missile);
+       missile.projectiledeathtype = WEP_DEVASTATOR.m_id;
+       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, WEP_CVAR(devastator, speedstart), 0);
+       missile.angles = vectoangles(missile.velocity);
+
+       missile.touch = W_Devastator_Touch;
+       missile.think = W_Devastator_Think;
+       missile.nextthink = time;
+       missile.cnt = time + WEP_CVAR(devastator, lifetime);
+       missile.flags = FL_PROJECTILE;
+       missile.missile_flags = MIF_SPLASH;
+
+       CSQCProjectile(missile, WEP_CVAR(devastator, guiderate) == 0 && WEP_CVAR(devastator, speedaccel) == 0, PROJECTILE_ROCKET, false); // because of fly sound
+
+       // muzzle flash for 1st person view
+       flash = spawn();
+       setmodel(flash, "models/flash.md3"); // precision set below
+       SUB_SetFade(flash, time, 0.1);
+       flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
+       W_AttachToShotorg(flash, '5 0 0');
+
+       // common properties
+       MUTATOR_CALLHOOK(EditProjectile, self, missile);
+}
+
+bool W_Devastator(int req)
+{
+       entity rock;
+       float rockfound;
+       float ammo_amount;
+       switch(req)
+       {
+               #if 0
+               case WR_AIM:
+               {
+                       // 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!
+                       {
+                               // decide whether to detonate rockets
+                               entity missile, targetlist, targ;
+                               targetlist = findchainfloat(bot_attack, true);
+                               for(missile = world; (missile = find(missile, classname, "rocket")); ) if(missile.realowner == self)
+                               {
+                                       targ = targetlist;
+                                       while(targ)
+                                       {
+                                               if(targ != missile.realowner && vlen(targ.origin - missile.origin) < WEP_CVAR(devastator, radius))
+                                               {
+                                                       self.BUTTON_ATCK2 = true;
+                                                       break;
+                                               }
+                                               targ = targ.chain;
+                                       }
+                               }
+
+                               if(self.BUTTON_ATCK2) self.BUTTON_ATCK = false;
+                       }
+
+                       return true;
+               }
+               #else
+               case WR_AIM:
+               {
+                       // 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!
+                       {
+                               // decide whether to detonate rockets
+                               entity missile, targetlist, targ;
+                               float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
+                               float selfdamage, teamdamage, enemydamage;
+                               edgedamage = WEP_CVAR(devastator, edgedamage);
+                               coredamage = WEP_CVAR(devastator, damage);
+                               edgeradius = WEP_CVAR(devastator, radius);
+                               recipricoledgeradius = 1 / edgeradius;
+                               selfdamage = 0;
+                               teamdamage = 0;
+                               enemydamage = 0;
+                               targetlist = findchainfloat(bot_attack, true);
+                               missile = find(world, classname, "rocket");
+                               while(missile)
+                               {
+                                       if(missile.realowner != self)
+                                       {
+                                               missile = find(missile, classname, "rocket");
+                                               continue;
+                                       }
+                                       targ = targetlist;
+                                       while(targ)
+                                       {
+                                               d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - missile.origin);
+                                               d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
+                                               // count potential damage according to type of target
+                                               if(targ == self)
+                                                       selfdamage = selfdamage + d;
+                                               else if(targ.team == self.team && teamplay)
+                                                       teamdamage = teamdamage + d;
+                                               else if(bot_shouldattack(targ))
+                                                       enemydamage = enemydamage + d;
+                                               targ = targ.chain;
+                                       }
+                                       missile = find(missile, classname, "rocket");
+                               }
+                               float desirabledamage;
+                               desirabledamage = enemydamage;
+                               if(time > self.invincible_finished && time > self.spawnshieldtime)
+                                       desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent;
+                               if(teamplay && self.team)
+                                       desirabledamage = desirabledamage - teamdamage;
+
+                               missile = find(world, classname, "rocket");
+                               while(missile)
+                               {
+                                       if(missile.realowner != self)
+                                       {
+                                               missile = find(missile, classname, "rocket");
+                                               continue;
+                                       }
+                                       makevectors(missile.v_angle);
+                                       targ = targetlist;
+                                       if(skill > 9) // normal players only do this for the target they are tracking
+                                       {
+                                               targ = targetlist;
+                                               while(targ)
+                                               {
+                                                       if(
+                                                               (v_forward * normalize(missile.origin - targ.origin)< 0.1)
+                                                               && desirabledamage > 0.1*coredamage
+                                                       )self.BUTTON_ATCK2 = true;
+                                                       targ = targ.chain;
+                                               }
+                                       }else{
+                                               float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
+                                               //As the distance gets larger, a correct detonation gets near imposible
+                                               //Bots are assumed to use the rocket spawnfunc_light to see if the rocket gets near a player
+                                               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))
+                                                                               self.BUTTON_ATCK2 = true;
+                                       //      dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
+                                       }
+
+                                       missile = find(missile, classname, "rocket");
+                               }
+                               // if we would be doing at X percent of the core damage, detonate it
+                               // 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))
+                                       self.BUTTON_ATCK2 = false;
+                               //if(self.BUTTON_ATCK2 == true)
+                               //      dprint(ftos(desirabledamage),"\n");
+                               if(self.BUTTON_ATCK2 == true) self.BUTTON_ATCK = false;
+                       }
+
+                       return true;
+               }
+               #endif
+               case WR_THINK:
+               {
+                       if(WEP_CVAR(devastator, reload_ammo) && self.clip_load < WEP_CVAR(devastator, ammo)) // forced reload
+                               WEP_ACTION(self.weapon, WR_RELOAD);
+                       else
+                       {
+                               if(self.BUTTON_ATCK)
+                               {
+                                       if(self.rl_release || WEP_CVAR(devastator, guidestop))
+                                       if(weapon_prepareattack(0, WEP_CVAR(devastator, refire)))
+                                       {
+                                               W_Devastator_Attack();
+                                               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(devastator, animtime), w_ready);
+                                               self.rl_release = 0;
+                                       }
+                               }
+                               else
+                                       self.rl_release = 1;
+
+                               if(self.BUTTON_ATCK2)
+                               if(self.switchweapon == WEP_DEVASTATOR.m_id)
+                               {
+                                       rockfound = 0;
+                                       for(rock = world; (rock = find(rock, classname, "rocket")); ) if(rock.realowner == self)
+                                       {
+                                               if(!rock.rl_detonate_later)
+                                               {
+                                                       rock.rl_detonate_later = true;
+                                                       rockfound = 1;
+                                               }
+                                       }
+                                       if(rockfound)
+                                               sound(self, CH_WEAPON_B, W_Sound("rocket_det"), VOL_BASE, ATTN_NORM);
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       //if(autocvar_sv_precacheweapons)
+                       //{
+                               precache_model("models/flash.md3");
+                               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;
+               }
+               case WR_SETUP:
+               {
+                       self.rl_release = 1;
+                       return true;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       #if 0
+                       // don't switch while guiding a missile
+                       if(ATTACK_FINISHED(self) <= time || self.weapon != WEP_DEVASTATOR.m_id)
+                       {
+                               ammo_amount = false;
+                               if(WEP_CVAR(devastator, reload_ammo))
+                               {
+                                       if(self.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo) && self.(weapon_load[WEP_DEVASTATOR.m_id]) < WEP_CVAR(devastator, ammo))
+                                               ammo_amount = true;
+                               }
+                               else if(self.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo))
+                                       ammo_amount = true;
+                               return !ammo_amount;
+                       }
+                       #endif
+                       #if 0
+                       if(self.rl_release == 0)
+                       {
+                               LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: TRUE\n", self.rl_release, self.WEP_AMMO(DEVASTATOR), WEP_CVAR(devastator, ammo));
+                               return true;
+                       }
+                       else
+                       {
+                               ammo_amount = self.WEP_AMMO(DEVASTATOR) >= WEP_CVAR(devastator, ammo);
+                               ammo_amount += self.(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo);
+                               LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: %s\n", self.rl_release, self.WEP_AMMO(DEVASTATOR), WEP_CVAR(devastator, ammo), (ammo_amount ? "TRUE" : "FALSE"));
+                               return ammo_amount;
+                       }
+                       #else
+                       ammo_amount = self.WEP_AMMO(DEVASTATOR) >= WEP_CVAR(devastator, ammo);
+                       ammo_amount += self.(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo);
+                       return ammo_amount;
+                       #endif
+               }
+               case WR_CHECKAMMO2:
+               {
+                       return false;
+               }
+               case WR_CONFIG:
+               {
+                       DEVASTATOR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RESETPLAYER:
+               {
+                       self.rl_release = 0;
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       W_Reload(WEP_CVAR(devastator, ammo), W_Sound("reload"));
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       return WEAPON_DEVASTATOR_SUICIDE;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH))
+                               return WEAPON_DEVASTATOR_MURDER_SPLASH;
+                       else
+                               return WEAPON_DEVASTATOR_MURDER_DIRECT;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_Devastator(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 12;
+                       pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), org2, '0 0 0', 1);
+                       if(!w_issilent)
+                               sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTN_NORM);
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("rocket_impact"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/electro.qc b/qcsrc/common/weapons/weapon/electro.qc
new file mode 100644 (file)
index 0000000..0f60fd2
--- /dev/null
@@ -0,0 +1,621 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ ELECTRO,
+/* function  */ W_Electro,
+/* ammotype  */ ammo_cells,
+/* impulse   */ 5,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* color     */ '0 0.5 1',
+/* modelname */ "electro",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairelectro 0.6",
+/* wepimg    */ "weaponelectro",
+/* refname   */ "electro",
+/* wepname   */ _("Electro")
+);
+
+#define ELECTRO_SETTINGS(w_cvar,w_prop) ELECTRO_SETTINGS_LIST(w_cvar, w_prop, ELECTRO, electro)
+#define ELECTRO_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, BOTH, ammo) \
+       w_cvar(id, sn, BOTH, animtime) \
+       w_cvar(id, sn, BOTH, damage) \
+       w_cvar(id, sn, BOTH, edgedamage) \
+       w_cvar(id, sn, BOTH, force) \
+       w_cvar(id, sn, BOTH, radius) \
+       w_cvar(id, sn, BOTH, refire) \
+       w_cvar(id, sn, BOTH, speed) \
+       w_cvar(id, sn, BOTH, spread) \
+       w_cvar(id, sn, BOTH, lifetime) \
+       w_cvar(id, sn, PRI,  comboradius) \
+       w_cvar(id, sn, PRI,  midaircombo_explode) \
+       w_cvar(id, sn, PRI,  midaircombo_interval) \
+       w_cvar(id, sn, PRI,  midaircombo_radius) \
+       w_cvar(id, sn, SEC,  bouncefactor) \
+       w_cvar(id, sn, SEC,  bouncestop) \
+       w_cvar(id, sn, SEC,  count) \
+       w_cvar(id, sn, SEC,  damageforcescale) \
+       w_cvar(id, sn, SEC,  damagedbycontents) \
+       w_cvar(id, sn, SEC,  health) \
+       w_cvar(id, sn, SEC,  refire2) \
+       w_cvar(id, sn, SEC,  speed_up) \
+       w_cvar(id, sn, SEC,  speed_z) \
+       w_cvar(id, sn, SEC,  touchexplode) \
+       w_cvar(id, sn, NONE, combo_comboradius) \
+       w_cvar(id, sn, NONE, combo_comboradius_thruwall) \
+       w_cvar(id, sn, NONE, combo_damage) \
+       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_speed) \
+       w_cvar(id, sn, NONE, combo_safeammocheck) \
+       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
+ELECTRO_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+.float electro_count;
+.float electro_secondarytime;
+void W_Electro_ExplodeCombo(void);
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_electro(void) { weapon_defaultspawnfunc(WEP_ELECTRO.m_id); }
+
+void W_Electro_TriggerCombo(vector org, float rad, entity own)
+{
+       entity e = WarpZone_FindRadius(org, rad, !WEP_CVAR(electro, combo_comboradius_thruwall));
+       while(e)
+       {
+               if(e.classname == "electro_orb")
+               {
+                       // do we allow thruwall triggering?
+                       if(WEP_CVAR(electro, combo_comboradius_thruwall))
+                       {
+                               // if distance is greater than thruwall distance, check to make sure it's not through a wall
+                               if(vlen(e.WarpZone_findradius_dist) > WEP_CVAR(electro, combo_comboradius_thruwall))
+                               {
+                                       WarpZone_TraceLine(org, e.origin, MOVE_NOMONSTERS, e);
+                                       if(trace_fraction != 1)
+                                       {
+                                               // trigger is through a wall and outside of thruwall range, abort
+                                               e = e.chain;
+                                               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.WarpZone_findradius_dist) / WEP_CVAR(electro, combo_speed))
+                                               :
+                                               0
+                                       )
+                               );
+               }
+               e = e.chain;
+       }
+}
+
+void W_Electro_ExplodeCombo(void)
+{
+       W_Electro_TriggerCombo(self.origin, WEP_CVAR(electro, combo_comboradius), self.realowner);
+
+       self.event_damage = func_null;
+
+       RadiusDamage(
+               self,
+               self.realowner,
+               WEP_CVAR(electro, combo_damage),
+               WEP_CVAR(electro, combo_edgedamage),
+               WEP_CVAR(electro, combo_radius),
+               world,
+               world,
+               WEP_CVAR(electro, combo_force),
+               WEP_ELECTRO.m_id | HITTYPE_BOUNCE, // use THIS type for a combo because primary can't bounce
+               world
+       );
+
+       remove(self);
+}
+
+void W_Electro_Explode(void)
+{
+       if(other.takedamage == DAMAGE_AIM)
+               if(IS_PLAYER(other))
+                       if(DIFF_TEAM(self.realowner, other))
+                               if(other.deadflag == DEAD_NO)
+                                       if(IsFlying(other))
+                                               Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH);
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       if(self.movetype == MOVETYPE_BOUNCE)
+       {
+               RadiusDamage(
+                       self,
+                       self.realowner,
+                       WEP_CVAR_SEC(electro, damage),
+                       WEP_CVAR_SEC(electro, edgedamage),
+                       WEP_CVAR_SEC(electro, radius),
+                       world,
+                       world,
+                       WEP_CVAR_SEC(electro, force),
+                       self.projectiledeathtype,
+                       other
+               );
+       }
+       else
+       {
+               W_Electro_TriggerCombo(self.origin, WEP_CVAR_PRI(electro, comboradius), self.realowner);
+               RadiusDamage(
+                       self,
+                       self.realowner,
+                       WEP_CVAR_PRI(electro, damage),
+                       WEP_CVAR_PRI(electro, edgedamage),
+                       WEP_CVAR_PRI(electro, radius),
+                       world,
+                       world,
+                       WEP_CVAR_PRI(electro, force),
+                       self.projectiledeathtype,
+                       other
+               );
+       }
+
+       remove(self);
+}
+
+void W_Electro_TouchExplode(void)
+{
+       PROJECTILE_TOUCH;
+       W_Electro_Explode();
+}
+
+void W_Electro_Bolt_Think(void)
+{
+       if(time >= self.ltime)
+       {
+               self.use();
+               return;
+       }
+
+       if(WEP_CVAR_PRI(electro, midaircombo_radius))
+       {
+               float found = 0;
+               entity e = WarpZone_FindRadius(self.origin, WEP_CVAR_PRI(electro, midaircombo_radius), true);
+
+               // loop through nearby orbs and trigger them
+               while(e)
+               {
+                       if(e.classname == "electro_orb")
+                       {
+                               // change owner to whoever caused the combo explosion
+                               e.realowner = self.realowner;
+                               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.WarpZone_findradius_dist) / WEP_CVAR(electro, combo_speed))
+                                                       :
+                                                       0
+                                               )
+                                       );
+
+                               ++found;
+                       }
+                       e = e.chain;
+               }
+
+               // if we triggered an orb, should we explode? if not, lets try again next time
+               if(found && WEP_CVAR_PRI(electro, midaircombo_explode))
+                       { self.use(); }
+               else
+                       { self.nextthink = min(time + WEP_CVAR_PRI(electro, midaircombo_interval), self.ltime); }
+       }
+       else { self.nextthink = self.ltime; }
+}
+
+void W_Electro_Attack_Bolt(void)
+{
+       entity proj;
+
+       W_DecreaseAmmo(WEP_CVAR_PRI(electro, ammo));
+
+       W_SetupShot_ProjectileSize(
+               self,
+               '0 0 -3',
+               '0 0 -3',
+               false,
+               2,
+               W_Sound("electro_fire"),
+               CH_WEAPON_A,
+               WEP_CVAR_PRI(electro, damage)
+       );
+
+       Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       proj = spawn();
+       proj.classname = "electro_bolt";
+       proj.owner = proj.realowner = self;
+       proj.bot_dodge = true;
+       proj.bot_dodgerating = WEP_CVAR_PRI(electro, damage);
+       proj.use = W_Electro_Explode;
+       proj.think = W_Electro_Bolt_Think;
+       proj.nextthink = time;
+       proj.ltime = time + WEP_CVAR_PRI(electro, lifetime);
+       PROJECTILE_MAKETRIGGER(proj);
+       proj.projectiledeathtype = WEP_ELECTRO.m_id;
+       setorigin(proj, w_shotorg);
+
+       proj.movetype = MOVETYPE_FLY;
+       W_SetupProjVelocity_PRI(proj, electro);
+       proj.angles = vectoangles(proj.velocity);
+       proj.touch = W_Electro_TouchExplode;
+       setsize(proj, '0 0 -3', '0 0 -3');
+       proj.flags = FL_PROJECTILE;
+       proj.missile_flags = MIF_SPLASH;
+
+       CSQCProjectile(proj, true, PROJECTILE_ELECTRO_BEAM, true);
+
+       MUTATOR_CALLHOOK(EditProjectile, self, proj);
+}
+
+void W_Electro_Orb_Touch(void)
+{
+       PROJECTILE_TOUCH;
+       if(other.takedamage == DAMAGE_AIM)
+               { if(WEP_CVAR_SEC(electro, touchexplode)) { W_Electro_Explode(); } }
+       else
+       {
+               //UpdateCSQCProjectile(self);
+               spamsound(self, CH_SHOTS, W_Sound("electro_bounce"), VOL_BASE, ATTEN_NORM);
+               self.projectiledeathtype |= HITTYPE_BOUNCE;
+       }
+}
+
+void W_Electro_Orb_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+       if(self.health <= 0)
+               return;
+
+       // note: combos are usually triggered by W_Electro_TriggerCombo, not damage
+       float is_combo = (inflictor.classname == "electro_orb_chain" || inflictor.classname == "electro_bolt");
+
+       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_combo ? 1 : -1)))
+               return; // g_projectiles_damage says to halt
+
+       self.health = self.health - damage;
+       if(self.health <= 0)
+       {
+               self.takedamage = DAMAGE_NO;
+               self.nextthink = time;
+               if(is_combo)
+               {
+                       // change owner to whoever caused the combo explosion
+                       self.realowner = inflictor.realowner;
+                       self.classname = "electro_orb_chain";
+                       self.think = W_Electro_ExplodeCombo;
+                       self.nextthink = time +
+                               (
+                                       // bound the length, inflictor may be in a galaxy far far away (warpzones)
+                                       min(
+                                               WEP_CVAR(electro, combo_radius),
+                                               vlen(self.origin - inflictor.origin)
+                                       )
+                                       /
+                                       // delay combo chains, looks cooler
+                                       WEP_CVAR(electro, combo_speed)
+                               );
+               }
+               else
+               {
+                       self.use = W_Electro_Explode;
+                       self.think = adaptor_think2use; // not _hittype_splash, as this runs "immediately"
+               }
+       }
+}
+
+void W_Electro_Attack_Orb(void)
+{
+       W_DecreaseAmmo(WEP_CVAR_SEC(electro, ammo));
+
+       W_SetupShot_ProjectileSize(
+               self,
+               '0 0 -4',
+               '0 0 -4',
+               false,
+               2,
+               W_Sound("electro_fire2"),
+               CH_WEAPON_A,
+               WEP_CVAR_SEC(electro, damage)
+       );
+
+       w_shotdir = v_forward; // no TrueAim for grenades please
+
+       Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       entity proj = spawn();
+       proj.classname = "electro_orb";
+       proj.owner = proj.realowner = self;
+       proj.use = W_Electro_Explode;
+       proj.think = adaptor_think2use_hittype_splash;
+       proj.bot_dodge = true;
+       proj.bot_dodgerating = WEP_CVAR_SEC(electro, damage);
+       proj.nextthink = time + WEP_CVAR_SEC(electro, lifetime);
+       PROJECTILE_MAKETRIGGER(proj);
+       proj.projectiledeathtype = WEP_ELECTRO.m_id | HITTYPE_SECONDARY;
+       setorigin(proj, w_shotorg);
+
+       //proj.glow_size = 50;
+       //proj.glow_color = 45;
+       proj.movetype = MOVETYPE_BOUNCE;
+       W_SetupProjVelocity_UP_SEC(proj, electro);
+       proj.touch = W_Electro_Orb_Touch;
+       setsize(proj, '0 0 -4', '0 0 -4');
+       proj.takedamage = DAMAGE_YES;
+       proj.damageforcescale = WEP_CVAR_SEC(electro, damageforcescale);
+       proj.health = WEP_CVAR_SEC(electro, health);
+       proj.event_damage = W_Electro_Orb_Damage;
+       proj.flags = FL_PROJECTILE;
+       proj.damagedbycontents = (WEP_CVAR_SEC(electro, damagedbycontents));
+
+       proj.bouncefactor = WEP_CVAR_SEC(electro, bouncefactor);
+       proj.bouncestop = WEP_CVAR_SEC(electro, bouncestop);
+       proj.missile_flags = MIF_SPLASH | MIF_ARC;
+
+#if 0
+       entity p2;
+       p2 = spawn();
+       copyentity(proj, p2);
+       setmodel(p2, "models/ebomb.mdl");
+       setsize(p2, proj.mins, proj.maxs);
+#endif
+
+       CSQCProjectile(proj, true, PROJECTILE_ELECTRO, false); // no culling, it has sound
+
+       MUTATOR_CALLHOOK(EditProjectile, self, proj);
+}
+
+void W_Electro_CheckAttack(void)
+{
+       if(self.electro_count > 1)
+       if(self.BUTTON_ATCK2)
+       if(weapon_prepareattack(1, -1))
+       {
+               W_Electro_Attack_Orb();
+               self.electro_count -= 1;
+               weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack);
+               return;
+       }
+       // WEAPONTODO: when the player releases the button, cut down the length of refire2?
+       w_ready();
+}
+
+.float bot_secondary_electromooth;
+bool W_Electro(int req)
+{
+       float ammo_amount;
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       self.BUTTON_ATCK = self.BUTTON_ATCK2 = false;
+                       if(vlen(self.origin-self.enemy.origin) > 1000) { self.bot_secondary_electromooth = 0; }
+                       if(self.bot_secondary_electromooth == 0)
+                       {
+                               float shoot;
+
+                               if(WEP_CVAR_PRI(electro, speed))
+                                       shoot = bot_aim(WEP_CVAR_PRI(electro, speed), 0, WEP_CVAR_PRI(electro, lifetime), false);
+                               else
+                                       shoot = bot_aim(1000000, 0, 0.001, false);
+
+                               if(shoot)
+                               {
+                                       self.BUTTON_ATCK = true;
+                                       if(random() < 0.01) self.bot_secondary_electromooth = 1;
+                               }
+                       }
+                       else
+                       {
+                               if(bot_aim(WEP_CVAR_SEC(electro, speed), WEP_CVAR_SEC(electro, speed_up), WEP_CVAR_SEC(electro, lifetime), true))
+                               {
+                                       self.BUTTON_ATCK2 = true;
+                                       if(random() < 0.03) self.bot_secondary_electromooth = 0;
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       if(autocvar_g_balance_electro_reload_ammo) // forced reload // WEAPONTODO
+                       {
+                               ammo_amount = 0;
+                               if(self.clip_load >= WEP_CVAR_PRI(electro, ammo))
+                                       ammo_amount = 1;
+                               if(self.clip_load >= WEP_CVAR_SEC(electro, ammo))
+                                       ammo_amount += 1;
+
+                               if(!ammo_amount)
+                               {
+                                       WEP_ACTION(self.weapon, WR_RELOAD);
+                                       return false;
+                               }
+
+                               return true;
+                       }
+
+                       if(self.BUTTON_ATCK)
+                       {
+                               if(weapon_prepareattack(0, WEP_CVAR_PRI(electro, refire)))
+                               {
+                                               W_Electro_Attack_Bolt();
+                                               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(electro, animtime), w_ready);
+                               }
+                       }
+                       else if(self.BUTTON_ATCK2)
+                       {
+                               if(time >= self.electro_secondarytime)
+                               if(weapon_prepareattack(1, WEP_CVAR_SEC(electro, refire)))
+                               {
+                                       W_Electro_Attack_Orb();
+                                       self.electro_count = WEP_CVAR_SEC(electro, count);
+                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack);
+                                       self.electro_secondarytime = time + WEP_CVAR_SEC(electro, refire2) * W_WeaponRateFactor();
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       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;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       ammo_amount = self.WEP_AMMO(ELECTRO) >= WEP_CVAR_PRI(electro, ammo);
+                       ammo_amount += self.(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_PRI(electro, ammo);
+                       return ammo_amount;
+               }
+               case WR_CHECKAMMO2:
+               {
+                       if(WEP_CVAR(electro, combo_safeammocheck)) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false.
+                       {
+                               ammo_amount = self.WEP_AMMO(ELECTRO) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
+                               ammo_amount += self.(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo);
+                       }
+                       else
+                       {
+                               ammo_amount = self.WEP_AMMO(ELECTRO) >= WEP_CVAR_SEC(electro, ammo);
+                               ammo_amount += self.(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_SEC(electro, ammo);
+                       }
+                       return ammo_amount;
+               }
+               case WR_CONFIG:
+               {
+                       ELECTRO_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RESETPLAYER:
+               {
+                       self.electro_secondarytime = time;
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       W_Reload(min(WEP_CVAR_PRI(electro, ammo), WEP_CVAR_SEC(electro, ammo)), W_Sound("reload"));
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                               return WEAPON_ELECTRO_SUICIDE_ORBS;
+                       else
+                               return WEAPON_ELECTRO_SUICIDE_BOLT;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                       {
+                               return WEAPON_ELECTRO_MURDER_ORBS;
+                       }
+                       else
+                       {
+                               if(w_deathtype & HITTYPE_BOUNCE)
+                                       return WEAPON_ELECTRO_MURDER_COMBO;
+                               else
+                                       return WEAPON_ELECTRO_MURDER_BOLT;
+                       }
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_Electro(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 6;
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                       {
+                               pointparticles(particleeffectnum(EFFECT_ELECTRO_BALLEXPLODE), org2, '0 0 0', 1);
+                               if(!w_issilent)
+                                       sound(self, CH_SHOTS, W_Sound("electro_impact"), VOL_BASE, ATTEN_NORM);
+                       }
+                       else
+                       {
+                               if(w_deathtype & HITTYPE_BOUNCE)
+                               {
+                                       // this is sent as "primary (w_deathtype & HITTYPE_BOUNCE)" to distinguish it from (w_deathtype & HITTYPE_SECONDARY) bounced balls
+                                       pointparticles(particleeffectnum(EFFECT_ELECTRO_COMBO), org2, '0 0 0', 1);
+                                       if(!w_issilent)
+                                               sound(self, CH_SHOTS, W_Sound("electro_impact_combo"), VOL_BASE, ATTEN_NORM);
+                               }
+                               else
+                               {
+                                       pointparticles(particleeffectnum(EFFECT_ELECTRO_IMPACT), org2, '0 0 0', 1);
+                                       if(!w_issilent)
+                                               sound(self, CH_SHOTS, W_Sound("electro_impact"), VOL_BASE, ATTEN_NORM);
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("electro_impact"));
+                       precache_sound(W_Sound("electro_impact_combo"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/fireball.qc b/qcsrc/common/weapons/weapon/fireball.qc
new file mode 100644 (file)
index 0000000..4f8f378
--- /dev/null
@@ -0,0 +1,486 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ FIREBALL,
+/* function  */ W_Fireball,
+/* ammotype  */ ammo_none,
+/* impulse   */ 9,
+/* flags     */ WEP_FLAG_SUPERWEAPON | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* color     */ '1 0.5 0',
+/* modelname */ "fireball",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairfireball",
+/* wepimg    */ "weaponfireball",
+/* refname   */ "fireball",
+/* wepname   */ _("Fireball")
+);
+
+#define FIREBALL_SETTINGS(w_cvar,w_prop) FIREBALL_SETTINGS_LIST(w_cvar, w_prop, FIREBALL, fireball)
+#define FIREBALL_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, damageforcescale) \
+       w_cvar(id, sn, BOTH, speed) \
+       w_cvar(id, sn, BOTH, spread) \
+       w_cvar(id, sn, BOTH, lifetime) \
+       w_cvar(id, sn, BOTH, laserburntime) \
+       w_cvar(id, sn, BOTH, laserdamage) \
+       w_cvar(id, sn, BOTH, laseredgedamage) \
+       w_cvar(id, sn, BOTH, laserradius) \
+       w_cvar(id, sn, PRI,  edgedamage) \
+       w_cvar(id, sn, PRI,  force) \
+       w_cvar(id, sn, PRI,  radius) \
+       w_cvar(id, sn, PRI,  health) \
+       w_cvar(id, sn, PRI,  refire2) \
+       w_cvar(id, sn, PRI,  bfgdamage) \
+       w_cvar(id, sn, PRI,  bfgforce) \
+       w_cvar(id, sn, PRI,  bfgradius) \
+       w_cvar(id, sn, SEC,  damagetime) \
+       w_cvar(id, sn, SEC,  speed_up) \
+       w_cvar(id, sn, SEC,  speed_z) \
+       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
+FIREBALL_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+.float bot_primary_fireballmooth; // whatever a mooth is
+.vector fireball_impactvec;
+.float fireball_primarytime;
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_fireball(void) { weapon_defaultspawnfunc(WEP_FIREBALL.m_id); }
+
+void W_Fireball_Explode(void)
+{
+       entity e;
+       float dist;
+       float points;
+       vector dir;
+       float d;
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       // 1. dist damage
+       d = (self.realowner.health + self.realowner.armorvalue);
+       RadiusDamage(self, self.realowner, WEP_CVAR_PRI(fireball, damage), WEP_CVAR_PRI(fireball, edgedamage), WEP_CVAR_PRI(fireball, radius), world, world, WEP_CVAR_PRI(fireball, force), self.projectiledeathtype, other);
+       if(self.realowner.health + self.realowner.armorvalue >= d)
+       if(!self.cnt)
+       {
+               modeleffect_spawn("models/sphere/sphere.md3", 0, 0, self.origin, '0 0 0', '0 0 0', '0 0 0', 0, WEP_CVAR_PRI(fireball, bfgradius), 0.2, 0.05, 0.25);
+
+               // 2. bfg effect
+               // NOTE: this cannot be made warpzone aware by design. So, better intentionally ignore warpzones here.
+               for(e = findradius(self.origin, WEP_CVAR_PRI(fireball, bfgradius)); e; e = e.chain)
+               if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self))
+               {
+                       // can we see fireball?
+                       traceline(e.origin + e.view_ofs, self.origin, MOVE_NORMAL, e);
+                       if(/* trace_startsolid || */ trace_fraction != 1) // startsolid should be never happening anyway
+                               continue;
+                       // can we see player who shot fireball?
+                       traceline(e.origin + e.view_ofs, self.realowner.origin + self.realowner.view_ofs, MOVE_NORMAL, e);
+                       if(trace_ent != self.realowner)
+                       if(/* trace_startsolid || */ trace_fraction != 1)
+                               continue;
+                       dist = vlen(self.origin - e.origin - e.view_ofs);
+                       points = (1 - sqrt(dist / WEP_CVAR_PRI(fireball, bfgradius)));
+                       if(points <= 0)
+                               continue;
+                       dir = normalize(e.origin + e.view_ofs - self.origin);
+
+                       if(accuracy_isgooddamage(self.realowner, e))
+                               accuracy_add(self.realowner, WEP_FIREBALL.m_id, 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);
+                       Send_Effect(EFFECT_FIREBALL_BFGDAMAGE, e.origin, -1 * dir, 1);
+               }
+       }
+
+       remove(self);
+}
+
+void W_Fireball_TouchExplode(void)
+{
+       PROJECTILE_TOUCH;
+       W_Fireball_Explode();
+}
+
+void W_Fireball_LaserPlay(float dt, float dist, float damage, float edgedamage, float burntime)
+{
+       entity e;
+       float d;
+       vector p;
+
+       if(damage <= 0)
+               return;
+
+       RandomSelection_Init();
+       for(e = WarpZone_FindRadius(self.origin, dist, true); e; e = e.chain)
+       if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self))
+       {
+               p = e.origin;
+               p.x += e.mins.x + random() * (e.maxs.x - e.mins.x);
+               p.y += e.mins.y + random() * (e.maxs.y - e.mins.y);
+               p.z += e.mins.z + random() * (e.maxs.z - e.mins.z);
+               d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p);
+               if(d < dist)
+               {
+                       e.fireball_impactvec = p;
+                       RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e));
+               }
+       }
+       if(RandomSelection_chosen_ent)
+       {
+               d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec);
+               d = damage + (edgedamage - damage) * (d / dist);
+               Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE);
+               //trailparticles(self, particleeffectnum(EFFECT_FIREBALL_LASER), self.origin, RandomSelection_chosen_ent.fireball_impactvec);
+               Send_Effect(EFFECT_FIREBALL_LASER, self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1);
+       }
+}
+
+void W_Fireball_Think(void)
+{
+       if(time > self.pushltime)
+       {
+               self.cnt = 1;
+               self.projectiledeathtype |= HITTYPE_SPLASH;
+               W_Fireball_Explode();
+               return;
+       }
+
+       W_Fireball_LaserPlay(0.1, WEP_CVAR_PRI(fireball, laserradius), WEP_CVAR_PRI(fireball, laserdamage), WEP_CVAR_PRI(fireball, laseredgedamage), WEP_CVAR_PRI(fireball, laserburntime));
+
+       self.nextthink = time + 0.1;
+}
+
+void W_Fireball_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+       if(self.health <= 0)
+               return;
+
+       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+               return; // g_projectiles_damage says to halt
+
+       self.health = self.health - damage;
+       if(self.health <= 0)
+       {
+               self.cnt = 1;
+               W_PrepareExplosionByDamage(attacker, W_Fireball_Explode);
+       }
+}
+
+void W_Fireball_Attack1(void)
+{
+       entity proj;
+
+       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));
+
+       Send_Effect(EFFECT_FIREBALL_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       proj = spawn();
+       proj.classname = "plasma_prim";
+       proj.owner = proj.realowner = self;
+       proj.bot_dodge = true;
+       proj.bot_dodgerating = WEP_CVAR_PRI(fireball, damage);
+       proj.pushltime = time + WEP_CVAR_PRI(fireball, lifetime);
+       proj.use = W_Fireball_Explode;
+       proj.think = W_Fireball_Think;
+       proj.nextthink = time;
+       proj.health = WEP_CVAR_PRI(fireball, health);
+       proj.team = self.team;
+       proj.event_damage = W_Fireball_Damage;
+       proj.takedamage = DAMAGE_YES;
+       proj.damageforcescale = WEP_CVAR_PRI(fireball, damageforcescale);
+       PROJECTILE_MAKETRIGGER(proj);
+       proj.projectiledeathtype = WEP_FIREBALL.m_id;
+       setorigin(proj, w_shotorg);
+
+       proj.movetype = MOVETYPE_FLY;
+       W_SetupProjVelocity_PRI(proj, fireball);
+       proj.angles = vectoangles(proj.velocity);
+       proj.touch = W_Fireball_TouchExplode;
+       setsize(proj, '-16 -16 -16', '16 16 16');
+       proj.flags = FL_PROJECTILE;
+    proj.missile_flags = MIF_SPLASH | MIF_PROXY;
+
+       CSQCProjectile(proj, true, PROJECTILE_FIREBALL, true);
+
+       MUTATOR_CALLHOOK(EditProjectile, self, proj);
+}
+
+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;
+       Send_Effect(EFFECT_FIREBALL_PRE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+}
+
+void W_Fireball_Attack1_Frame4(void)
+{
+       W_Fireball_Attack1();
+       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), w_ready);
+}
+
+void W_Fireball_Attack1_Frame3(void)
+{
+       W_Fireball_AttackEffect(0, '+1.25 +3.75 0');
+       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame4);
+}
+
+void W_Fireball_Attack1_Frame2(void)
+{
+       W_Fireball_AttackEffect(0, '-1.25 +3.75 0');
+       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame3);
+}
+
+void W_Fireball_Attack1_Frame1(void)
+{
+       W_Fireball_AttackEffect(1, '+1.25 -3.75 0');
+       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame2);
+}
+
+void W_Fireball_Attack1_Frame0(void)
+{
+       W_Fireball_AttackEffect(0, '-1.25 -3.75 0');
+       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);
+}
+
+void W_Fireball_Firemine_Think(void)
+{
+       if(time > self.pushltime)
+       {
+               remove(self);
+               return;
+       }
+
+       // make it "hot" once it leaves its owner
+       if(self.owner)
+       {
+               if(vlen(self.origin - self.owner.origin - self.owner.view_ofs) > WEP_CVAR_SEC(fireball, laserradius))
+               {
+                       self.cnt += 1;
+                       if(self.cnt == 3)
+                               self.owner = world;
+               }
+               else
+                       self.cnt = 0;
+       }
+
+       W_Fireball_LaserPlay(0.1, WEP_CVAR_SEC(fireball, laserradius), WEP_CVAR_SEC(fireball, laserdamage), WEP_CVAR_SEC(fireball, laseredgedamage), WEP_CVAR_SEC(fireball, laserburntime));
+
+       self.nextthink = time + 0.1;
+}
+
+void W_Fireball_Firemine_Touch(void)
+{
+       PROJECTILE_TOUCH;
+       if(other.takedamage == DAMAGE_AIM)
+       if(Fire_AddDamage(other, self.realowner, WEP_CVAR_SEC(fireball, damage), WEP_CVAR_SEC(fireball, damagetime), self.projectiledeathtype) >= 0)
+       {
+               remove(self);
+               return;
+       }
+       self.projectiledeathtype |= HITTYPE_BOUNCE;
+}
+
+void W_Fireball_Attack2(void)
+{
+       entity proj;
+       vector f_diff;
+       float c;
+
+       c = self.bulletcounter % 4;
+       switch(c)
+       {
+               case 0:
+                       f_diff = '-1.25 -3.75 0';
+                       break;
+               case 1:
+                       f_diff = '+1.25 -3.75 0';
+                       break;
+               case 2:
+                       f_diff = '-1.25 +3.75 0';
+                       break;
+               case 3:
+               default:
+                       f_diff = '+1.25 +3.75 0';
+                       break;
+       }
+       W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', false, 2, 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;
+
+       Send_Effect(EFFECT_FIREBALL_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       proj = spawn();
+       proj.owner = proj.realowner = self;
+       proj.classname = "grenade";
+       proj.bot_dodge = true;
+       proj.bot_dodgerating = WEP_CVAR_SEC(fireball, damage);
+       proj.movetype = MOVETYPE_BOUNCE;
+       proj.projectiledeathtype = WEP_FIREBALL.m_id | HITTYPE_SECONDARY;
+       proj.touch = W_Fireball_Firemine_Touch;
+       PROJECTILE_MAKETRIGGER(proj);
+       setsize(proj, '-4 -4 -4', '4 4 4');
+       setorigin(proj, w_shotorg);
+       proj.think = W_Fireball_Firemine_Think;
+       proj.nextthink = time;
+       proj.damageforcescale = WEP_CVAR_SEC(fireball, damageforcescale);
+       proj.pushltime = time + WEP_CVAR_SEC(fireball, lifetime);
+       W_SetupProjVelocity_UP_SEC(proj, fireball);
+
+       proj.angles = vectoangles(proj.velocity);
+       proj.flags = FL_PROJECTILE;
+    proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC;
+
+       CSQCProjectile(proj, true, PROJECTILE_FIREMINE, true);
+
+       MUTATOR_CALLHOOK(EditProjectile, self, proj);
+}
+
+bool W_Fireball(int req)
+{
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       self.BUTTON_ATCK = false;
+                       self.BUTTON_ATCK2 = false;
+                       if(self.bot_primary_fireballmooth == 0)
+                       {
+                               if(bot_aim(WEP_CVAR_PRI(fireball, speed), 0, WEP_CVAR_PRI(fireball, lifetime), false))
+                               {
+                                       self.BUTTON_ATCK = true;
+                                       if(random() < 0.02) self.bot_primary_fireballmooth = 0;
+                               }
+                       }
+                       else
+                       {
+                               if(bot_aim(WEP_CVAR_SEC(fireball, speed), WEP_CVAR_SEC(fireball, speed_up), WEP_CVAR_SEC(fireball, lifetime), true))
+                               {
+                                       self.BUTTON_ATCK2 = true;
+                                       if(random() < 0.01) self.bot_primary_fireballmooth = 1;
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       if(self.BUTTON_ATCK)
+                       {
+                               if(time >= self.fireball_primarytime)
+                               if(weapon_prepareattack(0, WEP_CVAR_PRI(fireball, refire)))
+                               {
+                                       W_Fireball_Attack1_Frame0();
+                                       self.fireball_primarytime = time + WEP_CVAR_PRI(fireball, refire2) * W_WeaponRateFactor();
+                               }
+                       }
+                       else if(self.BUTTON_ATCK2)
+                       {
+                               if(weapon_prepareattack(1, WEP_CVAR_SEC(fireball, refire)))
+                               {
+                                       W_Fireball_Attack2();
+                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(fireball, animtime), w_ready);
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_model(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(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;
+               }
+               case WR_SETUP:
+               {
+                       self.ammo_field = ammo_none;
+                       return true;
+               }
+               case WR_CHECKAMMO1:
+               case WR_CHECKAMMO2:
+               {
+                       return true; // fireball has infinite ammo
+               }
+               case WR_CONFIG:
+               {
+                       FIREBALL_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RESETPLAYER:
+               {
+                       self.fireball_primarytime = time;
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                               return WEAPON_FIREBALL_SUICIDE_FIREMINE;
+                       else
+                               return WEAPON_FIREBALL_SUICIDE_BLAST;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                               return WEAPON_FIREBALL_MURDER_FIREMINE;
+                       else
+                               return WEAPON_FIREBALL_MURDER_BLAST;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_Fireball(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                       {
+                               // firemine goes out silently
+                       }
+                       else
+                       {
+                               org2 = w_org + w_backoff * 16;
+                               pointparticles(particleeffectnum(EFFECT_FIREBALL_EXPLODE), org2, '0 0 0', 1);
+                               if(!w_issilent)
+                                       sound(self, CH_SHOTS, W_Sound("fireball_impact2"), VOL_BASE, ATTEN_NORM * 0.25); // long range boom
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("fireball_impact2"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/hagar.qc b/qcsrc/common/weapons/weapon/hagar.qc
new file mode 100644 (file)
index 0000000..4f1b905
--- /dev/null
@@ -0,0 +1,565 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ HAGAR,
+/* function  */ W_Hagar,
+/* ammotype  */ ammo_rockets,
+/* impulse   */ 8,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* color     */ '1 1 0.5',
+/* modelname */ "hagar",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairhagar 0.8",
+/* wepimg    */ "weaponhagar",
+/* refname   */ "hagar",
+/* wepname   */ _("Hagar")
+);
+
+#define HAGAR_SETTINGS(w_cvar,w_prop) HAGAR_SETTINGS_LIST(w_cvar, w_prop, HAGAR, hagar)
+#define HAGAR_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, BOTH, ammo) \
+       w_cvar(id, sn, BOTH, damage) \
+       w_cvar(id, sn, BOTH, edgedamage) \
+       w_cvar(id, sn, BOTH, force) \
+       w_cvar(id, sn, BOTH, radius) \
+       w_cvar(id, sn, BOTH, refire) \
+       w_cvar(id, sn, BOTH, speed) \
+       w_cvar(id, sn, BOTH, spread) \
+       w_cvar(id, sn, BOTH, damageforcescale) \
+       w_cvar(id, sn, BOTH, health) \
+       w_cvar(id, sn, PRI,  lifetime) \
+       w_cvar(id, sn, SEC,  load) \
+       w_cvar(id, sn, SEC,  load_max) \
+       w_cvar(id, sn, SEC,  load_abort) \
+       w_cvar(id, sn, SEC,  load_animtime) \
+       w_cvar(id, sn, SEC,  load_hold) \
+       w_cvar(id, sn, SEC,  load_speed) \
+       w_cvar(id, sn, SEC,  load_releasedeath) \
+       w_cvar(id, sn, SEC,  load_spread) \
+       w_cvar(id, sn, SEC,  load_spread_bias) \
+       w_cvar(id, sn, SEC,  load_linkexplode) \
+       w_cvar(id, sn, SEC,  lifetime_min) \
+       w_cvar(id, sn, SEC,  lifetime_rand) \
+       w_cvar(id, sn, NONE, secondary) \
+       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
+HAGAR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_hagar(void) { weapon_defaultspawnfunc(WEP_HAGAR.m_id); }
+
+// NO bounce protection, as bounces are limited!
+
+void W_Hagar_Explode(void)
+{
+       self.event_damage = func_null;
+       RadiusDamage(self, self.realowner, WEP_CVAR_PRI(hagar, damage), WEP_CVAR_PRI(hagar, edgedamage), WEP_CVAR_PRI(hagar, radius), world, world, WEP_CVAR_PRI(hagar, force), self.projectiledeathtype, other);
+
+       remove(self);
+}
+
+void W_Hagar_Explode2(void)
+{
+       self.event_damage = func_null;
+       RadiusDamage(self, self.realowner, WEP_CVAR_SEC(hagar, damage), WEP_CVAR_SEC(hagar, edgedamage), WEP_CVAR_SEC(hagar, radius), world, world, WEP_CVAR_SEC(hagar, force), self.projectiledeathtype, other);
+
+       remove(self);
+}
+
+void W_Hagar_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+       if(self.health <= 0)
+               return;
+
+       float is_linkexplode = ( ((inflictor.owner != world) ? (inflictor.owner == self.owner) : true)
+               && (inflictor.projectiledeathtype & HITTYPE_SECONDARY)
+               && (self.projectiledeathtype & HITTYPE_SECONDARY));
+
+       if(is_linkexplode)
+               is_linkexplode = (is_linkexplode && WEP_CVAR_SEC(hagar, load_linkexplode));
+       else
+               is_linkexplode = -1; // not secondary load, so continue as normal without exception.
+
+       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, is_linkexplode))
+               return; // g_projectiles_damage says to halt
+
+       self.health = self.health - damage;
+       self.angles = vectoangles(self.velocity);
+
+       if(self.health <= 0)
+               W_PrepareExplosionByDamage(attacker, self.think);
+}
+
+void W_Hagar_Touch(void)
+{
+       PROJECTILE_TOUCH;
+       self.use();
+}
+
+void W_Hagar_Touch2(void)
+{
+       PROJECTILE_TOUCH;
+
+       if(self.cnt > 0 || other.takedamage == DAMAGE_AIM) {
+               self.use();
+       } else {
+               self.cnt++;
+               Send_Effect(EFFECT_HAGAR_BOUNCE, self.origin, self.velocity, 1);
+               self.angles = vectoangles(self.velocity);
+               self.owner = world;
+               self.projectiledeathtype |= HITTYPE_BOUNCE;
+       }
+}
+
+void W_Hagar_Attack(void)
+{
+       entity missile;
+
+       W_DecreaseAmmo(WEP_CVAR_PRI(hagar, ammo));
+
+       W_SetupShot(self, false, 2, W_Sound("hagar_fire"), CH_WEAPON_A, WEP_CVAR_PRI(hagar, damage));
+
+       Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       missile = spawn();
+       missile.owner = missile.realowner = self;
+       missile.classname = "missile";
+       missile.bot_dodge = true;
+       missile.bot_dodgerating = WEP_CVAR_PRI(hagar, damage);
+
+       missile.takedamage = DAMAGE_YES;
+       missile.health = WEP_CVAR_PRI(hagar, health);
+       missile.damageforcescale = WEP_CVAR_PRI(hagar, damageforcescale);
+       missile.event_damage = W_Hagar_Damage;
+       missile.damagedbycontents = true;
+
+       missile.touch = W_Hagar_Touch;
+       missile.use = W_Hagar_Explode;
+       missile.think = adaptor_think2use_hittype_splash;
+       missile.nextthink = time + WEP_CVAR_PRI(hagar, lifetime);
+       PROJECTILE_MAKETRIGGER(missile);
+       missile.projectiledeathtype = WEP_HAGAR.m_id;
+       setorigin(missile, w_shotorg);
+       setsize(missile, '0 0 0', '0 0 0');
+
+       missile.movetype = MOVETYPE_FLY;
+       W_SetupProjVelocity_PRI(missile, hagar);
+
+       missile.angles = vectoangles(missile.velocity);
+       missile.flags = FL_PROJECTILE;
+       missile.missile_flags = MIF_SPLASH;
+
+       CSQCProjectile(missile, true, PROJECTILE_HAGAR, true);
+
+       MUTATOR_CALLHOOK(EditProjectile, self, missile);
+}
+
+void W_Hagar_Attack2(void)
+{
+       entity missile;
+
+       W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo));
+
+       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);
+
+       missile = spawn();
+       missile.owner = missile.realowner = self;
+       missile.classname = "missile";
+       missile.bot_dodge = true;
+       missile.bot_dodgerating = WEP_CVAR_SEC(hagar, damage);
+
+       missile.takedamage = DAMAGE_YES;
+       missile.health = WEP_CVAR_SEC(hagar, health);
+       missile.damageforcescale = WEP_CVAR_SEC(hagar, damageforcescale);
+       missile.event_damage = W_Hagar_Damage;
+       missile.damagedbycontents = true;
+
+       missile.touch = W_Hagar_Touch2;
+       missile.cnt = 0;
+       missile.use = W_Hagar_Explode2;
+       missile.think = adaptor_think2use_hittype_splash;
+       missile.nextthink = time + WEP_CVAR_SEC(hagar, lifetime_min) + random() * WEP_CVAR_SEC(hagar, lifetime_rand);
+       PROJECTILE_MAKETRIGGER(missile);
+       missile.projectiledeathtype = WEP_HAGAR.m_id | HITTYPE_SECONDARY;
+       setorigin(missile, w_shotorg);
+       setsize(missile, '0 0 0', '0 0 0');
+
+       missile.movetype = MOVETYPE_BOUNCEMISSILE;
+       W_SetupProjVelocity_SEC(missile, hagar);
+
+       missile.angles = vectoangles(missile.velocity);
+       missile.flags = FL_PROJECTILE;
+       missile.missile_flags = MIF_SPLASH;
+
+       CSQCProjectile(missile, true, PROJECTILE_HAGAR_BOUNCING, true);
+
+       MUTATOR_CALLHOOK(EditProjectile, self, missile);
+}
+
+.float hagar_loadstep, hagar_loadblock, hagar_loadbeep, hagar_warning;
+void W_Hagar_Attack2_Load_Release(void)
+{
+       // time to release the rockets we've loaded
+
+       entity missile;
+       float counter, shots, spread_pershot;
+       vector s;
+       vector forward, right, up;
+
+       if(!self.hagar_load)
+               return;
+
+       weapon_prepareattack_do(1, WEP_CVAR_SEC(hagar, refire));
+
+       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;
+       up = v_up;
+
+       shots = self.hagar_load;
+       missile = world;
+       for(counter = 0; counter < shots; ++counter)
+       {
+               missile = spawn();
+               missile.owner = missile.realowner = self;
+               missile.classname = "missile";
+               missile.bot_dodge = true;
+               missile.bot_dodgerating = WEP_CVAR_SEC(hagar, damage);
+
+               missile.takedamage = DAMAGE_YES;
+               missile.health = WEP_CVAR_SEC(hagar, health);
+               missile.damageforcescale = WEP_CVAR_SEC(hagar, damageforcescale);
+               missile.event_damage = W_Hagar_Damage;
+               missile.damagedbycontents = true;
+
+               missile.touch = W_Hagar_Touch; // not bouncy
+               missile.use = W_Hagar_Explode2;
+               missile.think = adaptor_think2use_hittype_splash;
+               missile.nextthink = time + WEP_CVAR_SEC(hagar, lifetime_min) + random() * WEP_CVAR_SEC(hagar, lifetime_rand);
+               PROJECTILE_MAKETRIGGER(missile);
+               missile.projectiledeathtype = WEP_HAGAR.m_id | HITTYPE_SECONDARY;
+               setorigin(missile, w_shotorg);
+               setsize(missile, '0 0 0', '0 0 0');
+               missile.movetype = MOVETYPE_FLY;
+               missile.missile_flags = MIF_SPLASH;
+
+               // per-shot spread calculation: the more shots there are, the less spread is applied (based on the bias cvar)
+               spread_pershot = ((shots - 1) / (WEP_CVAR_SEC(hagar, load_max) - 1));
+               spread_pershot = (1 - (spread_pershot * WEP_CVAR_SEC(hagar, load_spread_bias)));
+               spread_pershot = (WEP_CVAR_SEC(hagar, spread) * spread_pershot * g_weaponspreadfactor);
+
+               // pattern spread calculation
+               s = '0 0 0';
+               if(counter == 0)
+                       s = '0 0 0';
+               else
+               {
+                       makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
+                       s.y = v_forward.x;
+                       s.z = v_forward.y;
+               }
+               s = s * WEP_CVAR_SEC(hagar, load_spread) * g_weaponspreadfactor;
+
+               W_SetupProjVelocity_Explicit(missile, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_SEC(hagar, speed), 0, 0, spread_pershot, false);
+
+               missile.angles = vectoangles(missile.velocity);
+               missile.flags = FL_PROJECTILE;
+
+               CSQCProjectile(missile, true, PROJECTILE_HAGAR, true);
+
+               MUTATOR_CALLHOOK(EditProjectile, self, missile);
+       }
+
+       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hagar, load_animtime), w_ready);
+       self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, refire) * W_WeaponRateFactor();
+       self.hagar_load = 0;
+}
+
+void W_Hagar_Attack2_Load(void)
+{
+       // loadable hagar secondary attack, must always run each frame
+
+       if(time < game_starttime)
+               return;
+
+       bool loaded = self.hagar_load >= WEP_CVAR_SEC(hagar, load_max);
+
+       // this is different than WR_CHECKAMMO when it comes to reloading
+       bool enough_ammo;
+       if(self.items & IT_UNLIMITED_WEAPON_AMMO)
+               enough_ammo = true;
+       else if(autocvar_g_balance_hagar_reload_ammo)
+               enough_ammo = self.(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_SEC(hagar, ammo);
+       else
+               enough_ammo = self.WEP_AMMO(HAGAR) >= WEP_CVAR_SEC(hagar, ammo);
+
+       bool stopped = loaded || !enough_ammo;
+
+       if(self.BUTTON_ATCK2)
+       {
+               if(self.BUTTON_ATCK && WEP_CVAR_SEC(hagar, load_abort))
+               {
+                       if(self.hagar_load)
+                       {
+                               // if we pressed primary fire while loading, unload all rockets and abort
+                               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, 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();
+
+                               // require letting go of the alt fire button before we can load again
+                               self.hagar_loadblock = true;
+                       }
+               }
+               else
+               {
+                       // check if we can attempt to load another rocket
+                       if(!stopped)
+                       {
+                               if(!self.hagar_loadblock && self.hagar_loadstep < time)
+                               {
+                                       W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo));
+                                       self.weaponentity.state = WS_INUSE;
+                                       self.hagar_load += 1;
+                                       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))
+                                               stopped = true;
+                                       else
+                                               self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, load_speed) * W_WeaponRateFactor();
+                               }
+                       }
+                       if(stopped && !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, W_Sound("hagar_beep"), VOL_BASE, ATTN_NORM);
+                               self.hagar_loadbeep = true;
+                               self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, load_hold) * W_WeaponRateFactor();
+                       }
+               }
+       }
+       else if(self.hagar_loadblock)
+       {
+               // the alt fire button has been released, so re-enable loading if blocked
+               self.hagar_loadblock = false;
+       }
+
+       if(self.hagar_load)
+       {
+               // play warning sound if we're about to release
+               if(stopped && self.hagar_loadstep - 0.5 < time && WEP_CVAR_SEC(hagar, load_hold) >= 0)
+               {
+                       if(!self.hagar_warning) // 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, W_Sound("hagar_beep"), VOL_BASE, ATTN_NORM);
+                               self.hagar_warning = true;
+                       }
+               }
+
+               // release if player let go of button or if they've held it in too long
+               if(!self.BUTTON_ATCK2 || (stopped && self.hagar_loadstep < time && WEP_CVAR_SEC(hagar, load_hold) >= 0))
+               {
+                       self.weaponentity.state = WS_READY;
+                       W_Hagar_Attack2_Load_Release();
+               }
+       }
+       else
+       {
+               self.hagar_loadbeep = false;
+               self.hagar_warning = false;
+
+               // we aren't checking ammo during an attack, so we must do it here
+               if(!(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2)))
+               if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+               {
+                       // note: this doesn't force the switch
+                       W_SwitchToOtherWeapon(self);
+                       return;
+               }
+       }
+}
+
+bool W_Hagar(int req)
+{
+       float ammo_amount;
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       if(random()>0.15)
+                               self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(hagar, speed), 0, WEP_CVAR_PRI(hagar, lifetime), false);
+                       else // not using secondary_speed since these are only 15% and should cause some ricochets without re-aiming
+                               self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_PRI(hagar, speed), 0, WEP_CVAR_PRI(hagar, lifetime), false);
+
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       float loadable_secondary;
+                       loadable_secondary = (WEP_CVAR_SEC(hagar, load) && WEP_CVAR(hagar, secondary));
+
+                       if(loadable_secondary)
+                               W_Hagar_Attack2_Load(); // must always run each frame
+                       if(autocvar_g_balance_hagar_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(hagar, ammo), WEP_CVAR_SEC(hagar, ammo))) // forced reload
+                               WEP_ACTION(self.weapon, WR_RELOAD);
+                       else if(self.BUTTON_ATCK && !self.hagar_load && !self.hagar_loadblock) // not while secondary is loaded or awaiting reset
+                       {
+                               if(weapon_prepareattack(0, WEP_CVAR_PRI(hagar, refire)))
+                               {
+                                       W_Hagar_Attack();
+                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hagar, refire), w_ready);
+                               }
+                       }
+                       else if(self.BUTTON_ATCK2 && !loadable_secondary && WEP_CVAR(hagar, secondary))
+                       {
+                               if(weapon_prepareattack(1, WEP_CVAR_SEC(hagar, refire)))
+                               {
+                                       W_Hagar_Attack2();
+                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hagar, refire), w_ready);
+                               }
+                       }
+                       return true;
+               }
+               case WR_GONETHINK:
+               {
+                       // we lost the weapon and want to prepare switching away
+                       if(self.hagar_load)
+                       {
+                               self.weaponentity.state = WS_READY;
+                               W_Hagar_Attack2_Load_Release();
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       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;
+               }
+               case WR_SETUP:
+               {
+                       self.hagar_loadblock = false;
+
+                       if(self.hagar_load)
+                       {
+                               W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo) * self.hagar_load * -1); // give back ammo if necessary
+                               self.hagar_load = 0;
+                       }
+
+                       return true;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       ammo_amount = self.WEP_AMMO(HAGAR) >= WEP_CVAR_PRI(hagar, ammo);
+                       ammo_amount += self.(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_PRI(hagar, ammo);
+                       return ammo_amount;
+               }
+               case WR_CHECKAMMO2:
+               {
+                       ammo_amount = self.WEP_AMMO(HAGAR) >= WEP_CVAR_SEC(hagar, ammo);
+                       ammo_amount += self.(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_SEC(hagar, ammo);
+                       return ammo_amount;
+               }
+               case WR_CONFIG:
+               {
+                       HAGAR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RESETPLAYER:
+               {
+                       self.hagar_load = 0;
+                       return true;
+               }
+               case WR_PLAYERDEATH:
+               {
+                       // if we have any rockets loaded when we die, release them
+                       if(self.hagar_load && WEP_CVAR_SEC(hagar, load_releasedeath))
+                               W_Hagar_Attack2_Load_Release();
+
+                       return true;
+               }
+               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)), W_Sound("reload"));
+
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       return WEAPON_HAGAR_SUICIDE;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                               return WEAPON_HAGAR_MURDER_BURST;
+                       else
+                               return WEAPON_HAGAR_MURDER_SPRAY;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_Hagar(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 6;
+                       pointparticles(particleeffectnum(EFFECT_HAGAR_EXPLODE), org2, '0 0 0', 1);
+                       if(!w_issilent)
+                       {
+                               if(w_random<0.15)
+                                       sound(self, CH_SHOTS, W_Sound("hagexp1"), VOL_BASE, ATTN_NORM);
+                               else if(w_random<0.7)
+                                       sound(self, CH_SHOTS, W_Sound("hagexp2"), VOL_BASE, ATTN_NORM);
+                               else
+                                       sound(self, CH_SHOTS, W_Sound("hagexp3"), VOL_BASE, ATTN_NORM);
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("hagexp1"));
+                       precache_sound(W_Sound("hagexp2"));
+                       precache_sound(W_Sound("hagexp3"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/hlac.qc b/qcsrc/common/weapons/weapon/hlac.qc
new file mode 100644 (file)
index 0000000..4968529
--- /dev/null
@@ -0,0 +1,314 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ HLAC,
+/* function  */ W_HLAC,
+/* ammotype  */ ammo_cells,
+/* impulse   */ 6,
+/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* color     */ '0 1 0',
+/* modelname */ "hlac",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairhlac 0.6",
+/* wepimg    */ "weaponhlac",
+/* refname   */ "hlac",
+/* wepname   */ _("Heavy Laser Assault Cannon")
+);
+
+#define HLAC_SETTINGS(w_cvar,w_prop) HLAC_SETTINGS_LIST(w_cvar, w_prop, HLAC, hlac)
+#define HLAC_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, BOTH, ammo) \
+       w_cvar(id, sn, BOTH, animtime) \
+       w_cvar(id, sn, BOTH, damage) \
+       w_cvar(id, sn, BOTH, edgedamage) \
+       w_cvar(id, sn, BOTH, force) \
+       w_cvar(id, sn, BOTH, lifetime) \
+       w_cvar(id, sn, BOTH, radius) \
+       w_cvar(id, sn, BOTH, refire) \
+       w_cvar(id, sn, BOTH, speed) \
+       w_cvar(id, sn, BOTH, spread_crouchmod) \
+       w_cvar(id, sn, PRI,  spread_add) \
+       w_cvar(id, sn, PRI,  spread_max) \
+       w_cvar(id, sn, PRI,  spread_min) \
+       w_cvar(id, sn, NONE, secondary) \
+       w_cvar(id, sn, SEC,  shots) \
+       w_cvar(id, sn, SEC,  spread) \
+       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
+HLAC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_hlac(void) { weapon_defaultspawnfunc(WEP_HLAC.m_id); }
+
+void W_HLAC_Touch(void)
+{
+       float isprimary;
+
+       PROJECTILE_TOUCH;
+
+       self.event_damage = func_null;
+
+       isprimary = !(self.projectiledeathtype & HITTYPE_SECONDARY);
+
+       RadiusDamage(self, self.realowner, WEP_CVAR_BOTH(hlac, isprimary, damage), WEP_CVAR_BOTH(hlac, isprimary, edgedamage), WEP_CVAR_BOTH(hlac, isprimary, radius), world, world, WEP_CVAR_BOTH(hlac, isprimary, force), self.projectiledeathtype, other);
+
+       remove(self);
+}
+
+void W_HLAC_Attack(void)
+{
+       entity missile;
+    float spread;
+
+       W_DecreaseAmmo(WEP_CVAR_PRI(hlac, ammo));
+
+    spread = WEP_CVAR_PRI(hlac, spread_min) + (WEP_CVAR_PRI(hlac, spread_add) * self.misc_bulletcounter);
+    spread = min(spread,WEP_CVAR_PRI(hlac, spread_max));
+    if(self.crouch)
+        spread = spread * WEP_CVAR_PRI(hlac, spread_crouchmod);
+
+       W_SetupShot(self, false, 3, W_Sound("lasergun_fire"), CH_WEAPON_A, WEP_CVAR_PRI(hlac, damage));
+       Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+       if(!autocvar_g_norecoil)
+       {
+               self.punchangle_x = random() - 0.5;
+               self.punchangle_y = random() - 0.5;
+       }
+
+       missile = spawn();
+       missile.owner = missile.realowner = self;
+       missile.classname = "hlacbolt";
+       missile.bot_dodge = true;
+
+    missile.bot_dodgerating = WEP_CVAR_PRI(hlac, damage);
+
+       missile.movetype = MOVETYPE_FLY;
+       PROJECTILE_MAKETRIGGER(missile);
+
+       setorigin(missile, w_shotorg);
+       setsize(missile, '0 0 0', '0 0 0');
+
+       W_SetupProjVelocity_Basic(missile, WEP_CVAR_PRI(hlac, speed), spread);
+       //missile.angles = vectoangles(missile.velocity); // csqc
+
+       missile.touch = W_HLAC_Touch;
+       missile.think = SUB_Remove;
+
+    missile.nextthink = time + WEP_CVAR_PRI(hlac, lifetime);
+
+       missile.flags = FL_PROJECTILE;
+       missile.projectiledeathtype = WEP_HLAC.m_id;
+
+       CSQCProjectile(missile, true, PROJECTILE_HLAC, true);
+
+       MUTATOR_CALLHOOK(EditProjectile, self, missile);
+}
+
+void W_HLAC_Attack2(void)
+{
+       entity missile;
+    float spread;
+
+    spread = WEP_CVAR_SEC(hlac, spread);
+
+
+    if(self.crouch)
+        spread = spread * WEP_CVAR_SEC(hlac, spread_crouchmod);
+
+       W_SetupShot(self, false, 3, W_Sound("lasergun_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hlac, damage));
+       Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       missile = spawn();
+       missile.owner = missile.realowner = self;
+       missile.classname = "hlacbolt";
+       missile.bot_dodge = true;
+
+    missile.bot_dodgerating = WEP_CVAR_SEC(hlac, damage);
+
+       missile.movetype = MOVETYPE_FLY;
+       PROJECTILE_MAKETRIGGER(missile);
+
+       setorigin(missile, w_shotorg);
+       setsize(missile, '0 0 0', '0 0 0');
+
+       W_SetupProjVelocity_Basic(missile, WEP_CVAR_SEC(hlac, speed), spread);
+       //missile.angles = vectoangles(missile.velocity); // csqc
+
+       missile.touch = W_HLAC_Touch;
+       missile.think = SUB_Remove;
+
+    missile.nextthink = time + WEP_CVAR_SEC(hlac, lifetime);
+
+       missile.flags = FL_PROJECTILE;
+       missile.missile_flags = MIF_SPLASH;
+       missile.projectiledeathtype = WEP_HLAC.m_id | HITTYPE_SECONDARY;
+
+       CSQCProjectile(missile, true, PROJECTILE_HLAC, true);
+
+       MUTATOR_CALLHOOK(EditProjectile, self, missile);
+}
+
+// weapon frames
+void W_HLAC_Attack_Frame(void)
+{
+       if(self.weapon != self.switchweapon) // abort immediately if switching
+       {
+               w_ready();
+               return;
+       }
+
+       if(self.BUTTON_ATCK)
+       {
+               if(!WEP_ACTION(self.weapon, WR_CHECKAMMO1))
+               if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+               {
+                       W_SwitchWeapon_Force(self, w_getbestweapon(self));
+                       w_ready();
+                       return;
+               }
+
+               ATTACK_FINISHED(self) = time + WEP_CVAR_PRI(hlac, refire) * W_WeaponRateFactor();
+               W_HLAC_Attack();
+               self.misc_bulletcounter = self.misc_bulletcounter + 1;
+        weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hlac, refire), W_HLAC_Attack_Frame);
+       }
+       else
+       {
+               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hlac, animtime), w_ready);
+       }
+}
+
+void W_HLAC_Attack2_Frame(void)
+{
+    float i;
+
+       W_DecreaseAmmo(WEP_CVAR_SEC(hlac, ammo));
+
+    for(i=WEP_CVAR_SEC(hlac, shots);i>0;--i)
+        W_HLAC_Attack2();
+
+       if(!autocvar_g_norecoil)
+       {
+               self.punchangle_x = random() - 0.5;
+               self.punchangle_y = random() - 0.5;
+       }
+}
+
+bool W_HLAC(int req)
+{
+       float ammo_amount;
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(hlac, speed), 0, WEP_CVAR_PRI(hlac, lifetime), false);
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       if(autocvar_g_balance_hlac_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(hlac, ammo), WEP_CVAR_SEC(hlac, ammo))) // forced reload
+                               WEP_ACTION(self.weapon, WR_RELOAD);
+                       else if(self.BUTTON_ATCK)
+                       {
+                               if(weapon_prepareattack(0, WEP_CVAR_PRI(hlac, refire)))
+                               {
+                                       self.misc_bulletcounter = 0;
+                                       W_HLAC_Attack();
+                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hlac, refire), W_HLAC_Attack_Frame);
+                               }
+                       }
+
+                       else if(self.BUTTON_ATCK2 && WEP_CVAR(hlac, secondary))
+                       {
+                               if(weapon_prepareattack(1, WEP_CVAR_SEC(hlac, refire)))
+                               {
+                                       W_HLAC_Attack2_Frame();
+                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hlac, animtime), w_ready);
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       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;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       ammo_amount = self.WEP_AMMO(HLAC) >= WEP_CVAR_PRI(hlac, ammo);
+                       ammo_amount += self.(weapon_load[WEP_HLAC.m_id]) >= WEP_CVAR_PRI(hlac, ammo);
+                       return ammo_amount;
+               }
+               case WR_CHECKAMMO2:
+               {
+                       ammo_amount = self.WEP_AMMO(HLAC) >= WEP_CVAR_SEC(hlac, ammo);
+                       ammo_amount += self.(weapon_load[WEP_HLAC.m_id]) >= WEP_CVAR_SEC(hlac, ammo);
+                       return ammo_amount;
+               }
+               case WR_CONFIG:
+               {
+                       HLAC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       W_Reload(min(WEP_CVAR_PRI(hlac, ammo), WEP_CVAR_SEC(hlac, ammo)), W_Sound("reload"));
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       return WEAPON_HLAC_SUICIDE;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       return WEAPON_HLAC_MURDER;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_HLAC(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 6;
+                       pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1);
+                       if(!w_issilent)
+                               sound(self, CH_SHOTS, W_Sound("laserimpact"), VOL_BASE, ATTN_NORM);
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("laserimpact"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/hmg.qc b/qcsrc/common/weapons/weapon/hmg.qc
new file mode 100644 (file)
index 0000000..52b60e8
--- /dev/null
@@ -0,0 +1,208 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id     */ HMG,
+/* function     */ W_HeavyMachineGun,
+/* ammotype     */ ammo_nails,
+/* impulse      */ 3,
+/* flags        */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_SUPERWEAPON,
+/* rating       */ BOT_PICKUP_RATING_HIGH,
+/* color     */ '0.5 0.5 0',
+/* modelname */ "ok_hmg",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairuzi 0.6",
+/* wepimg    */ "weaponhmg",
+/* refname   */ "hmg",
+/* wepname   */ _("Heavy Machine Gun")
+);
+
+#define HMG_SETTINGS(w_cvar,w_prop) HMG_SETTINGS_LIST(w_cvar, w_prop, HMG, hmg)
+#define HMG_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, NONE, spread_min) \
+       w_cvar(id, sn, NONE, spread_max) \
+       w_cvar(id, sn, NONE, spread_add) \
+       w_cvar(id, sn, NONE, solidpenetration) \
+       w_cvar(id, sn, NONE, damage) \
+       w_cvar(id, sn, NONE, force) \
+       w_cvar(id, sn, NONE, refire) \
+       w_cvar(id, sn, NONE, ammo) \
+       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
+HMG_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+
+void spawnfunc_weapon_hmg() { weapon_defaultspawnfunc(WEP_HMG.m_id); }
+
+void W_HeavyMachineGun_Attack_Auto()
+{
+       if (!self.BUTTON_ATCK)
+       {
+               w_ready();
+               return;
+       }
+
+       if(!WEP_ACTION(self.weapon, WR_CHECKAMMO1))
+       if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+       {
+               W_SwitchWeapon_Force(self, w_getbestweapon(self));
+               w_ready();
+               return;
+       }
+
+       W_DecreaseAmmo(WEP_CVAR(hmg, ammo));
+
+       W_SetupShot (self, true, 0, W_Sound("uzi_fire"), CH_WEAPON_A, WEP_CVAR(hmg, damage));
+
+       if(!autocvar_g_norecoil)
+       {
+               self.punchangle_x = random () - 0.5;
+               self.punchangle_y = random () - 0.5;
+       }
+
+       float hmg_spread = bound(WEP_CVAR(hmg, spread_min), WEP_CVAR(hmg, spread_min) + (WEP_CVAR(hmg, spread_add) * self.misc_bulletcounter), WEP_CVAR(hmg, spread_max));
+       fireBullet(w_shotorg, w_shotdir, hmg_spread, WEP_CVAR(hmg, solidpenetration), WEP_CVAR(hmg, damage), WEP_CVAR(hmg, force), WEP_HMG.m_id, 0);
+
+       self.misc_bulletcounter = self.misc_bulletcounter + 1;
+
+       Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       W_MachineGun_MuzzleFlash();
+       W_AttachToShotorg(self.muzzle_flash, '5 0 0');
+
+       if (autocvar_g_casings >= 2) // casing code
+               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);
+
+       ATTACK_FINISHED(self) = time + WEP_CVAR(hmg, refire) * W_WeaponRateFactor();
+       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(hmg, refire), W_HeavyMachineGun_Attack_Auto);
+}
+
+bool W_HeavyMachineGun(int req)
+{
+       float ammo_amount;
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       if(vlen(self.origin-self.enemy.origin) < 3000 - bound(0, skill, 10) * 200)
+                               self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, false);
+                       else
+                               self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, false);
+
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       if(WEP_CVAR(hmg, reload_ammo) && self.clip_load < WEP_CVAR(hmg, ammo)) // forced reload
+                               WEP_ACTION(self.weapon, WR_RELOAD);
+                       else
+                       {
+                               if (self.BUTTON_ATCK)
+                               if (weapon_prepareattack(0, 0))
+                               {
+                                       self.misc_bulletcounter = 0;
+                                       W_HeavyMachineGun_Attack_Auto();
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_model ("models/uziflash.md3");
+                       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;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       ammo_amount = self.ammo_nails >= WEP_CVAR(hmg, ammo);
+
+                       if(autocvar_g_balance_hmg_reload_ammo)
+                               ammo_amount += self.(weapon_load[WEP_HMG.m_id]) >= WEP_CVAR(hmg, ammo);
+
+                       return ammo_amount;
+               }
+               case WR_CHECKAMMO2:
+               {
+                       ammo_amount = self.ammo_nails >= WEP_CVAR(hmg, ammo);
+
+                       if(autocvar_g_balance_hmg_reload_ammo)
+                               ammo_amount += self.(weapon_load[WEP_HMG.m_id]) >= WEP_CVAR(hmg, ammo);
+
+                       return ammo_amount;
+               }
+               case WR_CONFIG:
+               {
+                       HMG_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       W_Reload(WEP_CVAR(hmg, ammo), W_Sound("reload"));
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       return WEAPON_THINKING_WITH_PORTALS;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                               return WEAPON_HMG_MURDER_SNIPE;
+                       else
+                               return WEAPON_HMG_MURDER_SPRAY;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_HeavyMachineGun(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 2;
+                       pointparticles(particleeffectnum(EFFECT_MACHINEGUN_IMPACT), org2, w_backoff * 1000, 1);
+                       if(!w_issilent)
+                               if(w_random < 0.05)
+                                       sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTEN_NORM);
+                               else if(w_random < 0.1)
+                                       sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTEN_NORM);
+                               else if(w_random < 0.2)
+                                       sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTEN_NORM);
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("ric1"));
+                       precache_sound(W_Sound("ric2"));
+                       precache_sound(W_Sound("ric3"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/hook.qc b/qcsrc/common/weapons/weapon/hook.qc
new file mode 100644 (file)
index 0000000..6c5519e
--- /dev/null
@@ -0,0 +1,368 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ HOOK,
+/* function  */ W_Hook,
+/* ammotype  */ ammo_fuel,
+/* impulse   */ 0,
+/* flags     */ WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
+/* rating    */ 0,
+/* color     */ '0 0.5 0',
+/* modelname */ "hookgun",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairhook 0.5",
+/* wepimg    */ "weaponhook",
+/* refname   */ "hook",
+/* wepname   */ _("Grappling Hook")
+);
+
+#define HOOK_SETTINGS(w_cvar,w_prop) HOOK_SETTINGS_LIST(w_cvar, w_prop, HOOK, hook)
+#define HOOK_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, BOTH, animtime) \
+       w_cvar(id, sn, BOTH, refire) \
+       w_cvar(id, sn, PRI,  ammo) \
+       w_cvar(id, sn, PRI,  hooked_ammo) \
+       w_cvar(id, sn, PRI,  hooked_time_free) \
+       w_cvar(id, sn, PRI,  hooked_time_max) \
+       w_cvar(id, sn, SEC,  damage) \
+       w_cvar(id, sn, SEC,  duration) \
+       w_cvar(id, sn, SEC,  edgedamage) \
+       w_cvar(id, sn, SEC,  force) \
+       w_cvar(id, sn, SEC,  gravity) \
+       w_cvar(id, sn, SEC,  lifetime) \
+       w_cvar(id, sn, SEC,  power) \
+       w_cvar(id, sn, SEC,  radius) \
+       w_cvar(id, sn, SEC,  speed) \
+       w_cvar(id, sn, SEC,  health) \
+       w_cvar(id, sn, SEC,  damageforcescale) \
+       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
+HOOK_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+
+.float dmg;
+.float dmg_edge;
+.float dmg_radius;
+.float dmg_force;
+.float dmg_power;
+.float dmg_duration;
+.float dmg_last;
+.float hook_refire;
+.float hook_time_hooked;
+.float hook_time_fueldecrease;
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+
+void spawnfunc_weapon_hook(void)
+{
+       if(g_grappling_hook) // offhand hook
+       {
+               startitem_failed = true;
+               remove(self);
+               return;
+       }
+       weapon_defaultspawnfunc(WEP_HOOK.m_id);
+}
+
+void W_Hook_ExplodeThink(void)
+{
+       float dt, dmg_remaining_next, f;
+
+       dt = time - self.teleport_time;
+       dmg_remaining_next = pow(bound(0, 1 - dt / self.dmg_duration, 1), self.dmg_power);
+
+       f = self.dmg_last - dmg_remaining_next;
+       self.dmg_last = dmg_remaining_next;
+
+       RadiusDamage(self, self.realowner, self.dmg * f, self.dmg_edge * f, self.dmg_radius, self.realowner, world, self.dmg_force * f, self.projectiledeathtype, world);
+       self.projectiledeathtype |= HITTYPE_BOUNCE;
+       //RadiusDamage(self, world, self.dmg * f, self.dmg_edge * f, self.dmg_radius, world, world, self.dmg_force * f, self.projectiledeathtype, world);
+
+       if(dt < self.dmg_duration)
+               self.nextthink = time + 0.05; // soon
+       else
+               remove(self);
+}
+
+void W_Hook_Explode2(void)
+{
+       self.event_damage = func_null;
+       self.touch = func_null;
+       self.effects |= EF_NODRAW;
+
+       self.think = W_Hook_ExplodeThink;
+       self.nextthink = time;
+       self.dmg = WEP_CVAR_SEC(hook, damage);
+       self.dmg_edge = WEP_CVAR_SEC(hook, edgedamage);
+       self.dmg_radius = WEP_CVAR_SEC(hook, radius);
+       self.dmg_force = WEP_CVAR_SEC(hook, force);
+       self.dmg_power = WEP_CVAR_SEC(hook, power);
+       self.dmg_duration = WEP_CVAR_SEC(hook, duration);
+       self.teleport_time = time;
+       self.dmg_last = 1;
+       self.movetype = MOVETYPE_NONE;
+}
+
+void W_Hook_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+       if(self.health <= 0)
+               return;
+
+       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+               return; // g_projectiles_damage says to halt
+
+       self.health = self.health - damage;
+
+       if(self.health <= 0)
+               W_PrepareExplosionByDamage(self.realowner, W_Hook_Explode2);
+}
+
+void W_Hook_Touch2(void)
+{
+       PROJECTILE_TOUCH;
+       self.use();
+}
+
+void W_Hook_Attack2(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, W_Sound("hookbomb_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hook, damage));
+
+       gren = spawn();
+       gren.owner = gren.realowner = self;
+       gren.classname = "hookbomb";
+       gren.bot_dodge = true;
+       gren.bot_dodgerating = WEP_CVAR_SEC(hook, damage);
+       gren.movetype = MOVETYPE_TOSS;
+       PROJECTILE_MAKETRIGGER(gren);
+       gren.projectiledeathtype = WEP_HOOK.m_id | HITTYPE_SECONDARY;
+       setorigin(gren, w_shotorg);
+       setsize(gren, '0 0 0', '0 0 0');
+
+       gren.nextthink = time + WEP_CVAR_SEC(hook, lifetime);
+       gren.think = adaptor_think2use_hittype_splash;
+       gren.use = W_Hook_Explode2;
+       gren.touch = W_Hook_Touch2;
+
+       gren.takedamage = DAMAGE_YES;
+       gren.health = WEP_CVAR_SEC(hook, health);
+       gren.damageforcescale = WEP_CVAR_SEC(hook, damageforcescale);
+       gren.event_damage = W_Hook_Damage;
+       gren.damagedbycontents = true;
+       gren.missile_flags = MIF_SPLASH | MIF_ARC;
+
+       gren.velocity = '0 0 1' * WEP_CVAR_SEC(hook, speed);
+       if(autocvar_g_projectiles_newton_style)
+               gren.velocity = gren.velocity + self.velocity;
+
+       gren.gravity = WEP_CVAR_SEC(hook, gravity);
+       //W_SetupProjVelocity_Basic(gren); // just falling down!
+
+       gren.angles = '0 0 0';
+       gren.flags = FL_PROJECTILE;
+
+       CSQCProjectile(gren, true, PROJECTILE_HOOKBOMB, true);
+
+       MUTATOR_CALLHOOK(EditProjectile, self, gren);
+}
+
+bool W_Hook(int req)
+{
+       float hooked_time_max, hooked_fuel;
+
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       // no bot AI for hook (yet?)
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       if(self.BUTTON_ATCK || self.BUTTON_HOOK)
+                       {
+                               if(!self.hook)
+                               if(!(self.hook_state & HOOK_WAITING_FOR_RELEASE))
+                               if(!(self.hook_state & HOOK_FIRING))
+                               if(time > self.hook_refire)
+                               if(weapon_prepareattack(0, -1))
+                               {
+                                       W_DecreaseAmmo(WEP_CVAR_PRI(hook, ammo));
+                                       self.hook_state |= HOOK_FIRING;
+                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hook, animtime), w_ready);
+                               }
+                       }
+
+                       if(self.BUTTON_ATCK2)
+                       {
+                               if(weapon_prepareattack(1, WEP_CVAR_SEC(hook, refire)))
+                               {
+                                       W_Hook_Attack2();
+                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hook, animtime), w_ready);
+                               }
+                       }
+
+                       if(self.hook)
+                       {
+                               // if hooked, no bombs, and increase the timer
+                               self.hook_refire = max(self.hook_refire, time + WEP_CVAR_PRI(hook, refire) * W_WeaponRateFactor());
+
+                               // hook also inhibits health regeneration, but only for 1 second
+                               if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+                                       self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
+                       }
+
+                       if(self.hook && self.hook.state == 1)
+                       {
+                               hooked_time_max = WEP_CVAR_PRI(hook, hooked_time_max);
+                               if(hooked_time_max > 0)
+                               {
+                                       if( time > self.hook_time_hooked + hooked_time_max )
+                                               self.hook_state |= HOOK_REMOVING;
+                               }
+
+                               hooked_fuel = WEP_CVAR_PRI(hook, hooked_ammo);
+                               if(hooked_fuel > 0)
+                               {
+                                       if( time > self.hook_time_fueldecrease )
+                                       {
+                                               if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+                                               {
+                                                       if( self.ammo_fuel >= (time - self.hook_time_fueldecrease) * hooked_fuel )
+                                                       {
+                                                               W_DecreaseAmmo((time - self.hook_time_fueldecrease) * hooked_fuel);
+                                                               self.hook_time_fueldecrease = time;
+                                                               // decrease next frame again
+                                                       }
+                                                       else
+                                                       {
+                                                               self.ammo_fuel = 0;
+                                                               self.hook_state |= HOOK_REMOVING;
+                                                               W_SwitchWeapon_Force(self, w_getbestweapon(self));
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+                       else
+                       {
+                               self.hook_time_hooked = time;
+                               self.hook_time_fueldecrease = time + WEP_CVAR_PRI(hook, hooked_time_free);
+                       }
+
+                       if(self.BUTTON_CROUCH)
+                       {
+                               self.hook_state &= ~HOOK_PULLING;
+                               if(self.BUTTON_ATCK || self.BUTTON_HOOK)
+                                       self.hook_state &= ~HOOK_RELEASING;
+                               else
+                                       self.hook_state |= HOOK_RELEASING;
+                       }
+                       else
+                       {
+                               self.hook_state |= HOOK_PULLING;
+                               self.hook_state &= ~HOOK_RELEASING;
+
+                               if(self.BUTTON_ATCK || self.BUTTON_HOOK)
+                               {
+                                       // already fired
+                                       if(self.hook)
+                                               self.hook_state |= HOOK_WAITING_FOR_RELEASE;
+                               }
+                               else
+                               {
+                                       self.hook_state |= HOOK_REMOVING;
+                                       self.hook_state &= ~HOOK_WAITING_FOR_RELEASE;
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       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;
+               }
+               case WR_SETUP:
+               {
+                       self.hook_state &= ~HOOK_WAITING_FOR_RELEASE;
+                       return true;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       if(self.hook)
+                               return self.ammo_fuel > 0;
+                       else
+                               return self.ammo_fuel >= WEP_CVAR_PRI(hook, ammo);
+               }
+               case WR_CHECKAMMO2:
+               {
+                       // infinite ammo for now
+                       return true; // self.ammo_cells >= WEP_CVAR_SEC(hook, ammo); // WEAPONTODO: see above
+               }
+               case WR_CONFIG:
+               {
+                       HOOK_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RESETPLAYER:
+               {
+                       self.hook_refire = time;
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       return false;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       return WEAPON_HOOK_MURDER;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_Hook(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 2;
+                       pointparticles(particleeffectnum(EFFECT_HOOK_EXPLODE), org2, '0 0 0', 1);
+                       if(!w_issilent)
+                               sound(self, CH_SHOTS, W_Sound("hookbomb_impact"), VOL_BASE, ATTN_NORM);
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("hookbomb_impact"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/machinegun.qc b/qcsrc/common/weapons/weapon/machinegun.qc
new file mode 100644 (file)
index 0000000..da1eb33
--- /dev/null
@@ -0,0 +1,408 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ MACHINEGUN,
+/* function  */ W_MachineGun,
+/* ammotype  */ ammo_nails,
+/* impulse   */ 3,
+/* flags     */ WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* color     */ '1 1 0',
+/* modelname */ "uzi",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairuzi 0.6",
+/* wepimg    */ "weaponuzi",
+/* refname   */ "machinegun",
+/* wepname   */ _("Machine Gun")
+);
+
+#define MACHINEGUN_SETTINGS(w_cvar,w_prop) MACHINEGUN_SETTINGS_LIST(w_cvar, w_prop, MACHINEGUN, machinegun)
+#define MACHINEGUN_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, NONE, spread_min) \
+       w_cvar(id, sn, NONE, spread_max) \
+       w_cvar(id, sn, NONE, spread_add) \
+       w_cvar(id, sn, NONE, mode) \
+       w_cvar(id, sn, NONE, first) \
+       w_cvar(id, sn, NONE, first_damage) \
+       w_cvar(id, sn, NONE, first_force) \
+       w_cvar(id, sn, NONE, first_refire) \
+       w_cvar(id, sn, NONE, first_spread) \
+       w_cvar(id, sn, NONE, first_ammo) \
+       w_cvar(id, sn, NONE, solidpenetration) \
+       w_cvar(id, sn, NONE, sustained_damage) \
+       w_cvar(id, sn, NONE, sustained_force) \
+       w_cvar(id, sn, NONE, sustained_refire) \
+       w_cvar(id, sn, NONE, sustained_spread) \
+       w_cvar(id, sn, NONE, sustained_ammo) \
+       w_cvar(id, sn, NONE, burst) \
+       w_cvar(id, sn, NONE, burst_refire) \
+       w_cvar(id, sn, NONE, burst_refire2) \
+       w_cvar(id, sn, NONE, burst_animtime) \
+       w_cvar(id, sn, NONE, burst_speed) \
+       w_cvar(id, sn, NONE, burst_ammo) \
+       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
+MACHINEGUN_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+
+void spawnfunc_weapon_machinegun(void)
+{
+       if(autocvar_sv_q3acompat_machineshotgunswap)
+       if(self.classname != "droppedweapon")
+       {
+               weapon_defaultspawnfunc(WEP_SHOCKWAVE.m_id);
+               return;
+       }
+       weapon_defaultspawnfunc(WEP_MACHINEGUN.m_id);
+}
+void spawnfunc_weapon_uzi(void) { spawnfunc_weapon_machinegun(); }
+
+void W_MachineGun_MuzzleFlash_Think(void)
+{
+       self.frame = self.frame + 2;
+       self.scale = self.scale * 0.5;
+       self.alpha = self.alpha - 0.25;
+       self.nextthink = time + 0.05;
+
+       if(self.alpha <= 0)
+       {
+               self.think = SUB_Remove;
+               self.nextthink = time;
+               self.realowner.muzzle_flash = world;
+               return;
+       }
+
+}
+
+void W_MachineGun_MuzzleFlash(void)
+{
+       if(self.muzzle_flash == world)
+               self.muzzle_flash = spawn();
+
+       // muzzle flash for 1st person view
+       setmodel(self.muzzle_flash, "models/uziflash.md3"); // precision set below
+
+       self.muzzle_flash.scale = 0.75;
+       self.muzzle_flash.think = W_MachineGun_MuzzleFlash_Think;
+       self.muzzle_flash.nextthink = time + 0.02;
+       self.muzzle_flash.frame = 2;
+       self.muzzle_flash.alpha = 0.75;
+       self.muzzle_flash.angles_z = random() * 180;
+       self.muzzle_flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
+       self.muzzle_flash.owner = self.muzzle_flash.realowner = self;
+}
+
+void W_MachineGun_Attack(int deathtype)
+{
+       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;
+               self.punchangle_y = random() - 0.5;
+       }
+
+       // this attack_finished just enforces a cooldown at the end of a burst
+       ATTACK_FINISHED(self) = time + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor();
+
+       if(self.misc_bulletcounter == 1)
+               fireBullet(w_shotorg, w_shotdir, WEP_CVAR(machinegun, first_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, first_damage), WEP_CVAR(machinegun, first_force), deathtype, 0);
+       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);
+
+       Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       W_MachineGun_MuzzleFlash();
+       W_AttachToShotorg(self.muzzle_flash, '5 0 0');
+
+       // 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);
+
+       if(self.misc_bulletcounter == 1)
+               W_DecreaseAmmo(WEP_CVAR(machinegun, first_ammo));
+       else
+               W_DecreaseAmmo(WEP_CVAR(machinegun, sustained_ammo));
+}
+
+// weapon frames
+void W_MachineGun_Attack_Frame(void)
+{
+       if(self.weapon != self.switchweapon) // abort immediately if switching
+       {
+               w_ready();
+               return;
+       }
+       if(self.BUTTON_ATCK)
+       {
+               if(!WEP_ACTION(self.weapon, WR_CHECKAMMO2))
+               if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+               {
+                       W_SwitchWeapon_Force(self, w_getbestweapon(self));
+                       w_ready();
+                       return;
+               }
+               self.misc_bulletcounter = self.misc_bulletcounter + 1;
+               W_MachineGun_Attack(WEP_MACHINEGUN.m_id);
+               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Frame);
+       }
+       else
+               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), w_ready);
+}
+
+
+void W_MachineGun_Attack_Auto(void)
+{
+       float machinegun_spread;
+
+       if(!self.BUTTON_ATCK)
+       {
+               w_ready();
+               return;
+       }
+
+       if(!WEP_ACTION(self.weapon, WR_CHECKAMMO1))
+       if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+       {
+               W_SwitchWeapon_Force(self, w_getbestweapon(self));
+               w_ready();
+               return;
+       }
+
+       W_DecreaseAmmo(WEP_CVAR(machinegun, sustained_ammo));
+
+       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;
+               self.punchangle_y = random() - 0.5;
+       }
+
+       machinegun_spread = bound(WEP_CVAR(machinegun, spread_min), WEP_CVAR(machinegun, spread_min) + (WEP_CVAR(machinegun, spread_add) * self.misc_bulletcounter), WEP_CVAR(machinegun, spread_max));
+       fireBullet(w_shotorg, w_shotdir, machinegun_spread, WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), WEP_MACHINEGUN.m_id, 0);
+
+       self.misc_bulletcounter = self.misc_bulletcounter + 1;
+
+       Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       W_MachineGun_MuzzleFlash();
+       W_AttachToShotorg(self.muzzle_flash, '5 0 0');
+
+       if(autocvar_g_casings >= 2) // casing code
+               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);
+
+       ATTACK_FINISHED(self) = time + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor();
+       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Auto);
+}
+
+void W_MachineGun_Attack_Burst(void)
+{
+       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;
+               self.punchangle_y = random() - 0.5;
+       }
+
+       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.m_id, 0);
+
+       Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       W_MachineGun_MuzzleFlash();
+       W_AttachToShotorg(self.muzzle_flash, '5 0 0');
+
+       if(autocvar_g_casings >= 2) // casing code
+               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);
+
+       self.misc_bulletcounter = self.misc_bulletcounter + 1;
+       if(self.misc_bulletcounter == 0)
+       {
+               ATTACK_FINISHED(self) = time + WEP_CVAR(machinegun, burst_refire2) * W_WeaponRateFactor();
+               weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(machinegun, burst_animtime), w_ready);
+       }
+       else
+       {
+               weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(machinegun, burst_refire), W_MachineGun_Attack_Burst);
+       }
+
+}
+
+bool W_MachineGun(int req)
+{
+       float ammo_amount;
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       if(vlen(self.origin-self.enemy.origin) < 3000 - bound(0, skill, 10) * 200)
+                               self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, false);
+                       else
+                               self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, false);
+
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       if(WEP_CVAR(machinegun, reload_ammo) && self.clip_load < min(max(WEP_CVAR(machinegun, sustained_ammo), WEP_CVAR(machinegun, first_ammo)), WEP_CVAR(machinegun, burst_ammo))) // forced reload
+                               WEP_ACTION(self.weapon, WR_RELOAD);
+                       else if(WEP_CVAR(machinegun, mode) == 1)
+                       {
+                               if(self.BUTTON_ATCK)
+                               if(weapon_prepareattack(0, 0))
+                               {
+                                       self.misc_bulletcounter = 0;
+                                       W_MachineGun_Attack_Auto();
+                               }
+
+                               if(self.BUTTON_ATCK2)
+                               if(weapon_prepareattack(1, 0))
+                               {
+                                       if(!WEP_ACTION(self.weapon, WR_CHECKAMMO2))
+                                       if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+                                       {
+                                               W_SwitchWeapon_Force(self, w_getbestweapon(self));
+                                               w_ready();
+                                               return false;
+                                       }
+
+                                       W_DecreaseAmmo(WEP_CVAR(machinegun, burst_ammo));
+
+                                       self.misc_bulletcounter = WEP_CVAR(machinegun, burst) * -1;
+                                       W_MachineGun_Attack_Burst();
+                               }
+                       }
+                       else
+                       {
+
+                               if(self.BUTTON_ATCK)
+                               if(weapon_prepareattack(0, 0))
+                               {
+                                       self.misc_bulletcounter = 1;
+                                       W_MachineGun_Attack(WEP_MACHINEGUN.m_id); // sets attack_finished
+                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Frame);
+                               }
+
+                               if(self.BUTTON_ATCK2 && WEP_CVAR(machinegun, first))
+                               if(weapon_prepareattack(1, 0))
+                               {
+                                       self.misc_bulletcounter = 1;
+                                       W_MachineGun_Attack(WEP_MACHINEGUN.m_id | HITTYPE_SECONDARY); // sets attack_finished
+                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(machinegun, first_refire), w_ready);
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_model("models/uziflash.md3");
+                       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;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       if(WEP_CVAR(machinegun, mode) == 1)
+                               ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, sustained_ammo);
+                       else
+                               ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, first_ammo);
+
+                       if(WEP_CVAR(machinegun, reload_ammo))
+                       {
+                               if(WEP_CVAR(machinegun, mode) == 1)
+                                       ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, sustained_ammo);
+                               else
+                                       ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, first_ammo);
+                       }
+                       return ammo_amount;
+               }
+               case WR_CHECKAMMO2:
+               {
+                       if(WEP_CVAR(machinegun, mode) == 1)
+                               ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, burst_ammo);
+                       else
+                               ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, first_ammo);
+
+                       if(WEP_CVAR(machinegun, reload_ammo))
+                       {
+                               if(WEP_CVAR(machinegun, mode) == 1)
+                                       ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, burst_ammo);
+                               else
+                                       ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, first_ammo);
+                       }
+                       return ammo_amount;
+               }
+               case WR_CONFIG:
+               {
+                       MACHINEGUN_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       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:
+               {
+                       return WEAPON_THINKING_WITH_PORTALS;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                               return WEAPON_MACHINEGUN_MURDER_SNIPE;
+                       else
+                               return WEAPON_MACHINEGUN_MURDER_SPRAY;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_MachineGun(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 2;
+                       pointparticles(particleeffectnum(EFFECT_MACHINEGUN_IMPACT), org2, w_backoff * 1000, 1);
+                       if(!w_issilent)
+                               if(w_random < 0.05)
+                                       sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTN_NORM);
+                               else if(w_random < 0.1)
+                                       sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTN_NORM);
+                               else if(w_random < 0.2)
+                                       sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTN_NORM);
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("ric1"));
+                       precache_sound(W_Sound("ric2"));
+                       precache_sound(W_Sound("ric3"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/minelayer.qc b/qcsrc/common/weapons/weapon/minelayer.qc
new file mode 100644 (file)
index 0000000..8048957
--- /dev/null
@@ -0,0 +1,623 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ MINE_LAYER,
+/* function  */ W_MineLayer,
+/* ammotype  */ ammo_rockets,
+/* impulse   */ 4,
+/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_HIGH,
+/* color     */ '0.75 1 0',
+/* modelname */ "minelayer",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairminelayer 0.9",
+/* wepimg    */ "weaponminelayer",
+/* refname   */ "minelayer",
+/* wepname   */ _("Mine Layer")
+);
+
+#define MINELAYER_SETTINGS(w_cvar,w_prop) MINELAYER_SETTINGS_LIST(w_cvar, w_prop, MINE_LAYER, minelayer)
+#define MINELAYER_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, damage) \
+       w_cvar(id, sn, NONE, damageforcescale) \
+       w_cvar(id, sn, NONE, detonatedelay) \
+       w_cvar(id, sn, NONE, edgedamage) \
+       w_cvar(id, sn, NONE, force) \
+       w_cvar(id, sn, NONE, health) \
+       w_cvar(id, sn, NONE, lifetime) \
+       w_cvar(id, sn, NONE, lifetime_countdown) \
+       w_cvar(id, sn, NONE, limit) \
+       w_cvar(id, sn, NONE, protection) \
+       w_cvar(id, sn, NONE, proximityradius) \
+       w_cvar(id, sn, NONE, radius) \
+       w_cvar(id, sn, NONE, refire) \
+       w_cvar(id, sn, NONE, remote_damage) \
+       w_cvar(id, sn, NONE, remote_edgedamage) \
+       w_cvar(id, sn, NONE, remote_force) \
+       w_cvar(id, sn, NONE, remote_radius) \
+       w_cvar(id, sn, NONE, speed) \
+       w_cvar(id, sn, NONE, time) \
+       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
+MINELAYER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+void W_MineLayer_Think(void);
+.float minelayer_detonate, mine_explodeanyway;
+.float mine_time;
+.vector mine_orientation;
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_minelayer(void) { weapon_defaultspawnfunc(WEP_MINE_LAYER.m_id); }
+
+void W_MineLayer_Stick(entity to)
+{
+       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
+
+       entity newmine;
+       newmine = spawn();
+       newmine.classname = self.classname;
+
+       newmine.bot_dodge = self.bot_dodge;
+       newmine.bot_dodgerating = self.bot_dodgerating;
+
+       newmine.owner = self.owner;
+       newmine.realowner = self.realowner;
+       setsize(newmine, '-4 -4 -4', '4 4 4');
+       setorigin(newmine, self.origin);
+       setmodel(newmine, "models/mine.md3");
+       newmine.angles = vectoangles(-trace_plane_normal); // face against the surface
+
+       newmine.mine_orientation = -trace_plane_normal;
+
+       newmine.takedamage = self.takedamage;
+       newmine.damageforcescale = self.damageforcescale;
+       newmine.health = self.health;
+       newmine.event_damage = self.event_damage;
+       newmine.spawnshieldtime = self.spawnshieldtime;
+       newmine.damagedbycontents = true;
+
+       newmine.movetype = MOVETYPE_NONE; // lock the mine in place
+       newmine.projectiledeathtype = self.projectiledeathtype;
+
+       newmine.mine_time = self.mine_time;
+
+       newmine.touch = func_null;
+       newmine.think = W_MineLayer_Think;
+       newmine.nextthink = time;
+       newmine.cnt = self.cnt;
+       newmine.flags = self.flags;
+
+       remove(self);
+       self = newmine;
+
+       if(to)
+               SetMovetypeFollow(self, to);
+}
+
+void W_MineLayer_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_AIRSHOT);
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       RadiusDamage(self, self.realowner, WEP_CVAR(minelayer, damage), WEP_CVAR(minelayer, edgedamage), WEP_CVAR(minelayer, radius), world, world, WEP_CVAR(minelayer, force), self.projectiledeathtype, other);
+
+       if(self.realowner.weapon == WEP_MINE_LAYER.m_id)
+       {
+               entity oldself;
+               oldself = self;
+               self = self.realowner;
+               if(!WEP_ACTION(WEP_MINE_LAYER.m_id, WR_CHECKAMMO1))
+               {
+                       self.cnt = WEP_MINE_LAYER.m_id;
+                       ATTACK_FINISHED(self) = time;
+                       self.switchweapon = w_getbestweapon(self);
+               }
+               self = oldself;
+       }
+       self.realowner.minelayer_mines -= 1;
+       remove(self);
+}
+
+void W_MineLayer_DoRemoteExplode(void)
+{
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW)
+               self.velocity = self.mine_orientation; // particle fx and decals need .velocity
+
+       RadiusDamage(self, self.realowner, WEP_CVAR(minelayer, remote_damage), WEP_CVAR(minelayer, remote_edgedamage), WEP_CVAR(minelayer, remote_radius), world, world, WEP_CVAR(minelayer, remote_force), self.projectiledeathtype | HITTYPE_BOUNCE, world);
+
+       if(self.realowner.weapon == WEP_MINE_LAYER.m_id)
+       {
+               entity oldself;
+               oldself = self;
+               self = self.realowner;
+               if(!WEP_ACTION(WEP_MINE_LAYER.m_id, WR_CHECKAMMO1))
+               {
+                       self.cnt = WEP_MINE_LAYER.m_id;
+                       ATTACK_FINISHED(self) = time;
+                       self.switchweapon = w_getbestweapon(self);
+               }
+               self = oldself;
+       }
+       self.realowner.minelayer_mines -= 1;
+       remove(self);
+}
+
+void W_MineLayer_RemoteExplode(void)
+{
+       if(self.realowner.deadflag == DEAD_NO)
+               if((self.spawnshieldtime >= 0)
+                       ? (time >= self.spawnshieldtime) // timer
+                       : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > WEP_CVAR(minelayer, remote_radius)) // safety device
+               )
+               {
+                       W_MineLayer_DoRemoteExplode();
+               }
+}
+
+void W_MineLayer_ProximityExplode(void)
+{
+       // make sure no friend is in the mine's radius. If there is any, explosion is delayed until he's at a safe distance
+       if(WEP_CVAR(minelayer, protection) && self.mine_explodeanyway == 0)
+       {
+               entity head;
+               head = findradius(self.origin, WEP_CVAR(minelayer, radius));
+               while(head)
+               {
+                       if(head == self.realowner || SAME_TEAM(head, self.realowner))
+                               return;
+                       head = head.chain;
+               }
+       }
+
+       self.mine_time = 0;
+       W_MineLayer_Explode();
+}
+
+int W_MineLayer_Count(entity e)
+{
+       int minecount = 0;
+       entity mine;
+       for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == e)
+               minecount += 1;
+
+       return minecount;
+}
+
+void W_MineLayer_Think(void)
+{
+       entity head;
+
+       self.nextthink = time;
+
+       if(self.movetype == MOVETYPE_FOLLOW)
+       {
+               if(LostMovetypeFollow(self))
+               {
+                       UnsetMovetypeFollow(self);
+                       self.movetype = MOVETYPE_NONE;
+               }
+       }
+
+       // our lifetime has expired, it's time to die - mine_time just allows us to play a sound for this
+       // TODO: replace this mine_trigger.wav sound with a real countdown
+       if((time > self.cnt) && (!self.mine_time) && (self.cnt > 0))
+       {
+               if(WEP_CVAR(minelayer, lifetime_countdown) > 0)
+                       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)
+       {
+               other = world;
+               self.projectiledeathtype |= HITTYPE_BOUNCE;
+               W_MineLayer_Explode();
+               return;
+       }
+
+       // set the mine for detonation when a foe gets close enough
+       head = findradius(self.origin, WEP_CVAR(minelayer, proximityradius));
+       while(head)
+       {
+               if(IS_PLAYER(head) && head.deadflag == DEAD_NO && !head.frozen)
+               if(head != self.realowner && DIFF_TEAM(head, self.realowner)) // don't trigger for team mates
+               if(!self.mine_time)
+               {
+                       spamsound(self, CH_SHOTS, W_Sound("mine_trigger"), VOL_BASE, ATTN_NORM);
+                       self.mine_time = time + WEP_CVAR(minelayer, time);
+               }
+               head = head.chain;
+       }
+
+       // explode if it's time to
+       if(self.mine_time && time >= self.mine_time)
+       {
+               W_MineLayer_ProximityExplode();
+               return;
+       }
+
+       // remote detonation
+       if(self.realowner.weapon == WEP_MINE_LAYER.m_id)
+       if(self.realowner.deadflag == DEAD_NO)
+       if(self.minelayer_detonate)
+               W_MineLayer_RemoteExplode();
+}
+
+void W_MineLayer_Touch(void)
+{
+       if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW)
+               return; // we're already a stuck mine, why do we get called? TODO does this even happen?
+
+       if(WarpZone_Projectile_Touch())
+       {
+               if(wasfreed(self))
+                       self.realowner.minelayer_mines -= 1;
+               return;
+       }
+
+       if(other && IS_PLAYER(other) && other.deadflag == DEAD_NO)
+       {
+               // hit a player
+               // don't stick
+       }
+       else
+       {
+               W_MineLayer_Stick(other);
+       }
+}
+
+void W_MineLayer_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+       if(self.health <= 0)
+               return;
+
+       float is_from_enemy = (inflictor.realowner != self.realowner);
+
+       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_from_enemy ? 1 : -1)))
+               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_MineLayer_Explode);
+}
+
+void W_MineLayer_Attack(void)
+{
+       entity mine;
+       entity flash;
+
+       // scan how many mines we placed, and return if we reached our limit
+       if(WEP_CVAR(minelayer, limit))
+       {
+               if(self.minelayer_mines >= WEP_CVAR(minelayer, limit))
+               {
+                       // 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, W_Sound("unavailable"));
+                       return;
+               }
+       }
+
+       W_DecreaseAmmo(WEP_CVAR(minelayer, ammo));
+
+       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;
+       if(WEP_CVAR(minelayer, detonatedelay) >= 0)
+               mine.spawnshieldtime = time + WEP_CVAR(minelayer, detonatedelay);
+       else
+               mine.spawnshieldtime = -1;
+       mine.classname = "mine";
+       mine.bot_dodge = true;
+       mine.bot_dodgerating = WEP_CVAR(minelayer, damage) * 2; // * 2 because it can detonate inflight which makes it even more dangerous
+
+       mine.takedamage = DAMAGE_YES;
+       mine.damageforcescale = WEP_CVAR(minelayer, damageforcescale);
+       mine.health = WEP_CVAR(minelayer, health);
+       mine.event_damage = W_MineLayer_Damage;
+       mine.damagedbycontents = true;
+
+       mine.movetype = MOVETYPE_TOSS;
+       PROJECTILE_MAKETRIGGER(mine);
+       mine.projectiledeathtype = WEP_MINE_LAYER.m_id;
+       setsize(mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot
+
+       setorigin(mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point
+       W_SetupProjVelocity_Basic(mine, WEP_CVAR(minelayer, speed), 0);
+       mine.angles = vectoangles(mine.velocity);
+
+       mine.touch = W_MineLayer_Touch;
+       mine.think = W_MineLayer_Think;
+       mine.nextthink = time;
+       mine.cnt = (WEP_CVAR(minelayer, lifetime) - WEP_CVAR(minelayer, lifetime_countdown));
+       mine.flags = FL_PROJECTILE;
+       mine.missile_flags = MIF_SPLASH | MIF_ARC | MIF_PROXY;
+
+       if(mine.cnt > 0) { mine.cnt += time; }
+
+       CSQCProjectile(mine, true, PROJECTILE_MINE, true);
+
+       // muzzle flash for 1st person view
+       flash = spawn();
+       setmodel(flash, "models/flash.md3"); // precision set below
+       SUB_SetFade(flash, time, 0.1);
+       flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
+       W_AttachToShotorg(flash, '5 0 0');
+
+       // common properties
+
+       MUTATOR_CALLHOOK(EditProjectile, self, mine);
+
+       self.minelayer_mines = W_MineLayer_Count(self);
+}
+
+float W_MineLayer_PlacedMines(float detonate)
+{
+       entity mine;
+       float minfound = 0;
+
+       for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == self)
+       {
+               if(detonate)
+               {
+                       if(!mine.minelayer_detonate)
+                       {
+                               mine.minelayer_detonate = true;
+                               minfound = 1;
+                       }
+               }
+               else
+                       minfound = 1;
+       }
+       return minfound;
+}
+
+bool W_MineLayer(int req)
+{
+       entity mine;
+       float ammo_amount;
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       // aim and decide to fire if appropriate
+                       if(self.minelayer_mines >= WEP_CVAR(minelayer, limit))
+                               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!
+                       {
+                               // decide whether to detonate mines
+                               entity targetlist, targ;
+                               float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
+                               float selfdamage, teamdamage, enemydamage;
+                               edgedamage = WEP_CVAR(minelayer, edgedamage);
+                               coredamage = WEP_CVAR(minelayer, damage);
+                               edgeradius = WEP_CVAR(minelayer, radius);
+                               recipricoledgeradius = 1 / edgeradius;
+                               selfdamage = 0;
+                               teamdamage = 0;
+                               enemydamage = 0;
+                               targetlist = findchainfloat(bot_attack, true);
+                               mine = find(world, classname, "mine");
+                               while(mine)
+                               {
+                                       if(mine.realowner != self)
+                                       {
+                                               mine = find(mine, classname, "mine");
+                                               continue;
+                                       }
+                                       targ = targetlist;
+                                       while(targ)
+                                       {
+                                               d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin);
+                                               d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
+                                               // count potential damage according to type of target
+                                               if(targ == self)
+                                                       selfdamage = selfdamage + d;
+                                               else if(targ.team == self.team && teamplay)
+                                                       teamdamage = teamdamage + d;
+                                               else if(bot_shouldattack(targ))
+                                                       enemydamage = enemydamage + d;
+                                               targ = targ.chain;
+                                       }
+                                       mine = find(mine, classname, "mine");
+                               }
+                               float desirabledamage;
+                               desirabledamage = enemydamage;
+                               if(time > self.invincible_finished && time > self.spawnshieldtime)
+                                       desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent;
+                               if(teamplay && self.team)
+                                       desirabledamage = desirabledamage - teamdamage;
+
+                               mine = find(world, classname, "mine");
+                               while(mine)
+                               {
+                                       if(mine.realowner != self)
+                                       {
+                                               mine = find(mine, classname, "mine");
+                                               continue;
+                                       }
+                                       makevectors(mine.v_angle);
+                                       targ = targetlist;
+                                       if(skill > 9) // normal players only do this for the target they are tracking
+                                       {
+                                               targ = targetlist;
+                                               while(targ)
+                                               {
+                                                       if(
+                                                               (v_forward * normalize(mine.origin - targ.origin)< 0.1)
+                                                               && desirabledamage > 0.1*coredamage
+                                                       )self.BUTTON_ATCK2 = true;
+                                                       targ = targ.chain;
+                                               }
+                                       }else{
+                                               float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
+                                               //As the distance gets larger, a correct detonation gets near imposible
+                                               //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player
+                                               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))
+                                                                               self.BUTTON_ATCK2 = true;
+                                       //      dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
+                                       }
+
+                                       mine = find(mine, classname, "mine");
+                               }
+                               // if we would be doing at X percent of the core damage, detonate it
+                               // 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))
+                                       self.BUTTON_ATCK2 = false;
+                               //if(self.BUTTON_ATCK2 == true)
+                               //      dprint(ftos(desirabledamage),"\n");
+                               if(self.BUTTON_ATCK2 == true) self.BUTTON_ATCK = false;
+                       }
+
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       if(autocvar_g_balance_minelayer_reload_ammo && self.clip_load < WEP_CVAR(minelayer, ammo)) // forced reload
+                       {
+                               // not if we're holding the minelayer without enough ammo, but can detonate existing mines
+                               if(!(W_MineLayer_PlacedMines(false) && self.WEP_AMMO(MINE_LAYER) < WEP_CVAR(minelayer, ammo)))
+                                       WEP_ACTION(self.weapon, WR_RELOAD);
+                       }
+                       else if(self.BUTTON_ATCK)
+                       {
+                               if(weapon_prepareattack(0, WEP_CVAR(minelayer, refire)))
+                               {
+                                       W_MineLayer_Attack();
+                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(minelayer, animtime), w_ready);
+                               }
+                       }
+
+                       if(self.BUTTON_ATCK2)
+                       {
+                               if(W_MineLayer_PlacedMines(true))
+                                       sound(self, CH_WEAPON_B, W_Sound("mine_det"), VOL_BASE, ATTN_NORM);
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_model("models/flash.md3");
+                       precache_model("models/mine.md3");
+                       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;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       // don't switch while placing a mine
+                       if(ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER.m_id)
+                       {
+                               ammo_amount = self.WEP_AMMO(MINE_LAYER) >= WEP_CVAR(minelayer, ammo);
+                               ammo_amount += self.(weapon_load[WEP_MINE_LAYER.m_id]) >= WEP_CVAR(minelayer, ammo);
+                               return ammo_amount;
+                       }
+                       return true;
+               }
+               case WR_CHECKAMMO2:
+               {
+                       if(W_MineLayer_PlacedMines(false))
+                               return true;
+                       else
+                               return false;
+               }
+               case WR_CONFIG:
+               {
+                       MINELAYER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RESETPLAYER:
+               {
+                       self.minelayer_mines = 0;
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       W_Reload(WEP_CVAR(minelayer, ammo), W_Sound("reload"));
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       return WEAPON_MINELAYER_SUICIDE;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       return WEAPON_MINELAYER_MURDER;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_MineLayer(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 12;
+                       pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), org2, '0 0 0', 1);
+                       if(!w_issilent)
+                               sound(self, CH_SHOTS, W_Sound("mine_exp"), VOL_BASE, ATTN_NORM);
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("mine_exp"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/mortar.qc b/qcsrc/common/weapons/weapon/mortar.qc
new file mode 100644 (file)
index 0000000..850a6c3
--- /dev/null
@@ -0,0 +1,491 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ MORTAR,
+/* function  */ W_Mortar,
+/* ammotype  */ ammo_rockets,
+/* impulse   */ 4,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* color     */ '1 0 0',
+/* modelname */ "gl",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairgrenadelauncher 0.7",
+/* wepimg    */ "weapongrenadelauncher",
+/* refname   */ "mortar",
+/* wepname   */ _("Mortar")
+);
+
+#define MORTAR_SETTINGS(w_cvar,w_prop) MORTAR_SETTINGS_LIST(w_cvar, w_prop, MORTAR, mortar)
+#define MORTAR_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, BOTH, ammo) \
+       w_cvar(id, sn, BOTH, animtime) \
+       w_cvar(id, sn, NONE, bouncefactor) \
+       w_cvar(id, sn, NONE, bouncestop) \
+       w_cvar(id, sn, BOTH, damage) \
+       w_cvar(id, sn, BOTH, damageforcescale) \
+       w_cvar(id, sn, BOTH, edgedamage) \
+       w_cvar(id, sn, BOTH, force) \
+       w_cvar(id, sn, BOTH, health) \
+       w_cvar(id, sn, BOTH, lifetime) \
+       w_cvar(id, sn, SEC,  lifetime_bounce) \
+       w_cvar(id, sn, BOTH, lifetime_stick) \
+       w_cvar(id, sn, BOTH, radius) \
+       w_cvar(id, sn, BOTH, refire) \
+       w_cvar(id, sn, SEC,  remote_detonateprimary) \
+       w_cvar(id, sn, PRI,  remote_minbouncecnt) \
+       w_cvar(id, sn, BOTH, speed) \
+       w_cvar(id, sn, BOTH, speed_up) \
+       w_cvar(id, sn, BOTH, speed_z) \
+       w_cvar(id, sn, BOTH, spread) \
+       w_cvar(id, sn, BOTH, type) \
+       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
+MORTAR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+.float gl_detonate_later;
+.float gl_bouncecnt;
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+
+void spawnfunc_weapon_mortar(void) { weapon_defaultspawnfunc(WEP_MORTAR.m_id); }
+void spawnfunc_weapon_grenadelauncher(void) { spawnfunc_weapon_mortar(); }
+
+void W_Mortar_Grenade_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_AIRSHOT);
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       if(self.movetype == MOVETYPE_NONE)
+               self.velocity = self.oldvelocity;
+
+       RadiusDamage(self, self.realowner, WEP_CVAR_PRI(mortar, damage), WEP_CVAR_PRI(mortar, edgedamage), WEP_CVAR_PRI(mortar, radius), world, world, WEP_CVAR_PRI(mortar, force), self.projectiledeathtype, other);
+
+       remove(self);
+}
+
+void W_Mortar_Grenade_Explode2(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_AIRSHOT);
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       if(self.movetype == MOVETYPE_NONE)
+               self.velocity = self.oldvelocity;
+
+       RadiusDamage(self, self.realowner, WEP_CVAR_SEC(mortar, damage), WEP_CVAR_SEC(mortar, edgedamage), WEP_CVAR_SEC(mortar, radius), world, world, WEP_CVAR_SEC(mortar, force), self.projectiledeathtype, other);
+
+       remove(self);
+}
+
+
+void W_Mortar_Grenade_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+       if(self.health <= 0)
+               return;
+
+       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+               return; // g_projectiles_damage says to halt
+
+       self.health = self.health - damage;
+
+       if(self.health <= 0)
+               W_PrepareExplosionByDamage(attacker, self.use);
+}
+
+void W_Mortar_Grenade_Think1(void)
+{
+       self.nextthink = time;
+       if(time > self.cnt)
+       {
+               other = world;
+               self.projectiledeathtype |= HITTYPE_BOUNCE;
+               W_Mortar_Grenade_Explode();
+               return;
+       }
+       if(self.gl_detonate_later && self.gl_bouncecnt >= WEP_CVAR_PRI(mortar, remote_minbouncecnt))
+               W_Mortar_Grenade_Explode();
+}
+
+void W_Mortar_Grenade_Touch1(void)
+{
+       PROJECTILE_TOUCH;
+       if(other.takedamage == DAMAGE_AIM || WEP_CVAR_PRI(mortar, type) == 0) // always explode when hitting a player, or if normal mortar projectile
+       {
+               self.use();
+       }
+       else if(WEP_CVAR_PRI(mortar, type) == 1) // bounce
+       {
+               float r;
+               r = random() * 6;
+               if(r < 1)
+                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce1"), VOL_BASE, ATTN_NORM);
+               else if(r < 2)
+                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce2"), VOL_BASE, ATTN_NORM);
+               else if(r < 3)
+                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce3"), VOL_BASE, ATTN_NORM);
+               else if(r < 4)
+                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce4"), VOL_BASE, ATTN_NORM);
+               else if(r < 5)
+                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce5"), VOL_BASE, ATTN_NORM);
+               else
+                       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, W_Sound("grenade_stick"), VOL_BASE, ATTN_NORM);
+
+               // let it stick whereever it is
+               self.oldvelocity = self.velocity;
+               self.velocity = '0 0 0';
+               self.movetype = MOVETYPE_NONE; // also disables gravity
+               self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
+               UpdateCSQCProjectile(self);
+
+               // do not respond to any more touches
+               self.solid = SOLID_NOT;
+
+               self.nextthink = min(self.nextthink, time + WEP_CVAR_PRI(mortar, lifetime_stick));
+       }
+}
+
+void W_Mortar_Grenade_Touch2(void)
+{
+       PROJECTILE_TOUCH;
+       if(other.takedamage == DAMAGE_AIM || WEP_CVAR_SEC(mortar, type) == 0) // always explode when hitting a player, or if normal mortar projectile
+       {
+               self.use();
+       }
+       else if(WEP_CVAR_SEC(mortar, type) == 1) // bounce
+       {
+               float r;
+               r = random() * 6;
+               if(r < 1)
+                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce1"), VOL_BASE, ATTN_NORM);
+               else if(r < 2)
+                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce2"), VOL_BASE, ATTN_NORM);
+               else if(r < 3)
+                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce3"), VOL_BASE, ATTN_NORM);
+               else if(r < 4)
+                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce4"), VOL_BASE, ATTN_NORM);
+               else if(r < 5)
+                       spamsound(self, CH_SHOTS, W_Sound("grenade_bounce5"), VOL_BASE, ATTN_NORM);
+               else
+                       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;
+
+               if(WEP_CVAR_SEC(mortar, lifetime_bounce) && self.gl_bouncecnt == 1)
+                       self.nextthink = time + WEP_CVAR_SEC(mortar, lifetime_bounce);
+
+       }
+       else if(WEP_CVAR_SEC(mortar, type) == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick
+       {
+               spamsound(self, CH_SHOTS, W_Sound("grenade_stick"), VOL_BASE, ATTN_NORM);
+
+               // let it stick whereever it is
+               self.oldvelocity = self.velocity;
+               self.velocity = '0 0 0';
+               self.movetype = MOVETYPE_NONE; // also disables gravity
+               self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
+               UpdateCSQCProjectile(self);
+
+               // do not respond to any more touches
+               self.solid = SOLID_NOT;
+
+               self.nextthink = min(self.nextthink, time + WEP_CVAR_SEC(mortar, lifetime_stick));
+       }
+}
+
+void W_Mortar_Attack(void)
+{
+       entity gren;
+
+       W_DecreaseAmmo(WEP_CVAR_PRI(mortar, ammo));
+
+       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
+
+       Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       gren = spawn();
+       gren.owner = gren.realowner = self;
+       gren.classname = "grenade";
+       gren.bot_dodge = true;
+       gren.bot_dodgerating = WEP_CVAR_PRI(mortar, damage);
+       gren.movetype = MOVETYPE_BOUNCE;
+       gren.bouncefactor = WEP_CVAR(mortar, bouncefactor);
+       gren.bouncestop = WEP_CVAR(mortar, bouncestop);
+       PROJECTILE_MAKETRIGGER(gren);
+       gren.projectiledeathtype = WEP_MORTAR.m_id;
+       setorigin(gren, w_shotorg);
+       setsize(gren, '-3 -3 -3', '3 3 3');
+
+       gren.cnt = time + WEP_CVAR_PRI(mortar, lifetime);
+       gren.nextthink = time;
+       gren.think = W_Mortar_Grenade_Think1;
+       gren.use = W_Mortar_Grenade_Explode;
+       gren.touch = W_Mortar_Grenade_Touch1;
+
+       gren.takedamage = DAMAGE_YES;
+       gren.health = WEP_CVAR_PRI(mortar, health);
+       gren.damageforcescale = WEP_CVAR_PRI(mortar, damageforcescale);
+       gren.event_damage = W_Mortar_Grenade_Damage;
+       gren.damagedbycontents = true;
+       gren.missile_flags = MIF_SPLASH | MIF_ARC;
+       W_SetupProjVelocity_UP_PRI(gren, mortar);
+
+       gren.angles = vectoangles(gren.velocity);
+       gren.flags = FL_PROJECTILE;
+
+       if(WEP_CVAR_PRI(mortar, type) == 0 || WEP_CVAR_PRI(mortar, type) == 2)
+               CSQCProjectile(gren, true, PROJECTILE_GRENADE, true);
+       else
+               CSQCProjectile(gren, true, PROJECTILE_GRENADE_BOUNCING, true);
+
+       MUTATOR_CALLHOOK(EditProjectile, self, gren);
+}
+
+void W_Mortar_Attack2(void)
+{
+       entity gren;
+
+       W_DecreaseAmmo(WEP_CVAR_SEC(mortar, ammo));
+
+       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
+
+       Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       gren = spawn();
+       gren.owner = gren.realowner = self;
+       gren.classname = "grenade";
+       gren.bot_dodge = true;
+       gren.bot_dodgerating = WEP_CVAR_SEC(mortar, damage);
+       gren.movetype = MOVETYPE_BOUNCE;
+       gren.bouncefactor = WEP_CVAR(mortar, bouncefactor);
+       gren.bouncestop = WEP_CVAR(mortar, bouncestop);
+       PROJECTILE_MAKETRIGGER(gren);
+       gren.projectiledeathtype = WEP_MORTAR.m_id | HITTYPE_SECONDARY;
+       setorigin(gren, w_shotorg);
+       setsize(gren, '-3 -3 -3', '3 3 3');
+
+       gren.nextthink = time + WEP_CVAR_SEC(mortar, lifetime);
+       gren.think = adaptor_think2use_hittype_splash;
+       gren.use = W_Mortar_Grenade_Explode2;
+       gren.touch = W_Mortar_Grenade_Touch2;
+
+       gren.takedamage = DAMAGE_YES;
+       gren.health = WEP_CVAR_SEC(mortar, health);
+       gren.damageforcescale = WEP_CVAR_SEC(mortar, damageforcescale);
+       gren.event_damage = W_Mortar_Grenade_Damage;
+       gren.damagedbycontents = true;
+       gren.missile_flags = MIF_SPLASH | MIF_ARC;
+       W_SetupProjVelocity_UP_SEC(gren, mortar);
+
+       gren.angles = vectoangles(gren.velocity);
+       gren.flags = FL_PROJECTILE;
+
+       if(WEP_CVAR_SEC(mortar, type) == 0 || WEP_CVAR_SEC(mortar, type) == 2)
+               CSQCProjectile(gren, true, PROJECTILE_GRENADE, true);
+       else
+               CSQCProjectile(gren, true, PROJECTILE_GRENADE_BOUNCING, true);
+
+       MUTATOR_CALLHOOK(EditProjectile, self, gren);
+}
+
+.float bot_secondary_grenademooth;
+bool W_Mortar(int req)
+{
+       entity nade;
+       float nadefound;
+       float ammo_amount;
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       self.BUTTON_ATCK = false;
+                       self.BUTTON_ATCK2 = false;
+                       if(self.bot_secondary_grenademooth == 0) // WEAPONTODO: merge this into using WEP_CVAR_BOTH
+                       {
+                               if(bot_aim(WEP_CVAR_PRI(mortar, speed), WEP_CVAR_PRI(mortar, speed_up), WEP_CVAR_PRI(mortar, lifetime), true))
+                               {
+                                       self.BUTTON_ATCK = true;
+                                       if(random() < 0.01) self.bot_secondary_grenademooth = 1;
+                               }
+                       }
+                       else
+                       {
+                               if(bot_aim(WEP_CVAR_SEC(mortar, speed), WEP_CVAR_SEC(mortar, speed_up), WEP_CVAR_SEC(mortar, lifetime), true))
+                               {
+                                       self.BUTTON_ATCK2 = true;
+                                       if(random() < 0.02) self.bot_secondary_grenademooth = 0;
+                               }
+                       }
+
+                       return true;
+               }
+               /*case WR_CALCINFO:
+               {
+                       wepinfo_pri_refire = max3(sys_frametime, WEP_CVAR_PRI(mortar, refire), WEP_CVAR_PRI(mortar, animtime));
+                       wepinfo_pri_dps = (WEP_CVAR_PRI(mortar, damage) * (1 / wepinfo_pri_refire));
+                       wepinfo_pri_speed = (1 / max(1, (10000 / max(1, WEP_CVAR_PRI(mortar, speed)))));
+
+                       // for the range calculation, closer to 1 is better
+                       wepinfo_pri_range_max = 2000 * wepinfo_pri_speed;
+                       wepinfo_pri_range = wepinfo_pri_speed * WEP_CVAR_PRI(mortar,
+
+                       wepinfo_sec_refire = max3(sys_frametime, WEP_CVAR_SEC(mortar, refire), WEP_CVAR_SEC(mortar, animtime));
+                       wepinfo_sec_dps = (WEP_CVAR_SEC(mortar, damage) * (1 / wepinfo_sec_refire));
+
+                       wepinfo_sec_dps = (WEP_CVAR_SEC(mortar, damage) * (1 / max3(sys_frametime, WEP_CVAR_SEC(mortar, refire), WEP_CVAR_SEC(mortar, animtime))));
+                       wepinfo_ter_dps = 0;
+                       */
+               case WR_THINK:
+               {
+                       if(autocvar_g_balance_mortar_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo))) // forced reload
+                               WEP_ACTION(self.weapon, WR_RELOAD);
+                       else if(self.BUTTON_ATCK)
+                       {
+                               if(weapon_prepareattack(0, WEP_CVAR_PRI(mortar, refire)))
+                               {
+                                       W_Mortar_Attack();
+                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(mortar, animtime), w_ready);
+                               }
+                       }
+                       else if(self.BUTTON_ATCK2)
+                       {
+                               if(WEP_CVAR_SEC(mortar, remote_detonateprimary))
+                               {
+                                       nadefound = 0;
+                                       for(nade = world; (nade = find(nade, classname, "grenade")); ) if(nade.realowner == self)
+                                       {
+                                               if(!nade.gl_detonate_later)
+                                               {
+                                                       nade.gl_detonate_later = true;
+                                                       nadefound = 1;
+                                               }
+                                       }
+                                       if(nadefound)
+                                               sound(self, CH_WEAPON_B, W_Sound("rocket_det"), VOL_BASE, ATTN_NORM);
+                               }
+                               else if(weapon_prepareattack(1, WEP_CVAR_SEC(mortar, refire)))
+                               {
+                                       W_Mortar_Attack2();
+                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(mortar, animtime), w_ready);
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       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;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       ammo_amount = self.WEP_AMMO(MORTAR) >= WEP_CVAR_PRI(mortar, ammo);
+                       ammo_amount += self.(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_PRI(mortar, ammo);
+                       return ammo_amount;
+               }
+               case WR_CHECKAMMO2:
+               {
+                       ammo_amount = self.WEP_AMMO(MORTAR) >= WEP_CVAR_SEC(mortar, ammo);
+                       ammo_amount += self.(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_SEC(mortar, ammo);
+                       return ammo_amount;
+               }
+               case WR_CONFIG:
+               {
+                       MORTAR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       W_Reload(min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo)), W_Sound("reload")); // WEAPONTODO
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                               return WEAPON_MORTAR_SUICIDE_BOUNCE;
+                       else
+                               return WEAPON_MORTAR_SUICIDE_EXPLODE;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                               return WEAPON_MORTAR_MURDER_BOUNCE;
+                       else
+                               return WEAPON_MORTAR_MURDER_EXPLODE;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_Mortar(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 12;
+                       pointparticles(particleeffectnum(EFFECT_GRENADE_EXPLODE), org2, '0 0 0', 1);
+                       if(!w_issilent)
+                               sound(self, CH_SHOTS, W_Sound("grenade_impact"), VOL_BASE, ATTN_NORM);
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("grenade_impact"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/porto.qc b/qcsrc/common/weapons/weapon/porto.qc
new file mode 100644 (file)
index 0000000..2d08c0f
--- /dev/null
@@ -0,0 +1,431 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ PORTO,
+/* function  */ W_Porto,
+/* ammotype  */ ammo_none,
+/* impulse   */ 0,
+/* flags     */ WEP_TYPE_OTHER | WEP_FLAG_SUPERWEAPON,
+/* rating    */ 0,
+/* color     */ '0.5 0.5 0.5',
+/* modelname */ "porto",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairporto 0.6",
+/* wepimg    */ "weaponporto",
+/* refname   */ "porto",
+/* wepname   */ _("Port-O-Launch")
+);
+
+#define PORTO_SETTINGS(w_cvar,w_prop) PORTO_SETTINGS_LIST(w_cvar, w_prop, PORTO, porto)
+#define PORTO_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, BOTH, animtime) \
+       w_cvar(id, sn, BOTH, lifetime) \
+       w_cvar(id, sn, BOTH, refire) \
+       w_cvar(id, sn, BOTH, speed) \
+       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) \
+       w_prop(id, sn, float,  weaponstart, weaponstart) \
+       w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
+       w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
+
+#ifdef SVQC
+PORTO_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+.entity porto_current;
+.vector porto_v_angle; // holds "held" view angles
+.float porto_v_angle_held;
+.vector right_vector;
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+#include "../triggers/trigger/jumppads.qh"
+
+void spawnfunc_weapon_porto(void) { weapon_defaultspawnfunc(WEP_PORTO.m_id); }
+
+void W_Porto_Success(void)
+{
+       if(self.realowner == world)
+       {
+               objerror("Cannot succeed successfully: no owner\n");
+               return;
+       }
+
+       self.realowner.porto_current = world;
+       remove(self);
+}
+
+string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo);
+void W_Porto_Fail(float failhard)
+{
+       if(self.realowner == world)
+       {
+               objerror("Cannot fail successfully: no owner\n");
+               return;
+       }
+
+       // no portals here!
+       if(self.cnt < 0)
+       {
+               Portal_ClearWithID(self.realowner, self.portal_id);
+       }
+
+       self.realowner.porto_current = world;
+
+       if(self.cnt < 0 && !failhard && self.realowner.playerid == self.playerid && self.realowner.deadflag == DEAD_NO && !(self.realowner.weapons & WEPSET_PORTO))
+       {
+               setsize(self, '-16 -16 0', '16 16 32');
+               setorigin(self, self.origin + trace_plane_normal);
+               if(move_out_of_solid(self))
+               {
+                       self.flags = FL_ITEM;
+                       self.velocity = trigger_push_calculatevelocity(self.origin, self.realowner, 128);
+                       tracetoss(self, self);
+                       if(vlen(trace_endpos - self.realowner.origin) < 128)
+                       {
+                               W_ThrowNewWeapon(self.realowner, WEP_PORTO.m_id, 0, self.origin, self.velocity);
+                               Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_FAILED);
+                       }
+               }
+       }
+       remove(self);
+}
+
+void W_Porto_Remove(entity p)
+{
+       if(p.porto_current.realowner == p && p.porto_current.classname == "porto")
+       {
+               entity oldself;
+               oldself = self;
+               self = p.porto_current;
+               W_Porto_Fail(1);
+               self = oldself;
+       }
+}
+
+void W_Porto_Think(void)
+{
+       trace_plane_normal = '0 0 0';
+       if(self.realowner.playerid != self.playerid)
+               remove(self);
+       else
+               W_Porto_Fail(0);
+}
+
+void W_Porto_Touch(void)
+{
+       vector norm;
+
+       // do not use PROJECTILE_TOUCH here
+       // FIXME but DO handle warpzones!
+
+       if(other.classname == "portal")
+               return; // handled by the portal
+
+       norm = trace_plane_normal;
+       if(trace_ent.iscreature)
+       {
+               traceline(trace_ent.origin, trace_ent.origin + '0 0 2' * PL_MIN.z, MOVE_WORLDONLY, self);
+               if(trace_fraction >= 1)
+                       return;
+               if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP)
+                       return;
+               if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
+                       return;
+       }
+
+       if(self.realowner.playerid != self.playerid)
+       {
+               sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM);
+               remove(self);
+       }
+       else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP)
+       {
+               spamsound(self, CH_SHOTS, "porto/bounce.wav", VOL_BASE, ATTEN_NORM);
+               // just reflect
+               self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * trace_plane_normal);
+               self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * trace_plane_normal));
+       }
+       else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
+       {
+               sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM);
+               W_Porto_Fail(0);
+               if(self.cnt < 0)
+                       Portal_ClearAll_PortalsOnly(self.realowner);
+       }
+       else if(self.cnt == 0)
+       {
+               // in-portal only
+               if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
+               {
+                       sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM);
+                       trace_plane_normal = norm;
+                       Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN);
+                       W_Porto_Success();
+               }
+               else
+               {
+                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM);
+                       trace_plane_normal = norm;
+                       W_Porto_Fail(0);
+               }
+       }
+       else if(self.cnt == 1)
+       {
+               // out-portal only
+               if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
+               {
+                       sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM);
+                       trace_plane_normal = norm;
+                       Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT);
+                       W_Porto_Success();
+               }
+               else
+               {
+                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM);
+                       trace_plane_normal = norm;
+                       W_Porto_Fail(0);
+               }
+       }
+       else if(self.effects & EF_RED)
+       {
+               self.effects += EF_BLUE - EF_RED;
+               if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
+               {
+                       sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM);
+                       trace_plane_normal = norm;
+                       Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN);
+                       self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * norm);
+                       self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * norm));
+                       CSQCProjectile(self, true, PROJECTILE_PORTO_BLUE, true); // change type
+               }
+               else
+               {
+                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM);
+                       trace_plane_normal = norm;
+                       Portal_ClearAll_PortalsOnly(self.realowner);
+                       W_Porto_Fail(0);
+               }
+       }
+       else
+       {
+               if(self.realowner.portal_in.portal_id == self.portal_id)
+               {
+                       if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
+                       {
+                               sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTEN_NORM);
+                               trace_plane_normal = norm;
+                               Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT);
+                               W_Porto_Success();
+                       }
+                       else
+                       {
+                               sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM);
+                               Portal_ClearAll_PortalsOnly(self.realowner);
+                               W_Porto_Fail(0);
+                       }
+               }
+               else
+               {
+                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM);
+                       Portal_ClearAll_PortalsOnly(self.realowner);
+                       W_Porto_Fail(0);
+               }
+       }
+}
+
+void W_Porto_Attack(float type)
+{
+       entity gren;
+
+       W_SetupShot(self, false, 4, "porto/fire.wav", CH_WEAPON_A, 0);
+       // always shoot from the eye
+       w_shotdir = v_forward;
+       w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward;
+
+       //Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       gren = spawn();
+       gren.cnt = type;
+       gren.owner = gren.realowner = self;
+       gren.playerid = self.playerid;
+       gren.classname = "porto";
+       gren.bot_dodge = true;
+       gren.bot_dodgerating = 200;
+       gren.movetype = MOVETYPE_BOUNCEMISSILE;
+       PROJECTILE_MAKETRIGGER(gren);
+       gren.effects = EF_RED;
+       gren.scale = 4;
+       setorigin(gren, w_shotorg);
+       setsize(gren, '0 0 0', '0 0 0');
+
+       gren.nextthink = time + WEP_CVAR_BOTH(porto, (type <= 0), lifetime);
+       gren.think = W_Porto_Think;
+       gren.touch = W_Porto_Touch;
+
+       if(self.items & ITEM_Strength.m_itemid)
+               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);
+
+       gren.angles = vectoangles(gren.velocity);
+       gren.flags = FL_PROJECTILE;
+
+       gren.portal_id = time;
+       self.porto_current = gren;
+       gren.playerid = self.playerid;
+       fixedmakevectors(fixedvectoangles(gren.velocity));
+       gren.right_vector = v_right;
+
+       gren.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
+
+       if(type > 0)
+               CSQCProjectile(gren, true, PROJECTILE_PORTO_BLUE, true);
+       else
+               CSQCProjectile(gren, true, PROJECTILE_PORTO_RED, true);
+
+       MUTATOR_CALLHOOK(EditProjectile, self, gren);
+}
+
+bool w_nexball_weapon(int req); // WEAPONTODO
+bool W_Porto(int req)
+{
+       //vector v_angle_save;
+
+       if(g_nexball) { return w_nexball_weapon(req); }
+
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       self.BUTTON_ATCK = false;
+                       self.BUTTON_ATCK2 = false;
+                       if(!WEP_CVAR(porto, secondary))
+                               if(bot_aim(WEP_CVAR_PRI(porto, speed), 0, WEP_CVAR_PRI(porto, lifetime), false))
+                                       self.BUTTON_ATCK = true;
+
+                       return true;
+               }
+               case WR_CONFIG:
+               {
+                       PORTO_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       if(WEP_CVAR(porto, secondary))
+                       {
+                               if(self.BUTTON_ATCK)
+                               if(!self.porto_current)
+                               if(!self.porto_forbidden)
+                               if(weapon_prepareattack(0, WEP_CVAR_PRI(porto, refire)))
+                               {
+                                       W_Porto_Attack(0);
+                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready);
+                               }
+
+                               if(self.BUTTON_ATCK2)
+                               if(!self.porto_current)
+                               if(!self.porto_forbidden)
+                               if(weapon_prepareattack(1, WEP_CVAR_SEC(porto, refire)))
+                               {
+                                       W_Porto_Attack(1);
+                                       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(porto, animtime), w_ready);
+                               }
+                       }
+                       else
+                       {
+                               if(self.porto_v_angle_held)
+                               {
+                                       if(!self.BUTTON_ATCK2)
+                                       {
+                                               self.porto_v_angle_held = 0;
+
+                                               ClientData_Touch(self);
+                                       }
+                               }
+                               else
+                               {
+                                       if(self.BUTTON_ATCK2)
+                                       {
+                                               self.porto_v_angle = self.v_angle;
+                                               self.porto_v_angle_held = 1;
+
+                                               ClientData_Touch(self);
+                                       }
+                               }
+                               if(self.porto_v_angle_held)
+                                       makevectors(self.porto_v_angle); // override the previously set angles
+
+                               if(self.BUTTON_ATCK)
+                               if(!self.porto_current)
+                               if(!self.porto_forbidden)
+                               if(weapon_prepareattack(0, WEP_CVAR_PRI(porto, refire)))
+                               {
+                                       W_Porto_Attack(-1);
+                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready);
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_CHECKAMMO1:
+               case WR_CHECKAMMO2:
+               {
+                       // always allow infinite ammo
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       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");
+                       precache_sound("porto/expire.wav");
+                       precache_sound("porto/explode.wav");
+                       precache_sound("porto/fire.wav");
+                       precache_sound("porto/unsupported.wav");
+                       PORTO_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP);
+                       return true;
+               }
+               case WR_SETUP:
+               {
+                       self.ammo_field = ammo_none;
+                       return true;
+               }
+               case WR_RESETPLAYER:
+               {
+                       self.porto_current = world;
+                       return true;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_Porto(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       LOG_INFO("Since when does Porto send DamageInfo?\n");
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       // nothing to do
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/rifle.qc b/qcsrc/common/weapons/weapon/rifle.qc
new file mode 100644 (file)
index 0000000..ef3babd
--- /dev/null
@@ -0,0 +1,317 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ RIFLE,
+/* function  */ W_Rifle,
+/* ammotype  */ ammo_nails,
+/* impulse   */ 7,
+/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* color     */ '0.5 1 0',
+/* modelname */ "campingrifle",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairrifle 0.6",
+/* wepimg    */ "weaponrifle",
+/* refname   */ "rifle",
+/* wepname   */ _("Rifle")
+);
+
+#define RIFLE_SETTINGS(w_cvar,w_prop) RIFLE_SETTINGS_LIST(w_cvar, w_prop, RIFLE, rifle)
+#define RIFLE_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, BOTH, ammo) \
+       w_cvar(id, sn, BOTH, animtime) \
+       w_cvar(id, sn, BOTH, bullethail) \
+       w_cvar(id, sn, BOTH, burstcost) \
+       w_cvar(id, sn, BOTH, damage) \
+       w_cvar(id, sn, BOTH, force) \
+       w_cvar(id, sn, BOTH, refire) \
+       w_cvar(id, sn, BOTH, shots) \
+       w_cvar(id, sn, BOTH, solidpenetration) \
+       w_cvar(id, sn, BOTH, spread) \
+       w_cvar(id, sn, BOTH, tracer) \
+       w_cvar(id, sn, NONE, bursttime) \
+       w_cvar(id, sn, NONE, secondary) \
+       w_cvar(id, sn, SEC,  reload) \
+       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
+RIFLE_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+.float rifle_accumulator;
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_rifle(void) { weapon_defaultspawnfunc(WEP_RIFLE.m_id); }
+void spawnfunc_weapon_campingrifle(void) { spawnfunc_weapon_rifle(); }
+void spawnfunc_weapon_sniperrifle(void) { spawnfunc_weapon_rifle(); }
+
+void W_Rifle_FireBullet(float pSpread, float pDamage, float pForce, float pSolidPenetration, float pAmmo, int deathtype, float pTracer, float pShots, string pSound)
+{
+       float i;
+
+       W_DecreaseAmmo(pAmmo);
+
+       W_SetupShot(self, true, 2, pSound, CH_WEAPON_A, pDamage * pShots);
+
+       Send_Effect(EFFECT_RIFLE_MUZZLEFLASH, w_shotorg, w_shotdir * 2000, 1);
+
+       if(self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) // if zoomed, shoot from the eye
+       {
+               w_shotdir = v_forward;
+               w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward;
+       }
+
+       for(i = 0; i < pShots; ++i)
+               fireBullet(w_shotorg, w_shotdir, pSpread, pSolidPenetration, pDamage, pForce, deathtype, (pTracer ? EF_RED : EF_BLUE));
+
+       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 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.m_id, 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.m_id | HITTYPE_SECONDARY, WEP_CVAR_SEC(rifle, tracer), WEP_CVAR_SEC(rifle, shots), W_Sound("campingrifle_fire2"));
+}
+
+.void(void) rifle_bullethail_attackfunc;
+.float rifle_bullethail_frame;
+.float rifle_bullethail_animtime;
+.float rifle_bullethail_refire;
+void W_Rifle_BulletHail_Continue(void)
+{
+       float r, sw, af;
+
+       sw = self.switchweapon; // make it not detect weapon changes as reason to abort firing
+       af = ATTACK_FINISHED(self);
+       self.switchweapon = self.weapon;
+       ATTACK_FINISHED(self) = time;
+       LOG_INFO(ftos(self.WEP_AMMO(RIFLE)), "\n");
+       r = weapon_prepareattack(self.rifle_bullethail_frame == WFRAME_FIRE2, self.rifle_bullethail_refire);
+       if(self.switchweapon == self.weapon)
+               self.switchweapon = sw;
+       if(r)
+       {
+               self.rifle_bullethail_attackfunc();
+               weapon_thinkf(self.rifle_bullethail_frame, self.rifle_bullethail_animtime, W_Rifle_BulletHail_Continue);
+               LOG_INFO("thinkf set\n");
+       }
+       else
+       {
+               ATTACK_FINISHED(self) = af; // reset attack_finished if we didn't fire, so the last shot enforces the refire time
+               LOG_INFO("out of ammo... ", ftos(self.weaponentity.state), "\n");
+       }
+}
+
+void W_Rifle_BulletHail(float mode, void(void) AttackFunc, float fr, float animtime, float refire)
+{
+       // if we get here, we have at least one bullet to fire
+       AttackFunc();
+       if(mode)
+       {
+               // continue hail
+               self.rifle_bullethail_attackfunc = AttackFunc;
+               self.rifle_bullethail_frame = fr;
+               self.rifle_bullethail_animtime = animtime;
+               self.rifle_bullethail_refire = refire;
+               weapon_thinkf(fr, animtime, W_Rifle_BulletHail_Continue);
+       }
+       else
+       {
+               // just one shot
+               weapon_thinkf(fr, animtime, w_ready);
+       }
+}
+
+.float bot_secondary_riflemooth;
+bool W_Rifle(int req)
+{
+       float ammo_amount;
+
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       self.BUTTON_ATCK=false;
+                       self.BUTTON_ATCK2=false;
+                       if(vlen(self.origin-self.enemy.origin) > 1000)
+                               self.bot_secondary_riflemooth = 0;
+                       if(self.bot_secondary_riflemooth == 0)
+                       {
+                               if(bot_aim(1000000, 0, 0.001, false))
+                               {
+                                       self.BUTTON_ATCK = true;
+                                       if(random() < 0.01) self.bot_secondary_riflemooth = 1;
+                               }
+                       }
+                       else
+                       {
+                               if(bot_aim(1000000, 0, 0.001, false))
+                               {
+                                       self.BUTTON_ATCK2 = true;
+                                       if(random() < 0.03) self.bot_secondary_riflemooth = 0;
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       if(autocvar_g_balance_rifle_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(rifle, ammo), WEP_CVAR_SEC(rifle, ammo))) // forced reload
+                               WEP_ACTION(self.weapon, WR_RELOAD);
+                       else
+                       {
+                               self.rifle_accumulator = bound(time - WEP_CVAR(rifle, bursttime), self.rifle_accumulator, time);
+                               if(self.BUTTON_ATCK)
+                               if(weapon_prepareattack_check(0, WEP_CVAR_PRI(rifle, refire)))
+                               if(time >= self.rifle_accumulator + WEP_CVAR_PRI(rifle, burstcost))
+                               {
+                                       weapon_prepareattack_do(0, WEP_CVAR_PRI(rifle, refire));
+                                       W_Rifle_BulletHail(WEP_CVAR_PRI(rifle, bullethail), W_Rifle_Attack, WFRAME_FIRE1, WEP_CVAR_PRI(rifle, animtime), WEP_CVAR_PRI(rifle, refire));
+                                       self.rifle_accumulator += WEP_CVAR_PRI(rifle, burstcost);
+                               }
+                               if(self.BUTTON_ATCK2)
+                               {
+                                       if(WEP_CVAR(rifle, secondary))
+                                       {
+                                               if(WEP_CVAR_SEC(rifle, reload))
+                                                       WEP_ACTION(self.weapon, WR_RELOAD);
+                                               else
+                                               {
+                                                       if(weapon_prepareattack_check(1, WEP_CVAR_SEC(rifle, refire)))
+                                                       if(time >= self.rifle_accumulator + WEP_CVAR_SEC(rifle, burstcost))
+                                                       {
+                                                               weapon_prepareattack_do(1, WEP_CVAR_SEC(rifle, refire));
+                                                               W_Rifle_BulletHail(WEP_CVAR_SEC(rifle, bullethail), W_Rifle_Attack2, WFRAME_FIRE2, WEP_CVAR_SEC(rifle, animtime), WEP_CVAR_PRI(rifle, refire));
+                                                               self.rifle_accumulator += WEP_CVAR_SEC(rifle, burstcost);
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       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;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       ammo_amount = self.WEP_AMMO(RIFLE) >= WEP_CVAR_PRI(rifle, ammo);
+                       ammo_amount += self.(weapon_load[WEP_RIFLE.m_id]) >= WEP_CVAR_PRI(rifle, ammo);
+                       return ammo_amount;
+               }
+               case WR_CHECKAMMO2:
+               {
+                       ammo_amount = self.WEP_AMMO(RIFLE) >= WEP_CVAR_SEC(rifle, ammo);
+                       ammo_amount += self.(weapon_load[WEP_RIFLE.m_id]) >= WEP_CVAR_SEC(rifle, ammo);
+                       return ammo_amount;
+               }
+               case WR_CONFIG:
+               {
+                       RIFLE_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RESETPLAYER:
+               {
+                       self.rifle_accumulator = time - WEP_CVAR(rifle, bursttime);
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       W_Reload(min(WEP_CVAR_PRI(rifle, ammo), WEP_CVAR_SEC(rifle, ammo)), W_Sound("reload"));
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       return WEAPON_THINKING_WITH_PORTALS;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                       {
+                               if(w_deathtype & HITTYPE_BOUNCE)
+                                       return WEAPON_RIFLE_MURDER_HAIL_PIERCING;
+                               else
+                                       return WEAPON_RIFLE_MURDER_HAIL;
+                       }
+                       else
+                       {
+                               if(w_deathtype & HITTYPE_BOUNCE)
+                                       return WEAPON_RIFLE_MURDER_PIERCING;
+                               else
+                                       return WEAPON_RIFLE_MURDER;
+                       }
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_Rifle(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 2;
+                       pointparticles(particleeffectnum(EFFECT_RIFLE_IMPACT), org2, w_backoff * 1000, 1);
+                       if(!w_issilent)
+                       {
+                               if(w_random < 0.2)
+                                       sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTN_NORM);
+                               else if(w_random < 0.4)
+                                       sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTN_NORM);
+                               else if(w_random < 0.5)
+                                       sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTN_NORM);
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("ric1"));
+                       precache_sound(W_Sound("ric2"));
+                       precache_sound(W_Sound("ric3"));
+                       if(autocvar_cl_reticle && autocvar_cl_reticle_weapon)
+                       {
+                               precache_pic("gfx/reticle_nex");
+                       }
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       if(button_zoom || zoomscript_caught)
+                       {
+                               reticle_image = "gfx/reticle_nex";
+                               return true;
+                       }
+                       else
+                       {
+                               // no weapon specific image for this weapon
+                               return false;
+                       }
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/rpc.qc b/qcsrc/common/weapons/weapon/rpc.qc
new file mode 100644 (file)
index 0000000..8a34490
--- /dev/null
@@ -0,0 +1,265 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id     */ RPC,
+/* function     */ W_RocketPropelledChainsaw,
+/* ammotype     */ ammo_rockets,
+/* impulse      */ 7,
+/* flags        */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH | WEP_FLAG_SUPERWEAPON,
+/* rating       */ BOT_PICKUP_RATING_HIGH,
+/* color     */ '0.5 0.5 0',
+/* modelname */ "ok_rl",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairrocketlauncher 0.7",
+/* wepimg    */ "weaponrpc",
+/* refname   */ "rpc",
+/* wepname      */ _("Rocket Propelled Chainsaw")
+);
+
+#define RPC_SETTINGS(w_cvar,w_prop) RPC_SETTINGS_LIST(w_cvar, w_prop, RPC, rpc)
+#define RPC_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, damage) \
+       w_cvar(id, sn, NONE, damage2) \
+       w_cvar(id, sn, NONE, damageforcescale) \
+       w_cvar(id, sn, NONE, edgedamage) \
+       w_cvar(id, sn, NONE, force) \
+       w_cvar(id, sn, NONE, health) \
+       w_cvar(id, sn, NONE, lifetime) \
+       w_cvar(id, sn, NONE, radius) \
+       w_cvar(id, sn, NONE, refire) \
+       w_cvar(id, sn, NONE, speed) \
+       w_cvar(id, sn, NONE, speedaccel) \
+       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
+RPC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_rpc() { weapon_defaultspawnfunc(WEP_RPC.m_id); }
+
+void W_RocketPropelledChainsaw_Explode()
+{
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       RadiusDamage (self, self.realowner, WEP_CVAR(rpc, damage), WEP_CVAR(rpc, edgedamage), WEP_CVAR(rpc, radius), world, world, WEP_CVAR(rpc, force), self.projectiledeathtype, other);
+
+       remove (self);
+}
+
+void W_RocketPropelledChainsaw_Touch (void)
+{
+       if(WarpZone_Projectile_Touch())
+               if(wasfreed(self))
+                       return;
+
+       W_RocketPropelledChainsaw_Explode();
+}
+
+void W_RocketPropelledChainsaw_Damage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+       if (self.health <= 0)
+               return;
+
+       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+               return; // g_projectiles_damage says to halt
+
+       self.health = self.health - damage;
+
+       if (self.health <= 0)
+               W_PrepareExplosionByDamage(attacker, W_RocketPropelledChainsaw_Explode);
+}
+
+void W_RocketPropelledChainsaw_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, WEP_CVAR(rpc, damage2), self.projectiledeathtype, self.origin, normalize(self.origin - other.origin) * WEP_CVAR(rpc, force));
+
+       self.velocity = self.pos1 * (self.cnt + (WEP_CVAR(rpc, speedaccel) * sys_frametime));
+
+       UpdateCSQCProjectile(self);
+       self.nextthink = time;
+}
+
+void W_RocketPropelledChainsaw_Attack (void)
+{
+       entity missile = spawn(); //WarpZone_RefSys_SpawnSameRefSys(self);
+       entity flash = spawn ();
+
+       W_DecreaseAmmo(WEP_CVAR(rpc, ammo));
+       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;
+       missile.bot_dodge = true;
+       missile.bot_dodgerating = WEP_CVAR(rpc, damage) * 2;
+
+       missile.takedamage = DAMAGE_YES;
+       missile.damageforcescale = WEP_CVAR(rpc, damageforcescale);
+       missile.health = WEP_CVAR(rpc, health);
+       missile.event_damage = W_RocketPropelledChainsaw_Damage;
+       missile.damagedbycontents = true;
+       missile.movetype = MOVETYPE_FLY;
+
+       missile.projectiledeathtype = WEP_RPC.m_id;
+       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, WEP_CVAR(rpc, speed), 0);
+
+       missile.touch = W_RocketPropelledChainsaw_Touch;
+
+       missile.think = W_RocketPropelledChainsaw_Think;
+       missile.cnt = time + WEP_CVAR(rpc, lifetime);
+       missile.nextthink = time;
+       missile.flags = FL_PROJECTILE;
+
+       CSQCProjectile(missile, true, PROJECTILE_RPC, false);
+
+       setmodel(flash, "models/flash.md3"); // precision set below
+       SUB_SetFade (flash, time, 0.1);
+       flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
+       W_AttachToShotorg(flash, '5 0 0');
+       missile.pos1 = missile.velocity;
+
+       MUTATOR_CALLHOOK(EditProjectile, self, missile);
+}
+
+bool W_RocketPropelledChainsaw(int req)
+{
+       float ammo_amount = false;
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       self.BUTTON_ATCK = bot_aim(WEP_CVAR(rpc, speed), 0, WEP_CVAR(rpc, lifetime), false);
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       if(WEP_CVAR(rpc, reload_ammo) && self.clip_load < WEP_CVAR(rpc, ammo))
+                               WEP_ACTION(self.weapon, WR_RELOAD);
+                       else
+                       {
+                               if (self.BUTTON_ATCK)
+                               {
+                                       if(weapon_prepareattack(0, WEP_CVAR(rpc, refire)))
+                                       {
+                                               W_RocketPropelledChainsaw_Attack();
+                                               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(rpc, animtime), w_ready);
+                                       }
+                               }
+
+                               if (self.BUTTON_ATCK2)
+                               {
+                                       // to-do
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_model ("models/flash.md3");
+                       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;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       ammo_amount = self.WEP_AMMO(RPC) >= WEP_CVAR(rpc, ammo);
+                       ammo_amount += self.(weapon_load[WEP_RPC.m_id]) >= WEP_CVAR(rpc, ammo);
+                       return ammo_amount;
+               }
+               case WR_CHECKAMMO2:
+               {
+                       return false;
+               }
+               case WR_CONFIG:
+               {
+                       RPC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       W_Reload(WEP_CVAR(rpc, ammo), W_Sound("reload"));
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH))
+                               return WEAPON_RPC_SUICIDE_SPLASH;
+                       else
+                               return WEAPON_RPC_SUICIDE_DIRECT;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                               return WEAPON_BLASTER_MURDER;
+                       else if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH))
+                               return WEAPON_RPC_MURDER_SPLASH;
+                       else
+                               return WEAPON_RPC_MURDER_DIRECT;
+               }
+       }
+
+       return false;
+}
+#endif
+
+#ifdef CSQC
+bool W_RocketPropelledChainsaw(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 12;
+                       pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), org2, '0 0 0', 1);
+                       if(!w_issilent)
+                               sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM);
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("rocket_impact"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/seeker.qc b/qcsrc/common/weapons/weapon/seeker.qc
new file mode 100644 (file)
index 0000000..69c4974
--- /dev/null
@@ -0,0 +1,793 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ SEEKER,
+/* function  */ W_Seeker,
+/* ammotype  */ ammo_rockets,
+/* impulse   */ 8,
+/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* color     */ '0.5 1 0',
+/* modelname */ "seeker",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairseeker 0.8",
+/* wepimg    */ "weaponseeker",
+/* refname   */ "seeker",
+/* wepname   */ _("T.A.G. Seeker")
+);
+
+#define SEEKER_SETTINGS(w_cvar,w_prop) SEEKER_SETTINGS_LIST(w_cvar, w_prop, SEEKER, seeker)
+#define SEEKER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, NONE, type) \
+       w_cvar(id, sn, NONE, flac_ammo) \
+       w_cvar(id, sn, NONE, flac_animtime) \
+       w_cvar(id, sn, NONE, flac_damage) \
+       w_cvar(id, sn, NONE, flac_edgedamage) \
+       w_cvar(id, sn, NONE, flac_force) \
+       w_cvar(id, sn, NONE, flac_lifetime) \
+       w_cvar(id, sn, NONE, flac_lifetime_rand) \
+       w_cvar(id, sn, NONE, flac_radius) \
+       w_cvar(id, sn, NONE, flac_refire) \
+       w_cvar(id, sn, NONE, flac_speed) \
+       w_cvar(id, sn, NONE, flac_speed_up) \
+       w_cvar(id, sn, NONE, flac_speed_z) \
+       w_cvar(id, sn, NONE, flac_spread) \
+       w_cvar(id, sn, NONE, missile_accel) \
+       w_cvar(id, sn, NONE, missile_ammo) \
+       w_cvar(id, sn, NONE, missile_animtime) \
+       w_cvar(id, sn, NONE, missile_count) \
+       w_cvar(id, sn, NONE, missile_damage) \
+       w_cvar(id, sn, NONE, missile_damageforcescale) \
+       w_cvar(id, sn, NONE, missile_decel) \
+       w_cvar(id, sn, NONE, missile_delay) \
+       w_cvar(id, sn, NONE, missile_edgedamage) \
+       w_cvar(id, sn, NONE, missile_force) \
+       w_cvar(id, sn, NONE, missile_health) \
+       w_cvar(id, sn, NONE, missile_lifetime) \
+       w_cvar(id, sn, NONE, missile_proxy) \
+       w_cvar(id, sn, NONE, missile_proxy_delay) \
+       w_cvar(id, sn, NONE, missile_proxy_maxrange) \
+       w_cvar(id, sn, NONE, missile_radius) \
+       w_cvar(id, sn, NONE, missile_refire) \
+       w_cvar(id, sn, NONE, missile_smart) \
+       w_cvar(id, sn, NONE, missile_smart_mindist) \
+       w_cvar(id, sn, NONE, missile_smart_trace_max) \
+       w_cvar(id, sn, NONE, missile_smart_trace_min) \
+       w_cvar(id, sn, NONE, missile_speed) \
+       w_cvar(id, sn, NONE, missile_speed_max) \
+       w_cvar(id, sn, NONE, missile_speed_up) \
+       w_cvar(id, sn, NONE, missile_speed_z) \
+       w_cvar(id, sn, NONE, missile_spread) \
+       w_cvar(id, sn, NONE, missile_turnrate) \
+       w_cvar(id, sn, NONE, tag_ammo) \
+       w_cvar(id, sn, NONE, tag_animtime) \
+       w_cvar(id, sn, NONE, tag_damageforcescale) \
+       w_cvar(id, sn, NONE, tag_health) \
+       w_cvar(id, sn, NONE, tag_lifetime) \
+       w_cvar(id, sn, NONE, tag_refire) \
+       w_cvar(id, sn, NONE, tag_speed) \
+       w_cvar(id, sn, NONE, tag_spread) \
+       w_cvar(id, sn, NONE, tag_tracker_lifetime) \
+       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
+SEEKER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+.entity tag_target, wps_tag_tracker;
+.float tag_time;
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_seeker(void) { weapon_defaultspawnfunc(WEP_SEEKER.m_id); }
+
+// ============================
+// Begin: Missile functions, these are general functions to be manipulated by other code
+// ============================
+void W_Seeker_Missile_Explode(void)
+{
+       self.event_damage = func_null;
+       RadiusDamage(self, self.realowner, WEP_CVAR(seeker, missile_damage), WEP_CVAR(seeker, missile_edgedamage), WEP_CVAR(seeker, missile_radius), world, world, WEP_CVAR(seeker, missile_force), self.projectiledeathtype, other);
+
+       remove(self);
+}
+
+void W_Seeker_Missile_Touch(void)
+{
+       PROJECTILE_TOUCH;
+
+       W_Seeker_Missile_Explode();
+}
+
+void W_Seeker_Missile_Think(void)
+{
+       entity e;
+       vector desireddir, olddir, newdir, eorg;
+       float turnrate;
+       float dist;
+       float spd;
+
+       if(time > self.cnt)
+       {
+               self.projectiledeathtype |= HITTYPE_SPLASH;
+               W_Seeker_Missile_Explode();
+       }
+
+       spd = vlen(self.velocity);
+       spd = bound(
+               spd - WEP_CVAR(seeker, missile_decel) * frametime,
+               WEP_CVAR(seeker, missile_speed_max),
+               spd + WEP_CVAR(seeker, missile_accel) * frametime
+       );
+
+       if(self.enemy != world)
+               if(self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
+                       self.enemy = world;
+
+       if(self.enemy != world)
+       {
+               e               = self.enemy;
+               eorg            = 0.5 * (e.absmin + e.absmax);
+               turnrate        = WEP_CVAR(seeker, missile_turnrate); // how fast to turn
+               desireddir      = normalize(eorg - self.origin);
+               olddir          = normalize(self.velocity); // get my current direction
+               dist            = vlen(eorg - self.origin);
+
+               // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P )
+               if(WEP_CVAR(seeker, missile_smart) && (dist > WEP_CVAR(seeker, missile_smart_mindist)))
+               {
+                       // Is it a better idea (shorter distance) to trace to the target itself?
+                       if( vlen(self.origin + olddir * self.wait) < dist)
+                               traceline(self.origin, self.origin + olddir * self.wait, false, self);
+                       else
+                               traceline(self.origin, eorg, false, self);
+
+                       // Setup adaptive tracelength
+                       self.wait = bound(WEP_CVAR(seeker, missile_smart_trace_min), vlen(self.origin - trace_endpos), self.wait = WEP_CVAR(seeker, missile_smart_trace_max));
+
+                       // Calc how important it is that we turn and add this to the desierd (enemy) dir.
+                       desireddir  = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5);
+               }
+
+               newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy
+               self.velocity = newdir * spd; // make me fly in the new direction at my flight speed
+       }
+       else
+               dist = 0;
+
+       // Proxy
+       if(WEP_CVAR(seeker, missile_proxy))
+       {
+               if(dist <= WEP_CVAR(seeker, missile_proxy_maxrange))
+               {
+                       if(self.autoswitch == 0)
+                       {
+                               self.autoswitch = time + WEP_CVAR(seeker, missile_proxy_delay);
+                       }
+                       else
+                       {
+                               if(self.autoswitch <= time)
+                               {
+                                       W_Seeker_Missile_Explode();
+                                       self.autoswitch = 0;
+                               }
+                       }
+               }
+               else
+               {
+                       if(self.autoswitch != 0)
+                               self.autoswitch = 0;
+               }
+       }
+       ///////////////
+
+       if(self.enemy.deadflag != DEAD_NO)
+       {
+               self.enemy = world;
+               self.cnt = time + 1 + (random() * 4);
+               self.nextthink = self.cnt;
+               return;
+       }
+
+       //self.angles = vectoangles(self.velocity);                     // turn model in the new flight direction
+       self.nextthink = time;// + 0.05; // csqc projectiles
+       UpdateCSQCProjectile(self);
+}
+
+
+
+void W_Seeker_Missile_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+       if(self.health <= 0)
+               return;
+
+       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+               return; // g_projectiles_damage says to halt
+
+       if(self.realowner == attacker)
+               self.health = self.health - (damage * 0.25);
+       else
+               self.health = self.health - damage;
+
+       if(self.health <= 0)
+               W_PrepareExplosionByDamage(attacker, W_Seeker_Missile_Explode);
+}
+
+/*
+void W_Seeker_Missile_Animate(void)
+{
+       self.frame = self.frame +1;
+       self.nextthink = time + 0.05;
+
+       if(self.enemy != world)
+               if(self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
+                       self.enemy = world;
+
+       if(self.frame == 5)
+       {
+               self.think           = W_Seeker_Missile_Think;
+               self.nextthink       = time;// + cvar("g_balance_seeker_missile_activate_delay"); // cant dealy with csqc projectiles
+
+               if(autocvar_g_balance_seeker_missile_proxy)
+                       self.movetype    = MOVETYPE_BOUNCEMISSILE;
+               else
+                       self.movetype    = MOVETYPE_FLYMISSILE;
+       }
+
+       UpdateCSQCProjectile(self);
+}
+*/
+
+void W_Seeker_Fire_Missile(vector f_diff, entity m_target)
+{
+       entity missile;
+
+       W_DecreaseAmmo(WEP_CVAR(seeker, missile_ammo));
+
+       makevectors(self.v_angle);
+       W_SetupShot_ProjectileSize(self, '-2 -2 -2', '2 2 2', false, 2, W_Sound("seeker_fire"), CH_WEAPON_A, 0);
+       w_shotorg += f_diff;
+       Send_Effect(EFFECT_SEEKER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       //self.detornator         = false;
+
+       missile                 = spawn();
+       missile.owner           = missile.realowner = self;
+       missile.classname       = "seeker_missile";
+       missile.bot_dodge       = true;
+       missile.bot_dodgerating = WEP_CVAR(seeker, missile_damage);
+
+       missile.think           = W_Seeker_Missile_Think;
+       missile.touch           = W_Seeker_Missile_Touch;
+       missile.event_damage    = W_Seeker_Missile_Damage;
+       missile.nextthink       = time;// + 0.2;// + cvar("g_balance_seeker_missile_activate_delay");
+       missile.cnt             = time + WEP_CVAR(seeker, missile_lifetime);
+       missile.enemy           = m_target;
+       missile.solid           = SOLID_BBOX;
+       missile.scale           = 2;
+       missile.takedamage      = DAMAGE_YES;
+       missile.health          = WEP_CVAR(seeker, missile_health);
+       missile.damageforcescale = WEP_CVAR(seeker, missile_damageforcescale);
+       missile.damagedbycontents = true;
+       //missile.think           = W_Seeker_Missile_Animate; // csqc projectiles.
+
+       if(missile.enemy != world)
+               missile.projectiledeathtype = WEP_SEEKER.m_id | HITTYPE_SECONDARY;
+       else
+               missile.projectiledeathtype = WEP_SEEKER.m_id;
+
+
+       setorigin(missile, w_shotorg);
+       setsize(missile, '-4 -4 -4', '4 4 4');
+       missile.movetype    = MOVETYPE_FLYMISSILE;
+       missile.flags       = FL_PROJECTILE;
+       missile.missile_flags = MIF_SPLASH | MIF_GUIDED_TAG;
+
+       W_SetupProjVelocity_UP_PRE(missile, seeker, missile_);
+
+       missile.angles = vectoangles(missile.velocity);
+
+       CSQCProjectile(missile, false, PROJECTILE_SEEKER, true);
+
+       MUTATOR_CALLHOOK(EditProjectile, self, missile);
+}
+
+// ============================
+// Begin: FLAC, close range attack meant for defeating rockets which are coming at you.
+// ============================
+void W_Seeker_Flac_Explode(void)
+{
+       self.event_damage = func_null;
+
+       RadiusDamage(self, self.realowner, WEP_CVAR(seeker, flac_damage), WEP_CVAR(seeker, flac_edgedamage), WEP_CVAR(seeker, flac_radius), world, world, WEP_CVAR(seeker, flac_force), self.projectiledeathtype, other);
+
+       remove(self);
+}
+
+void W_Seeker_Flac_Touch(void)
+{
+       PROJECTILE_TOUCH;
+
+       W_Seeker_Flac_Explode();
+}
+
+void W_Seeker_Fire_Flac(void)
+{
+       entity missile;
+       vector f_diff;
+       float c;
+
+       W_DecreaseAmmo(WEP_CVAR(seeker, flac_ammo));
+
+       c = self.bulletcounter % 4;
+       switch(c)
+       {
+               case 0:
+                       f_diff = '-1.25 -3.75 0';
+                       break;
+               case 1:
+                       f_diff = '+1.25 -3.75 0';
+                       break;
+               case 2:
+                       f_diff = '-1.25 +3.75 0';
+                       break;
+               case 3:
+               default:
+                       f_diff = '+1.25 +3.75 0';
+                       break;
+       }
+       W_SetupShot_ProjectileSize(self, '-2 -2 -2', '2 2 2', false, 2, W_Sound("flac_fire"), CH_WEAPON_A, WEP_CVAR(seeker, flac_damage));
+       w_shotorg += f_diff;
+
+       Send_Effect(EFFECT_HAGAR_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       missile                                 = spawn();
+       missile.owner                   = missile.realowner = self;
+       missile.classname               = "missile";
+       missile.bot_dodge               = true;
+       missile.bot_dodgerating = WEP_CVAR(seeker, flac_damage);
+       missile.touch                   = W_Seeker_Flac_Explode;
+       missile.use                     = W_Seeker_Flac_Explode;
+       missile.think                   = adaptor_think2use_hittype_splash;
+       missile.nextthink               = time + WEP_CVAR(seeker, flac_lifetime) + WEP_CVAR(seeker, flac_lifetime_rand);
+       missile.solid                   = SOLID_BBOX;
+       missile.movetype                = MOVETYPE_FLY;
+       missile.projectiledeathtype = WEP_SEEKER.m_id;
+       missile.projectiledeathtype = WEP_SEEKER.m_id | HITTYPE_SECONDARY;
+       missile.flags                           = FL_PROJECTILE;
+       missile.missile_flags       = MIF_SPLASH;
+
+       // csqc projectiles
+       //missile.angles                                = vectoangles(missile.velocity);
+       //missile.scale = 0.4; // BUG: the model is too big
+
+       setorigin(missile, w_shotorg);
+       setsize(missile, '-2 -2 -2', '2 2 2');
+
+       W_SetupProjVelocity_UP_PRE(missile, seeker, flac_);
+       CSQCProjectile(missile, true, PROJECTILE_FLAC, true);
+
+       MUTATOR_CALLHOOK(EditProjectile, self, missile);
+}
+
+// ============================
+// Begin: Tag and rocket controllers
+// ============================
+entity W_Seeker_Tagged_Info(entity isowner, entity istarget)
+{
+       entity tag;
+       for(tag = world; (tag = find(tag, classname, "tag_tracker")); )
+               if((tag.realowner == isowner) && (tag.tag_target == istarget))
+                       return tag;
+
+       return world;
+}
+
+void W_Seeker_Attack(void)
+{
+       entity tracker, closest_target;
+
+       closest_target = world;
+       for(tracker = world; (tracker = find(tracker, classname, "tag_tracker")); ) if (tracker.realowner == self)
+       {
+               if(closest_target)
+               {
+                       if(vlen(self.origin - tracker.tag_target.origin) < vlen(self.origin - closest_target.origin))
+                               closest_target = tracker.tag_target;
+               }
+               else
+                       closest_target = tracker.tag_target;
+       }
+
+       traceline(self.origin + self.view_ofs, closest_target.origin, MOVE_NOMONSTERS, self);
+       if((!closest_target) || ((trace_fraction < 1) && (trace_ent != closest_target)))
+               closest_target = world;
+
+       W_Seeker_Fire_Missile('0 0 0', closest_target);
+}
+
+void W_Seeker_Vollycontroller_Think(void) // TODO: Merge this with W_Seeker_Attack
+{
+       float c;
+       entity oldself,oldenemy;
+       self.cnt = self.cnt - 1;
+
+       if((!(self.realowner.items & IT_UNLIMITED_AMMO) && self.realowner.WEP_AMMO(SEEKER) < WEP_CVAR(seeker, missile_ammo)) || (self.cnt <= -1) || (self.realowner.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER.m_id))
+       {
+               remove(self);
+               return;
+       }
+
+       self.nextthink = time + WEP_CVAR(seeker, missile_delay) * W_WeaponRateFactor();
+
+       oldself = self;
+       self = self.realowner;
+
+       oldenemy = self.enemy;
+       self.enemy = oldself.enemy;
+
+       c = self.cnt % 4;
+       switch(c)
+       {
+               case 0:
+                       W_Seeker_Fire_Missile('-1.25 -3.75 0', self.enemy);
+                       break;
+               case 1:
+                       W_Seeker_Fire_Missile('+1.25 -3.75 0', self.enemy);
+                       break;
+               case 2:
+                       W_Seeker_Fire_Missile('-1.25 +3.75 0', self.enemy);
+                       break;
+               case 3:
+               default:
+                       W_Seeker_Fire_Missile('+1.25 +3.75 0', self.enemy);
+                       break;
+       }
+
+       self.enemy = oldenemy;
+       self = oldself;
+}
+
+void W_Seeker_Tracker_Think(void)
+{
+       // commit suicide if: You die OR target dies OR you switch away from the seeker OR commit suicide if lifetime is up
+       if((self.realowner.deadflag != DEAD_NO) || (self.tag_target.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER.m_id)
+       || (time > self.tag_time + WEP_CVAR(seeker, tag_tracker_lifetime)))
+       {
+               if(self)
+               {
+                       WaypointSprite_Kill(self.tag_target.wps_tag_tracker);
+                       remove(self);
+               }
+               return;
+       }
+
+       // Update the think method information
+       self.nextthink = time;
+}
+
+// ============================
+// Begin: Tag projectile
+// ============================
+void W_Seeker_Tag_Explode(void)
+{
+       //if(other==self.realowner)
+       //    return;
+       Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER.m_id | HITTYPE_BOUNCE, other.species, self);
+
+       remove(self);
+}
+
+void W_Seeker_Tag_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+       if(self.health <= 0)
+               return;
+       self.health = self.health - damage;
+       if(self.health <= 0)
+               W_Seeker_Tag_Explode();
+}
+
+void W_Seeker_Tag_Touch(void)
+{
+       vector dir;
+       vector org2;
+       entity e;
+
+       PROJECTILE_TOUCH;
+
+       dir     = normalize(self.realowner.origin - self.origin);
+       org2    = findbetterlocation(self.origin, 8);
+
+       te_knightspike(org2);
+
+       self.event_damage = func_null;
+       Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER.m_id | HITTYPE_BOUNCE | HITTYPE_SECONDARY, other.species, self);
+
+       if(other.takedamage == DAMAGE_AIM && other.deadflag == DEAD_NO)
+       {
+               // check to see if this person is already tagged by me
+               entity tag = W_Seeker_Tagged_Info(self.realowner, other);
+
+               if(tag != world)
+               {
+                       if(other.wps_tag_tracker && (WEP_CVAR(seeker, type) == 1)) // don't attach another waypointsprite without killing the old one first
+                               WaypointSprite_Kill(other.wps_tag_tracker);
+
+                       tag.tag_time = time;
+               }
+               else
+               {
+                       //sprint(self.realowner, strcat("You just tagged ^2", other.netname, "^7 with a tracking device!\n"));
+                       e             = spawn();
+                       e.cnt         = WEP_CVAR(seeker, missile_count);
+                       e.classname   = "tag_tracker";
+                       e.owner       = self.owner;
+                       e.realowner   = self.realowner;
+
+                       if(WEP_CVAR(seeker, type) == 1)
+                       {
+                               e.tag_target  = other;
+                               e.tag_time    = time;
+                               e.think       = W_Seeker_Tracker_Think;
+                       }
+                       else
+                       {
+                               e.enemy     = other;
+                               e.think     = W_Seeker_Vollycontroller_Think;
+                       }
+
+                       e.nextthink   = time;
+               }
+
+               if(WEP_CVAR(seeker, type) == 1)
+               {
+                       WaypointSprite_Spawn(WP_Seeker, WEP_CVAR(seeker, tag_tracker_lifetime), 0, other, '0 0 64', self.realowner, 0, other, wps_tag_tracker, true, RADARICON_TAGGED);
+                       WaypointSprite_UpdateRule(other.wps_tag_tracker, 0, SPRITERULE_DEFAULT);
+               }
+       }
+
+       remove(self);
+       return;
+}
+
+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, W_Sound("tag_fire"), CH_WEAPON_A, WEP_CVAR(seeker, missile_damage) * WEP_CVAR(seeker, missile_count));
+
+       missile                 = spawn();
+       missile.owner           = missile.realowner = self;
+       missile.classname       = "seeker_tag";
+       missile.bot_dodge       = true;
+       missile.bot_dodgerating = 50;
+       missile.touch           = W_Seeker_Tag_Touch;
+       missile.think           = SUB_Remove;
+       missile.nextthink       = time + WEP_CVAR(seeker, tag_lifetime);
+       missile.movetype        = MOVETYPE_FLY;
+       missile.solid           = SOLID_BBOX;
+
+       missile.takedamage       = DAMAGE_YES;
+       missile.event_damage     = W_Seeker_Tag_Damage;
+       missile.health           = WEP_CVAR(seeker, tag_health);
+       missile.damageforcescale = WEP_CVAR(seeker, tag_damageforcescale);
+
+       setorigin(missile, w_shotorg);
+       setsize(missile, '-2 -2 -2', '2 2 2');
+
+       missile.flags       = FL_PROJECTILE;
+       //missile.missile_flags = MIF_..?;
+
+       missile.movetype    = MOVETYPE_FLY;
+       W_SetupProjVelocity_PRE(missile, seeker, tag_);
+       missile.angles = vectoangles(missile.velocity);
+
+       CSQCProjectile(missile, true, PROJECTILE_TAG, false); // has sound
+
+       MUTATOR_CALLHOOK(EditProjectile, self, missile);
+}
+
+// ============================
+// Begin: Genereal weapon functions
+// ============================
+
+bool W_Seeker(int req)
+{
+       float ammo_amount;
+
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       if(WEP_CVAR(seeker, type) == 1)
+                               if(W_Seeker_Tagged_Info(self, self.enemy) != world)
+                                       self.BUTTON_ATCK = bot_aim(WEP_CVAR(seeker, missile_speed_max), 0, WEP_CVAR(seeker, missile_lifetime), false);
+                               else
+                                       self.BUTTON_ATCK2 = bot_aim(WEP_CVAR(seeker, tag_speed), 0, WEP_CVAR(seeker, tag_lifetime), false);
+                       else
+                               self.BUTTON_ATCK = bot_aim(WEP_CVAR(seeker, tag_speed), 0, WEP_CVAR(seeker, tag_lifetime), false);
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       if(autocvar_g_balance_seeker_reload_ammo && self.clip_load < min(WEP_CVAR(seeker, missile_ammo), WEP_CVAR(seeker, tag_ammo))) // forced reload
+                               WEP_ACTION(self.weapon, WR_RELOAD);
+
+                       else if(self.BUTTON_ATCK)
+                       {
+                               if(WEP_CVAR(seeker, type) == 1)
+                               {
+                                       if(weapon_prepareattack(0, WEP_CVAR(seeker, missile_refire)))
+                                       {
+                                               W_Seeker_Attack();
+                                               weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, missile_animtime), w_ready);
+                                       }
+                               }
+                               else
+                               {
+                                       if(weapon_prepareattack(0, WEP_CVAR(seeker, tag_refire)))
+                                       {
+                                               W_Seeker_Fire_Tag();
+                                               weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, tag_animtime), w_ready);
+                                       }
+                               }
+                       }
+
+                       else if(self.BUTTON_ATCK2)
+                       {
+                               if(WEP_CVAR(seeker, type) == 1)
+                               {
+                                       if(weapon_prepareattack(0, WEP_CVAR(seeker, tag_refire)))
+                                       {
+                                               W_Seeker_Fire_Tag();
+                                               weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, tag_animtime), w_ready);
+                                       }
+                               }
+                               else
+                               {
+                                       if(weapon_prepareattack(0, WEP_CVAR(seeker, flac_refire)))
+                                       {
+                                               W_Seeker_Fire_Flac();
+                                               weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, flac_animtime), w_ready);
+                                       }
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       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;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       if(WEP_CVAR(seeker, type) == 1)
+                       {
+                               ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, missile_ammo);
+                               ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, missile_ammo);
+                       }
+                       else
+                       {
+                               ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, tag_ammo);
+                               ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, tag_ammo);
+                       }
+                       return ammo_amount;
+               }
+               case WR_CHECKAMMO2:
+               {
+                       if(WEP_CVAR(seeker, type) == 1)
+                       {
+                               ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, tag_ammo);
+                               ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, tag_ammo);
+                       }
+                       else
+                       {
+                               ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, flac_ammo);
+                               ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, flac_ammo);
+                       }
+                       return ammo_amount;
+               }
+               case WR_CONFIG:
+               {
+                       SEEKER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       W_Reload(min(WEP_CVAR(seeker, missile_ammo), WEP_CVAR(seeker, tag_ammo)), W_Sound("reload"));
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       return WEAPON_SEEKER_SUICIDE;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                               return WEAPON_SEEKER_MURDER_TAG;
+                       else
+                               return WEAPON_SEEKER_MURDER_SPRAY;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_Seeker(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 6;
+                       if(w_deathtype & HITTYPE_BOUNCE)
+                       {
+                               if(w_deathtype & HITTYPE_SECONDARY)
+                               {
+                                       if(!w_issilent)
+                                               sound(self, CH_SHOTS, W_Sound("tag_impact"), 1, ATTEN_NORM);
+                               }
+                               else
+                               {
+                                       pointparticles(particleeffectnum(EFFECT_HAGAR_EXPLODE), org2, '0 0 0', 1);
+                                       if(!w_issilent)
+                                       {
+                                               if(w_random<0.15)
+                                                       sound(self, CH_SHOTS, W_Sound("tagexp1"), 1, ATTEN_NORM);
+                                               else if(w_random<0.7)
+                                                       sound(self, CH_SHOTS, W_Sound("tagexp2"), 1, ATTEN_NORM);
+                                               else
+                                                       sound(self, CH_SHOTS, W_Sound("tagexp3"), 1, ATTEN_NORM);
+                                       }
+                               }
+                       }
+                       else
+                       {
+                               pointparticles(particleeffectnum(EFFECT_HAGAR_EXPLODE), org2, '0 0 0', 1);
+                               if(!w_issilent)
+                               {
+                                       if(w_random<0.15)
+                                               sound(self, CH_SHOTS, W_Sound("seekerexp1"), 1, ATTEN_NORM);
+                                       else if(w_random<0.7)
+                                               sound(self, CH_SHOTS, W_Sound("seekerexp2"), 1, ATTEN_NORM);
+                                       else
+                                               sound(self, CH_SHOTS, W_Sound("seekerexp3"), 1, ATTEN_NORM);
+                               }
+                       }
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("seekerexp1"));
+                       precache_sound(W_Sound("seekerexp2"));
+                       precache_sound(W_Sound("seekerexp3"));
+                       precache_sound(W_Sound("tagexp1"));
+                       precache_sound(W_Sound("tagexp2"));
+                       precache_sound(W_Sound("tagexp3"));
+                       precache_sound(W_Sound("tag_impact"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/shockwave.qc b/qcsrc/common/weapons/weapon/shockwave.qc
new file mode 100644 (file)
index 0000000..3287ea3
--- /dev/null
@@ -0,0 +1,896 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ SHOCKWAVE,
+/* function  */ W_Shockwave,
+/* ammotype  */ ammo_none,
+/* impulse   */ 2,
+/* flags     */ WEP_FLAG_NORMAL | WEP_TYPE_HITSCAN | WEP_FLAG_CANCLIMB | WEP_FLAG_MUTATORBLOCKED,
+/* rating    */ BOT_PICKUP_RATING_LOW,
+/* color     */ '0.5 0.25 0',
+/* modelname */ "shotgun",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairshotgun 0.7",
+/* wepimg    */ "weaponshotgun",
+/* refname   */ "shockwave",
+/* wepname   */ _("Shockwave")
+);
+
+#define SHOCKWAVE_SETTINGS(w_cvar,w_prop) SHOCKWAVE_SETTINGS_LIST(w_cvar, w_prop, SHOCKWAVE, shockwave)
+#define SHOCKWAVE_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, NONE, blast_animtime) \
+       w_cvar(id, sn, NONE, blast_damage) \
+       w_cvar(id, sn, NONE, blast_distance) \
+       w_cvar(id, sn, NONE, blast_edgedamage) \
+       w_cvar(id, sn, NONE, blast_force) \
+       w_cvar(id, sn, NONE, blast_force_forwardbias) \
+       w_cvar(id, sn, NONE, blast_force_zscale) \
+       w_cvar(id, sn, NONE, blast_jump_damage) \
+       w_cvar(id, sn, NONE, blast_jump_edgedamage) \
+       w_cvar(id, sn, NONE, blast_jump_force) \
+       w_cvar(id, sn, NONE, blast_jump_force_velocitybias) \
+       w_cvar(id, sn, NONE, blast_jump_force_zscale) \
+       w_cvar(id, sn, NONE, blast_jump_multiplier_accuracy) \
+       w_cvar(id, sn, NONE, blast_jump_multiplier_distance) \
+       w_cvar(id, sn, NONE, blast_jump_multiplier_min) \
+       w_cvar(id, sn, NONE, blast_jump_radius) \
+       w_cvar(id, sn, NONE, blast_multiplier_accuracy) \
+       w_cvar(id, sn, NONE, blast_multiplier_distance) \
+       w_cvar(id, sn, NONE, blast_multiplier_min) \
+       w_cvar(id, sn, NONE, blast_refire) \
+       w_cvar(id, sn, NONE, blast_splash_damage) \
+       w_cvar(id, sn, NONE, blast_splash_edgedamage) \
+       w_cvar(id, sn, NONE, blast_splash_force) \
+       w_cvar(id, sn, NONE, blast_splash_force_forwardbias) \
+       w_cvar(id, sn, NONE, blast_splash_multiplier_accuracy) \
+       w_cvar(id, sn, NONE, blast_splash_multiplier_distance) \
+       w_cvar(id, sn, NONE, blast_splash_multiplier_min) \
+       w_cvar(id, sn, NONE, blast_splash_radius) \
+       w_cvar(id, sn, NONE, blast_spread_max) \
+       w_cvar(id, sn, NONE, blast_spread_min) \
+       w_cvar(id, sn, NONE, melee_animtime) \
+       w_cvar(id, sn, NONE, melee_damage) \
+       w_cvar(id, sn, NONE, melee_delay) \
+       w_cvar(id, sn, NONE, melee_force) \
+       w_cvar(id, sn, NONE, melee_multihit) \
+       w_cvar(id, sn, NONE, melee_no_doubleslap) \
+       w_cvar(id, sn, NONE, melee_nonplayerdamage) \
+       w_cvar(id, sn, NONE, melee_range) \
+       w_cvar(id, sn, NONE, melee_refire) \
+       w_cvar(id, sn, NONE, melee_swing_side) \
+       w_cvar(id, sn, NONE, melee_swing_up) \
+       w_cvar(id, sn, NONE, melee_time) \
+       w_cvar(id, sn, NONE, melee_traces) \
+       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
+SHOCKWAVE_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+#endif
+#ifdef CSQC
+void Net_ReadShockwaveParticle(void);
+.vector sw_shotorg;
+.vector sw_shotdir;
+.float sw_distance;
+.float sw_spread_max;
+.float sw_spread_min;
+.float sw_time;
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_shockwave(void)
+{
+       //if(autocvar_sv_q3acompat_machineshockwaveswap) // WEAPONTODO
+       if(autocvar_sv_q3acompat_machineshotgunswap)
+       if(self.classname != "droppedweapon")
+       {
+               weapon_defaultspawnfunc(WEP_MACHINEGUN.m_id);
+               return;
+       }
+       weapon_defaultspawnfunc(WEP_SHOCKWAVE.m_id);
+}
+
+const float MAX_SHOCKWAVE_HITS = 10;
+//#define DEBUG_SHOCKWAVE
+
+.float swing_prev;
+.entity swing_alreadyhit;
+.float shockwave_blasttime;
+entity shockwave_hit[MAX_SHOCKWAVE_HITS];
+float shockwave_hit_damage[MAX_SHOCKWAVE_HITS];
+vector shockwave_hit_force[MAX_SHOCKWAVE_HITS];
+
+// MELEE ATTACK MODE
+void W_Shockwave_Melee_Think(void)
+{
+       // declarations
+       float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
+       entity target_victim;
+       vector targpos;
+
+       // check to see if we can still continue, otherwise give up now
+       if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR(shockwave, melee_no_doubleslap))
+       {
+               remove(self);
+               return;
+       }
+
+       // set start time of melee
+       if(!self.cnt)
+       {
+               self.cnt = time;
+               W_PlayStrengthSound(self.realowner);
+       }
+
+       // update values for v_* vectors
+       makevectors(self.realowner.v_angle);
+
+       // calculate swing percentage based on time
+       meleetime = WEP_CVAR(shockwave, melee_time) * W_WeaponRateFactor();
+       swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10);
+       f = ((1 - swing) * WEP_CVAR(shockwave, melee_traces));
+
+       // perform the traces needed for this frame
+       for(i=self.swing_prev; i < f; ++i)
+       {
+               swing_factor = ((1 - (i / WEP_CVAR(shockwave, melee_traces))) * 2 - 1);
+
+               targpos = (self.realowner.origin + self.realowner.view_ofs
+                       + (v_forward * WEP_CVAR(shockwave, melee_range))
+                       + (v_up * swing_factor * WEP_CVAR(shockwave, melee_swing_up))
+                       + (v_right * swing_factor * WEP_CVAR(shockwave, melee_swing_side)));
+
+               WarpZone_traceline_antilag(
+                       self.realowner,
+                       (self.realowner.origin + self.realowner.view_ofs),
+                       targpos,
+                       false,
+                       self.realowner,
+                       ANTILAG_LATENCY(self.realowner)
+               );
+
+               // draw lightning beams for debugging
+#ifdef DEBUG_SHOCKWAVE
+               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');
+#endif
+
+               is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body" || IS_MONSTER(trace_ent));
+
+               if((trace_fraction < 1) // if trace is good, apply the damage and remove self if necessary
+                       && (trace_ent.takedamage == DAMAGE_AIM)
+                       && (trace_ent != self.swing_alreadyhit)
+                       && (is_player || WEP_CVAR(shockwave, melee_nonplayerdamage)))
+               {
+                       target_victim = trace_ent; // so it persists through other calls
+
+                       if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught
+                               swing_damage = (WEP_CVAR(shockwave, melee_damage) * min(1, swing_factor + 1));
+                       else
+                               swing_damage = (WEP_CVAR(shockwave, melee_nonplayerdamage) * min(1, swing_factor + 1));
+
+                       // trigger damage with this calculated info
+                       Damage(
+                               target_victim,
+                               self.realowner,
+                               self.realowner,
+                               swing_damage,
+                               (WEP_SHOCKWAVE.m_id | HITTYPE_SECONDARY),
+                               (self.realowner.origin + self.realowner.view_ofs),
+                               (v_forward * WEP_CVAR(shockwave, melee_force))
+                       );
+
+                       // handle accuracy
+                       if(accuracy_isgooddamage(self.realowner, target_victim))
+                               { accuracy_add(self.realowner, WEP_SHOCKWAVE.m_id, 0, swing_damage); }
+
+                       #ifdef DEBUG_SHOCKWAVE
+                       LOG_INFO(sprintf(
+                               "MELEE: %s hitting %s with %f damage (factor: %f) at %f time.\n",
+                               self.realowner.netname,
+                               target_victim.netname,
+                               swing_damage,
+                               swing_factor,
+                               time
+                       ));
+                       #endif
+
+                       // allow multiple hits with one swing, but not against the same player twice
+                       if(WEP_CVAR(shockwave, melee_multihit))
+                       {
+                               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_Shockwave_Melee(void)
+{
+       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;
+       meleetemp = spawn();
+       meleetemp.owner = meleetemp.realowner = self;
+       meleetemp.think = W_Shockwave_Melee_Think;
+       meleetemp.nextthink = time + WEP_CVAR(shockwave, melee_delay) * W_WeaponRateFactor();
+       W_SetupShot_Range(self, true, 0, "", 0, WEP_CVAR(shockwave, melee_damage), WEP_CVAR(shockwave, melee_range));
+}
+
+// SHOCKWAVE ATTACK MODE
+float W_Shockwave_Attack_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(shockwave, blast_spread_min) * (1 - spreadlimit))
+                       +
+                       (WEP_CVAR(shockwave, blast_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_Shockwave_Attack_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_Shockwave_Attack_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_Shockwave_Attack_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_Shockwave_Attack_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;
+}
+
+float W_Shockwave_Attack_CheckHit(
+       float queue,
+       entity head,
+       vector final_force,
+       float final_damage)
+{
+       if(!head) { return false; }
+       float i;
+
+       for(i = 0; 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_Shockwave_Send(void)
+{
+       WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
+       WriteByte(MSG_BROADCAST, TE_CSQC_SHOCKWAVEPARTICLE);
+       WriteCoord(MSG_BROADCAST, w_shotorg.x);
+       WriteCoord(MSG_BROADCAST, w_shotorg.y);
+       WriteCoord(MSG_BROADCAST, w_shotorg.z);
+       WriteCoord(MSG_BROADCAST, w_shotdir.x);
+       WriteCoord(MSG_BROADCAST, w_shotdir.y);
+       WriteCoord(MSG_BROADCAST, w_shotdir.z);
+       WriteShort(MSG_BROADCAST, WEP_CVAR(shockwave, blast_distance));
+       WriteByte(MSG_BROADCAST, bound(0, WEP_CVAR(shockwave, blast_spread_max), 255));
+       WriteByte(MSG_BROADCAST, bound(0, WEP_CVAR(shockwave, blast_spread_min), 255));
+       WriteByte(MSG_BROADCAST, num_for_edict(self));
+}
+
+void W_Shockwave_Attack(void)
+{
+       // declarations
+       float multiplier, multiplier_from_accuracy, multiplier_from_distance;
+       float final_damage;
+       vector final_force, center, vel;
+       entity head;
+
+       float i, queue = 0;
+
+       // set up the shot direction
+       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;
+       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
+       W_Shockwave_Send();
+       Damage_DamageInfo(
+               attack_hitpos,
+               WEP_CVAR(shockwave, blast_splash_damage),
+               WEP_CVAR(shockwave, blast_splash_edgedamage),
+               WEP_CVAR(shockwave, blast_splash_radius),
+               w_shotdir * WEP_CVAR(shockwave, blast_splash_force),
+               WEP_SHOCKWAVE.m_id,
+               0,
+               self
+       );
+
+       // splash damage/jumping trace
+       head = WarpZone_FindRadius(
+               attack_hitpos,
+               max(
+                       WEP_CVAR(shockwave, blast_splash_radius),
+                       WEP_CVAR(shockwave, blast_jump_radius)
+               ),
+               false
+       );
+
+       while(head)
+       {
+               if(head.takedamage)
+               {
+                       float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest);
+
+                       if((head == self) && (distance_to_head <= WEP_CVAR(shockwave, blast_jump_radius)))
+                       {
+                               // ========================
+                               //  BLAST JUMP CALCULATION
+                               // ========================
+
+                               // calculate importance of distance and accuracy for this attack
+                               multiplier_from_accuracy = (1 -
+                                       (distance_to_head ?
+                                               min(1, (distance_to_head / WEP_CVAR(shockwave, blast_jump_radius)))
+                                               :
+                                               0
+                                       )
+                               );
+                               multiplier_from_distance = (1 -
+                                       (distance_to_hit ?
+                                               min(1, (distance_to_hit / distance_to_end))
+                                               :
+                                               0
+                                       )
+                               );
+                               multiplier =
+                                       max(
+                                               WEP_CVAR(shockwave, blast_jump_multiplier_min),
+                                               (
+                                                       (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_jump_multiplier_accuracy))
+                                                       +
+                                                       (multiplier_from_distance * WEP_CVAR(shockwave, blast_jump_multiplier_distance))
+                                               )
+                                       );
+
+                               // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
+                               final_damage =
+                                       (
+                                               (WEP_CVAR(shockwave, blast_jump_damage) * multiplier)
+                                               +
+                                               (WEP_CVAR(shockwave, blast_jump_edgedamage) * (1 - multiplier))
+                                       );
+
+                               // figure out the direction of force
+                               vel = normalize(combine_to_vector(head.velocity.x, head.velocity.y, 0));
+                               vel *=
+                                       (
+                                               bound(0, (vlen(vel) / autocvar_sv_maxspeed), 1)
+                                               *
+                                               WEP_CVAR(shockwave, blast_jump_force_velocitybias)
+                                       );
+                               final_force = normalize((CENTER_OR_VIEWOFS(head) - attack_hitpos) + vel);
+
+                               // now multiply the direction by force units
+                               final_force *= (WEP_CVAR(shockwave, blast_jump_force) * multiplier);
+                               final_force.z *= WEP_CVAR(shockwave, blast_jump_force_zscale);
+
+                               // trigger damage with this calculated info
+                               Damage(
+                                       head,
+                                       self,
+                                       self,
+                                       final_damage,
+                                       WEP_SHOCKWAVE.m_id,
+                                       head.origin,
+                                       final_force
+                               );
+
+                               #ifdef DEBUG_SHOCKWAVE
+                               LOG_INFO(sprintf(
+                                       "SELF HIT: multiplier = %f, damage = %f, force = %f... "
+                                       "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n",
+                                       multiplier,
+                                       final_damage,
+                                       vlen(final_force),
+                                       multiplier_from_accuracy,
+                                       multiplier_from_distance
+                               ));
+                               #endif
+                       }
+                       else if(distance_to_head <= WEP_CVAR(shockwave, blast_splash_radius))
+                       {
+                               // ==========================
+                               //  BLAST SPLASH CALCULATION
+                               // ==========================
+
+                               // calculate importance of distance and accuracy for this attack
+                               multiplier_from_accuracy = (1 -
+                                       (distance_to_head ?
+                                               min(1, (distance_to_head / WEP_CVAR(shockwave, blast_splash_radius)))
+                                               :
+                                               0
+                                       )
+                               );
+                               multiplier_from_distance = (1 -
+                                       (distance_to_hit ?
+                                               min(1, (distance_to_hit / distance_to_end))
+                                               :
+                                               0
+                                       )
+                               );
+                               multiplier =
+                                       max(
+                                               WEP_CVAR(shockwave, blast_splash_multiplier_min),
+                                               (
+                                                       (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_splash_multiplier_accuracy))
+                                                       +
+                                                       (multiplier_from_distance * WEP_CVAR(shockwave, blast_splash_multiplier_distance))
+                                               )
+                                       );
+
+                               // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
+                               final_damage =
+                                       (
+                                               (WEP_CVAR(shockwave, blast_splash_damage) * multiplier)
+                                               +
+                                               (WEP_CVAR(shockwave, blast_splash_edgedamage) * (1 - multiplier))
+                                       );
+
+                               // figure out the direction of force
+                               final_force = (w_shotdir * WEP_CVAR(shockwave, blast_splash_force_forwardbias));
+                               final_force = normalize(CENTER_OR_VIEWOFS(head) - (attack_hitpos - final_force));
+                               //te_lightning2(world, attack_hitpos, (attack_hitpos + (final_force * 200)));
+
+                               // now multiply the direction by force units
+                               final_force *= (WEP_CVAR(shockwave, blast_splash_force) * multiplier);
+                               final_force.z *= WEP_CVAR(shockwave, blast_force_zscale);
+
+                               // queue damage with this calculated info
+                               if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { queue = min(queue + 1, MAX_SHOCKWAVE_HITS); }
+
+                               #ifdef DEBUG_SHOCKWAVE
+                               LOG_INFO(sprintf(
+                                       "SPLASH HIT: multiplier = %f, damage = %f, force = %f... "
+                                       "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n",
+                                       multiplier,
+                                       final_damage,
+                                       vlen(final_force),
+                                       multiplier_from_accuracy,
+                                       multiplier_from_distance
+                               ));
+                               #endif
+                       }
+               }
+               head = head.chain;
+       }
+
+       // cone damage trace
+       head = WarpZone_FindRadius(w_shotorg, WEP_CVAR(shockwave, blast_distance), false);
+       while(head)
+       {
+               if((head != self) && head.takedamage)
+               {
+                       // ========================
+                       //  BLAST CONE CALCULATION
+                       // ========================
+
+                       // 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 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);
+                       a = h * (normalize(center - self.origin) * w_shotdir);
+                       // WEAPONTODO: replace with simpler method
+
+                       vector nearest_on_line = (w_shotorg + a * w_shotdir);
+                       vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line);
+
+                       if((vlen(head.WarpZone_findradius_dist) <= WEP_CVAR(shockwave, blast_distance))
+                               && (W_Shockwave_Attack_IsVisible(head, nearest_on_line, w_shotorg, attack_endpos)))
+                       {
+                               // calculate importance of distance and accuracy for this attack
+                               multiplier_from_accuracy = (1 -
+                                       W_Shockwave_Attack_CheckSpread(
+                                               nearest_to_attacker,
+                                               nearest_on_line,
+                                               w_shotorg,
+                                               attack_endpos
+                                       )
+                               );
+                               multiplier_from_distance = (1 -
+                                       (distance_to_hit ?
+                                               min(1, (vlen(head.WarpZone_findradius_dist) / distance_to_end))
+                                               :
+                                               0
+                                       )
+                               );
+                               multiplier =
+                                       max(
+                                               WEP_CVAR(shockwave, blast_multiplier_min),
+                                               (
+                                                       (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_multiplier_accuracy))
+                                                       +
+                                                       (multiplier_from_distance * WEP_CVAR(shockwave, blast_multiplier_distance))
+                                               )
+                                       );
+
+                               // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
+                               final_damage =
+                                       (
+                                               (WEP_CVAR(shockwave, blast_damage) * multiplier)
+                                               +
+                                               (WEP_CVAR(shockwave, blast_edgedamage) * (1 - multiplier))
+                                       );
+
+                               // figure out the direction of force
+                               final_force = (w_shotdir * WEP_CVAR(shockwave, blast_force_forwardbias));
+                               final_force = normalize(center - (nearest_on_line - final_force));
+                               //te_lightning2(world, nearest_on_line, (attack_hitpos + (final_force * 200)));
+
+                               // now multiply the direction by force units
+                               final_force *= (WEP_CVAR(shockwave, blast_force) * multiplier);
+                               final_force.z *= WEP_CVAR(shockwave, blast_force_zscale);
+
+                               // queue damage with this calculated info
+                               if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { queue = min(queue + 1, MAX_SHOCKWAVE_HITS); }
+
+                               #ifdef DEBUG_SHOCKWAVE
+                               LOG_INFO(sprintf(
+                                       "BLAST HIT: multiplier = %f, damage = %f, force = %f... "
+                                       "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n",
+                                       multiplier,
+                                       final_damage,
+                                       vlen(final_force),
+                                       multiplier_from_accuracy,
+                                       multiplier_from_distance
+                               ));
+                               #endif
+                       }
+               }
+               head = head.chain;
+       }
+
+       for(i = 1; i <= queue; ++i)
+       {
+               head = shockwave_hit[i-1];
+               final_force = shockwave_hit_force[i-1];
+               final_damage = shockwave_hit_damage[i-1];
+
+               Damage(
+                       head,
+                       self,
+                       self,
+                       final_damage,
+                       WEP_SHOCKWAVE.m_id,
+                       head.origin,
+                       final_force
+               );
+
+               if(accuracy_isgooddamage(self.realowner, head))
+               {
+                       LOG_INFO("wtf\n");
+                       accuracy_add(self.realowner, WEP_SHOCKWAVE.m_id, 0, final_damage);
+               }
+
+               #ifdef DEBUG_SHOCKWAVE
+               LOG_INFO(sprintf(
+                       "SHOCKWAVE by %s: damage = %f, force = %f.\n",
+                       self.netname,
+                       final_damage,
+                       vlen(final_force)
+               ));
+               #endif
+
+               shockwave_hit[i-1] = world;
+               shockwave_hit_force[i-1] = '0 0 0';
+               shockwave_hit_damage[i-1] = 0;
+       }
+}
+
+bool W_Shockwave(int req)
+{
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       if(vlen(self.origin - self.enemy.origin) <= WEP_CVAR(shockwave, melee_range))
+                               { 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)
+                       {
+                               if(time >= self.shockwave_blasttime) // handle refire separately so the secondary can be fired straight after a primary
+                               {
+                                       if(weapon_prepareattack(0, WEP_CVAR(shockwave, blast_animtime)))
+                                       {
+                                               W_Shockwave_Attack();
+                                               self.shockwave_blasttime = time + WEP_CVAR(shockwave, blast_refire) * W_WeaponRateFactor();
+                                               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(shockwave, blast_animtime), w_ready);
+                                       }
+                               }
+                       }
+                       else if(self.BUTTON_ATCK2)
+                       {
+                               //if(self.clip_load >= 0) // we are not currently reloading
+                               if(!self.crouch) // no crouchmelee please
+                               if(weapon_prepareattack(1, WEP_CVAR(shockwave, melee_refire)))
+                               {
+                                       // attempt forcing playback of the anim by switching to another anim (that we never play) here...
+                                       weapon_thinkf(WFRAME_FIRE1, 0, W_Shockwave_Melee);
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_model("models/uziflash.md3");
+                       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(W_Sound("lasergun_fire"));
+                       precache_sound(W_Sound("shotgun_melee"));
+                       SHOCKWAVE_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP);
+                       return true;
+               }
+               case WR_CHECKAMMO1:
+               case WR_CHECKAMMO2:
+               {
+                       // shockwave has infinite ammo
+                       return true;
+               }
+               case WR_CONFIG:
+               {
+                       SHOCKWAVE_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       return WEAPON_THINKING_WITH_PORTALS;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                               return WEAPON_SHOCKWAVE_MURDER_SLAP;
+                       else
+                               return WEAPON_SHOCKWAVE_MURDER;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+// WEAPONTODO: add client side settings for these
+const float SW_MAXALPHA = 0.5;
+const float SW_FADETIME = 0.4;
+const float SW_DISTTOMIN = 200;
+void Draw_Shockwave()
+{
+       // fading/removal control
+       float a = bound(0, (SW_MAXALPHA - ((time - self.sw_time) / SW_FADETIME)), SW_MAXALPHA);
+       if(a < ALPHA_MIN_VISIBLE) { remove(self); }
+
+       // WEAPONTODO: save this only once when creating the entity
+       vector sw_color = getcsqcplayercolor(self.sv_entnum); // GetTeamRGB(GetPlayerColor(self.sv_entnum));
+
+       // WEAPONTODO: trace to find what we actually hit
+       vector endpos = (self.sw_shotorg + (self.sw_shotdir * self.sw_distance));
+
+       vectorvectors(self.sw_shotdir);
+       vector right = v_right; // save this for when we do makevectors later
+       vector up = v_up; // save this for when we do makevectors later
+
+       // WEAPONTODO: combine and simplify these calculations
+       vector min_end = ((self.sw_shotorg + (self.sw_shotdir * SW_DISTTOMIN)) + (up * self.sw_spread_min));
+       vector max_end = (endpos + (up * self.sw_spread_max));
+       float spread_to_min = vlen(normalize(min_end - self.sw_shotorg) - self.sw_shotdir);
+       float spread_to_max = vlen(normalize(max_end - min_end) - self.sw_shotdir);
+
+       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 deviation, angle = '0 0 0';
+       float counter, divisions = 20;
+       for(counter = 0; counter < divisions; ++counter)
+       {
+               // perfect circle effect lines
+               makevectors('0 360 0' * (0.75 + (counter - 0.5) / divisions));
+               angle.y = v_forward.x;
+               angle.z = v_forward.y;
+
+               // first do the spread_to_min effect
+               deviation = angle * spread_to_min;
+               deviation = ((self.sw_shotdir + (right * deviation.y) + (up * deviation.z)));
+               new_min_dist = SW_DISTTOMIN;
+               new_min_end = (self.sw_shotorg + (deviation * new_min_dist));
+               //te_lightning2(world, new_min_end, self.sw_shotorg);
+
+               // then calculate spread_to_max effect
+               deviation = angle * spread_to_max;
+               deviation = ((self.sw_shotdir + (right * deviation.y) + (up * deviation.z)));
+               new_max_dist = vlen(new_min_end - 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)
+               {
+                       // draw from shot origin to min spread radius
+                       R_BeginPolygon("", DRAWFLAG_NORMAL);
+                       R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a);
+                       R_PolygonVertex(new_min_end, '0 0 0', sw_color, a);
+                       R_PolygonVertex(self.sw_shotorg, '0 0 0', sw_color, a);
+                       R_EndPolygon();
+
+                       // draw from min spread radius to max spread radius
+                       R_BeginPolygon("", DRAWFLAG_NORMAL);
+                       R_PolygonVertex(new_min_end, '0 0 0', sw_color, a);
+                       R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a);
+                       R_PolygonVertex(prev_max_end, '0 0 0', sw_color, a);
+                       R_PolygonVertex(new_max_end, '0 0 0', sw_color, a);
+                       R_EndPolygon();
+               }
+
+               prev_min_end = new_min_end;
+               prev_max_end = new_max_end;
+
+               // last division only
+               if((counter + 1) == divisions)
+               {
+                       // draw from shot origin to min spread radius
+                       R_BeginPolygon("", DRAWFLAG_NORMAL);
+                       R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a);
+                       R_PolygonVertex(first_min_end, '0 0 0', sw_color, a);
+                       R_PolygonVertex(self.sw_shotorg, '0 0 0', sw_color, a);
+                       R_EndPolygon();
+
+                       // draw from min spread radius to max spread radius
+                       R_BeginPolygon("", DRAWFLAG_NORMAL);
+                       R_PolygonVertex(first_min_end, '0 0 0', sw_color, a);
+                       R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a);
+                       R_PolygonVertex(prev_max_end, '0 0 0', sw_color, a);
+                       R_PolygonVertex(first_max_end, '0 0 0', sw_color, a);
+                       R_EndPolygon();
+               }
+       }
+}
+
+void Net_ReadShockwaveParticle(void)
+{
+       entity shockwave;
+       shockwave = spawn();
+       shockwave.draw = Draw_Shockwave;
+
+       shockwave.sw_shotorg_x = ReadCoord(); shockwave.sw_shotorg_y = ReadCoord(); shockwave.sw_shotorg_z = ReadCoord();
+       shockwave.sw_shotdir_x = ReadCoord(); shockwave.sw_shotdir_y = ReadCoord(); shockwave.sw_shotdir_z = ReadCoord();
+
+       shockwave.sw_distance = ReadShort();
+       shockwave.sw_spread_max = ReadByte();
+       shockwave.sw_spread_min = ReadByte();
+
+       shockwave.sv_entnum = ReadByte();
+
+       shockwave.sw_time = time;
+}
+
+bool W_Shockwave(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       // handled by Net_ReadShockwaveParticle
+                       //vector org2;
+                       //org2 = w_org + w_backoff * 2;
+                       //pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1);
+                       return false;
+               }
+               case WR_INIT:
+               {
+                       //precache_sound(W_Sound("ric1"));
+                       //precache_sound(W_Sound("ric2"));
+                       //precache_sound(W_Sound("ric3"));
+                       return false;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/shotgun.qc b/qcsrc/common/weapons/weapon/shotgun.qc
new file mode 100644 (file)
index 0000000..9024bbf
--- /dev/null
@@ -0,0 +1,392 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ SHOTGUN,
+/* function  */ W_Shotgun,
+/* ammotype  */ ammo_shells,
+/* impulse   */ 2,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
+/* rating    */ BOT_PICKUP_RATING_LOW,
+/* color     */ '0.5 0.25 0',
+/* modelname */ "shotgun",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairshotgun 0.65",
+/* wepimg    */ "weaponshotgun",
+/* refname   */ "shotgun",
+/* wepname   */ _("Shotgun")
+);
+
+#define SHOTGUN_SETTINGS(w_cvar,w_prop) SHOTGUN_SETTINGS_LIST(w_cvar, w_prop, SHOTGUN, shotgun)
+#define SHOTGUN_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, PRI,  ammo) \
+       w_cvar(id, sn, BOTH, animtime) \
+       w_cvar(id, sn, BOTH, refire) \
+       w_cvar(id, sn, PRI,  bullets) \
+       w_cvar(id, sn, BOTH, damage) \
+       w_cvar(id, sn, BOTH, force) \
+       w_cvar(id, sn, PRI,  solidpenetration) \
+       w_cvar(id, sn, PRI,  spread) \
+       w_cvar(id, sn, NONE, secondary) \
+       w_cvar(id, sn, SEC,  melee_time) \
+       w_cvar(id, sn, SEC,  melee_no_doubleslap) \
+       w_cvar(id, sn, SEC,  melee_traces) \
+       w_cvar(id, sn, SEC,  melee_swing_up) \
+       w_cvar(id, sn, SEC,  melee_swing_side) \
+       w_cvar(id, sn, SEC,  melee_nonplayerdamage) \
+       w_cvar(id, sn, SEC,  melee_multihit) \
+       w_cvar(id, sn, SEC,  melee_delay) \
+       w_cvar(id, sn, SEC,  melee_range) \
+       w_cvar(id, sn, SEC,  alt_animtime) \
+       w_cvar(id, sn, SEC,  alt_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
+SHOTGUN_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_shotgun(void) { weapon_defaultspawnfunc(WEP_SHOTGUN.m_id); }
+
+void W_Shotgun_Attack(float isprimary)
+{
+       float   sc;
+       entity flash;
+
+       W_DecreaseAmmo(WEP_CVAR_PRI(shotgun, ammo));
+
+       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.m_id, 0);
+
+       Send_Effect(EFFECT_SHOTGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, WEP_CVAR_PRI(shotgun, ammo));
+
+       // casing code
+       if(autocvar_g_casings >= 1)
+               for(sc = 0;sc < WEP_CVAR_PRI(shotgun, ammo);sc = sc + 1)
+                       SpawnCasing(((random() * 50 + 50) * v_right) - (v_forward * (random() * 25 + 25)) - ((random() * 5 - 30) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 1, self);
+
+       // muzzle flash for 1st person view
+       flash = spawn();
+       setmodel(flash, "models/uziflash.md3"); // precision set below
+       flash.think = SUB_Remove;
+       flash.nextthink = time + 0.06;
+       flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
+       W_AttachToShotorg(flash, '5 0 0');
+}
+
+.float swing_prev;
+.entity swing_alreadyhit;
+void W_Shotgun_Melee_Think(void)
+{
+       // declarations
+       float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
+       entity target_victim;
+       vector targpos;
+
+       if(!self.cnt) // set start time of melee
+       {
+               self.cnt = time;
+               W_PlayStrengthSound(self.realowner);
+       }
+
+       makevectors(self.realowner.v_angle); // update values for v_* vectors
+
+       // calculate swing percentage based on time
+       meleetime = WEP_CVAR_SEC(shotgun, melee_time) * W_WeaponRateFactor();
+       swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10);
+       f = ((1 - swing) * WEP_CVAR_SEC(shotgun, melee_traces));
+
+       // check to see if we can still continue, otherwise give up now
+       if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR_SEC(shotgun, 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_SEC(shotgun, melee_traces))) * 2 - 1);
+
+               targpos = (self.realowner.origin + self.realowner.view_ofs
+                       + (v_forward * WEP_CVAR_SEC(shotgun, melee_range))
+                       + (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));
+
+               // 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(trace_ent));
+
+               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_SEC(shotgun, melee_nonplayerdamage)))
+               {
+                       target_victim = trace_ent; // so it persists through other calls
+
+                       if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught.
+                               swing_damage = (WEP_CVAR_SEC(shotgun, damage) * min(1, swing_factor + 1));
+                       else
+                               swing_damage = (WEP_CVAR_SEC(shotgun, melee_nonplayerdamage) * min(1, swing_factor + 1));
+
+                       //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n"));
+
+                       Damage(target_victim, self.realowner, self.realowner,
+                               swing_damage, WEP_SHOTGUN.m_id | HITTYPE_SECONDARY,
+                               self.realowner.origin + self.realowner.view_ofs,
+                               v_forward * WEP_CVAR_SEC(shotgun, force));
+
+                       if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_SHOTGUN.m_id, 0, swing_damage); }
+
+                       // draw large red flash for debugging
+                       //te_customflash(targpos, 200, 2, '15 0 0');
+
+                       if(WEP_CVAR_SEC(shotgun, melee_multihit)) // allow multiple hits with one swing, but not against the same player twice.
+                       {
+                               self.swing_alreadyhit = target_victim;
+                               continue; // move along to next trace
+                       }
+                       else
+                       {
+                               remove(self);
+                               return;
+                       }
+               }
+       }
+
+       if(time >= self.cnt + meleetime)
+       {
+               // melee is finished
+               remove(self);
+               return;
+       }
+       else
+       {
+               // set up next frame
+               self.swing_prev = i;
+               self.nextthink = time;
+       }
+}
+
+void W_Shotgun_Attack2(void)
+{
+       sound(self, CH_WEAPON_A, W_Sound("shotgun_melee"), VOL_BASE, ATTEN_NORM);
+       weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(shotgun, animtime), w_ready);
+
+       entity meleetemp;
+       meleetemp = spawn();
+       meleetemp.realowner = self;
+       meleetemp.think = W_Shotgun_Melee_Think;
+       meleetemp.nextthink = time + WEP_CVAR_SEC(shotgun, melee_delay) * W_WeaponRateFactor();
+       W_SetupShot_Range(self, true, 0, "", 0, WEP_CVAR_SEC(shotgun, damage), WEP_CVAR_SEC(shotgun, melee_range));
+}
+
+// alternate secondary weapon frames
+void W_Shotgun_Attack3_Frame2()
+{
+       if (!WEP_ACTION(self.weapon, WR_CHECKAMMO2))
+       if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+       {
+               W_SwitchWeapon_Force(self, w_getbestweapon(self));
+               w_ready();
+               return;
+       }
+
+       sound(self, CH_WEAPON_SINGLE, "misc/null.wav", VOL_BASE, ATTN_NORM); // kill previous sound
+       W_Shotgun_Attack(true); // actually is secondary, but we trick the last shot into playing full reload sound
+       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), w_ready);
+}
+void W_Shotgun_Attack3_Frame1()
+{
+       if (!WEP_ACTION(self.weapon, WR_CHECKAMMO2))
+       if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+       {
+               W_SwitchWeapon_Force(self, w_getbestweapon(self));
+               w_ready();
+               return;
+       }
+
+       W_Shotgun_Attack(false);
+       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame2);
+}
+
+.float shotgun_primarytime;
+
+float W_Shotgun(float req)
+{
+       float ammo_amount;
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       if(vlen(self.origin-self.enemy.origin) <= WEP_CVAR_SEC(shotgun, melee_range))
+                               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(WEP_CVAR(shotgun, reload_ammo) && self.clip_load < WEP_CVAR_PRI(shotgun, ammo)) // forced reload
+                       {
+                               // don't force reload an empty shotgun if its melee attack is active
+                               if(WEP_CVAR(shotgun, secondary) < 2)
+                                       WEP_ACTION(self.weapon, WR_RELOAD);
+                       }
+                       else
+                       {
+                               if(self.BUTTON_ATCK)
+                               {
+                                       if(time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary
+                                       {
+                                               if(weapon_prepareattack(0, WEP_CVAR_PRI(shotgun, animtime)))
+                                               {
+                                                       W_Shotgun_Attack(true);
+                                                       self.shotgun_primarytime = time + WEP_CVAR_PRI(shotgun, refire) * W_WeaponRateFactor();
+                                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(shotgun, animtime), w_ready);
+                                               }
+                                       }
+                               }
+                               else if(self.BUTTON_ATCK2 && WEP_CVAR(shotgun, secondary) == 2)
+                               {
+                                       if(time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary
+                                       {
+                                               if(weapon_prepareattack(0, WEP_CVAR_SEC(shotgun, alt_animtime)))
+                                               {
+                                                       W_Shotgun_Attack(false);
+                                                       self.shotgun_primarytime = time + WEP_CVAR_SEC(shotgun, alt_refire) * W_WeaponRateFactor();
+                                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame1);
+                                               }
+                                       }
+                               }
+                       }
+                       if(self.clip_load >= 0) // we are not currently reloading
+                       if(!self.crouch) // no crouchmelee please
+                       if(WEP_CVAR(shotgun, secondary) == 1)
+                       if((self.BUTTON_ATCK && self.WEP_AMMO(SHOTGUN) <= 0 && !(self.items & IT_UNLIMITED_WEAPON_AMMO)) || self.BUTTON_ATCK2)
+                       if(weapon_prepareattack(1, WEP_CVAR_SEC(shotgun, refire)))
+                       {
+                               // attempt forcing playback of the anim by switching to another anim (that we never play) here...
+                               weapon_thinkf(WFRAME_FIRE1, 0, W_Shotgun_Attack2);
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_model("models/uziflash.md3");
+                       precache_model(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(W_Sound("shotgun_fire"));
+                       precache_sound(W_Sound("shotgun_melee"));
+                       SHOTGUN_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP);
+                       return true;
+               }
+               case WR_SETUP:
+               {
+                       self.ammo_field = ammo_none;
+                       return true;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       ammo_amount = self.WEP_AMMO(SHOTGUN) >= WEP_CVAR_PRI(shotgun, ammo);
+                       ammo_amount += self.(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
+                       return ammo_amount;
+               }
+               case WR_CHECKAMMO2:
+               {
+                       if(IS_BOT_CLIENT(self))
+                       if(vlen(self.origin-self.enemy.origin) > WEP_CVAR_SEC(shotgun, melee_range))
+                               return false; // bots cannot use secondary out of range (fixes constant melee when out of ammo)
+                       switch(WEP_CVAR(shotgun, secondary))
+                       {
+                               case 1: return true; // melee does not use ammo
+                               case 2: // secondary triple shot
+                               {
+                                       ammo_amount = self.WEP_AMMO(SHOTGUN) >= WEP_CVAR_PRI(shotgun, ammo);
+                                       ammo_amount += self.(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo);
+                                       return ammo_amount;
+                               }
+                               default: return false; // secondary unavailable
+                       }
+               }
+               case WR_CONFIG:
+               {
+                       SHOTGUN_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       W_Reload(WEP_CVAR_PRI(shotgun, ammo), W_Sound("reload")); // WEAPONTODO
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       return WEAPON_THINKING_WITH_PORTALS;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                               return WEAPON_SHOTGUN_MURDER_SLAP;
+                       else
+                               return WEAPON_SHOTGUN_MURDER;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+.float prevric;
+float W_Shotgun(float req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 2;
+                       pointparticles(particleeffectnum(EFFECT_SHOTGUN_IMPACT), org2, w_backoff * 1000, 1);
+                       if(!w_issilent && time - self.prevric > 0.25)
+                       {
+                               if(w_random < 0.0165)
+                                       sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTEN_NORM);
+                               else if(w_random < 0.033)
+                                       sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTEN_NORM);
+                               else if(w_random < 0.05)
+                                       sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTEN_NORM);
+                               self.prevric = time;
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("ric1"));
+                       precache_sound(W_Sound("ric2"));
+                       precache_sound(W_Sound("ric3"));
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/tuba.qc b/qcsrc/common/weapons/weapon/tuba.qc
new file mode 100644 (file)
index 0000000..686c89e
--- /dev/null
@@ -0,0 +1,514 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ TUBA,
+/* function  */ W_Tuba,
+/* ammotype  */ ammo_none,
+/* impulse   */ 1,
+/* flags     */ WEP_FLAG_HIDDEN | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* color     */ '0 1 0',
+/* modelname */ "tuba",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairtuba",
+/* wepimg    */ "weapontuba",
+/* refname   */ "tuba",
+/* xgettext:no-c-format */
+/* wepname   */ _("@!#%'n Tuba")
+);
+
+#define TUBA_SETTINGS(w_cvar,w_prop) TUBA_SETTINGS_LIST(w_cvar, w_prop, TUBA, tuba)
+#define TUBA_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, NONE, animtime) \
+       w_cvar(id, sn, NONE, attenuation) \
+       w_cvar(id, sn, NONE, damage) \
+       w_cvar(id, sn, NONE, edgedamage) \
+       w_cvar(id, sn, NONE, fadetime) \
+       w_cvar(id, sn, NONE, force) \
+       w_cvar(id, sn, NONE, pitchstep) \
+       w_cvar(id, sn, NONE, radius) \
+       w_cvar(id, sn, NONE, refire) \
+       w_cvar(id, sn, NONE, volume) \
+       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
+TUBA_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+float W_Tuba_MarkClientOnlyFieldsAsUsed() {
+       // These variables are only used by client/tuba.qc. TODO: move client/tuba.qc code here.
+       return WEP_CVAR(tuba, fadetime) + WEP_CVAR(tuba, pitchstep) + WEP_CVAR(tuba, volume);
+}
+
+.entity tuba_note;
+.float tuba_smoketime;
+.float tuba_instrument;
+
+#define MAX_TUBANOTES 32
+.float tuba_lastnotes_last;
+.float tuba_lastnotes_cnt; // over
+.vector tuba_lastnotes[MAX_TUBANOTES];
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_tuba(void) { weapon_defaultspawnfunc(WEP_TUBA.m_id); }
+
+bool W_Tuba_HasPlayed(entity pl, string melody, int instrument, bool ignorepitch, float mintempo, float maxtempo)
+{
+       float i, j, mmin, mmax, nolength;
+       float n = tokenize_console(melody);
+       if(n > pl.tuba_lastnotes_cnt)
+               return false;
+       float pitchshift = 0;
+
+       if(instrument >= 0)
+               if(pl.tuba_instrument != instrument)
+                       return false;
+
+       // verify notes...
+       nolength = false;
+       for(i = 0; i < n; ++i)
+       {
+               vector v = pl.(tuba_lastnotes[(pl.tuba_lastnotes_last - i + MAX_TUBANOTES) % MAX_TUBANOTES]);
+               float ai = stof(argv(n - i - 1));
+               float np = floor(ai);
+               if(ai == np)
+                       nolength = true;
+               // n counts the last played notes BACKWARDS
+               // _x is start
+               // _y is end
+               // _z is note pitch
+               if(ignorepitch && i == 0)
+               {
+                       pitchshift = np - v.z;
+               }
+               else
+               {
+                       if(v.z + pitchshift != np)
+                               return false;
+               }
+       }
+
+       // now we know the right NOTES were played
+       if(!nolength)
+       {
+               // verify rhythm...
+               float ti = 0;
+               if(maxtempo > 0)
+                       mmin = 240 / maxtempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec
+               else
+                       mmin = 0;
+               if(mintempo > 0)
+                       mmax = 240 / mintempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec
+               else
+                       mmax = 240; // you won't try THAT hard... (tempo 1)
+               //printf("initial tempo rules: %f %f\n", mmin, mmax);
+
+               for(i = 0; i < n; ++i)
+               {
+                       vector vi = pl.(tuba_lastnotes[(pl.tuba_lastnotes_last - i + MAX_TUBANOTES) % MAX_TUBANOTES]);
+                       float ai = stof(argv(n - i - 1));
+                       ti -= 1 / (ai - floor(ai));
+                       float tj = ti;
+                       for(j = i+1; j < n; ++j)
+                       {
+                               vector vj = pl.(tuba_lastnotes[(pl.tuba_lastnotes_last - j + MAX_TUBANOTES) % MAX_TUBANOTES]);
+                               float aj = stof(argv(n - j - 1));
+                               tj -= (aj - floor(aj));
+
+                               // note i should be at m*ti+b
+                               // note j should be at m*tj+b
+                               // so:
+                               // we have a LINE l, so that
+                               // vi_x <= l(ti) <= vi_y
+                               // vj_x <= l(tj) <= vj_y
+                               // what is m?
+
+                               // vi_x <= vi_y <= vj_x <= vj_y
+                               // ti <= tj
+                               //printf("first note: %f to %f, should be %f\n", vi_x, vi_y, ti);
+                               //printf("second note: %f to %f, should be %f\n", vj_x, vj_y, tj);
+                               //printf("m1 = %f\n", (vi_x - vj_y) / (ti - tj));
+                               //printf("m2 = %f\n", (vi_y - vj_x) / (ti - tj));
+                               mmin = max(mmin, (vi.x - vj.y) / (ti - tj)); // lower bound
+                               mmax = min(mmax, (vi.y - vj.x) / (ti - tj)); // upper bound
+                       }
+               }
+
+               if(mmin > mmax) // rhythm fail
+                       return false;
+       }
+
+       pl.tuba_lastnotes_cnt = 0;
+
+       return true;
+}
+
+void W_Tuba_NoteOff(void)
+{
+       // we have a note:
+       //   on: self.spawnshieldtime
+       //   off: time
+       //   note: self.cnt
+       if(self.owner.tuba_note == self)
+       {
+               self.owner.tuba_lastnotes_last = (self.owner.tuba_lastnotes_last + 1) % MAX_TUBANOTES;
+               self.owner.(tuba_lastnotes[self.owner.tuba_lastnotes_last]) = eX * self.spawnshieldtime + eY * time + eZ * self.cnt;
+               self.owner.tuba_note = world;
+               self.owner.tuba_lastnotes_cnt = bound(0, self.owner.tuba_lastnotes_cnt + 1, MAX_TUBANOTES);
+
+               string s;
+               s = trigger_magicear_processmessage_forallears(self.owner, 0, world, string_null);
+               if(s != "")
+               {
+                       // simulate a server message
+                       switch(self.tuba_instrument)
+                       {
+                               default:
+                               case 0: // Tuba
+                                       bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Tuba: ^7", s, "\n"));
+                                       break;
+                               case 1:
+                                       bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Accordeon: ^7", s, "\n"));
+                                       break;
+                               case 2:
+                                       bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Klein Bottle: ^7", s, "\n"));
+                                       break;
+                       }
+               }
+       }
+       remove(self);
+}
+
+int W_Tuba_GetNote(entity pl, int hittype)
+{
+       float movestate = 5;
+       if (pl.movement.x < 0)          movestate -= 3;
+       else if (pl.movement.x > 0)     movestate += 3;
+       if (pl.movement.y < 0)          movestate -= 1;
+       else if (pl.movement.y > 0)     movestate += 1;
+
+       int note = 0;
+       switch(movestate)
+       {
+       // layout: originally I wanted
+       //   eb e  e#=f
+       //   B  c  d
+       //   Gb G  G#
+       // but then you only use forward and right key. So to make things more
+       // interesting, I swapped B with e#. Har har har...
+       //   eb e  B
+       // f=e# c  d
+       //   Gb G  G#
+               case 1: note = -6; break; // Gb
+               case 2: note = -5; break; // G
+               case 3: note = -4; break; // G#
+               case 4: note = +5; break; // e#
+               default:
+               case 5: note =  0; break; // c
+               case 6: note = +2; break; // d
+               case 7: note = +3; break; // eb
+               case 8: note = +4; break; // e
+               case 9: note = -1; break; // B
+       }
+       if(pl.BUTTON_CROUCH)
+               note -= 12;
+       if(pl.BUTTON_JUMP)
+               note += 12;
+       if(hittype & HITTYPE_SECONDARY)
+               note += 7;
+
+       // we support two kinds of tubas, those tuned in Eb and those tuned in C
+       // kind of tuba currently is player slot number, or team number if in
+       // teamplay
+       // that way, holes in the range of notes are "plugged"
+       if(teamplay)
+       {
+               if(pl.team == NUM_TEAM_2 || pl.team == NUM_TEAM_4)
+                       note += 3;
+       }
+       else
+       {
+               if(pl.clientcolors & 1)
+                       note += 3;
+       }
+
+       // total range of notes:
+       //                       0
+       //                 ***  ** ****
+       //                        ***  ** ****
+       //     ***  ** ****
+       //            ***  ** ****
+       //     ***  ********************* ****
+       //     -18.........................+12
+       //        ***  ********************* ****
+       //     -18............................+15
+       //     with jump: ... +24
+       //     ... +27
+       return note;
+}
+
+bool W_Tuba_NoteSendEntity(entity to, int sf)
+{
+       int f;
+
+       msg_entity = to;
+       if(!sound_allowed(MSG_ONE, self.realowner))
+               return false;
+
+       WriteByte(MSG_ENTITY, ENT_CLIENT_TUBANOTE);
+       WriteByte(MSG_ENTITY, sf);
+       if(sf & 1)
+       {
+               WriteChar(MSG_ENTITY, self.cnt);
+               f = 0;
+               if(self.realowner != to)
+                       f |= 1;
+               f |= 2 * self.tuba_instrument;
+               WriteByte(MSG_ENTITY, f);
+       }
+       if(sf & 2)
+       {
+               WriteCoord(MSG_ENTITY, self.origin.x);
+               WriteCoord(MSG_ENTITY, self.origin.y);
+               WriteCoord(MSG_ENTITY, self.origin.z);
+       }
+       return true;
+}
+
+void W_Tuba_NoteThink(void)
+{
+       float dist_mult;
+       float vol0, vol1;
+       vector dir0, dir1;
+       vector v;
+       entity e;
+       if(time > self.teleport_time)
+       {
+               W_Tuba_NoteOff();
+               return;
+       }
+       self.nextthink = time;
+       dist_mult = WEP_CVAR(tuba, attenuation) / autocvar_snd_soundradius;
+       FOR_EACH_REALCLIENT(e)
+       if(e != self.realowner)
+       {
+               v = self.origin - (e.origin + e.view_ofs);
+               vol0 = max(0, 1 - vlen(v) * dist_mult);
+               dir0 = normalize(v);
+               v = self.realowner.origin - (e.origin + e.view_ofs);
+               vol1 = max(0, 1 - vlen(v) * dist_mult);
+               dir1 = normalize(v);
+               if(fabs(vol0 - vol1) > 0.005) // 0.5 percent change in volume
+               {
+                       setorigin(self, self.realowner.origin);
+                       self.SendFlags |= 2;
+                       break;
+               }
+               if(dir0 * dir1 < 0.9994) // 2 degrees change in angle
+               {
+                       setorigin(self, self.realowner.origin);
+                       self.SendFlags |= 2;
+                       break;
+               }
+       }
+}
+
+void W_Tuba_NoteOn(float hittype)
+{
+       vector o;
+       float n;
+
+       W_SetupShot(self, false, 2, "", 0, WEP_CVAR(tuba, damage));
+
+       n = W_Tuba_GetNote(self, hittype);
+
+       hittype = 0;
+       if(self.tuba_instrument & 1)
+               hittype |= HITTYPE_SECONDARY;
+       if(self.tuba_instrument & 2)
+               hittype |= HITTYPE_BOUNCE;
+
+       if(self.tuba_note)
+       {
+               if(self.tuba_note.cnt != n || self.tuba_note.tuba_instrument != self.tuba_instrument)
+               {
+                       entity oldself = self;
+                       self = self.tuba_note;
+                       W_Tuba_NoteOff();
+                       self = oldself;
+               }
+       }
+
+       if(!self.tuba_note)
+       {
+               self.tuba_note = spawn();
+               self.tuba_note.owner = self.tuba_note.realowner = self;
+               self.tuba_note.cnt = n;
+               self.tuba_note.tuba_instrument = self.tuba_instrument;
+               self.tuba_note.think = W_Tuba_NoteThink;
+               self.tuba_note.nextthink = time;
+               self.tuba_note.spawnshieldtime = time;
+               Net_LinkEntity(self.tuba_note, false, 0, W_Tuba_NoteSendEntity);
+       }
+
+       self.tuba_note.teleport_time = time + WEP_CVAR(tuba, refire) * 2 * W_WeaponRateFactor(); // so it can get prolonged safely
+
+       //sound(self, c, TUBA_NOTE(n), bound(0, VOL_BASE * cvar("g_balance_tuba_volume"), 1), autocvar_g_balance_tuba_attenuation);
+       RadiusDamage(self, self, WEP_CVAR(tuba, damage), WEP_CVAR(tuba, edgedamage), WEP_CVAR(tuba, radius), world, world, WEP_CVAR(tuba, force), hittype | WEP_TUBA.m_id, world);
+
+       o = gettaginfo(self.exteriorweaponentity, 0);
+       if(time > self.tuba_smoketime)
+       {
+               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;
+       }
+}
+
+bool W_Tuba(int req)
+{
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       // bots cannot play the Tuba well yet
+                       // I think they should start with the recorder first
+                       if(vlen(self.origin - self.enemy.origin) < WEP_CVAR(tuba, radius))
+                       {
+                               if(random() > 0.5)
+                                       self.BUTTON_ATCK = 1;
+                               else
+                                       self.BUTTON_ATCK2 = 1;
+                       }
+
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       if(self.BUTTON_ATCK)
+                       if(weapon_prepareattack(0, WEP_CVAR(tuba, refire)))
+                       {
+                               W_Tuba_NoteOn(0);
+                               //weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_tuba_animtime, w_ready);
+                               weapon_thinkf(WFRAME_IDLE, WEP_CVAR(tuba, animtime), w_ready);
+                       }
+                       if(self.BUTTON_ATCK2)
+                       if(weapon_prepareattack(1, WEP_CVAR(tuba, refire)))
+                       {
+                               W_Tuba_NoteOn(HITTYPE_SECONDARY);
+                               //weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_tuba_animtime, w_ready);
+                               weapon_thinkf(WFRAME_IDLE, WEP_CVAR(tuba, animtime), w_ready);
+                       }
+                       if(self.tuba_note)
+                       {
+                               if(!self.BUTTON_ATCK && !self.BUTTON_ATCK2)
+                               {
+                                       entity oldself = self;
+                                       self = self.tuba_note;
+                                       W_Tuba_NoteOff();
+                                       self = oldself;
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_model(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;
+               }
+               case WR_SETUP:
+               {
+                       self.ammo_field = ammo_none;
+                       self.tuba_instrument = 0;
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       // switch to alternate instruments :)
+                       if(self.weaponentity.state == WS_READY)
+                       {
+                               switch(self.tuba_instrument)
+                               {
+                                       case 0:
+                                               self.tuba_instrument = 1;
+                                               self.weaponname = "akordeon";
+                                               break;
+                                       case 1:
+                                               self.tuba_instrument = 2;
+                                               self.weaponname = "kleinbottle";
+                                               break;
+                                       case 2:
+                                               self.tuba_instrument = 0;
+                                               self.weaponname = "tuba";
+                                               break;
+                               }
+                               W_SetupShot(self, false, 0, "", 0, 0);
+                               Send_Effect(EFFECT_TELEPORT, w_shotorg, '0 0 0', 1);
+                               self.weaponentity.state = WS_INUSE;
+                               weapon_thinkf(WFRAME_RELOAD, 0.5, w_ready);
+                       }
+
+                       return true;
+               }
+               case WR_CHECKAMMO1:
+               case WR_CHECKAMMO2:
+               {
+                       return true; // tuba has infinite ammo
+               }
+               case WR_CONFIG:
+               {
+                       TUBA_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_BOUNCE)
+                               return WEAPON_KLEINBOTTLE_SUICIDE;
+                       else if(w_deathtype & HITTYPE_SECONDARY)
+                               return WEAPON_ACCORDEON_SUICIDE;
+                       else
+                               return WEAPON_TUBA_SUICIDE;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       if(w_deathtype & HITTYPE_BOUNCE)
+                               return WEAPON_KLEINBOTTLE_MURDER;
+                       else if(w_deathtype & HITTYPE_SECONDARY)
+                               return WEAPON_ACCORDEON_MURDER;
+                       else
+                               return WEAPON_TUBA_MURDER;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+bool W_Tuba(int req)
+{
+       // nothing to do here; particles of tuba are handled differently
+       // WEAPONTODO
+
+       switch(req)
+       {
+               case WR_ZOOMRETICLE:
+               {
+                       // no weapon specific image for this weapon
+                       return false;
+               }
+       }
+
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/vaporizer.qc b/qcsrc/common/weapons/weapon/vaporizer.qc
new file mode 100644 (file)
index 0000000..4ed2dc2
--- /dev/null
@@ -0,0 +1,476 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ VAPORIZER,
+/* function  */ W_Vaporizer,
+/* ammotype  */ ammo_cells,
+/* impulse   */ 7,
+/* flags     */ WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_FLAG_SUPERWEAPON | WEP_TYPE_HITSCAN,
+/* rating    */ BOT_PICKUP_RATING_HIGH,
+/* color     */ '0.5 1 1',
+/* modelname */ "minstanex",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairminstanex 0.6",
+/* wepimg    */ "weaponminstanex",
+/* refname   */ "vaporizer",
+/* wepname   */ _("Vaporizer")
+);
+
+#define VAPORIZER_SETTINGS(w_cvar,w_prop) VAPORIZER_SETTINGS_LIST(w_cvar, w_prop, VAPORIZER, vaporizer)
+#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, refire) \
+       w_cvar(id, sn, SEC, ammo) \
+       w_cvar(id, sn, SEC, animtime) \
+       w_cvar(id, sn, SEC, damage) \
+       w_cvar(id, sn, SEC, delay) \
+       w_cvar(id, sn, SEC, edgedamage) \
+       w_cvar(id, sn, SEC, force) \
+       w_cvar(id, sn, SEC, lifetime) \
+       w_cvar(id, sn, SEC, radius) \
+       w_cvar(id, sn, SEC, refire) \
+       w_cvar(id, sn, SEC, shotangle) \
+       w_cvar(id, sn, SEC, speed) \
+       w_cvar(id, sn, SEC, spread) \
+       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
+VAPORIZER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+.float vaporizer_lasthit;
+.float jump_interval;
+.float jump_interval2;
+.bool held_down;
+.float rm_force;
+.float rm_damage;
+.float rm_edmg;
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_vaporizer(void) { weapon_defaultspawnfunc(WEP_VAPORIZER.m_id); }
+void spawnfunc_weapon_minstanex(void) { spawnfunc_weapon_vaporizer(); }
+
+void W_RocketMinsta_Explosion(vector loc)
+{
+       if(accuracy_canbegooddamage(self))
+               accuracy_add(self, WEP_DEVASTATOR.m_id, autocvar_g_rm_damage, 0);
+       entity dmgent = spawn();
+       dmgent.owner = dmgent.realowner = self;
+       setorigin(dmgent, loc);
+       RadiusDamage (dmgent, self, autocvar_g_rm_damage, autocvar_g_rm_edgedamage, autocvar_g_rm_radius, world, world, autocvar_g_rm_force, WEP_DEVASTATOR.m_id | HITTYPE_SPLASH, other);
+       remove(dmgent);
+}
+
+void W_Vaporizer_Attack(void)
+{
+       float flying;
+       flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last
+
+       W_SetupShot(self, true, 0, "", CH_WEAPON_A, 10000);
+       // 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, 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.m_id);
+
+       if(yoda && flying)
+               Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA);
+       if(damage_goodhits && self.vaporizer_lasthit)
+       {
+               Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_IMPRESSIVE);
+               damage_goodhits = 0; // only every second time
+       }
+
+       self.vaporizer_lasthit = damage_goodhits;
+
+       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);
+       switch(self.team)
+       {
+               case NUM_TEAM_1:   // Red
+                       if(damage_goodhits)
+                               Send_Effect(EFFECT_VAPORIZER_RED_HIT, w_shotorg, v, 1);
+                       else
+                               Send_Effect(EFFECT_VAPORIZER_RED, w_shotorg, v, 1);
+                       break;
+               case NUM_TEAM_2:   // Blue
+                       if(damage_goodhits)
+                               Send_Effect(EFFECT_VAPORIZER_BLUE_HIT, w_shotorg, v, 1);
+                       else
+                               Send_Effect(EFFECT_VAPORIZER_BLUE, w_shotorg, v, 1);
+                       break;
+               case NUM_TEAM_3:   // Yellow
+                       if(damage_goodhits)
+                               Send_Effect(EFFECT_VAPORIZER_YELLOW_HIT, w_shotorg, v, 1);
+                       else
+                               Send_Effect(EFFECT_VAPORIZER_YELLOW, w_shotorg, v, 1);
+                       break;
+               case NUM_TEAM_4:   // Pink
+                       if(damage_goodhits)
+                               Send_Effect(EFFECT_VAPORIZER_PINK_HIT, w_shotorg, v, 1);
+                       else
+                               Send_Effect(EFFECT_VAPORIZER_PINK, w_shotorg, v, 1);
+                       break;
+               default:
+                       if(damage_goodhits)
+                               Send_Effect_("TE_TEI_G3_HIT", w_shotorg, v, 1);
+                       else
+                               Send_Effect_("TE_TEI_G3", w_shotorg, v, 1);
+                       break;
+       }
+
+       if(autocvar_g_rm)
+       if(!(trace_dphitq3surfaceflags & (Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT)))
+               W_RocketMinsta_Explosion(trace_endpos);
+
+       W_DecreaseAmmo(((g_instagib) ? 1 : WEP_CVAR_PRI(vaporizer, ammo)));
+}
+
+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.m_id;
+       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.m_id;
+        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);
+
+        MUTATOR_CALLHOOK(EditProjectile, self, proj);
+        counter++;
+    }
+}
+
+void W_RocketMinsta_Attack3 (void)
+{
+       makevectors(self.v_angle);
+       
+       entity proj;
+       float counter = 0;
+       float total = 1;
+
+       int w = self.weapon;
+       self.weapon = WEP_ELECTRO.m_id;
+       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.m_id;
+        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);
+
+        MUTATOR_CALLHOOK(EditProjectile, self, proj);
+        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));
+
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       if(self.WEP_AMMO(VAPORIZER) > 0)
+                               self.BUTTON_ATCK = bot_aim(1000000, 0, 1, false);
+                       else
+                               self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_SEC(vaporizer, speed), 0, WEP_CVAR_SEC(vaporizer, lifetime), false); // WEAPONTODO: replace with proper vaporizer cvars
+
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       // if the laser uses load, we also consider its ammo for reloading
+                       if(WEP_CVAR(vaporizer, reload_ammo) && WEP_CVAR_SEC(vaporizer, ammo) && self.clip_load < min(vaporizer_ammo, WEP_CVAR_SEC(vaporizer, ammo))) // forced reload
+                               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);
+                       if(self.BUTTON_ATCK && (self.ammo_cells || !autocvar_g_rm) && !forbidWeaponUse(self))
+                       {
+                               if(weapon_prepareattack(0, WEP_CVAR_PRI(vaporizer, refire)))
+                               {
+                                       W_Vaporizer_Attack();
+                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(vaporizer, animtime), w_ready);
+                               }
+                       }
+                       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();
+
+                                       // decrease ammo for the laser?
+                                       if(WEP_CVAR_SEC(vaporizer, ammo))
+                                               W_DecreaseAmmo(WEP_CVAR_SEC(vaporizer, ammo));
+
+                                       // ugly instagib hack to reuse the fire mode of the laser
+                                       makevectors(self.v_angle);
+                                       int oldwep = self.weapon; // we can't avoid this hack
+                                       self.weapon = WEP_BLASTER.m_id;
+                                       W_Blaster_Attack(
+                                               WEP_BLASTER.m_id | HITTYPE_SECONDARY,
+                                               WEP_CVAR_SEC(vaporizer, shotangle),
+                                               WEP_CVAR_SEC(vaporizer, damage),
+                                               WEP_CVAR_SEC(vaporizer, edgedamage),
+                                               WEP_CVAR_SEC(vaporizer, radius),
+                                               WEP_CVAR_SEC(vaporizer, force),
+                                               WEP_CVAR_SEC(vaporizer, speed),
+                                               WEP_CVAR_SEC(vaporizer, spread),
+                                               WEP_CVAR_SEC(vaporizer, delay),
+                                               WEP_CVAR_SEC(vaporizer, lifetime)
+                                       );
+                                       self.weapon = oldwep;
+
+                                       // now do normal refire
+                                       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(W_Model("g_minstanex.md3"));
+                       precache_model(W_Model("v_minstanex.md3"));
+                       precache_model(W_Model("h_minstanex.iqm"));
+                       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;
+               }
+               case WR_SETUP:
+               {
+                       self.ammo_field = WEP_AMMO(VAPORIZER);
+                       self.vaporizer_lasthit = 0;
+                       return true;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       ammo_amount = self.WEP_AMMO(VAPORIZER) >= vaporizer_ammo;
+                       ammo_amount += self.(weapon_load[WEP_VAPORIZER.m_id]) >= vaporizer_ammo;
+                       return ammo_amount;
+               }
+               case WR_CHECKAMMO2:
+               {
+                       if(!WEP_CVAR_SEC(vaporizer, ammo))
+                               return true;
+                       ammo_amount = self.WEP_AMMO(VAPORIZER) >= WEP_CVAR_SEC(vaporizer, ammo);
+                       ammo_amount += self.(weapon_load[WEP_VAPORIZER.m_id]) >= WEP_CVAR_SEC(vaporizer, ammo);
+                       return ammo_amount;
+               }
+               case WR_CONFIG:
+               {
+                       VAPORIZER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RESETPLAYER:
+               {
+                       self.vaporizer_lasthit = 0;
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       float used_ammo;
+                       if(WEP_CVAR_SEC(vaporizer, ammo))
+                               used_ammo = min(vaporizer_ammo, WEP_CVAR_SEC(vaporizer, ammo));
+                       else
+                               used_ammo = vaporizer_ammo;
+
+                       W_Reload(used_ammo, W_Sound("reload"));
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       return WEAPON_THINKING_WITH_PORTALS;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       return WEAPON_VAPORIZER_MURDER;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+float W_Vaporizer(float req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2 = w_org + w_backoff * 6;
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                       {
+                               pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1);
+                               if(!w_issilent) { sound(self, CH_SHOTS, W_Sound("laserimpact"), VOL_BASE, ATTN_NORM); }
+                       }
+                       else
+                       {
+                               pointparticles(particleeffectnum(EFFECT_VORTEX_IMPACT), org2, '0 0 0', 1);
+                               if(!w_issilent) { sound(self, CH_SHOTS, W_Sound("neximpact"), VOL_BASE, ATTN_NORM); }
+                       }
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("laserimpact"));
+                       precache_sound(W_Sound("neximpact"));
+                       if(autocvar_cl_reticle && autocvar_cl_reticle_weapon)
+                       {
+                               precache_pic("gfx/reticle_nex");
+                       }
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       if(button_zoom || zoomscript_caught)
+                       {
+                               reticle_image = "gfx/reticle_nex";
+                               return true;
+                       }
+                       else
+                       {
+                               // no weapon specific image for this weapon
+                               return false;
+                       }
+               }
+       }
+       return false;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/weapon/vortex.qc b/qcsrc/common/weapons/weapon/vortex.qc
new file mode 100644 (file)
index 0000000..3aa17e5
--- /dev/null
@@ -0,0 +1,362 @@
+#ifndef IMPLEMENTATION
+REGISTER_WEAPON(
+/* WEP_##id  */ VORTEX,
+/* function  */ W_Vortex,
+/* ammotype  */ ammo_cells,
+/* impulse   */ 7,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
+/* rating    */ BOT_PICKUP_RATING_HIGH,
+/* color     */ '0.5 1 1',
+/* modelname */ "nex",
+/* simplemdl */ "foobar",
+/* crosshair */ "gfx/crosshairnex 0.65",
+/* wepimg    */ "weaponnex",
+/* refname   */ "vortex",
+/* wepname   */ _("Vortex")
+);
+
+#define VORTEX_SETTINGS(w_cvar,w_prop) VORTEX_SETTINGS_LIST(w_cvar, w_prop, VORTEX, vortex)
+#define VORTEX_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
+       w_cvar(id, sn, BOTH, ammo) \
+       w_cvar(id, sn, BOTH, animtime) \
+       w_cvar(id, sn, BOTH, damage) \
+       w_cvar(id, sn, BOTH, force) \
+       w_cvar(id, sn, BOTH, damagefalloff_mindist) \
+       w_cvar(id, sn, BOTH, damagefalloff_maxdist) \
+       w_cvar(id, sn, BOTH, damagefalloff_halflife) \
+       w_cvar(id, sn, BOTH, damagefalloff_forcehalflife) \
+       w_cvar(id, sn, BOTH, refire) \
+       w_cvar(id, sn, NONE, charge) \
+       w_cvar(id, sn, NONE, charge_mindmg) \
+       w_cvar(id, sn, NONE, charge_shot_multiplier) \
+       w_cvar(id, sn, NONE, charge_animlimit) \
+       w_cvar(id, sn, NONE, charge_limit) \
+       w_cvar(id, sn, NONE, charge_rate) \
+       w_cvar(id, sn, NONE, charge_rot_rate) \
+       w_cvar(id, sn, NONE, charge_rot_pause) \
+       w_cvar(id, sn, NONE, charge_start) \
+       w_cvar(id, sn, NONE, charge_minspeed) \
+       w_cvar(id, sn, NONE, charge_maxspeed) \
+       w_cvar(id, sn, NONE, charge_velocity_rate) \
+       w_cvar(id, sn, NONE, secondary) \
+       w_cvar(id, sn, SEC,  chargepool) \
+       w_cvar(id, sn, SEC,  chargepool_regen) \
+       w_cvar(id, sn, SEC,  chargepool_pause_regen) \
+       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
+VORTEX_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
+
+.float vortex_lasthit;
+#endif
+#endif
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+void spawnfunc_weapon_vortex(void) { weapon_defaultspawnfunc(WEP_VORTEX.m_id); }
+void spawnfunc_weapon_nex(void) { spawnfunc_weapon_vortex(); }
+
+void SendCSQCVortexBeamParticle(float charge) {
+       vector v;
+       v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
+       WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
+       WriteByte(MSG_BROADCAST, TE_CSQC_VORTEXBEAMPARTICLE);
+       WriteCoord(MSG_BROADCAST, w_shotorg.x);
+       WriteCoord(MSG_BROADCAST, w_shotorg.y);
+       WriteCoord(MSG_BROADCAST, w_shotorg.z);
+       WriteCoord(MSG_BROADCAST, v.x);
+       WriteCoord(MSG_BROADCAST, v.y);
+       WriteCoord(MSG_BROADCAST, v.z);
+       WriteByte(MSG_BROADCAST, bound(0, 255 * charge, 255));
+}
+
+void W_Vortex_Attack(float issecondary)
+{
+       float mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, myammo, charge;
+
+       mydmg = WEP_CVAR_BOTH(vortex, !issecondary, damage);
+       myforce = WEP_CVAR_BOTH(vortex, !issecondary, force);
+       mymindist = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_mindist);
+       mymaxdist = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_maxdist);
+       myhalflife = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_halflife);
+       myforcehalflife = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_forcehalflife);
+       myammo = WEP_CVAR_BOTH(vortex, !issecondary, ammo);
+
+       float flying;
+       flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last
+
+       if(WEP_CVAR(vortex, charge))
+       {
+               charge = WEP_CVAR(vortex, charge_mindmg) / mydmg + (1 - WEP_CVAR(vortex, charge_mindmg) / mydmg) * self.vortex_charge;
+               self.vortex_charge *= WEP_CVAR(vortex, charge_shot_multiplier); // do this AFTER setting mydmg/myforce
+               // O RLY? -- divVerent
+               // YA RLY -- FruitieX
+       }
+       else
+               charge = 1;
+       mydmg *= charge;
+       myforce *= charge;
+
+       W_SetupShot(self, true, 5, W_Sound("nexfire"), 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
+       {
+               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;
+       damage_goodhits = 0;
+       FireRailgunBullet(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, WEP_VORTEX.m_id);
+
+       if(yoda && flying)
+               Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA);
+       if(damage_goodhits && self.vortex_lasthit)
+       {
+               Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_IMPRESSIVE);
+               damage_goodhits = 0; // only every second time
+       }
+
+       self.vortex_lasthit = damage_goodhits;
+
+       //beam and muzzle flash done on client
+       SendCSQCVortexBeamParticle(charge);
+
+       W_DecreaseAmmo(myammo);
+}
+
+void spawnfunc_weapon_vortex(void); // defined in t_items.qc
+
+.float vortex_chargepool_pauseregen_finished;
+bool W_Vortex(int req)
+{
+       float dt;
+       float ammo_amount;
+       switch(req)
+       {
+               case WR_AIM:
+               {
+                       if(bot_aim(1000000, 0, 1, false))
+                               self.BUTTON_ATCK = true;
+                       else
+                       {
+                               if(WEP_CVAR(vortex, charge))
+                                       self.BUTTON_ATCK2 = true;
+                       }
+                       return true;
+               }
+               case WR_THINK:
+               {
+                       if(WEP_CVAR(vortex, charge) && self.vortex_charge < WEP_CVAR(vortex, charge_limit))
+                               self.vortex_charge = min(1, self.vortex_charge + WEP_CVAR(vortex, charge_rate) * frametime / W_TICSPERFRAME);
+
+                       if(WEP_CVAR_SEC(vortex, chargepool))
+                               if(self.vortex_chargepool_ammo < 1)
+                               {
+                                       if(self.vortex_chargepool_pauseregen_finished < time)
+                                               self.vortex_chargepool_ammo = min(1, self.vortex_chargepool_ammo + WEP_CVAR_SEC(vortex, chargepool_regen) * frametime / W_TICSPERFRAME);
+                                       self.pauseregen_finished = max(self.pauseregen_finished, time + WEP_CVAR_SEC(vortex, chargepool_pause_regen));
+                               }
+
+                       if(autocvar_g_balance_vortex_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(vortex, ammo), WEP_CVAR_SEC(vortex, ammo))) // forced reload
+                               WEP_ACTION(self.weapon, WR_RELOAD);
+                       else
+                       {
+                               if(self.BUTTON_ATCK)
+                               {
+                                       if(weapon_prepareattack(0, WEP_CVAR_PRI(vortex, refire)))
+                                       {
+                                               W_Vortex_Attack(0);
+                                               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(vortex, animtime), w_ready);
+                                       }
+                               }
+                               if((WEP_CVAR(vortex, charge) && !WEP_CVAR(vortex, secondary)) ? (self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) : self.BUTTON_ATCK2)
+                               {
+                                       if(WEP_CVAR(vortex, charge))
+                                       {
+                                               self.vortex_charge_rottime = time + WEP_CVAR(vortex, charge_rot_pause);
+                                               dt = frametime / W_TICSPERFRAME;
+
+                                               if(self.vortex_charge < 1)
+                                               {
+                                                       if(WEP_CVAR_SEC(vortex, chargepool))
+                                                       {
+                                                               if(WEP_CVAR_SEC(vortex, ammo))
+                                                               {
+                                                                       // always deplete if secondary is held
+                                                                       self.vortex_chargepool_ammo = max(0, self.vortex_chargepool_ammo - WEP_CVAR_SEC(vortex, ammo) * dt);
+
+                                                                       dt = min(dt, (1 - self.vortex_charge) / WEP_CVAR(vortex, charge_rate));
+                                                                       self.vortex_chargepool_pauseregen_finished = time + WEP_CVAR_SEC(vortex, chargepool_pause_regen);
+                                                                       dt = min(dt, self.vortex_chargepool_ammo);
+                                                                       dt = max(0, dt);
+
+                                                                       self.vortex_charge += dt * WEP_CVAR(vortex, charge_rate);
+                                                               }
+                                                       }
+
+                                                       else if(WEP_CVAR_SEC(vortex, ammo))
+                                                       {
+                                                               if(self.BUTTON_ATCK2) // only eat ammo when the button is pressed
+                                                               {
+                                                                       dt = min(dt, (1 - self.vortex_charge) / WEP_CVAR(vortex, charge_rate));
+                                                                       if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
+                                                                       {
+                                                                               // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
+                                                                               if(autocvar_g_balance_vortex_reload_ammo)
+                                                                               {
+                                                                                       dt = min(dt, (self.clip_load - WEP_CVAR_PRI(vortex, ammo)) / WEP_CVAR_SEC(vortex, ammo));
+                                                                                       dt = max(0, dt);
+                                                                                       if(dt > 0)
+                                                                                       {
+                                                                                               self.clip_load = max(WEP_CVAR_SEC(vortex, ammo), self.clip_load - WEP_CVAR_SEC(vortex, ammo) * dt);
+                                                                                       }
+                                                                                       self.(weapon_load[WEP_VORTEX.m_id]) = self.clip_load;
+                                                                               }
+                                                                               else
+                                                                               {
+                                                                                       dt = min(dt, (self.WEP_AMMO(VORTEX) - WEP_CVAR_PRI(vortex, ammo)) / WEP_CVAR_SEC(vortex, ammo));
+                                                                                       dt = max(0, dt);
+                                                                                       if(dt > 0)
+                                                                                       {
+                                                                                               self.WEP_AMMO(VORTEX) = max(WEP_CVAR_SEC(vortex, ammo), self.WEP_AMMO(VORTEX) - WEP_CVAR_SEC(vortex, ammo) * dt);
+                                                                                       }
+                                                                               }
+                                                                       }
+                                                                       self.vortex_charge += dt * WEP_CVAR(vortex, charge_rate);
+                                                               }
+                                                       }
+
+                                                       else
+                                                       {
+                                                               dt = min(dt, (1 - self.vortex_charge) / WEP_CVAR(vortex, charge_rate));
+                                                               self.vortex_charge += dt * WEP_CVAR(vortex, charge_rate);
+                                                       }
+                                               }
+                                       }
+                                       else if(WEP_CVAR(vortex, secondary))
+                                       {
+                                               if(weapon_prepareattack(0, WEP_CVAR_SEC(vortex, refire)))
+                                               {
+                                                       W_Vortex_Attack(1);
+                                                       weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(vortex, animtime), w_ready);
+                                               }
+                                       }
+                               }
+                       }
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_model("models/nexflash.md3");
+                       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;
+               }
+               case WR_SETUP:
+               {
+                       self.vortex_lasthit = 0;
+                       return true;
+               }
+               case WR_CHECKAMMO1:
+               {
+                       ammo_amount = self.WEP_AMMO(VORTEX) >= WEP_CVAR_PRI(vortex, ammo);
+                       ammo_amount += (autocvar_g_balance_vortex_reload_ammo && self.(weapon_load[WEP_VORTEX.m_id]) >= WEP_CVAR_PRI(vortex, ammo));
+                       return ammo_amount;
+               }
+               case WR_CHECKAMMO2:
+               {
+                       if(WEP_CVAR(vortex, secondary))
+                       {
+                               // don't allow charging if we don't have enough ammo
+                               ammo_amount = self.WEP_AMMO(VORTEX) >= WEP_CVAR_SEC(vortex, ammo);
+                               ammo_amount += self.(weapon_load[WEP_VORTEX.m_id]) >= WEP_CVAR_SEC(vortex, ammo);
+                               return ammo_amount;
+                       }
+                       else
+                       {
+                               return false; // zoom is not a fire mode
+                       }
+               }
+               case WR_CONFIG:
+               {
+                       VORTEX_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
+                       return true;
+               }
+               case WR_RESETPLAYER:
+               {
+                       self.vortex_lasthit = 0;
+                       return true;
+               }
+               case WR_RELOAD:
+               {
+                       W_Reload(min(WEP_CVAR_PRI(vortex, ammo), WEP_CVAR_SEC(vortex, ammo)), W_Sound("reload"));
+                       return true;
+               }
+               case WR_SUICIDEMESSAGE:
+               {
+                       return WEAPON_THINKING_WITH_PORTALS;
+               }
+               case WR_KILLMESSAGE:
+               {
+                       return WEAPON_VORTEX_MURDER;
+               }
+       }
+       return false;
+}
+#endif
+#ifdef CSQC
+float autocvar_g_balance_vortex_secondary = 0; // WEAPONTODO
+bool W_Vortex(int req)
+{
+       switch(req)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 6;
+                       pointparticles(particleeffectnum(EFFECT_VORTEX_IMPACT), org2, '0 0 0', 1);
+                       if(!w_issilent)
+                               sound(self, CH_SHOTS, W_Sound("neximpact"), VOL_BASE, ATTN_NORM);
+
+                       return true;
+               }
+               case WR_INIT:
+               {
+                       precache_sound(W_Sound("neximpact"));
+                       if(autocvar_cl_reticle && autocvar_cl_reticle_weapon)
+                       {
+                               precache_pic("gfx/reticle_nex");
+                       }
+                       return true;
+               }
+               case WR_ZOOMRETICLE:
+               {
+                       if(button_zoom || zoomscript_caught || (!WEP_CVAR(vortex, secondary) && button_attack2))
+                       {
+                               reticle_image = "gfx/reticle_nex";
+                               return true;
+                       }
+                       else
+                       {
+                               // no weapon specific image for this weapon
+                               return false;
+                       }
+               }
+       }
+       return false;
+}
+#endif
+#endif
index 98d5b041c213e5b918b930576257588f342a253b..5eeafddce5dee77d4419240197cd379eed35d415 100644 (file)
@@ -1,8 +1,15 @@
 #ifndef CVAR_H
 #define CVAR_H
 
-#define CVAR_DESCRIBE(var, desc)    localcmd(sprintf("\nseta %s \"%s\" \"%s\"\n", #var, ftos(autocvar_##var), desc))
-#define CVAR_DESCRIBESTR(var, desc) localcmd(sprintf("\nseta %s \"%s\" \"%s\"\n", #var, autocvar_##var, desc))
-#define CVAR_DESCRIBEVEC(var, desc) localcmd(sprintf("\nseta %s \"%v\" \"%s\"\n", #var, autocvar_##var, desc))
+#define CVAR_DESCRIBE(set, var, desc)    localcmd(sprintf("\n"set" %1$s \"$%1$s\" \"%2$s\"\n", #var, desc))
+
+#define AUTOCVAR_4(set, var, type, desc) \
+    STATIC_INIT(autocvar_##var) { CVAR_DESCRIBE(set, var, desc); } \
+    type autocvar_##var
+#define AUTOCVAR_5(set, var, type, default, desc) \
+    AUTOCVAR_4(set, var, type, desc) = default
+#define _AUTOCVAR(...) OVERLOAD(AUTOCVAR, __VA_ARGS__)
+#define AUTOCVAR_SAVE(...) _AUTOCVAR("seta", __VA_ARGS__)
+#define AUTOCVAR(...) _AUTOCVAR("set", __VA_ARGS__)
 
 #endif
index aee7166e1b3676a0a5052c454b664466836d5c81..08563a118ac7f486ddb1955887e8a8ef54294f03 100644 (file)
@@ -54,6 +54,7 @@
 #include "xonotic/dialog_hudpanel_physics.qc"
 #include "xonotic/dialog_hudpanel_powerups.qc"
 #include "xonotic/dialog_hudpanel_pressedkeys.qc"
+#include "xonotic/dialog_hudpanel_quickmenu.qc"
 #include "xonotic/dialog_hudpanel_racetimer.qc"
 #include "xonotic/dialog_hudpanel_radar.qc"
 #include "xonotic/dialog_hudpanel_score.qc"
diff --git a/qcsrc/menu/xonotic/dialog_hudpanel_quickmenu.qc b/qcsrc/menu/xonotic/dialog_hudpanel_quickmenu.qc
new file mode 100644 (file)
index 0000000..4d46845
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef DIALOG_HUDPANEL_QUICKMENU_H
+#define DIALOG_HUDPANEL_QUICKMENU_H
+#include "rootdialog.qc"
+CLASS(XonoticHUDQuickMenuDialog, XonoticRootDialog)
+       METHOD(XonoticHUDQuickMenuDialog, fill, void(entity));
+       ATTRIB(XonoticHUDQuickMenuDialog, title, string, _("Quick Menu Panel"))
+       ATTRIB(XonoticHUDQuickMenuDialog, color, vector, SKINCOLOR_DIALOG_TEAMSELECT)
+       ATTRIB(XonoticHUDQuickMenuDialog, intendedWidth, float, 0.4)
+       ATTRIB(XonoticHUDQuickMenuDialog, rows, float, 15)
+       ATTRIB(XonoticHUDQuickMenuDialog, columns, float, 4)
+       ATTRIB(XonoticHUDQuickMenuDialog, name, string, "HUDquickmenu")
+ENDCLASS(XonoticHUDQuickMenuDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void XonoticHUDQuickMenuDialog_fill(entity me)
+{
+       entity e;
+       string panelname = "quickmenu";
+
+       DIALOG_HUDPANEL_COMMON_NOTOGGLE();
+
+       me.TR(me);
+               me.TD(me, 1, 4, e = makeXonoticTextLabel(0, _("Text alignment:")));
+       me.TR(me);
+               me.TDempty(me, 0.2);
+                       me.TD(me, 1, 3.8/3, e = makeXonoticRadioButton(3, "hud_panel_quickmenu_align", "0", _("Left")));
+                       me.TD(me, 1, 3.8/3, e = makeXonoticRadioButton(3, "hud_panel_quickmenu_align", "0.5", _("Center")));
+                       me.TD(me, 1, 3.8/3, e = makeXonoticRadioButton(3, "hud_panel_quickmenu_align", "1", _("Right")));
+}
+#endif
index eafa1842fd84332d721fcb8af47f8d120fa3a111..244d6b2b206ecf55a047a2223391063341bd4d87 100644 (file)
@@ -132,6 +132,10 @@ void MainWindow_configureMainWindow(entity me)
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
 
+       i = NEW(XonoticHUDQuickMenuDialog);
+       i.configureDialog(i);
+       me.addItemRightCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+
        // dialogs used by settings
        me.userbindEditDialog = i = NEW(XonoticUserbindEditDialog);
        i.configureDialog(i);
index 5549778be82c2f50ce7928b3e5fe5e6bd6f52557..58ddfc0364f784e7ef60dd76055dd286ed9c6d15 100644 (file)
@@ -119,12 +119,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)
index ac89f06a7382dc91aec1d68427af2b4b0a6269c1..a796dc12d5220c870db79f9c1dd6c44b4b9754ed 100644 (file)
@@ -114,9 +114,5 @@ 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);
 #endif
index 593488e4425e96259e15a2d2b228d0c7223da452..c56f70598e60b696e4f18c7a70d6f20cce995633 100644 (file)
@@ -211,12 +211,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;
@@ -232,9 +229,9 @@ void havocbot_role_dm()
        }
 }
 
-void havocbot_chooserole_dm()
+void havocbot_chooserole_generic()
 {
-       self.havocbot_role = havocbot_role_dm;
+       self.havocbot_role = havocbot_role_generic;
 }
 
 void havocbot_chooserole()
@@ -245,6 +242,6 @@ void havocbot_chooserole()
                return;
        else if (g_keyhunt)
                havocbot_chooserole_kh();
-       else // assume anything else is deathmatch
-               havocbot_chooserole_dm();
+       else
+               havocbot_chooserole_generic();
 }
index 4d5821c811c5ef5fda2db59fd6fb4a9db4d33ed6..82bc9036158a3f343cf41c35108c2028d6c5c25a 100644 (file)
@@ -1033,7 +1033,13 @@ 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);
+       vector vecs = '0 0 0';
+       if(dragger.weaponentity.movedir_x > 0)
+               vecs = dragger.weaponentity.movedir;
+
+       vector dv = v_right * -vecs_y + v_up * vecs_z;
+
+       te_lightning1(draggee, dragger.origin + dragger.view_ofs + dv, curorigin);
 }
 
 float Drag_CanDrag(entity dragger)
index 6d804149725cd560a773f4a42bc8db788d3fcb04..4b9d9294c16bc322c2a978ce1f76ae167c75afdb 100644 (file)
@@ -362,8 +362,9 @@ void FixPlayermodel()
                }
        }
 
-       MUTATOR_CALLHOOK(FixPlayermodel, defaultmodel);
+       MUTATOR_CALLHOOK(FixPlayermodel, defaultmodel, defaultskin);
        defaultmodel = ret_string;
+       defaultskin = ret_int;
 
        if(defaultmodel != "")
        {
@@ -1067,8 +1068,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;
@@ -1797,49 +1796,36 @@ void SpectateCopy(entity spectatee) {
     }
 }
 
-float SpectateUpdate()
+bool SpectateUpdate()
 {
        if(!self.enemy)
-           return 0;
+           return false;
 
        if(!IS_PLAYER(self.enemy) || self == self.enemy)
        {
                SetSpectator(self, world);
-               return 0;
+               return false;
        }
 
        SpectateCopy(self.enemy);
 
-       return 1;
+       return true;
 }
 
-float SpectateSet()
+bool 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");
+       msg_entity = self;
+       WriteByte(MSG_ONE, SVC_SETVIEW);
+       WriteEntity(MSG_ONE, self.enemy);
+       self.movetype = MOVETYPE_NONE;
+       accuracy_resend(self);
 
-               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);
+       if(!SpectateUpdate())
+               PutObserverInServer();
 
-               if(!SpectateUpdate())
-                       PutObserverInServer();
-       //}
        return true;
 }
 
@@ -1855,47 +1841,47 @@ void SetSpectator(entity player, entity spectatee)
        if(player.enemy && player.enemy.arc_beam) { player.enemy.arc_beam.SendFlags |= ARC_SF_SETTINGS; }
 }
 
-float Spectate(entity pl)
+bool Spectate(entity pl)
 {
        if(g_ca && !autocvar_g_ca_spectate_enemies && self.caplayer)
-       if(pl.team != self.team)
-               return 0;
+       if(DIFF_TEAM(pl, self))
+               return false;
 
        SetSpectator(self, pl);
        return SpectateSet();
 }
 
 // Returns next available player to spectate if g_ca_spectate_enemies == 0
-entity CA_SpectateNext(entity start) {
-       if (start.team == self.team) {
+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;
 }
 
-float SpectateNext()
+bool 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");
@@ -1906,7 +1892,7 @@ float SpectateNext()
        return SpectateSet();
 }
 
-float SpectatePrev()
+bool SpectatePrev()
 {
        // NOTE: chain order is from the highest to the lower entnum (unlike find)
        other = findchain(classname, "player");
@@ -1922,12 +1908,12 @@ float SpectatePrev()
        if (g_ca && !autocvar_g_ca_spectate_enemies && self.caplayer)
        {
                do { other = other.chain; }
-               while(other && other.team != self.team);
+               while(other && DIFF_TEAM(other, self));
 
                if (!other)
                {
                        other = first;
-                       while(other.team != self.team)
+                       while(other && DIFF_TEAM(other, self))
                                other = other.chain;
                        if(other == self.enemy)
                                return true;
@@ -2308,7 +2294,7 @@ void PlayerPreThink (void)
 
        // Savage: Check for nameless players
        if (isInvisibleString(self.netname)) {
-               string new_name = strzone(strcat("Player@", self.netaddress));
+               string new_name = strzone(strcat("Player@", ftos(self.playerid)));
                if(autocvar_sv_eventlog)
                        GameLogEcho(strcat(":name:", ftos(self.playerid), ":", new_name));
                if(self.netname_previous)
index 8ed9619d540f7cdcc6beeefee22e11f596c63c98..9add7e8080891d20e7244279d3a66d95ae31bced 100644 (file)
@@ -707,12 +707,28 @@ float Say(entity source, float teamsay, entity privatesay, string msgin, float f
                }
                else if(teamsay)
                {
-                       msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
+                       if(strstrofs(msgin, "/me", 0) >= 0)
+                       {
+                               //msgin = strreplace("/me", "", msgin);
+                               //msgin = substring(msgin, 3, strlen(msgin));
+                               msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin);
+                               msgstr = strcat("\{1}\{13}^4* ", "^7", msgin);
+                       }
+                       else
+                               msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
                        cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
                }
                else
                {
-                       msgstr = strcat("\{1}", colorprefix, namestr, "^7: ", msgin);
+                       if(strstrofs(msgin, "/me", 0) >= 0)
+                       {
+                               //msgin = strreplace("/me", "", msgin);
+                               //msgin = substring(msgin, 3, strlen(msgin));
+                               msgin = strreplace("/me", strcat(colorprefix, namestr), msgin);
+                               msgstr = strcat("\{1}^4* ", "^7", msgin);
+                       }
+                       else
+                               msgstr = strcat("\{1}", colorprefix, namestr, "^7: ", msgin);
                        cmsgstr = "";
                }
                msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
index 1bd7097954693b3560562c12a7be8f3b92042935..d38baf784a49ebac0ff823fb3f789edba57dda29 100644 (file)
@@ -560,7 +560,18 @@ void ClientCommand_tell(float request, float argc, string command)
                                {
                                        if(tell_to != self) // and we're allowed to send to them :D
                                        {
-                                               Say(self, false, tell_to, substring(command, argv_start_index(next_token), argv_end_index(-1) - argv_start_index(next_token)), true);
+                                               // workaround for argv indexes indexing ascii chars instead of utf8 chars
+                                               // In this case when the player name contains utf8 chars
+                                               // the message gets partially trimmed in the beginning.
+                                               // Potentially this bug affects any substring call that uses
+                                               // argv_start_index and argv_end_index.
+
+                                               string utf8_enable_save = cvar_string("utf8_enable");
+                                               cvar_set("utf8_enable", "0");
+                                               string msg = substring(command, argv_start_index(next_token), argv_end_index(-1) - argv_start_index(next_token));
+                                               cvar_set("utf8_enable", utf8_enable_save);
+
+                                               Say(self, false, tell_to, msg, true);
                                                return;
                                        }
                                        else { print_to(self, "You can't ^2tell^7 a message to yourself."); return; }
index a19b62ba8463151dae9b856b83d0106f2f331789..5b7011ee743a1abb0ed55fc3723eca65f7741d23 100644 (file)
@@ -301,7 +301,9 @@ string formatmessage(string msg)
                        case "S": replacement = ftos(vlen(self.velocity)); break;
                        default:
                        {
-                               MUTATOR_CALLHOOK(FormatMessage, escape, replacement);
+                               MUTATOR_CALLHOOK(FormatMessage, escape, replacement, msg);
+                               escape = format_escape;
+                               replacement = format_replacement;
                                break;
                        }
                }
index 25a8d12c56948eab8d8ab32770e06b68a5af3f96..3d086337075e7c6602f4f4ec00dfe483daabb445 100644 (file)
@@ -137,9 +137,11 @@ MUTATOR_HOOKABLE(SpectateCopy, EV_SpectateCopy);
     /**/ i(string, format_escape) \
     /**/ i(string, format_replacement) \
     /**/ o(string, format_replacement) \
+    /**/ i(string, format_message) \
     /**/
 string format_escape;
 string format_replacement;
+string format_message;
 MUTATOR_HOOKABLE(FormatMessage, EV_FormatMessage);
 
 /** returns 1 if throwing the current weapon shall not be allowed */
@@ -584,7 +586,10 @@ MUTATOR_HOOKABLE(FireBullet_Hit, EV_FireBullet_Hit);
 #define EV_FixPlayermodel(i, o) \
     /**/ i(string, ret_string) \
     /**/ o(string, ret_string) \
+    /**/ i(int, ret_int) \
+    /**/ o(int, ret_int) \
     /**/
+int ret_int;
 MUTATOR_HOOKABLE(FixPlayermodel, EV_FixPlayermodel);
 
 /** Return error to play frag remaining announcements */
index 65c7ba6e56635a727abfae89c2341a92fb422815..2416425e834228a54b421f0bc1d152a9fd7939d8 100644 (file)
@@ -356,6 +356,14 @@ MUTATOR_HOOKFUNCTION(ka_PlayerPowerups)
        return 0;
 }
 
+MUTATOR_HOOKFUNCTION(ka_BotShouldAttack)
+{
+       // if neither player has ball then don't attack unless the ball is on the ground
+       if(!checkentity.ballcarried && !self.ballcarried && ka_ball.owner)
+               return true;
+       return false;
+}
+
 MUTATOR_HOOKFUNCTION(ka_BotRoles)
 {
        if (self.ballcarried)
@@ -430,6 +438,7 @@ 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(BotShouldAttack, ka_BotShouldAttack, CBC_ORDER_ANY);
        MUTATOR_HOOK(HavocBot_ChooseRole, ka_BotRoles, CBC_ORDER_ANY);
 
        MUTATOR_ONADD