]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Buffs mutator, loosely based on the old relics mutator
authorMario <mario.mario@y7mail.com>
Fri, 30 May 2014 14:55:58 +0000 (00:55 +1000)
committerMario <mario.mario@y7mail.com>
Fri, 30 May 2014 14:55:58 +0000 (00:55 +1000)
31 files changed:
_hud_descriptions.cfg
hud_luminos.cfg
hud_luminos_minimal.cfg
hud_luminos_minimal_xhair.cfg
hud_luminos_old.cfg
hud_nexuiz.cfg
mutators.cfg
qcsrc/client/Main.qc
qcsrc/client/autocvars.qh
qcsrc/client/hud.qc
qcsrc/client/hud.qh
qcsrc/client/progs.src
qcsrc/common/buffs.qc [new file with mode: 0644]
qcsrc/common/buffs.qh [new file with mode: 0644]
qcsrc/common/constants.qh
qcsrc/common/deathtypes.qh
qcsrc/common/notifications.qh
qcsrc/menu/classes.c
qcsrc/menu/xonotic/dialog_hudpanel_buffs.c [new file with mode: 0644]
qcsrc/menu/xonotic/mainwindow.c
qcsrc/server/autocvars.qh
qcsrc/server/cl_client.qc
qcsrc/server/cl_physics.qc
qcsrc/server/cl_weaponsystem.qc
qcsrc/server/g_world.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/mutators/base.qh
qcsrc/server/mutators/mutator_buffs.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator_buffs.qh [new file with mode: 0644]
qcsrc/server/mutators/mutators.qh
qcsrc/server/progs.src

index 5f21867d353600ab2aa32dbad1c137f6d3a8ccae..5818c461c392995e3ec74a15035d5a671ef50efe 100644 (file)
@@ -296,3 +296,13 @@ seta hud_panel_centerprint_fade_subsequent_passtwo "" "division factor for the s
 seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "" "minimum factor that the second pass can fade to"
 seta hud_panel_centerprint_fade_subsequent_minfontsize "" "minimum factor for the font size from the subsequent fading effects"
 seta hud_panel_centerprint_fade_minfontsize "" "minimum factor for the font size from the fading in/out effects"
+
+seta hud_panel_buffs "" "enable/disable this panel"
+seta hud_panel_buffs_pos "" "position of this panel"
+seta hud_panel_buffs_size "" "size of this panel"
+seta hud_panel_buffs_bg "" "if set to something else than \"\" = override default background"
+seta hud_panel_buffs_bg_color "" "if set to something else than \"\" = override default panel background color"
+seta hud_panel_buffs_bg_color_team "" "override panel color with team color in team based games"
+seta hud_panel_buffs_bg_alpha "" "if set to something else than \"\" = override default panel background alpha"
+seta hud_panel_buffs_bg_border "" "if set to something else than \"\" = override default size of border around the background"
+seta hud_panel_buffs_bg_padding "" "if set to something else than \"\" = override default padding of contents from border"
index f2b4fa4bd8504dca76aae7b705b2d948614518c0..86c72ea2a931613bed8cce4c8bb43c467d4eeb61 100644 (file)
@@ -295,4 +295,14 @@ seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5"
 seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75"
 seta hud_panel_centerprint_fade_minfontsize "0"
 
+seta hud_panel_buffs 1
+seta hud_panel_buffs_pos "0.450000 0.855000"
+seta hud_panel_buffs_size "0.050000 0.070000"
+seta hud_panel_buffs_bg "0"
+seta hud_panel_buffs_bg_color ""
+seta hud_panel_buffs_bg_color_team ""
+seta hud_panel_buffs_bg_alpha ""
+seta hud_panel_buffs_bg_border ""
+seta hud_panel_buffs_bg_padding ""
+
 menu_sync
index c401132ac3d2fe00f44ca3d1c3da806f85ea6a5b..a992b3acf6edf653c58c20e9de892f48d849707b 100644 (file)
@@ -295,4 +295,14 @@ seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5"
 seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75"
 seta hud_panel_centerprint_fade_minfontsize "0"
 
+seta hud_panel_buffs 1
+seta hud_panel_buffs_pos "0.450000 0.855000"
+seta hud_panel_buffs_size "0.050000 0.070000"
+seta hud_panel_buffs_bg "0"
+seta hud_panel_buffs_bg_color ""
+seta hud_panel_buffs_bg_color_team ""
+seta hud_panel_buffs_bg_alpha ""
+seta hud_panel_buffs_bg_border ""
+seta hud_panel_buffs_bg_padding ""
+
 menu_sync
index 518492ed4a7998973481221c835453a6f4d71bab..2541415591686da3e76a39e943af2031e028b7a6 100644 (file)
@@ -295,4 +295,14 @@ seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5"
 seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75"
 seta hud_panel_centerprint_fade_minfontsize "0"
 
+seta hud_panel_buffs 1
+seta hud_panel_buffs_pos "0.450000 0.855000"
+seta hud_panel_buffs_size "0.050000 0.070000"
+seta hud_panel_buffs_bg "0"
+seta hud_panel_buffs_bg_color ""
+seta hud_panel_buffs_bg_color_team ""
+seta hud_panel_buffs_bg_alpha ""
+seta hud_panel_buffs_bg_border ""
+seta hud_panel_buffs_bg_padding ""
+
 menu_sync
index 0a5f05955ef8d1a24b1fd5dbcbc95db0e8de84bf..96573750170eefb84340ef2d3d405fdba26626d1 100644 (file)
@@ -295,4 +295,14 @@ seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5"
 seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75"
 seta hud_panel_centerprint_fade_minfontsize "0"
 
+seta hud_panel_buffs 1
+seta hud_panel_buffs_pos "0.450000 0.855000"
+seta hud_panel_buffs_size "0.050000 0.070000"
+seta hud_panel_buffs_bg "0"
+seta hud_panel_buffs_bg_color ""
+seta hud_panel_buffs_bg_color_team ""
+seta hud_panel_buffs_bg_alpha ""
+seta hud_panel_buffs_bg_border ""
+seta hud_panel_buffs_bg_padding ""
+
 menu_sync
index acf3ad921e84dd874a96f9b8bd6ca8d4253679ee..9fa75602cf5a450594462a82783aeb93fe29aa8c 100644 (file)
@@ -295,4 +295,14 @@ seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5"
 seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75"
 seta hud_panel_centerprint_fade_minfontsize "0"
 
+seta hud_panel_buffs 1
+seta hud_panel_buffs_pos "0.450000 0.855000"
+seta hud_panel_buffs_size "0.050000 0.070000"
+seta hud_panel_buffs_bg "0"
+seta hud_panel_buffs_bg_color ""
+seta hud_panel_buffs_bg_color_team ""
+seta hud_panel_buffs_bg_alpha ""
+seta hud_panel_buffs_bg_border ""
+seta hud_panel_buffs_bg_padding ""
+
 menu_sync
index c762ef9dec047aaa26cebd9320da67e40aad6aef..55fabef2202bf9abbbcf1350925d6393f10db6ab 100644 (file)
@@ -162,3 +162,52 @@ set g_campcheck_interval 10
 set g_campcheck_damage 100
 set g_campcheck_distance 1800
 
+
+// =======
+//  buffs
+// =======
+set cl_buffs_autoreplace 1 "automatically drop current buff when picking up another"
+set g_buffs 0 "enable buffs (requires buff items or powerups)"
+set g_buffs_waypoint_distance 1024 "maximum distance at which buff waypoint can be seen from item"
+set g_buffs_randomize 1 "randomize buff type when player drops buff"
+set g_buffs_random_lifetime 30 "re-spawn the buff again if it hasn't been touched after this time in seconds"
+set g_buffs_random_location 0 "randomize buff location on start and when reset"
+set g_buffs_random_location_attempts 10 "number of random locations a single buff will attempt to respawn at before giving up"
+set g_buffs_spawn_count 5 "how many buffs to spawn on the map if none exist already"
+set g_buffs_replace_powerups 1 "replace powerups on the map with random buffs"
+set g_buffs_cooldown_activate 5 "cooldown period when buff is first activated"
+set g_buffs_cooldown_respawn 3 "cooldown period when buff is reloading"
+set g_buffs_ammo 1 "enable ammo buff"
+set g_buffs_resistance 1 "enable resistance buff"
+set g_buffs_resistance_blockpercent 0.7 "damage reduction multiplier, higher values mean less damage"
+set g_buffs_medic 1 "enable medic buff"
+set g_buffs_medic_survive_chance 0.6 "multiplier chance of player surviving a fatal hit"
+set g_buffs_medic_survive_health 5 "amount of health player survives with after taking a fatal hit"
+set g_buffs_medic_rot 0.7 "health rot rate multiplier"
+set g_buffs_medic_max 1.5 "stable health medic limit multiplier"
+set g_buffs_medic_regen 1.7 "health medic rate multiplier"
+set g_buffs_vengeance 1 "enable vengeance buff"
+set g_buffs_vengeance_damage 0.9 "damage multiplier while player is under the influence of vengeance"
+set g_buffs_vengeance_damage_take 1.1 "damage multiplier player takes while under the influence of vengeance"
+set g_buffs_vengeance_damage_multiplier 0.6 "amount of damage dealt the attacker takes when hitting a target with vengeance"
+set g_buffs_bash 1 "enable bash buff"
+set g_buffs_bash_force 2 "bash force multiplier"
+set g_buffs_bash_force_self 1.2 "bash self force multiplier"
+set g_buffs_disability 1 "enable disability buff"
+set g_buffs_disability_time 3 "time in seconds for target disability"
+set g_buffs_disability_speed 0.5 "player speed multiplier while disabled"
+set g_buffs_disability_rate 1.7 "player weapon rate multiplier while disabled"
+set g_buffs_speed 1 "enable speed buff"
+set g_buffs_speed_speed 1.7 "player speed multiplier while holding speed buff"
+set g_buffs_speed_rate 0.8 "player weapon rate multiplier while holding speed buff"
+set g_buffs_speed_damage_take 1.2 "damage taken multiplier while holding speed buff"
+set g_buffs_speed_regen 1.2 "regeneration speed multiplier while holding speed buff"
+set g_buffs_vampire 1 "enable vampire buff"
+set g_buffs_vampire_damage_steal 0.6 "damage stolen multiplier while holding vampire buff"
+set g_buffs_jump 1 "enable jump buff"
+set g_buffs_jump_height 600 "jump height while holding jump buff"
+set g_buffs_flight 1 "enable flight buff"
+set g_buffs_flight_gravity 0.3 "player gravity multiplier while holding flight buff"
+set g_buffs_invisible 1 "enable invisibility buff"
+set g_buffs_invisible_alpha 0.1 "player invisibility multiplier while holding invisible buff"
+
index 4ea750ce5dbc5c465713037d84ada9ad97b579c0..c948c9a70e1e7f9c34d7f1ba8d6009fd221691a9 100644 (file)
@@ -109,6 +109,7 @@ void CSQC_Init(void)
        CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
        CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
        CALL_ACCUMULATED_FUNCTION(RegisterHUD_Panels);
+       CALL_ACCUMULATED_FUNCTION(RegisterBuffs);
 
        WaypointSprite_Load();
 
index 67a03259382feba3d5e14adf53087c834eac6491..5449b6737482bef6a560aefa441bde4ab1f4cdd5 100644 (file)
@@ -290,6 +290,8 @@ float autocvar_hud_panel_powerups_baralign;
 float autocvar_hud_panel_powerups_flip;
 float autocvar_hud_panel_powerups_iconalign;
 float autocvar_hud_panel_powerups_progressbar;
+float autocvar_hud_panel_buffs;
+//float autocvar_hud_panel_buffs_iconalign;
 string autocvar_hud_panel_powerups_progressbar_shield;
 string autocvar_hud_panel_powerups_progressbar_strength;
 string autocvar_hud_panel_powerups_progressbar_superweapons;
index bb5eb4a68f534081953aa7d860c69602659d7fa3..9bb7b5331f62bb3b92099c0bd10550edf1155c7a 100644 (file)
@@ -4366,6 +4366,66 @@ void HUD_CenterPrint (void)
        }
 }
 
+// Buffs (#18)
+//
+void HUD_Buffs(void)
+{
+       float buffs = getstati(STAT_BUFFS, 0, 24);
+       if(!autocvar__hud_configure)
+       {
+               if(!autocvar_hud_panel_buffs) return;
+               if(spectatee_status == -1) return;
+               if(getstati(STAT_HEALTH) <= 0) return;
+               if(!buffs) return;
+       }
+       else
+       {
+               buffs = Buff_Type_first.items; // force first buff
+       }
+       
+       float b = 0; // counter to tell other functions that we have buffs
+       entity e;
+       string s = "";
+       for(e = Buff_Type_first; e; e = e.enemy) if(buffs & e.items)
+       {
+               ++b;
+               string o = strcat(rgb_to_hexcolor(Buff_Color(e.items)), Buff_PrettyName(e.items));
+               if(s == "")
+                       s = o;
+               else
+                       s = strcat(s, " ", o);
+       }
+
+       HUD_Panel_UpdateCvars();
+
+       draw_beginBoldFont();
+
+       vector pos, mySize;
+       pos = panel_pos;
+       mySize = panel_size;
+
+       HUD_Panel_DrawBg(bound(0, b, 1));
+       if(panel_bg_padding)
+       {
+               pos += '1 1 0' * panel_bg_padding;
+               mySize -= '2 2 0' * panel_bg_padding;
+       }
+
+       //float panel_ar = mySize_x/mySize_y;
+       //float is_vertical = (panel_ar < 1);
+       //float buff_iconalign = autocvar_hud_panel_buffs_iconalign;
+       vector buff_offset = '0 0 0';
+       
+       for(e = Buff_Type_first; e; e = e.enemy) if(buffs & e.items)
+       {
+               //DrawNumIcon(pos + buff_offset, mySize, shield, "shield", is_vertical, buff_iconalign, '1 1 1', 1);
+               drawcolorcodedstring_aspect(pos + buff_offset, s, mySize, panel_fg_alpha * 0.5, DRAWFLAG_NORMAL);
+       }
+
+       draw_endBoldFont();
+}
+
+
 /*
 ==================
 Main HUD system
index 5c062c5facb3400224105766e1628651f83c8c50..908b6c1d5ec2445140d3773ea7612ccd7bf69e78 100644 (file)
@@ -114,7 +114,8 @@ float current_player;
        HUD_PANEL(ENGINEINFO   , HUD_EngineInfo   , engineinfo) \
        HUD_PANEL(INFOMESSAGES , HUD_InfoMessages , infomessages) \
        HUD_PANEL(PHYSICS      , HUD_Physics      , physics) \
-       HUD_PANEL(CENTERPRINT  , HUD_CenterPrint  , centerprint)
+       HUD_PANEL(CENTERPRINT  , HUD_CenterPrint  , centerprint) \
+       HUD_PANEL(BUFFS        , HUD_Buffs        , buffs) 
 
 #define HUD_PANEL(NAME,draw_func,name) \
        float HUD_PANEL_##NAME; \
index 1a518c5a697f9079977a896ae21cf841cdedeb17..19edfbb5b196b9a88a1cd9249849f6cf9532abeb 100644 (file)
@@ -16,6 +16,7 @@ Defs.qc
 
 ../common/teams.qh
 ../common/util.qh
+../common/buffs.qh
 ../common/test.qh
 ../common/counting.qh
 ../common/items.qh
@@ -117,6 +118,8 @@ command/cl_cmd.qc
 
 ../common/monsters/monsters.qc
 
+../common/buffs.qc
+
 ../warpzonelib/anglestransform.qc
 ../warpzonelib/mathlib.qc
 ../warpzonelib/common.qc
diff --git a/qcsrc/common/buffs.qc b/qcsrc/common/buffs.qc
new file mode 100644 (file)
index 0000000..f9075d0
--- /dev/null
@@ -0,0 +1,44 @@
+vector Buff_Color(float buff_id)
+{
+       entity e;
+       for(e = Buff_Type_first; e; e = e.enemy)
+               if(buff_id == e.items)
+                       return e.colormod;
+       return '1 1 1';
+}
+
+string Buff_PrettyName(float buff_id)
+{
+       entity e;
+       for(e = Buff_Type_first; e; e = e.enemy)
+               if(buff_id == e.items)
+                       return e.message;
+       return "";
+}
+
+string Buff_Name(float buff_id)
+{
+       entity e;
+       for(e = Buff_Type_first; e; e = e.enemy)
+               if(buff_id == e.items)
+                       return e.netname;
+       return "";
+}
+
+float Buff_Type_FromName(string buff_name)
+{
+       entity e;
+       for(e = Buff_Type_first; e; e = e.enemy)
+               if(buff_name == e.netname)
+                       return e.items;
+       return 0;
+}
+
+float Buff_Skin(float buff_id)
+{
+       entity e;
+       for(e = Buff_Type_first; e; e = e.enemy)
+               if(buff_id == e.items)
+                       return e.skin;
+       return 0;
+}
diff --git a/qcsrc/common/buffs.qh b/qcsrc/common/buffs.qh
new file mode 100644 (file)
index 0000000..b61d530
--- /dev/null
@@ -0,0 +1,83 @@
+entity Buff_Type_first;
+entity Buff_Type_last;
+.entity enemy; // internal next pointer
+
+var float BUFF_LAST = 1;
+
+.float items; // buff ID
+.string netname; // buff name
+.string message; // human readable name
+.vector colormod; // buff color
+.float skin; // buff skin
+
+#define REGISTER_BUFF(hname,sname,NAME,bskin,bcolor) \
+       var float BUFF_##NAME; \
+       var entity Buff_Type##sname; \
+       void RegisterBuffs_##sname() \
+       { \
+               BUFF_##NAME = BUFF_LAST * 2; \
+               BUFF_LAST = BUFF_##NAME; \
+               Buff_Type##sname = spawn(); \
+               Buff_Type##sname.items = BUFF_##NAME; \
+               Buff_Type##sname.netname = #sname; \
+               Buff_Type##sname.message = hname; \
+               Buff_Type##sname.skin = bskin; \
+               Buff_Type##sname.colormod = bcolor; \
+               if(!Buff_Type_first) \
+                       Buff_Type_first = Buff_Type##sname; \
+               if(Buff_Type_last) \
+                       Buff_Type_last.enemy = Buff_Type##sname; \
+               Buff_Type_last = Buff_Type##sname; \
+       } \
+       ACCUMULATE_FUNCTION(RegisterBuffs, RegisterBuffs_##sname)
+
+REGISTER_BUFF(_("Ammo"),ammo,AMMO,3,'0.2 1 0.2');
+REGISTER_BUFF(_("Resistance"),resistance,RESISTANCE,0,'0.3 0.2 1');
+REGISTER_BUFF(_("Speed"),speed,SPEED,9,'1 1 0.2');
+REGISTER_BUFF(_("Medic"),medic,MEDIC,1,'1 0.3 1');
+REGISTER_BUFF(_("Bash"),bash,BASH,5,'1 0.4 0');
+REGISTER_BUFF(_("Vampire"),vampire,VAMPIRE,2,'1 0.15 0');
+REGISTER_BUFF(_("Disability"),disability,DISABILITY,7,'0.66 0.66 0.73');
+REGISTER_BUFF(_("Vengeance"),vengeance,VENGEANCE,15,'0.55 0.5 1');
+REGISTER_BUFF(_("Jump"),jump,JUMP,10,'0.7 0.2 1');
+REGISTER_BUFF(_("Flight"),flight,FLIGHT,11,'1 0.2 0.5');
+REGISTER_BUFF(_("Invisible"),invisible,INVISIBLE,12,'0.9 0.9 0.9');
+
+#ifdef SVQC
+.float buffs;
+void buff_Init(entity ent);
+void buff_Init_Compat(entity ent, float replacement);
+
+#define BUFF_SPAWNFUNC(e,b,t) void spawnfunc_item_buff_##e() { self.buffs = b; self.team = t; buff_Init(self); }
+#define BUFF_SPAWNFUNC_Q3TA_COMPAT(o,r) void spawnfunc_##o() { buff_Init_Compat(self,r); }
+#define BUFF_SPAWNFUNCS(e,b)                         \
+        BUFF_SPAWNFUNC(e,           b,  0)           \
+        BUFF_SPAWNFUNC(e##_team1,   b,  NUM_TEAM_1) \
+        BUFF_SPAWNFUNC(e##_team2,   b,  NUM_TEAM_2) \
+        BUFF_SPAWNFUNC(e##_team3,   b,  NUM_TEAM_3) \
+        BUFF_SPAWNFUNC(e##_team4,   b,  NUM_TEAM_4) 
+
+BUFF_SPAWNFUNCS(resistance,            BUFF_RESISTANCE)
+BUFF_SPAWNFUNCS(ammo,                  BUFF_AMMO)
+BUFF_SPAWNFUNCS(speed,                 BUFF_SPEED)
+BUFF_SPAWNFUNCS(medic,                 BUFF_MEDIC)
+BUFF_SPAWNFUNCS(bash,                  BUFF_BASH)
+BUFF_SPAWNFUNCS(vampire,               BUFF_VAMPIRE)
+BUFF_SPAWNFUNCS(disability,            BUFF_DISABILITY)
+BUFF_SPAWNFUNCS(vengeance,             BUFF_VENGEANCE)
+BUFF_SPAWNFUNCS(jump,                  BUFF_JUMP)
+BUFF_SPAWNFUNCS(flight,                        BUFF_FLIGHT)
+BUFF_SPAWNFUNCS(invisible,             BUFF_INVISIBLE)
+BUFF_SPAWNFUNCS(random,                        0)
+
+BUFF_SPAWNFUNC_Q3TA_COMPAT(item_doubler,    BUFF_MEDIC)
+BUFF_SPAWNFUNC_Q3TA_COMPAT(item_resistance,    BUFF_RESISTANCE)
+BUFF_SPAWNFUNC_Q3TA_COMPAT(item_scout,      BUFF_SPEED)
+BUFF_SPAWNFUNC_Q3TA_COMPAT(item_ammoregen,  BUFF_AMMO)
+#endif
+
+vector Buff_Color(float buff_id);
+string Buff_PrettyName(float buff_id);
+string Buff_Name(float buff_id);
+float Buff_Type_FromName(string buff_name);
+float Buff_Skin(float buff_id);
index e02fad45f06090b6b3d8e24bfed12f7ade651548..552fd74c451c12ce66a1f6eff374dd58aeefef1b 100644 (file)
@@ -185,6 +185,8 @@ const float STAT_WEAPONS3 = 75;
 const float STAT_MONSTERS_TOTAL = 76;
 const float STAT_MONSTERS_KILLED = 77;
 
+const float STAT_BUFFS = 78;
+
 // mod stats (1xx)
 const float STAT_REDALIVE = 100;
 const float STAT_BLUEALIVE = 101;
index 1265e7eea05736d3ac2270e1e0b3a33af5e141c5..e61568217c70b36e43e9c80cec80709b678bf6c0 100644 (file)
@@ -4,6 +4,8 @@
 
 #define DEATHTYPES \
        DEATHTYPE(DEATH_AUTOTEAMCHANGE,         DEATH_SELF_AUTOTEAMCHANGE,          NO_MSG,                        DEATH_SPECIAL_START) \
+       DEATHTYPE(DEATH_BUFF_VAMPIRE,           NO_MSG,                             DEATH_MURDER_VAMPIRE,          DEATH_BUFF_FIRST) \
+       DEATHTYPE(DEATH_BUFF_VENGEANCE,         NO_MSG,                             DEATH_MURDER_VENGEANCE,        DEATH_BUFF_LAST) \
        DEATHTYPE(DEATH_CAMP,                   DEATH_SELF_CAMP,                    NO_MSG,                        NORMAL_POS) \
        DEATHTYPE(DEATH_CHEAT,                  DEATH_SELF_CHEAT,                   DEATH_MURDER_CHEAT,            NORMAL_POS) \
        DEATHTYPE(DEATH_CUSTOM,                 DEATH_SELF_CUSTOM,                  NO_MSG,                        NORMAL_POS) \
@@ -95,6 +97,7 @@ DEATHTYPES
 #define DEATH_ISVEHICLE(t)            ((t) >= DEATH_VHFIRST && (t) <= DEATH_VHLAST)
 #define DEATH_ISTURRET(t)             ((t) >= DEATH_TURRET_FIRST && (t) <= DEATH_TURRET_LAST)
 #define DEATH_ISMONSTER(t)            ((t) >= DEATH_MONSTER_FIRST && (t) <= DEATH_MONSTER_LAST)
+#define DEATH_ISBUFF(t)               ((t) >= DEATH_BUFF_FIRST && (t) <= DEATH_BUFF_LAST)
 #define DEATH_WEAPONOFWEAPONDEATH(t)  ((t) & DEATH_WEAPONMASK)
 #define DEATH_ISWEAPON(t,w)           (!DEATH_ISSPECIAL(t) && DEATH_WEAPONOFWEAPONDEATH(t) == (w))
 #define DEATH_WEAPONOF(t)             (DEATH_ISSPECIAL(t) ? 0 : DEATH_WEAPONOFWEAPONDEATH(t))
index 003c0fcc1d64fc4fa0e112576e4342f5933b5d36..92d2830ff9ea99857b8fc0aab192e7f3681d195b 100644 (file)
@@ -378,6 +378,8 @@ void Send_Notification_WOCOVA(
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_WAKI_DEATH,     3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_death",         _("^BG%s%s^K1 got caught in the blast when ^BG%s^K1's Racer exploded%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_WAKI_GUN,       3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_death",         _("^BG%s%s^K1 was bolted down by ^BG%s^K1's Racer%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_WAKI_ROCKET,    3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_death",         _("^BG%s%s^K1 couldn't find shelter from ^BG%s^K1's Racer%s%s"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VAMPIRE,           3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_death",         _("^BG%s%s^K1's blood was sucked by ^BG%s^K1%s%s"), "") \
+       MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VENGEANCE,         3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_death",         _("^BG%s%s^K1 was destroyed by the vengeful ^BG%s^K1%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VOID,              3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_void",          _("^BG%s%s^K1 was thrown into a world of hurt by ^BG%s^K1%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_AUTOTEAMCHANGE,      2, 1, "s1 s2loc death_team", "",         "",                     _("^BG%s^K1 was moved into the %s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_BETRAYAL,            2, 1, "s1 s2loc spree_lost", "s1",       "notify_teamkill_red",  _("^BG%s^K1 became enemies with the Lord of Teamplay%s%s"), "") \
@@ -439,6 +441,10 @@ void Send_Notification_WOCOVA(
        MSG_INFO_NOTIF(1, INFO_ROUND_OVER,                     0, 0, "", "",                            "",                     _("^BGRound over, there's no winner"), "") \
        MSG_INFO_NOTIF(1, INFO_FREEZETAG_SELF,                 1, 0, "s1", "",                          "",                     _("^BG%s^K1 froze themself"), "") \
        MSG_INFO_NOTIF(1, INFO_GODMODE_OFF,                    0, 1, "f1", "",                          "",                     _("^BGGodmode saved you %s units of damage, cheater!"), "") \
+       MSG_INFO_NOTIF(1, INFO_ITEM_BUFF,                      1, 1, "s1 item_buffname", "",            "",                     _("^BG%s^BG got the %s^BG Buff!"), "") \
+       MSG_INFO_NOTIF(1, INFO_ITEM_BUFF_LOST,                 1, 1, "s1 item_buffname", "",            "",                     _("^BG%s^BG lost the %s^BG Buff!"), "") \
+       MSG_INFO_NOTIF(1, INFO_ITEM_BUFF_DROP,                 0, 1, "item_buffname", "",               "",                     _("^BGYou dropped the %s^BG Buff!"), "") \
+       MSG_INFO_NOTIF(1, INFO_ITEM_BUFF_GOT,                  0, 1, "item_buffname", "",               "",                     _("^BGYou got the %s^BG Buff!"), "") \
        MSG_INFO_NOTIF(0, INFO_ITEM_WEAPON_DONTHAVE,           0, 1, "item_wepname", "",                      "",               _("^BGYou do not have the ^F1%s"), "") \
        MSG_INFO_NOTIF(0, INFO_ITEM_WEAPON_DROP,               1, 1, "item_wepname item_wepammo", "",         "",               _("^BGYou dropped the ^F1%s^BG%s"), "") \
        MSG_INFO_NOTIF(0, INFO_ITEM_WEAPON_GOT,                0, 1, "item_wepname", "",                      "",               _("^BGYou got the ^F1%s"), "") \
@@ -627,6 +633,8 @@ void Send_Notification_WOCOVA(
        MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_SELF,              0, 0, "",             NO_CPID,             "0 0", _("^K1You froze yourself"), "") \
        MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_SPAWN_LATE,        0, 0, "",             NO_CPID,             "0 0", _("^K1Round already started, you spawn as frozen"), "") \
        MSG_CENTER_NOTIF(1, CENTER_INVASION_SUPERMONSTER,       1, 0, "s1",           NO_CPID,             "0 0", _("^K1A %s has arrived!"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_ITEM_BUFF_DROP,              0, 1, "item_buffname",                     CPID_ITEM, "item_centime 0", _("^BGYou dropped the %s^BG Buff!"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_ITEM_BUFF_GOT,               0, 1, "item_buffname",                     CPID_ITEM, "item_centime 0", _("^BGYou got the %s^BG Buff!"), "") \
        MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_DONTHAVE,        0, 1, "item_wepname",                      CPID_ITEM, "item_centime 0", _("^BGYou do not have the ^F1%s"), "") \
        MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_DROP,            1, 1, "item_wepname item_wepammo",         CPID_ITEM, "item_centime 0", _("^BGYou dropped the ^F1%s^BG%s"), "") \
        MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_GOT,             0, 1, "item_wepname",                      CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1%s"), "") \
@@ -716,6 +724,8 @@ void Send_Notification_WOCOVA(
        MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_WAKI_DEATH,           NO_MSG,        INFO_DEATH_MURDER_VH_WAKI_DEATH,           NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_WAKI_GUN,             NO_MSG,        INFO_DEATH_MURDER_VH_WAKI_GUN,             NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_WAKI_ROCKET,          NO_MSG,        INFO_DEATH_MURDER_VH_WAKI_ROCKET,          NO_MSG) \
+       MSG_MULTI_NOTIF(1, DEATH_MURDER_VAMPIRE,                 NO_MSG,        INFO_DEATH_MURDER_VAMPIRE,                 NO_MSG) \
+       MSG_MULTI_NOTIF(1, DEATH_MURDER_VENGEANCE,               NO_MSG,        INFO_DEATH_MURDER_VENGEANCE,               NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_VOID,                    NO_MSG,        INFO_DEATH_MURDER_VOID,                    NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_AUTOTEAMCHANGE,            NO_MSG,        INFO_DEATH_SELF_AUTOTEAMCHANGE,            CENTER_DEATH_SELF_AUTOTEAMCHANGE) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_BETRAYAL,                  NO_MSG,        INFO_DEATH_SELF_BETRAYAL,                  CENTER_DEATH_SELF_BETRAYAL) \
@@ -766,6 +776,8 @@ void Send_Notification_WOCOVA(
        MSG_MULTI_NOTIF(1, DEATH_SELF_VH_WAKI_DEATH,             NO_MSG,        INFO_DEATH_SELF_VH_WAKI_DEATH,             CENTER_DEATH_SELF_VH_WAKI_DEATH) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_VH_WAKI_ROCKET,            NO_MSG,        INFO_DEATH_SELF_VH_WAKI_ROCKET,            CENTER_DEATH_SELF_VH_WAKI_ROCKET) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_VOID,                      NO_MSG,        INFO_DEATH_SELF_VOID,                      CENTER_DEATH_SELF_VOID) \
+       MSG_MULTI_NOTIF(1, ITEM_BUFF_DROP,                       NO_MSG,        INFO_ITEM_BUFF_DROP,                       CENTER_ITEM_BUFF_DROP) \
+       MSG_MULTI_NOTIF(1, ITEM_BUFF_GOT,                        NO_MSG,        INFO_ITEM_BUFF_GOT,                        CENTER_ITEM_BUFF_GOT) \
        MSG_MULTI_NOTIF(1, ITEM_WEAPON_DONTHAVE,                 NO_MSG,        INFO_ITEM_WEAPON_DONTHAVE,                 CENTER_ITEM_WEAPON_DONTHAVE) \
        MSG_MULTI_NOTIF(1, ITEM_WEAPON_DROP,                     NO_MSG,        INFO_ITEM_WEAPON_DROP,                     CENTER_ITEM_WEAPON_DROP) \
        MSG_MULTI_NOTIF(1, ITEM_WEAPON_GOT,                      NO_MSG,        INFO_ITEM_WEAPON_GOT,                      CENTER_ITEM_WEAPON_GOT) \
@@ -933,6 +945,7 @@ var float autocvar_notification_show_sprees_center_specialonly = TRUE;
     item_wepname: return full name of a weapon from weaponid
     item_wepammo: ammo display for weapon from string
     item_centime: amount of time to display weapon message in centerprint
+    item_buffname: return full name of a buff from buffid
     death_team: show the full name of the team a player is switching from
 */
 
@@ -985,6 +998,7 @@ string arg_slot[NOTIF_MAX_ARGS];
     ARG_CASE(ARG_CS_SV,     "spree_end",     (autocvar_notification_show_sprees ? notif_arg_spree_inf(-1, "", "", f1) : "")) \
     ARG_CASE(ARG_CS_SV,     "spree_lost",    (autocvar_notification_show_sprees ? notif_arg_spree_inf(-2, "", "", f1) : "")) \
     ARG_CASE(ARG_CS_SV,     "item_wepname",  W_Name(f1)) \
+    ARG_CASE(ARG_CS_SV,     "item_buffname", sprintf("%s%s", rgb_to_hexcolor(Buff_Color(f1)), Buff_PrettyName(f1))) \
     ARG_CASE(ARG_CS_SV,     "item_wepammo",  (s1 != "" ? sprintf(_(" with %s"), s1) : "")) \
     ARG_CASE(ARG_DC,        "item_centime",  ftos(autocvar_notification_item_centerprinttime)) \
     ARG_CASE(ARG_SV,        "death_team",    Team_ColoredFullName(f1)) \
index 1de86b6474fe51e529d809a0eeeb69bd9738e572..8752fa6fe02f34e22d61f36cc9c6fd787e5a3cb1 100644 (file)
 #include "xonotic/dialog_hudpanel_weapons.c"
 #include "xonotic/dialog_hudpanel_physics.c"
 #include "xonotic/dialog_hudpanel_centerprint.c"
+#include "xonotic/dialog_hudpanel_buffs.c"
 #include "xonotic/slider_picmip.c"
diff --git a/qcsrc/menu/xonotic/dialog_hudpanel_buffs.c b/qcsrc/menu/xonotic/dialog_hudpanel_buffs.c
new file mode 100644 (file)
index 0000000..ac1033c
--- /dev/null
@@ -0,0 +1,22 @@
+#ifdef INTERFACE
+CLASS(XonoticHUDBuffsDialog) EXTENDS(XonoticRootDialog)
+       METHOD(XonoticHUDBuffsDialog, fill, void(entity))
+       ATTRIB(XonoticHUDBuffsDialog, title, string, _("Buffs Panel"))
+       ATTRIB(XonoticHUDBuffsDialog, color, vector, SKINCOLOR_DIALOG_TEAMSELECT)
+       ATTRIB(XonoticHUDBuffsDialog, intendedWidth, float, 0.4)
+       ATTRIB(XonoticHUDBuffsDialog, rows, float, 15)
+       ATTRIB(XonoticHUDBuffsDialog, columns, float, 4)
+       ATTRIB(XonoticHUDBuffsDialog, name, string, "HUDbuffs")
+       ATTRIB(XonoticHUDBuffsDialog, requiresConnection, float, TRUE)
+ENDCLASS(XonoticHUDBuffsDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void XonoticHUDBuffsDialog_fill(entity me)
+{
+       entity e;
+       string panelname = "buffs";
+
+       DIALOG_HUDPANEL_COMMON();
+}
+#endif
index 72cecea19cfce5ce4e9627e2adcad594eb79fa93..834242b1c5b4134ffb0d502cb347da94c7a95b81 100644 (file)
@@ -125,6 +125,10 @@ void MainWindow_configureMainWindow(entity me)
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
 
+       i = spawnXonoticHUDBuffsDialog();
+       i.configureDialog(i);
+       me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+
 
        // dialogs used by settings
        me.userbindEditDialog = i = spawnXonoticUserbindEditDialog();
index 674c95b14ea735e2b67e0cb063f21702e2a1c754..ce6da5c5268605ca0c3eaceef4147ac87011464b 100644 (file)
@@ -1278,3 +1278,33 @@ float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
 float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death;
 float autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health;
 float autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath;
+float autocvar_g_buffs_waypoint_distance;
+float autocvar_g_buffs_randomize;
+float autocvar_g_buffs_random_lifetime;
+float autocvar_g_buffs_random_location;
+float autocvar_g_buffs_random_location_attempts;
+float autocvar_g_buffs_spawn_count;
+float autocvar_g_buffs_replace_powerups;
+float autocvar_g_buffs_cooldown_activate;
+float autocvar_g_buffs_cooldown_respawn;
+float autocvar_g_buffs_resistance_blockpercent;
+float autocvar_g_buffs_medic_survive_chance;
+float autocvar_g_buffs_medic_survive_health;
+float autocvar_g_buffs_medic_rot;
+float autocvar_g_buffs_medic_max;
+float autocvar_g_buffs_medic_regen;
+float autocvar_g_buffs_vengeance_damage_multiplier;
+float autocvar_g_buffs_bash_force;
+float autocvar_g_buffs_bash_force_self;
+float autocvar_g_buffs_disability_time;
+float autocvar_g_buffs_disability_speed;
+float autocvar_g_buffs_disability_rate;
+float autocvar_g_buffs_speed_speed;
+float autocvar_g_buffs_speed_rate;
+float autocvar_g_buffs_speed_damage_take;
+float autocvar_g_buffs_speed_regen;
+float autocvar_g_buffs_vampire_damage_steal;
+float autocvar_g_buffs_invisible_alpha;
+float autocvar_g_buffs_flight_gravity;
+float autocvar_g_buffs_jump_height;
+
index d06bc96bed3d933ca54e9d8c74c25917f9dcfa03..fb12db4ef9cd110c65642e80ff594990544d85ba 100644 (file)
@@ -1585,17 +1585,26 @@ float CalcRotRegen(float current, float regenstable, float regenfactor, float re
 
 void player_regen (void)
 {
+       float max_mod, regen_mod, rot_mod, limit_mod;
+       max_mod = regen_mod = rot_mod = limit_mod = 1;
+       regen_mod_max = max_mod;
+       regen_mod_regen = regen_mod;
+       regen_mod_rot = rot_mod;
+       regen_mod_limit = limit_mod;
        if(!MUTATOR_CALLHOOK(PlayerRegen))
        {
-               float minh, mina, maxh, maxa, limith, limita, max_mod, regen_mod, rot_mod, limit_mod;
+               float minh, mina, maxh, maxa, limith, limita;
                maxh = autocvar_g_balance_health_rotstable;
                maxa = autocvar_g_balance_armor_rotstable;
                minh = autocvar_g_balance_health_regenstable;
                mina = autocvar_g_balance_armor_regenstable;
                limith = autocvar_g_balance_health_limit;
                limita = autocvar_g_balance_armor_limit;
-
-               max_mod = regen_mod = rot_mod = limit_mod = 1;
+               
+               max_mod = regen_mod_max;
+               regen_mod = regen_mod_regen;
+               rot_mod = regen_mod_rot;
+               limit_mod = regen_mod_limit;
 
                maxh = maxh * max_mod;
                minh = minh * max_mod;
index 0d0dd3138aa9600e06e0aef3fb152d6410d30987..4bfc2887067a3968c7498882b8f74a0512c18b15 100644 (file)
@@ -20,14 +20,15 @@ When you press the jump key
 void PlayerJump (void)
 {
        float doublejump = FALSE;
+       float mjumpheight = autocvar_sv_jumpvelocity;
 
        player_multijump = doublejump;
+       player_jumpheight = mjumpheight;
        if(MUTATOR_CALLHOOK(PlayerJump))
                return;
 
        doublejump = player_multijump;
-
-       float mjumpheight;
+       mjumpheight = player_jumpheight;
 
        if (autocvar_sv_doublejump)
        {
@@ -44,7 +45,6 @@ void PlayerJump (void)
                }
        }
 
-       mjumpheight = autocvar_sv_jumpvelocity;
        if (self.waterlevel >= WATERLEVEL_SWIMMING)
        {
                self.velocity_z = self.stat_sv_maxspeed * 0.7;
index f3d675f35e71a3000ef814d410c43930cb959b59..67a1ddb1c028a13071c66e485e7c998908adb6cb 100644 (file)
@@ -14,6 +14,10 @@ float W_WeaponRateFactor()
        float t;
        t = 1.0 / g_weaponratefactor;
 
+       weapon_rate = t;
+       MUTATOR_CALLHOOK(WeaponRateFactor);
+       t = weapon_rate;
+
        return t;
 }
 
index c779f9d4bedc1e3a4ec8c50d946b62c18c8a3e4e..80176c25e76bc9b9443e9c3cd2f6bfa8077d3407 100644 (file)
@@ -546,6 +546,7 @@ void spawnfunc___init_dedicated_server(void)
        CALL_ACCUMULATED_FUNCTION(RegisterGametypes);
        CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
        CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
+       CALL_ACCUMULATED_FUNCTION(RegisterBuffs);
 
        MapInfo_Enumerate();
        MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0);
@@ -595,6 +596,7 @@ void spawnfunc_worldspawn (void)
        CALL_ACCUMULATED_FUNCTION(RegisterGametypes);
        CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
        CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
+       CALL_ACCUMULATED_FUNCTION(RegisterBuffs);
 
        ServerProgsDB = db_load(strcat("server.db", autocvar_sessionid));
 
index 5dc6f2e39346ff99098d6fe4a3a0768012b3db0a..7efaf33c58852d41eee9327a04b38ce8028d6189 100644 (file)
@@ -930,6 +930,7 @@ void readlevelcvars(void)
        CHECK_MUTATOR_ADD("g_nades", mutator_nades, 1);
        CHECK_MUTATOR_ADD("g_sandbox", sandbox, 1);
        CHECK_MUTATOR_ADD("g_campcheck", mutator_campcheck, 1);
+       CHECK_MUTATOR_ADD("g_buffs", mutator_buffs, 1);
 
        #undef CHECK_MUTATOR_ADD
 
index 0d2d7c96a64d48e1e53daa4dfb6b3c8b14a0d88d..198b7d991b1393b169d7aae7ffd1b2d2d6f99607 100644 (file)
@@ -77,6 +77,7 @@ MUTATOR_HOOKABLE(PlayerJump);
        // called when a player presses the jump key
        // INPUT, OUTPUT:
                float player_multijump;
+               float player_jumpheight;
 
 MUTATOR_HOOKABLE(GiveFragsForKill);
        // called when someone was fragged by "self", and is expected to change frag_score to adjust scoring for the kill
@@ -102,6 +103,11 @@ MUTATOR_HOOKABLE(SpectateCopy);
 MUTATOR_HOOKABLE(ForbidThrowCurrentWeapon);
        // returns 1 if throwing the current weapon shall not be allowed
 
+MUTATOR_HOOKABLE(WeaponRateFactor);
+       // allows changing attack rate
+       // INPUT, OUTPUT:
+               float weapon_rate;
+
 MUTATOR_HOOKABLE(SetStartItems);
        // adjusts {warmup_}start_{items,weapons,ammo_{cells,rockets,nails,shells,fuel}}
 
@@ -222,6 +228,11 @@ MUTATOR_HOOKABLE(PlayerPowerups);
 MUTATOR_HOOKABLE(PlayerRegen);
        // called every player think frame
        // return 1 to disable regen
+       // INPUT, OUTPUT:
+               float regen_mod_max;
+               float regen_mod_regen;
+               float regen_mod_rot;
+               float regen_mod_limit;
 
 MUTATOR_HOOKABLE(PlayerUseKey);
        // called when the use key is pressed
diff --git a/qcsrc/server/mutators/mutator_buffs.qc b/qcsrc/server/mutators/mutator_buffs.qc
new file mode 100644 (file)
index 0000000..70b5246
--- /dev/null
@@ -0,0 +1,744 @@
+float buffs_BuffModel_Customize()
+{
+       float same_team = (SAME_TEAM(other, self.owner) || (IS_SPEC(other) && SAME_TEAM(other.enemy, self.owner)));
+       if(self.owner.alpha <= 0.5 && !same_team && self.owner.alpha != 0)
+               return FALSE;
+               
+       if(other == self.owner || (IS_SPEC(other) && other.enemy == self.owner))
+       {
+               // somewhat hide the model, but keep the glow
+               self.effects = 0;
+               self.alpha = -1;
+       }
+       else
+       {
+               self.effects = EF_FULLBRIGHT | EF_LOWPRECISION;
+               self.alpha = 1;
+       }
+       return TRUE;
+}
+
+// buff item
+float buff_Waypoint_visible_for_player(entity plr)
+{
+    if(!self.owner.buff_active && !self.owner.buff_activetime)
+        return FALSE;
+               
+       if(plr.buffs)
+       {
+               if(plr.cvar_cl_buffs_autoreplace)
+               {
+                       if(plr.buffs == self.owner.buffs)
+                               return FALSE;
+               }
+               else
+                       return FALSE;
+       }
+
+    return WaypointSprite_visible_for_player(plr);
+}
+
+void buff_Waypoint_Spawn(entity e)
+{
+    WaypointSprite_Spawn(Buff_PrettyName(e.buffs), 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs_z, world, e.team, e, buff_waypoint, TRUE, RADARICON_POWERUP, e.glowmod);
+    WaypointSprite_UpdateTeamRadar(e.buff_waypoint, RADARICON_POWERUP, e.glowmod);
+    e.buff_waypoint.waypointsprite_visible_for_player = buff_Waypoint_visible_for_player;
+}
+
+void buff_SetCooldown(float cd)
+{
+       cd = max(0, cd);
+
+       if(!self.buff_waypoint)
+               buff_Waypoint_Spawn(self);
+
+       WaypointSprite_UpdateBuildFinished(self.buff_waypoint, time + cd);
+       self.buff_activetime = cd;
+       self.buff_active = !cd;
+}
+
+void buff_Respawn(entity ent)
+{
+       if(gameover) { return; }
+       
+       vector oldbufforigin = ent.origin;
+       
+       if(!MoveToRandomMapLocation(ent, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, ((autocvar_g_buffs_random_location_attempts > 0) ? autocvar_g_buffs_random_location_attempts : 10), 1024, 256))
+       {
+               entity spot = SelectSpawnPoint(TRUE);
+               setorigin(self, (spot.origin + '0 0 200') + (randomvec() * 300));
+               self.angles = spot.angles;
+       }
+       
+       makevectors(ent.angles);
+       ent.velocity = '0 0 200';
+       ent.angles = '0 0 0';
+       if(autocvar_g_buffs_random_lifetime > 0)
+               ent.lifetime = time + autocvar_g_buffs_random_lifetime;
+
+       pointparticles(particleeffectnum("electro_combo"), oldbufforigin + ((ent.mins + ent.maxs) * 0.5), '0 0 0', 1);
+       pointparticles(particleeffectnum("electro_combo"), CENTER_OR_VIEWOFS(ent), '0 0 0', 1);
+       
+       WaypointSprite_Ping(ent.buff_waypoint);
+       
+       sound(ent, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+}
+
+void buff_Touch()
+{
+       if(gameover) { return; }
+
+       if(ITEM_TOUCH_NEEDKILL())
+       {
+               buff_Respawn(self);
+               return;
+       }
+
+       if((self.team && DIFF_TEAM(other, self))
+       || (other.freezetag_frozen)
+       || (other.vehicle)
+       || (!IS_PLAYER(other))
+       || (!self.buff_active)
+       )
+       {
+               // can't touch this
+               return;
+       }
+
+       if(other.buffs)
+       {
+               if(other.cvar_cl_buffs_autoreplace && other.buffs != self.buffs)
+               {
+                       //Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_DROP, other.buffs);
+                       Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ITEM_BUFF_LOST, other.netname, other.buffs);
+
+                       other.buffs = 0;
+                       //sound(other, CH_TRIGGER, "relics/relic_effect.wav", VOL_BASE, ATTN_NORM);
+               }
+               else { return; } // do nothing
+       }
+               
+       self.owner = other;
+       self.buff_active = FALSE;
+       self.lifetime = 0;
+       
+       Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_GOT, self.buffs);
+       Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_INFO, INFO_ITEM_BUFF, other.netname, self.buffs);
+
+       pointparticles(particleeffectnum("item_pickup"), CENTER_OR_VIEWOFS(self), '0 0 0', 1);
+       sound(other, CH_TRIGGER, "misc/shield_respawn.wav", VOL_BASE, ATTN_NORM);
+       other.buffs |= (self.buffs);
+}
+
+void buff_NewType(entity ent, float cb)
+{
+       entity e;
+       RandomSelection_Init();
+       for(e = Buff_Type_first; e; e = e.enemy)
+       {
+               if(e.items == BUFF_AMMO && ((start_items & IT_UNLIMITED_WEAPON_AMMO) || (start_items & IT_UNLIMITED_AMMO)))
+                       continue;
+               if(e.items == BUFF_VAMPIRE && cvar("g_vampire"))
+                       continue;
+               if(!cvar(strcat("g_buffs_", e.netname)))
+                       continue;
+               RandomSelection_Add(world, e.items, string_null, 1, 1 / e.count); // if it's already been chosen, give it a lower priority
+               e.count += 1;
+       }
+       ent.buffs = RandomSelection_chosen_float;
+}
+
+void buff_Think()
+{
+       if(self.buffs != self.oldbuffs)
+       {
+               self.color = Buff_Color(self.buffs);
+               self.glowmod = ((self.team) ? Team_ColorRGB(self.team) + '0.1 0.1 0.1' : self.color);
+               self.skin = Buff_Skin(self.buffs);
+               
+               setmodel(self, "models/relics/relic.md3");
+
+               if(self.buff_waypoint)
+               {
+                       //WaypointSprite_Disown(self.buff_waypoint, 1);
+                       WaypointSprite_Kill(self.buff_waypoint);
+                       buff_Waypoint_Spawn(self);
+                       if(self.buff_activetime)
+                               WaypointSprite_UpdateBuildFinished(self.buff_waypoint, time + self.buff_activetime - frametime);
+               }
+
+               self.oldbuffs = self.buffs;
+       }
+
+       if(!gameover)
+       if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime)
+       if(!self.buff_activetime_updated)
+       {
+               buff_SetCooldown(self.buff_activetime);
+               self.buff_activetime_updated = TRUE;
+    }
+
+       if(!self.buff_active && !self.buff_activetime)
+       if(!self.owner || self.owner.freezetag_frozen || self.owner.deadflag != DEAD_NO || !self.owner.iscreature || !(self.owner.buffs & self.buffs))
+       {
+               buff_SetCooldown(autocvar_g_buffs_cooldown_respawn + frametime);
+               self.owner = world;
+               if(autocvar_g_buffs_randomize)
+                       buff_NewType(self, self.buffs);
+                       
+               if(autocvar_g_buffs_random_location || (self.spawnflags & 1))
+                       buff_Respawn(self);
+       }
+       
+       if(self.buff_activetime)
+       if(!gameover)
+       if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime)
+       {
+               self.buff_activetime = max(0, self.buff_activetime - frametime);
+
+               if(!self.buff_activetime)
+               {
+                       self.buff_active = TRUE;
+                       sound(self, CH_TRIGGER, "misc/strength_respawn.wav", VOL_BASE, ATTN_NORM);
+                       pointparticles(particleeffectnum("item_respawn"), CENTER_OR_VIEWOFS(self), '0 0 0', 1);
+               }
+       }
+
+       if(!self.buff_active)
+       {
+               self.alpha = 0.3;
+               self.effects &= ~(EF_FULLBRIGHT);
+               self.pflags = 0;
+       }
+       else
+       {
+               self.alpha = 1;
+               self.effects |= EF_FULLBRIGHT;
+               self.light_lev = 220 + 36 * sin(time);
+               self.pflags = PFLAGS_FULLDYNAMIC;
+
+               if(self.team && !self.buff_waypoint)
+                       buff_Waypoint_Spawn(self);
+                       
+               if(self.lifetime)
+               if(time >= self.lifetime)
+                       buff_Respawn(self);
+       }
+    
+       self.nextthink = time;
+       //self.angles_y = time * 110.1;
+}
+
+void buff_Waypoint_Reset()
+{
+       WaypointSprite_Kill(self.buff_waypoint);
+
+       if(self.buff_activetime) { buff_Waypoint_Spawn(self); }
+}
+
+void buff_Reset()
+{
+       if(autocvar_g_buffs_randomize)
+               buff_NewType(self, self.buffs);
+       self.owner = world;
+       buff_SetCooldown(autocvar_g_buffs_cooldown_activate);
+       buff_Waypoint_Reset();
+       self.buff_activetime_updated = FALSE;
+       
+       if(autocvar_g_buffs_random_location || (self.spawnflags & 1))
+               buff_Respawn(self);
+}
+
+void buff_Init(entity ent)
+{
+       if(!cvar("g_buffs")) { remove(self); return; }
+       
+       if(!teamplay && self.team) { self.team = 0; }
+
+       entity oldself = self;
+       self = ent;
+       if(!self.buffs || !cvar(strcat("g_buffs_", Buff_Name(self.buffs))))
+               buff_NewType(self, 0);
+       
+       self.classname = "item_buff";
+       self.solid = SOLID_TRIGGER;
+       self.flags = FL_ITEM;
+       self.think = buff_Think;
+       self.touch = buff_Touch;
+       self.reset = buff_Reset;
+       self.nextthink = time + 0.1;
+       self.gravity = 1;
+       self.movetype = MOVETYPE_TOSS;
+       self.scale = 1;
+       self.skin = Buff_Skin(self.buffs);
+       self.effects = EF_FULLBRIGHT | EF_STARDUST | EF_NOSHADOW;
+       self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY;
+       //self.gravity = 100;
+       self.color = Buff_Color(self.buffs);
+       self.glowmod = ((self.team) ? Team_ColorRGB(self.team) + '0.1 0.1 0.1' : self.color);
+       buff_SetCooldown(autocvar_g_buffs_cooldown_activate + game_starttime);
+       self.buff_active = !self.buff_activetime;
+       self.pflags = PFLAGS_FULLDYNAMIC;
+
+       setmodel(self, "models/relics/relic.md3");
+       setsize(self, BUFF_MIN, BUFF_MAX);
+       
+       if(cvar("g_buffs_random_location") || (self.spawnflags & 1))
+               buff_Respawn(self);
+       
+       self = oldself;
+}
+
+void buff_Init_Compat(entity ent, float replacement)
+{
+       if(ent.spawnflags & 2)
+               ent.team = NUM_TEAM_1;
+       else if(ent.spawnflags & 4)
+               ent.team = NUM_TEAM_2;
+
+       ent.buffs = replacement;
+
+       buff_Init(ent);
+}
+
+void buff_SpawnReplacement(entity ent, entity old)
+{
+       setorigin(ent, old.origin);
+       ent.angles = old.angles;
+       ent.noalign = old.noalign;
+       
+       buff_Init(ent);
+}
+
+// mutator hooks
+MUTATOR_HOOKFUNCTION(buffs_PlayerDamage_SplitHealthArmor)
+{
+       if(DEATH_ISBUFF(frag_deathtype)) { return FALSE; } // oh no you don't
+
+       if(frag_target.buffs & BUFF_RESISTANCE)
+       {
+               vector v = healtharmor_applydamage(50, autocvar_g_buffs_resistance_blockpercent, frag_deathtype, frag_damage);
+               damage_take = v_x;
+               damage_save = v_y;
+       }
+
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_PlayerDamage_Calculate)
+{
+       if(DEATH_ISBUFF(frag_deathtype)) { return FALSE; } // oh no you don't
+
+       if(frag_target.buffs & BUFF_SPEED)
+       if(frag_target != frag_attacker)
+               frag_damage *= autocvar_g_buffs_speed_damage_take;
+
+       if(frag_target.buffs & BUFF_MEDIC)
+       if((frag_target.health - frag_damage) <= 0)
+       if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
+       if(frag_attacker)
+       if(random() <= autocvar_g_buffs_medic_survive_chance)
+               frag_damage = frag_target.health - autocvar_g_buffs_medic_survive_health;
+               
+       if(frag_target.buffs & BUFF_VENGEANCE)
+       if(frag_attacker)
+       if(frag_attacker != frag_target)
+       if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
+       {
+               vector v = healtharmor_applydamage(frag_attacker.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_BUFF_VENGEANCE, frag_damage * autocvar_g_buffs_vengeance_damage_multiplier);
+               float take = v_x;
+               Damage(frag_attacker, frag_target, frag_target, take, DEATH_BUFF_VENGEANCE, frag_attacker.origin, '0 0 0');
+       }
+
+       if(frag_target.buffs & BUFF_BASH)
+       if(frag_attacker != frag_target)
+       if(vlen(frag_force))
+               frag_force = '0 0 0';
+       
+       if(frag_attacker.buffs & BUFF_BASH)
+       if(vlen(frag_force))
+       if(frag_attacker == frag_target)
+               frag_force *= autocvar_g_buffs_bash_force_self;
+       else
+               frag_force *= autocvar_g_buffs_bash_force;
+       
+       if(frag_attacker.buffs & BUFF_DISABILITY)
+       if(frag_target != frag_attacker)
+               frag_target.buff_disability_time = time + autocvar_g_buffs_disability_time;
+
+       if(frag_attacker.buffs & BUFF_MEDIC)
+       if(SAME_TEAM(frag_attacker, frag_target))
+       if(frag_attacker != frag_target)
+       {
+               frag_target.health = min(g_pickup_healthmega_max, frag_target.health + frag_damage);
+               frag_damage = 0;
+       }
+
+       // this... is ridiculous (TODO: fix!)
+       if(frag_attacker.buffs & BUFF_VAMPIRE)
+       if(!frag_target.vehicle)
+       if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
+       if(frag_target.deadflag == DEAD_NO)
+       if(IS_PLAYER(frag_target) || (frag_target.flags & FL_MONSTER))
+       if(frag_attacker != frag_target)
+       if(!frag_target.freezetag_frozen)
+       if(frag_target.takedamage)
+       if(DIFF_TEAM(frag_attacker, frag_target))
+               frag_attacker.health = bound(0, frag_attacker.health + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.health), g_pickup_healthsmall_max);
+
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_PlayerSpawn)
+{
+       self.buffs = 0;
+       // reset timers here to prevent them continuing after re-spawn
+       self.buff_disability_time = 0;
+       self.buff_disability_effect_time = 0;
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_PlayerPhysics)
+{
+       if(self.buffs & BUFF_SPEED)
+       {
+               self.stat_sv_maxspeed *= autocvar_g_buffs_speed_speed;
+               self.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_speed_speed;
+       }
+       
+       if(time < self.buff_disability_time)
+       {
+               self.stat_sv_maxspeed *= autocvar_g_buffs_disability_speed;
+               self.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_disability_speed;
+       }
+
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_PlayerJump)
+{
+       if(self.buffs & BUFF_JUMP)
+               player_jumpheight = autocvar_g_buffs_jump_height;
+
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_MonsterMove)
+{
+       if(time < self.buff_disability_time)
+       {
+               monster_speed_walk *= autocvar_g_buffs_disability_speed;
+               monster_speed_run *= autocvar_g_buffs_disability_speed;
+       }
+
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_PlayerDies)
+{
+       if(self.buffs)
+       {
+               Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, self.buffs);
+               self.buffs = 0;
+               
+               if(self.buff_model)
+               {
+                       remove(self.buff_model);
+                       self.buff_model = world;
+               }
+       }
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_PlayerUseKey)
+{
+       if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
+       if(self.buffs)
+       {
+               Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_BUFF_DROP, self.buffs);
+               Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, self.buffs);
+
+               self.buffs = 0;
+               sound(self, CH_TRIGGER, "relics/relic_effect.wav", VOL_BASE, ATTN_NORM);
+               return TRUE;
+       }
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_RemovePlayer)
+{
+       if(self.buff_model)
+       {
+               remove(self.buff_model);
+               self.buff_model = world;
+       }
+       
+       // also reset timers here to prevent them continuing after spectating
+       self.buff_disability_time = 0;
+       self.buff_disability_effect_time = 0;
+
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_OnEntityPreSpawn)
+{
+       if(autocvar_g_buffs_replace_powerups)
+       switch(self.classname)
+       {
+               case "item_strength":
+               case "item_invincible":
+               {
+                       entity e = spawn();
+                       buff_SpawnReplacement(e, self);
+                       return TRUE;
+               }
+       }
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_WeaponRate)
+{
+       if(self.buffs & BUFF_SPEED)
+               weapon_rate *= autocvar_g_buffs_speed_rate;
+               
+       if(time < self.buff_disability_time)
+               weapon_rate *= autocvar_g_buffs_disability_rate;
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_PlayerThink)
+{
+       if(gameover || self.deadflag != DEAD_NO) { return FALSE; }
+       
+       if(time < self.buff_disability_time)
+       if(time >= self.buff_disability_effect_time)
+       {
+               pointparticles(particleeffectnum("smoking"), self.origin + ((self.mins + self.maxs) * 0.5), '0 0 0', 1);
+               self.buff_disability_effect_time = time + 0.5;
+       }
+       
+       if(self.freezetag_frozen)
+       {
+               if(self.buffs)
+               {
+                       Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, self.buffs);
+                       self.buffs = 0;
+               }
+       }
+
+       if(!(self.oldbuffs & BUFF_JUMP) && !(self.buffs & BUFF_JUMP))
+               self.stat_jumpheight = autocvar_sv_jumpvelocity; // reset so we don't break anything
+       else if((self.buffs & BUFF_JUMP) && self.stat_jumpheight != autocvar_g_buffs_jump_height)
+               self.stat_jumpheight = autocvar_g_buffs_jump_height;
+               
+       if((self.buffs & BUFF_INVISIBLE) && (self.oldbuffs & BUFF_INVISIBLE))
+       if(self.alpha != autocvar_g_buffs_invisible_alpha)
+               self.alpha = autocvar_g_buffs_invisible_alpha;
+
+       if(self.buffs != self.oldbuffs)
+       {
+               if(self.oldbuffs & BUFF_AMMO)
+               {
+                       if(self.buff_ammo_prev_infitems)
+                               self.items |= IT_UNLIMITED_WEAPON_AMMO;
+                       else
+                               self.items &= ~IT_UNLIMITED_WEAPON_AMMO;
+               }
+               else if(self.buffs & BUFF_AMMO)
+               {
+                       self.buff_ammo_prev_infitems = (self.items & IT_UNLIMITED_WEAPON_AMMO);
+                       self.items |= IT_UNLIMITED_WEAPON_AMMO;
+                       if(!self.ammo_shells) { self.ammo_shells = 20; }
+                       if(!self.ammo_cells) { self.ammo_cells = 20; }
+                       if(!self.ammo_rockets) { self.ammo_rockets = 20; }
+                       if(!self.ammo_nails) { self.ammo_nails = 20; }
+                       if(!self.ammo_fuel) { self.ammo_fuel = 20; }
+               }
+               
+               if(self.oldbuffs & BUFF_JUMP)
+                       self.stat_jumpheight = autocvar_sv_jumpvelocity;
+               else if(self.buffs & BUFF_JUMP)
+                       self.stat_jumpheight = autocvar_g_buffs_jump_height;
+               
+               if(self.oldbuffs & BUFF_INVISIBLE)
+               {
+                       if(time < self.strength_finished && g_minstagib)
+                               self.alpha = autocvar_g_minstagib_invis_alpha;
+                       else
+                               self.alpha = self.buff_invisible_prev_alpha;
+               }
+               else if(self.buffs & BUFF_INVISIBLE)
+               {
+                       if(time < self.strength_finished && g_minstagib)
+                               self.buff_invisible_prev_alpha = default_player_alpha;
+                       else
+                               self.buff_invisible_prev_alpha = self.alpha;
+                       self.alpha = autocvar_g_buffs_invisible_alpha;
+               }
+               
+               if(self.oldbuffs & BUFF_FLIGHT)
+                       self.gravity = self.buff_flight_prev_gravity;
+               else if(self.buffs & BUFF_FLIGHT)
+               {
+                       self.buff_flight_prev_gravity = self.gravity;
+                       self.gravity = autocvar_g_buffs_flight_gravity;
+               }
+
+               self.oldbuffs = self.buffs;
+               if(self.buffs)
+               {
+                       if(!self.buff_model)
+                       {
+                               self.buff_model = spawn();
+                               setmodel(self.buff_model, "models/relics/relic.md3");
+                               setsize(self.buff_model, '0 0 -40', '0 0 40');
+                               setattachment(self.buff_model, self, "");
+                               setorigin(self.buff_model, '0 0 1' * (self.buff_model.maxs_z * 1));
+                               self.buff_model.owner = self;
+                               self.buff_model.scale = 0.7;
+                               self.buff_model.pflags = PFLAGS_FULLDYNAMIC;
+                               self.buff_model.light_lev = 200;
+                               self.buff_model.customizeentityforclient = buffs_BuffModel_Customize;
+                       }
+                       self.buff_model.color = Buff_Color(self.buffs);
+                       self.buff_model.glowmod = ((self.buff_model.team) ? Team_ColorRGB(self.buff_model.team) + '0.1 0.1 0.1' : self.buff_model.color);
+                       self.buff_model.skin = Buff_Skin(self.buffs);
+                       
+                       self.effects |= EF_NOSHADOW;
+               }
+               else
+               {
+                       remove(self.buff_model);
+                       self.buff_model = world;
+                       
+                       self.effects &= ~(EF_NOSHADOW);
+               }
+       }
+
+       if(self.buff_model)
+       {
+               self.buff_model.effects = self.effects;
+               self.buff_model.effects |= EF_LOWPRECISION;
+               self.buff_model.effects = self.buff_model.effects & EFMASK_CHEAP; // eat performance
+               
+               self.buff_model.alpha = self.alpha;
+       }
+
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_SpectateCopy)
+{
+       self.buffs = other.buffs;
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_VehicleEnter)
+{
+       vh_vehicle.buffs = vh_player.buffs;
+       vh_player.buffs = 0;
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_VehicleExit)
+{
+       vh_player.buffs = vh_vehicle.buffs;
+       vh_vehicle.buffs = 0;
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_PlayerRegen)
+{
+       if(self.buffs & BUFF_MEDIC)
+       {
+               regen_mod_rot = autocvar_g_buffs_medic_rot;
+               regen_mod_limit = regen_mod_max = autocvar_g_buffs_medic_max;
+               regen_mod_regen = autocvar_g_buffs_medic_regen;
+       }
+       
+       if(self.buffs & BUFF_SPEED)
+               regen_mod_regen = autocvar_g_buffs_speed_regen;
+
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_GetCvars)
+{
+       GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_buffs_autoreplace, "cl_buffs_autoreplace");
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":Buffs");
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(buffs_BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", Buffs");
+       return FALSE;
+}
+
+void buffs_DelayedInit()
+{
+       if(autocvar_g_buffs_spawn_count > 0)
+       if(find(world, classname, "item_buff") == world)
+       {
+               float i;
+               for(i = 0; i < autocvar_g_buffs_spawn_count; ++i)
+               {
+                       entity e = spawn();
+                       e.spawnflags |= 1; // always randomize
+                       e.velocity = randomvec() * 250; // this gets reset anyway if random location works
+                       buff_Init(e);
+               }
+       }
+}
+
+void buffs_Initialize()
+{
+       precache_model("models/relics/relic.md3");
+       precache_sound("misc/strength_respawn.wav");
+       precache_sound("misc/shield_respawn.wav");
+       precache_sound("relics/relic_effect.wav");
+       precache_sound("weapons/rocket_impact.wav");
+       precache_sound("keepaway/respawn.wav");
+
+       addstat(STAT_BUFFS, AS_INT, buffs);
+       addstat(STAT_MOVEVARS_JUMPVELOCITY, AS_FLOAT, stat_jumpheight);
+       
+       InitializeEntity(world, buffs_DelayedInit, INITPRIO_FINDTARGET);
+}
+
+MUTATOR_DEFINITION(mutator_buffs)
+{
+       MUTATOR_HOOK(PlayerDamage_SplitHealthArmor, buffs_PlayerDamage_SplitHealthArmor, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerDamage_Calculate, buffs_PlayerDamage_Calculate, CBC_ORDER_LAST);
+       MUTATOR_HOOK(PlayerSpawn, buffs_PlayerSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerPhysics, buffs_PlayerPhysics, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerJump, buffs_PlayerJump, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterMove, buffs_MonsterMove, CBC_ORDER_ANY);
+       MUTATOR_HOOK(SpectateCopy, buffs_SpectateCopy, CBC_ORDER_ANY);
+       MUTATOR_HOOK(VehicleEnter, buffs_VehicleEnter, CBC_ORDER_ANY);
+       MUTATOR_HOOK(VehicleExit, buffs_VehicleExit, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerRegen, buffs_PlayerRegen, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerDies, buffs_PlayerDies, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerUseKey, buffs_PlayerUseKey, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MakePlayerObserver, buffs_RemovePlayer, CBC_ORDER_ANY);
+       MUTATOR_HOOK(ClientDisconnect, buffs_RemovePlayer, CBC_ORDER_ANY);
+       MUTATOR_HOOK(OnEntityPreSpawn, buffs_OnEntityPreSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(WeaponRateFactor, buffs_WeaponRate, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerPreThink, buffs_PlayerThink, CBC_ORDER_ANY);
+       MUTATOR_HOOK(GetCvars, buffs_GetCvars, CBC_ORDER_ANY);
+       MUTATOR_HOOK(BuildMutatorsString, buffs_BuildMutatorsString, CBC_ORDER_ANY);
+       MUTATOR_HOOK(BuildMutatorsPrettyString, buffs_BuildMutatorsPrettyString, CBC_ORDER_ANY);
+
+       MUTATOR_ONADD
+       {
+               buffs_Initialize();
+       }
+
+       return FALSE;
+}
diff --git a/qcsrc/server/mutators/mutator_buffs.qh b/qcsrc/server/mutators/mutator_buffs.qh
new file mode 100644 (file)
index 0000000..66540b8
--- /dev/null
@@ -0,0 +1,28 @@
+// buff specific variables \\
+//
+// ammo
+.float buff_ammo_prev_infitems;
+// invisible
+.float buff_invisible_prev_alpha;
+// flight
+.float buff_flight_prev_gravity;
+// jump
+.float stat_jumpheight;
+const float STAT_MOVEVARS_JUMPVELOCITY = 250; // engine hack
+// disability
+.float buff_disability_time;
+.float buff_disability_effect_time;
+
+// buff definitions
+.float buff_active;
+.float buff_activetime;
+.float buff_activetime_updated;
+.entity buff_waypoint;
+.float oldbuffs; // for updating effects
+.entity buff_model; // controls effects (TODO: make csqc)
+
+#define BUFF_MIN ('-16 -16 -20')
+#define BUFF_MAX ('16 16 20')
+
+// client side options
+.float cvar_cl_buffs_autoreplace;
index ecab77c88025dff20bf63c4778f811dbd5261da4..ab519e98f9d8d711fe798624d57f8393997bc8b2 100644 (file)
@@ -29,5 +29,6 @@ MUTATOR_DECLARATION(mutator_multijump);
 MUTATOR_DECLARATION(mutator_melee_only);
 MUTATOR_DECLARATION(mutator_nades);
 MUTATOR_DECLARATION(mutator_campcheck);
+MUTATOR_DECLARATION(mutator_buffs);
 
 MUTATOR_DECLARATION(sandbox);
index 1ae22e2029f7ff518aff3b21533a54a07163673d..abb047989c8d563b77c434e5daa87f8f3d24b9bf 100644 (file)
@@ -15,6 +15,7 @@ sys-post.qh
 ../common/constants.qh
 ../common/teams.qh
 ../common/util.qh
+../common/buffs.qh
 ../common/test.qh
 ../common/counting.qh
 ../common/items.qh
@@ -50,6 +51,7 @@ mutators/gamemode_lms.qh
 mutators/gamemode_invasion.qh
 mutators/mutator_dodging.qh
 mutators/mutator_nades.qh
+mutators/mutator_buffs.qh
 
 //// tZork Turrets ////
 tturrets/include/turrets_early.qh
@@ -214,6 +216,8 @@ target_music.qc
 
 ../common/items.qc
 
+../common/buffs.qc
+
 
 accuracy.qc
 ../csqcmodellib/sv_model.qc
@@ -266,6 +270,7 @@ mutators/mutator_multijump.qc
 mutators/mutator_melee_only.qc
 mutators/mutator_nades.qc
 mutators/mutator_campcheck.qc
+mutators/mutator_buffs.qc
 
 ../warpzonelib/anglestransform.qc
 ../warpzonelib/mathlib.qc