]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into mirceakitsune/physical_entities
authorMircea Kitsune <sonichedgehog_hyperblast00@yahoo.com>
Sun, 13 May 2012 09:08:03 +0000 (12:08 +0300)
committerMircea Kitsune <sonichedgehog_hyperblast00@yahoo.com>
Sun, 13 May 2012 09:08:03 +0000 (12:08 +0300)
1  2 
defaultXonotic.cfg
qcsrc/server/autocvars.qh
qcsrc/server/cl_weapons.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/mutators/base.qh
qcsrc/server/mutators/mutators.qh
qcsrc/server/progs.src
qcsrc/server/t_items.qc

diff --combined defaultXonotic.cfg
index 11a08eb438264b653638e7f3f2ef5cdb8f74e15b,0c399bb183cde7a50c4ff80e690bd59764527ffc..15b420d9dfe29f3c124906b201d29ced8eab9e1f
@@@ -793,6 -793,23 +793,23 @@@ set g_multijump 0        "Number of multiple j
  set g_multijump_add 0 "0 = make the current z velocity equal to jumpvelocity, 1 = add jumpvelocity to the current z velocity"
  set g_multijump_speed -999999 "Minimum vertical speed a player must have in order to jump again"
  
+ set g_throughfloor_debug 0 "enable debugging messages for throughfloor calculations"
+ set g_throughfloor_damage_max_stddev 2 "Maximum standard deviation for splash damage"
+ set g_throughfloor_force_max_stddev 10 "Maximum standard deviation for splash force"
+ set g_throughfloor_min_steps_player 1 "Minimum number of steps for splash damage"
+ set g_throughfloor_min_steps_other 1 "Minimum number of steps for splash damage"
+ set g_throughfloor_max_steps_player 100 "Maximum number of steps for splash damage"
+ set g_throughfloor_max_steps_other 10 "Maximum number of steps for splash damage"
+ // note: for damage X, 0.25 * ((1-g_throughfloor_damage)*X / g_throughfloor_damage_max_stddev)^2 steps are used
+ // for these numbers:
+ //   damage  25: 3
+ //   damage  60: 15
+ //   damage  80: 25
+ //   damage 200: 157
+ //   force  250: 10
+ //   force  300: 15
+ //   force  600: 57
  // effects
  r_glsl_vertextextureblend_usebothalphas 1 // allows to abuse texture blending as detail texture
  r_glsl_postprocess 0 // but note, hud_postprocessing enables this
@@@ -1386,10 -1403,6 +1403,10 @@@ set menu_use_default_hostname 
  alias sethostname "set menu_use_default_hostname 0; hostname $*"
  
  set sv_foginterval 1 "force enable fog in regular intervals"
 +
 +set g_physical_items 0 "1 uses ODE physics for dropped weapons, 2 for all items, requires physics_ode to be enabled"
 +set g_physical_items_damageforcescale 3 "how affected physical weapons are by damage"
 +set g_physical_items_reset 1 "return map items to their original lotation after being picked up"
  
  // Audio track names (for old-style "cd loop NUMBER" usage)
  set _cdtrack_first "1"
@@@ -1750,7 -1763,6 +1767,6 @@@ cl_decals_newsystem 
  set g_weaponreplace_laser ""
  set g_weaponreplace_shotgun ""
  set g_weaponreplace_uzi ""
- set g_weaponreplace_minelayer ""
  set g_weaponreplace_grenadelauncher ""
  set g_weaponreplace_electro ""
  set g_weaponreplace_crylink ""
@@@ -1760,11 -1772,8 +1776,8 @@@ set g_weaponreplace_rocketlauncher "
  set g_weaponreplace_porto ""
  set g_weaponreplace_minstanex ""
  set g_weaponreplace_hook ""
- set g_weaponreplace_hlac ""
- set g_weaponreplace_rifle ""
  set g_weaponreplace_tuba ""
  set g_weaponreplace_fireball ""
- set g_weaponreplace_seeker ""
  set sv_q3acompat_machineshotgunswap 0 "shorthand for swapping uzi and shotgun (for Q3A map compatibility in mapinfo files)"
  
  set g_movement_highspeed 1 "movement speed modification factor (only changes movement when above maxspeed)"
@@@ -1916,6 -1925,7 +1929,7 @@@ scr_loadingscreen_scale_base 
  scr_loadingscreen_scale_limit 2
  
  // other config files
+ exec mutator_new_toys.cfg // run BEFORE balance to make sure balance wins
  exec balanceXonotic.cfg
  exec ctfscoring-ai.cfg
  exec effects-normal.cfg
index 5d1b691832956cbf4ad648b8446202d3a7ce5f69,5e1c65f796e159525962b01e0cd69074475710f8..1e6f6e835436e089247903d9a68c7941ca173e52
@@@ -978,8 -978,15 +978,15 @@@ float autocvar_g_telefrags
  float autocvar_g_telefrags_avoid;
  float autocvar_g_telefrags_teamplay;
  float autocvar_g_teleport_maxspeed;
+ float autocvar_g_throughfloor_debug;
  float autocvar_g_throughfloor_damage;
  float autocvar_g_throughfloor_force;
+ float autocvar_g_throughfloor_damage_max_stddev;
+ float autocvar_g_throughfloor_force_max_stddev;
+ float autocvar_g_throughfloor_min_steps_player;
+ float autocvar_g_throughfloor_max_steps_player;
+ float autocvar_g_throughfloor_min_steps_other;
+ float autocvar_g_throughfloor_max_steps_other;
  float autocvar_g_triggerimpulse_accel_multiplier;
  float autocvar_g_triggerimpulse_accel_power;
  float autocvar_g_triggerimpulse_directional_multiplier;
@@@ -1216,6 -1223,3 +1223,6 @@@ float autocvar_g_sandbox_object_materia
  float autocvar_g_sandbox_object_material_velocity_factor;
  float autocvar_g_max_info_autoscreenshot;
  float autocvar_physics_ode;
 +float autocvar_g_physical_items;
 +float autocvar_g_physical_items_damageforcescale;
 +float autocvar_g_physical_items_reset;
index 64f6778436b8a0de9c7cab2fe275e0f84cc03070,59efb74d5458508fab560ddb0f5338fa12013372..10ae0f44e2875351400b59275d4f66865067dd6f
@@@ -163,11 -163,6 +163,6 @@@ string W_Name(float weaponid
        return (get_weaponinfo(weaponid)).message;
  }
  
- float W_WeaponBit(float wpn)
- {
-       return (get_weaponinfo(wpn)).weapons;
- }
  float W_AmmoItemCode(float wpn)
  {
        return (get_weaponinfo(wpn)).items & IT_AMMO;
@@@ -203,7 -198,7 +198,7 @@@ string W_ThrowNewWeapon(entity own, flo
        wep.flags |= FL_TOSSED;
        wep.colormap = own.colormap;
  
-       if(W_WeaponBit(wpn) & WEPBIT_SUPERWEAPONS)
+       if(WEPSET_CONTAINS_AW(WEPBIT_SUPERWEAPONS, wpn))
        {
                if(own.items & IT_UNLIMITED_SUPERWEAPONS)
                {
                {
                        float superweapons = 1;
                        for(i = WEP_FIRST; i <= WEP_LAST; ++i)
-                               if(own.weapons & WEPBIT_SUPERWEAPONS & W_WeaponBit(i))
-                                       ++superweapons;
+                               if(WEPSET_CONTAINS_AW(WEPBIT_SUPERWEAPONS, i))
+                                       if(WEPSET_CONTAINS_EW(own, i))
+                                               ++superweapons;
                        if(superweapons <= 1)
                        {
                                wep.superweapons_finished = own.superweapons_finished;
                wep.savenextthink = wep.nextthink;
                wep.nextthink = min(wep.nextthink, time + 0.5);
                wep.pickup_anyway = TRUE; // these are ALWAYS pickable
 +
                return s;
        }
  }
  
  float W_IsWeaponThrowable(float w)
  {
-       float wb, wa;
+       float wa;
  
        if (!autocvar_g_pickup_items)
                return 0;
        if (g_nexball && w == WEP_GRENADE_LAUNCHER)
                return 0;
  
-       wb = W_WeaponBit(w);
-       if(!wb)
-               return 0;
        wa = W_AmmoItemCode(w);
-       if(start_weapons & wb)
+       if(WEPSET_CONTAINS_AW(start_weapons, w))
        {
                // start weapons that take no ammo can't be dropped (this prevents dropping the laser, as long as it continues to use no ammo)
                if(start_items & IT_UNLIMITED_WEAPON_AMMO)
  // toss current weapon
  void W_ThrowWeapon(vector velo, vector delta, float doreduce)
  {
-       float w, wb;
+       float w;
        string a;
  
        w = self.weapon;
        if(!W_IsWeaponThrowable(w))
                return;
  
-       wb = W_WeaponBit(w);
-       if(self.weapons & wb != wb)
+       if(!WEPSET_CONTAINS_EW(self, w))
                return;
+       WEPSET_ANDNOT_EW(self, w);
  
-       self.weapons &~= wb;
        W_SwitchWeapon_Force(self, w_getbestweapon(self));
        a = W_ThrowNewWeapon(self, w, doreduce, self.origin + delta, velo);
        if not(a)
@@@ -462,8 -453,8 +454,8 @@@ void W_WeaponFrame(
        //if (self.button0)
        //      print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n");
  
-       float wb;
-       wb = W_WeaponBit(self.weapon);
+       float w;
+       w = self.weapon;
  
        // call the think code which may fire the weapon
        // and do so multiple times to resolve framerate dependency issues if the
        while (c < W_TICSPERFRAME)
        {
                c = c + 1;
-               if(wb && ((self.weapons & wb) == 0))
+               if(w && !WEPSET_CONTAINS_EW(self, w))
                {
                        if(self.weapon == self.switchweapon)
                                W_SwitchWeapon_Force(self, w_getbestweapon(self));
-                       wb = 0;
+                       w = 0;
                }
  
                v_forward = fo;
                v_right = ri;
                v_up = up;
  
-               if(wb)
+               if(w)
                        weapon_action(self.weapon, WR_THINK);
                else
                        weapon_action(self.weapon, WR_GONETHINK);
                self.currentammo = 1;
  #endif
  }
+ string W_Apply_Weaponreplace(string in)
+ {
+       float n = tokenize_console(in);
+       string out = "";
+       float i;
+       for(i = 0; i < n; ++i)
+       {
+               string s = argv(i);
+               string r = cvar_string(strcat("g_weaponreplace_", s));
+               if(r == "")
+                       out = strcat(out, " ", s);
+               else if(r != "0")
+                       out = strcat(out, " ", r);
+       }
+       return substring(out, 1, -1);
+ }
index 88b29fe27123dd2138058b573137baffbea0dab7,3826b683af6c16ceb8ca7d33aa108ed06269e8e7..95f2cb533b49c758d2fe378ee79ddd5745ee862d
@@@ -526,7 -526,7 +526,7 @@@ string W_FixWeaponOrder_ForceComplete_A
  }
  void GetCvars(float f)
  {
-       string s;
+       string s = string_null;
  
        if (f > 0)
                s = strcat1(argv(f));
@@@ -740,6 -740,7 +740,7 @@@ float g_pickup_healthmega_anyway
  float g_pickup_ammo_anyway;
  float g_pickup_weapons_anyway;
  float g_weaponarena;
+ WEPSET_DECLARE_A(g_weaponarena_weapons);
  float g_weaponarena_random;
  float g_weaponarena_random_with_laser;
  string g_weaponarena_list;
@@@ -749,7 -750,9 +750,9 @@@ float g_weapondamagefactor
  float g_weaponforcefactor;
  float g_weaponspreadfactor;
  
- float start_weapons;
+ WEPSET_DECLARE_A(start_weapons);
+ WEPSET_DECLARE_A(start_weapons_default);
+ WEPSET_DECLARE_A(start_weapons_defaultmask);
  float start_items;
  float start_ammo_shells;
  float start_ammo_nails;
@@@ -758,7 -761,9 +761,9 @@@ float start_ammo_cells
  float start_ammo_fuel;
  float start_health;
  float start_armorvalue;
- float warmup_start_weapons;
+ WEPSET_DECLARE_A(warmup_start_weapons);
+ WEPSET_DECLARE_A(warmup_start_weapons_default);
+ WEPSET_DECLARE_A(warmup_start_weapons_defaultmask);
  float warmup_start_ammo_shells;
  float warmup_start_ammo_nails;
  float warmup_start_ammo_rockets;
@@@ -774,27 -779,42 +779,42 @@@ entity get_weaponinfo(float w)
  float want_weapon(string cvarprefix, entity weaponinfo, float allguns)
  {
        var float i = weaponinfo.weapon;
+       var float d = 0;
  
        if (!i)
                return 0;
  
-       var float t = cvar(strcat(cvarprefix, weaponinfo.netname));
-       if (t < 0) // "default" weapon selection
+       if (g_lms || g_ca || allguns)
        {
-               if (g_lms || g_ca || allguns)
-                       t = (weaponinfo.spawnflags & WEP_FLAG_NORMAL);
-               else if(t < -1)
-                       t = 0;
-               else if (g_cts)
-                       t = (i == WEP_SHOTGUN);
-               else if (g_nexball)
-                       t = 0; // weapon is set a few lines later
+               if(weaponinfo.spawnflags & WEP_FLAG_NORMAL)
+                       d = TRUE;
                else
-                       t = (i == WEP_LASER || i == WEP_SHOTGUN);
-               if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook
-                       t |= (i == WEP_HOOK);
+                       d = FALSE;
        }
+       else if (g_cts)
+               d = (i == WEP_SHOTGUN);
+       else if (g_nexball)
+               d = 0; // weapon is set a few lines later
+       else
+               d = (i == WEP_LASER || i == WEP_SHOTGUN);
+               
+       if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook
+               d |= (i == WEP_HOOK);
+       if(weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED) // never default mutator blocked guns
+               d = 0;
+       var float t = cvar(strcat(cvarprefix, weaponinfo.netname));
+       
+       //print(strcat("want_weapon: ", weaponinfo.netname, " - d: ", ftos(d), ", t: ", ftos(t), ". \n"));
+       
+       // bit order in t:
+       // 1: want or not
+       // 2: is default?
+       // 4: is set by default?
+       if(t < 0)
+               t = 4 | (3 * d);
+       else
+               t |= (2 * d);
  
        return t;
  }
@@@ -806,7 -826,9 +826,9 @@@ void readplayerstartcvars(
        string s;
  
        // initialize starting values for players
-       start_weapons = 0;
+       WEPSET_CLEAR_A(start_weapons);
+       WEPSET_CLEAR_A(start_weapons_default);
+       WEPSET_CLEAR_A(start_weapons_defaultmask);
        start_items = 0;
        start_ammo_shells = 0;
        start_ammo_nails = 0;
        start_armorvalue = cvar("g_balance_armor_start");
  
        g_weaponarena = 0;
+       WEPSET_CLEAR_A(g_weaponarena_weapons);
        s = cvar_string("g_weaponarena");
        if (s == "0" || s == "")
        {
        }
        else if (s == "all")
        {
+               g_weaponarena = 1;
                g_weaponarena_list = "All Weapons";
                for (j = WEP_FIRST; j <= WEP_LAST; ++j)
                {
                        e = get_weaponinfo(j);
-                       g_weaponarena |= e.weapons;
-                       weapon_action(e.weapon, WR_PRECACHE);
+                       if not(e.spawnflags & WEP_FLAG_MUTATORBLOCKED)
+                               WEPSET_OR_AW(g_weaponarena_weapons, j);
                }
        }
        else if (s == "most")
        {
+               g_weaponarena = 1;
                g_weaponarena_list = "Most Weapons";
                for (j = WEP_FIRST; j <= WEP_LAST; ++j)
                {
                        e = get_weaponinfo(j);
-                       if (e.spawnflags & WEP_FLAG_NORMAL)
-                       {
-                               g_weaponarena |= e.weapons;
-                               weapon_action(e.weapon, WR_PRECACHE);
-                       }
+                       if not(e.spawnflags & WEP_FLAG_MUTATORBLOCKED)
+                               if (e.spawnflags & WEP_FLAG_NORMAL)
+                                       WEPSET_OR_AW(g_weaponarena_weapons, j);
                }
        }
        else if (s == "none")
        {
+               g_weaponarena = 1;
                g_weaponarena_list = "No Weapons";
-               g_weaponarena = WEPBIT_ALL + 1; // this supports no single weapon bit!
        }
        else
        {
+               g_weaponarena = 1;
                t = tokenize_console(s);
                g_weaponarena_list = "";
                for (i = 0; i < t; ++i)
                                e = get_weaponinfo(j);
                                if (e.netname == s)
                                {
-                                       g_weaponarena |= e.weapons;
-                                       weapon_action(e.weapon, WR_PRECACHE);
+                                       WEPSET_OR_AW(g_weaponarena_weapons, j);
                                        g_weaponarena_list = strcat(g_weaponarena_list, e.message, " & ");
                                        break;
                                }
                g_minstagib = 0; // incompatible
                g_pinata = 0; // incompatible
                g_weapon_stay = 0; // incompatible
-               start_weapons = g_weaponarena;
+               WEPSET_COPY_AA(start_weapons, g_weaponarena_weapons);
                if(!(g_lms || g_ca))
                        start_items |= IT_UNLIMITED_AMMO;
        }
                g_bloodloss = 0; // incompatible
                start_health = 100;
                start_armorvalue = 0;
-               start_weapons = WEPBIT_MINSTANEX;
-               weapon_action(WEP_MINSTANEX, WR_PRECACHE);
+               WEPSET_COPY_AW(start_weapons, WEP_MINSTANEX);
                g_minstagib_invis_alpha = cvar("g_minstagib_invis_alpha");
                start_items |= IT_UNLIMITED_SUPERWEAPONS;
  
                for (i = WEP_FIRST; i <= WEP_LAST; ++i)
                {
                        e = get_weaponinfo(i);
-                       if(want_weapon("g_start_weapon_", e, FALSE))
-                               start_weapons |= e.weapons;
+                       float w = want_weapon("g_start_weapon_", e, FALSE);
+                       if(w & 1)
+                               WEPSET_OR_AW(start_weapons, i);
+                       if(w & 2)
+                               WEPSET_OR_AW(start_weapons_default, i);
+                       if(w & 4)
+                               WEPSET_OR_AW(start_weapons_defaultmask, i);
                }
        }
  
                warmup_start_ammo_fuel = start_ammo_fuel;
                warmup_start_health = start_health;
                warmup_start_armorvalue = start_armorvalue;
-               warmup_start_weapons = start_weapons;
+               WEPSET_COPY_AA(warmup_start_weapons, start_weapons);
+               WEPSET_COPY_AA(warmup_start_weapons_default, start_weapons_default);
+               WEPSET_COPY_AA(warmup_start_weapons_defaultmask, start_weapons_defaultmask);
  
                if (!g_weaponarena && !g_minstagib && !g_ca)
                {
                        warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
                        warmup_start_health = cvar("g_warmup_start_health");
                        warmup_start_armorvalue = cvar("g_warmup_start_armor");
-                       warmup_start_weapons = 0;
+                       WEPSET_CLEAR_A(warmup_start_weapons);
+                       WEPSET_CLEAR_A(warmup_start_weapons_default);
+                       WEPSET_CLEAR_A(warmup_start_weapons_defaultmask);
                        for (i = WEP_FIRST; i <= WEP_LAST; ++i)
                        {
                                e = get_weaponinfo(i);
-                               if(want_weapon("g_start_weapon_", e, cvar("g_warmup_allguns")))
-                                       warmup_start_weapons |= e.weapons;
+                               float w = want_weapon("g_start_weapon_", e, cvar("g_warmup_allguns"));
+                               if(w & 1)
+                                       WEPSET_OR_AW(warmup_start_weapons, i);
+                               if(w & 2)
+                                       WEPSET_OR_AW(warmup_start_weapons_default, i);
+                               if(w & 4)
+                                       WEPSET_OR_AW(warmup_start_weapons_defaultmask, i);
                        }
                }
        }
  
-       if (g_jetpack || (g_grappling_hook && (start_weapons & WEPBIT_HOOK)))
+       if (g_jetpack)
+               start_items |= IT_JETPACK;
+       MUTATOR_CALLHOOK(SetStartItems);
+       if ((start_items & IT_JETPACK) || (g_grappling_hook && WEPSET_CONTAINS_AW(start_weapons, WEP_HOOK)))
        {
                g_grappling_hook = 0; // these two can't coexist, as they use the same button
                start_items |= IT_FUEL_REGEN;
                warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
        }
  
-       if (g_jetpack)
-               start_items |= IT_JETPACK;
-       MUTATOR_CALLHOOK(SetStartItems);
        for (i = WEP_FIRST; i <= WEP_LAST; ++i)
        {
                e = get_weaponinfo(i);
-               if(e.weapons & (start_weapons | warmup_start_weapons))
-                       weapon_action(e.weapon, WR_PRECACHE);
+               if(WEPSET_CONTAINS_AW(start_weapons, i) || WEPSET_CONTAINS_AW(warmup_start_weapons, i))
+                       weapon_action(i, WR_PRECACHE);
        }
  
        start_ammo_shells = max(0, start_ammo_shells);
@@@ -1065,21 -1102,26 +1102,28 @@@ float sv_pitch_fixyaw
  string GetGametype(); // g_world.qc
  void readlevelcvars(void)
  {
-       // first load all the mutators
-       if(cvar("g_invincible_projectiles"))
-               MUTATOR_ADD(mutator_invincibleprojectiles);
-       if(cvar("g_nix"))
-               MUTATOR_ADD(mutator_nix);
+       g_minstagib = cvar("g_minstagib");
+       // load ALL the mutators
        if(cvar("g_dodging"))
                MUTATOR_ADD(mutator_dodging);
-       if(cvar("g_rocket_flying"))
-               MUTATOR_ADD(mutator_rocketflying);
-       if(cvar("g_vampire"))
-               MUTATOR_ADD(mutator_vampire);
        if(cvar("g_spawn_near_teammate"))
                MUTATOR_ADD(mutator_spawn_near_teammate);
 +      if(cvar("g_physical_items"))
 +              MUTATOR_ADD(mutator_physical_items);
+       if(!g_minstagib)
+       {
+               if(cvar("g_invincible_projectiles"))
+                       MUTATOR_ADD(mutator_invincibleprojectiles);
+               if(cvar("g_new_toys"))
+                       MUTATOR_ADD(mutator_new_toys);
+               if(cvar("g_nix"))
+                       MUTATOR_ADD(mutator_nix);
+               if(cvar("g_rocket_flying"))
+                       MUTATOR_ADD(mutator_rocketflying);
+               if(cvar("g_vampire"))
+                       MUTATOR_ADD(mutator_vampire);
+       }
  
        // is this a mutator? is this a mode?
        if(cvar("g_sandbox"))
        g_grappling_hook = cvar("g_grappling_hook");
        g_jetpack = cvar("g_jetpack");
        g_midair = cvar("g_midair");
-       g_minstagib = cvar("g_minstagib");
        g_norecoil = cvar("g_norecoil");
        g_bloodloss = cvar("g_bloodloss");
        sv_maxidle = cvar("sv_maxidle");
@@@ -1658,7 -1699,7 +1701,7 @@@ void make_safe_for_remove(entity e
  {
      if (e.initialize_entity)
      {
-         entity ent, prev;
+         entity ent, prev = world;
          for (ent = initialize_entity_first; ent; )
          {
              if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
@@@ -1730,6 -1771,7 +1773,7 @@@ void InitializeEntity(entity e, void(vo
      e.initialize_entity_order = order;
  
      cur = initialize_entity_first;
+     prev = world;
      for (;;)
      {
          if (!cur || cur.initialize_entity_order > order)
@@@ -2080,7 -2122,7 +2124,7 @@@ void race_writeTime(string map, float t
        float newpos;
        newpos = race_readPos(map, t);
  
-       float i, prevpos;
+       float i, prevpos = 0;
        for(i = 1; i <= RANKINGS_CNT; ++i)
        {
                if(race_readUID(map, i) == myuid)
@@@ -2142,6 -2184,8 +2186,8 @@@ float MoveToRandomMapLocation(entity e
      org = world.mins;
      delta = world.maxs - world.mins;
  
+     start = end = org;
      for (i = 0; i < attempts; ++i)
      {
          start_x = org_x + random() * delta_x;
@@@ -2398,12 -2442,8 +2444,8 @@@ vector shotorg_adjust(vector vecs, floa
  void attach_sameorigin(entity e, entity to, string tag)
  {
      vector org, t_forward, t_left, t_up, e_forward, e_up;
-     vector org0, ang0;
      float tagscale;
  
-     ang0 = e.angles;
-     org0 = e.origin;
      org = e.origin - gettaginfo(to, gettagindex(to, tag));
      tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
      t_forward = v_forward * tagscale;
index ff5de6cfa4086ccca2460827d7c34427f8b93d2a,236e311b6a0cb8c0fa7fa39631678b8b46f6ab64..57777e4b9a9379d79a9d6deaef692caaf69e55df
@@@ -205,9 -205,10 +205,16 @@@ MUTATOR_HOOKABLE(SV_StartFrame)
  MUTATOR_HOOKABLE(SetModname);
        // OUT
        string modname; // name of the mutator/mod if it warrants showing as such in the server browser
 +      
 +MUTATOR_HOOKABLE(Item_Spawn);
 +      // called for each item being spawned on a map, including dropped weapons
 +      // return 1 to remove an item
 +      // INPUT
 +      entity self; // the item
+ MUTATOR_HOOKABLE(SetWeaponreplace);
+       // IN
+               entity self; // map entity
+               entity other; // weapon info
+       // IN+OUT
+               string ret_string;
index 4d019eec00379f9ed4c92cc05d8f3bba9793008c,4e7d9a6512bd47b8806d2f791fa5fc04cd9ab035..66aa94a33faa5a8f4f907bef58da070817910a25
@@@ -3,13 -3,12 +3,13 @@@ MUTATOR_DECLARATION(gamemode_freezetag)
  MUTATOR_DECLARATION(gamemode_keepaway);
  MUTATOR_DECLARATION(gamemode_nexball);
  
+ MUTATOR_DECLARATION(mutator_dodging);
  MUTATOR_DECLARATION(mutator_invincibleprojectiles);
+ MUTATOR_DECLARATION(mutator_new_toys);
  MUTATOR_DECLARATION(mutator_nix);
- MUTATOR_DECLARATION(mutator_dodging);
  MUTATOR_DECLARATION(mutator_rocketflying);
- MUTATOR_DECLARATION(mutator_vampire);
- MUTATOR_DECLARATION(mutator_spawn_near_teammate);
  MUTATOR_DECLARATION(mutator_spawn_near_teammate);
 +MUTATOR_DECLARATION(mutator_physical_items);
+ MUTATOR_DECLARATION(mutator_vampire);
  
  MUTATOR_DECLARATION(sandbox);
diff --combined qcsrc/server/progs.src
index d8210e77065b7193553c5088776e181683cc2aec,1890eddd97a679ae3f279e258dcfe0eb55c6c9f4..f2e0114990b5a790cef02c7262bd1169022b0db6
@@@ -210,12 -210,12 +210,13 @@@ mutators/gamemode_keyhunt.q
  mutators/gamemode_freezetag.qc
  mutators/gamemode_keepaway.qc
  mutators/mutator_invincibleproj.qc
+ mutators/mutator_new_toys.qc
  mutators/mutator_nix.qc
  mutators/mutator_dodging.qc
  mutators/mutator_rocketflying.qc
  mutators/mutator_vampire.qc
  mutators/mutator_spawn_near_teammate.qc
 +mutators/mutator_physical_items.qc
  mutators/sandbox.qc
  
  ../warpzonelib/anglestransform.qc
diff --combined qcsrc/server/t_items.qc
index e1d405da5caeaddb9540b74a4063e7be4211ca98,ac029e6577659f3e243a610219b9a3f290d15bc4..a08333e768e9f9767e91cf9e70361729613c74d3
@@@ -29,7 -29,7 +29,7 @@@ float have_pickup_item(void
                if(g_ca)
                        return FALSE;
                if(g_weaponarena)
-                       if((self.weapons & WEPBIT_ALL) || (self.items & IT_AMMO))
+                       if(!WEPSET_EMPTY_E(self) || (self.items & IT_AMMO))
                                return FALSE;
        }
        return TRUE;
@@@ -83,7 -83,7 +83,7 @@@ float Item_Customize(
  {
        if(self.spawnshieldtime)
                return TRUE;
-       if(self.weapons != (self.weapons & other.weapons))
+       if(!WEPSET_CONTAINS_ALL_EE(other, self))
        {
                self.colormod = '0 0 0';
                self.glowmod = self.colormod;
@@@ -209,7 -209,7 +209,7 @@@ void Item_RespawnCountdown (void
                if(self.count == 1)
                {
                        string name;
-                       vector rgb;
+                       vector rgb = '1 0 1';
                        name = string_null;
                        if(g_minstagib)
                        {
  
  void Item_ScheduleRespawnIn(entity e, float t)
  {
-       if((e.flags & FL_POWERUP) || (e.weapons & WEPBIT_SUPERWEAPONS))
+       if((e.flags & FL_POWERUP) || WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS))
        {
                e.think = Item_RespawnCountdown;
                e.nextthink = time + max(0, t - ITEM_RESPAWN_TICKS);
@@@ -348,7 -348,6 +348,6 @@@ float Item_GiveTo(entity item, entity p
        float pickedup;
        float it;
        float i;
-       entity e;
  
        // if nothing happens to player, just return without taking the item
        pickedup = FALSE;
                        // else if(item.items == IT_CELLS)
                        //      AnnounceTo(player, "ammo");
  
-                       if (item.weapons & WEPBIT_MINSTANEX)
+                       if (WEPSET_CONTAINS_EW(item, WEP_MINSTANEX))
                                W_GiveWeapon (player, WEP_MINSTANEX, item.netname);
                        player.health = 100;
                }
                if (player.switchweapon == w_getbestweapon(player))
                        _switchweapon = TRUE;
  
-               if not(player.weapons & W_WeaponBit(player.switchweapon))
+               if not(WEPSET_CONTAINS_EW(player, player.switchweapon))
                        _switchweapon = TRUE;
  
                pickedup |= Item_GiveAmmoTo(item, player, ammo_fuel, g_pickup_fuel_max, ITEM_MODE_FUEL);
                pickedup |= Item_GiveAmmoTo(item, player, armorvalue, item.max_armorvalue, ITEM_MODE_ARMOR);
  
                if (item.flags & FL_WEAPON)
-               if ((it = item.weapons - (item.weapons & player.weapons)) || (item.spawnshieldtime && self.pickup_anyway))
                {
-                       pickedup = TRUE;
-                       for(i = WEP_FIRST; i <= WEP_LAST; ++i)
+                       WEPSET_DECLARE_A(it);
+                       WEPSET_COPY_AE(it, item);
+                       WEPSET_ANDNOT_AE(it, player);
+                       if (!WEPSET_EMPTY_A(it) || (item.spawnshieldtime && self.pickup_anyway))
                        {
-                               e = get_weaponinfo(i);
-                               if(it & e.weapons)
-                                       W_GiveWeapon (player, e.weapon, item.netname);
+                               pickedup = TRUE;
+                               for(i = WEP_FIRST; i <= WEP_LAST; ++i)
+                                       if(WEPSET_CONTAINS_AW(it, i))
+                                               W_GiveWeapon (player, i, item.netname);
                        }
                }
  
@@@ -573,7 -575,7 +575,7 @@@ void Item_Reset(
                if(self.waypointsprite_attached)
                        WaypointSprite_Kill(self.waypointsprite_attached);
  
-               if((self.flags & FL_POWERUP) | (self.weapons & WEPBIT_SUPERWEAPONS)) // do not spawn powerups initially!
+               if((self.flags & FL_POWERUP) | WEPSET_CONTAINS_ANY_EA(self, WEPBIT_SUPERWEAPONS)) // do not spawn powerups initially!
                        Item_ScheduleInitialRespawn(self);
        }
  }
@@@ -622,10 -624,10 +624,10 @@@ float generic_pickupevalfunc(entity pla
  
  float weapon_pickupevalfunc(entity player, entity item)
  {
-       float c, i, j, position;
+       float c, j, position;
  
        // See if I have it already
-       if(player.weapons & item.weapons == item.weapons)
+       if(!WEPSET_CONTAINS_ALL_EE(player, item))
        {
                // If I can pick it up
                if(!item.spawnshieldtime)
        // If custom weapon priorities for bots is enabled rate most wanted weapons higher
        if( bot_custom_weapon && c )
        {
-               for(i = WEP_FIRST; i <= WEP_LAST ; ++i)
-               {
-                       // Find weapon
-                       if( (get_weaponinfo(i)).weapons & item.weapons  != item.weapons )
-                               continue;
-                       // Find the highest position on any range
-                       position = -1;
-                       for(j = 0; j < WEP_LAST ; ++j){
-                               if(
-                                               bot_weapons_far[j] == i ||
-                                               bot_weapons_mid[j] == i ||
-                                               bot_weapons_close[j] == i
-                                 )
-                               {
-                                       position = j;
-                                       break;
-                               }
-                       }
-                       // Rate it
-                       if (position >= 0 )
+               // Find the highest position on any range
+               position = -1;
+               for(j = 0; j < WEP_LAST ; ++j){
+                       if(
+                                       bot_weapons_far[j] == item.weapon ||
+                                       bot_weapons_mid[j] == item.weapon ||
+                                       bot_weapons_close[j] == item.weapon
+                         )
                        {
-                               position = WEP_LAST - position;
-                               // item.bot_pickupbasevalue is overwritten here
-                               return (BOT_PICKUP_RATING_LOW + ( (BOT_PICKUP_RATING_HIGH - BOT_PICKUP_RATING_LOW) * (position / WEP_LAST ))) * c;
+                               position = j;
+                               break;
                        }
                }
+               // Rate it
+               if (position >= 0 )
+               {
+                       position = WEP_LAST - position;
+                       // item.bot_pickupbasevalue is overwritten here
+                       return (BOT_PICKUP_RATING_LOW + ( (BOT_PICKUP_RATING_HIGH - BOT_PICKUP_RATING_LOW) * (position / WEP_LAST ))) * c;
+               }
        }
  
        return item.bot_pickupbasevalue * c;
  
  float commodity_pickupevalfunc(entity player, entity item)
  {
-       float c, i, need_shells, need_nails, need_rockets, need_cells, need_fuel;
+       float c, i;
+       float need_shells = FALSE, need_nails = FALSE, need_rockets = FALSE, need_cells = FALSE, need_fuel = FALSE;
        entity wi;
        c = 0;
  
        {
                wi = get_weaponinfo(i);
  
-               if not(wi.weapons & player.weapons)
+               if not(WEPSET_CONTAINS_EW(player, i))
                        continue;
  
                if(wi.items & IT_SHELLS)
@@@ -747,10 -743,30 +743,30 @@@ void StartItem (string itemmodel, strin
  {
        startitem_failed = FALSE;
  
+       if(self.model == "")
+               self.model = itemmodel;
+       if(self.item_pickupsound == "")
+               self.item_pickupsound = pickupsound;
+       if(!self.respawntime) // both need to be set
+       {
+               self.respawntime = defaultrespawntime;
+               self.respawntimejitter = defaultrespawntimejitter;
+       }
        self.items = itemid;
-       self.weapons = weaponid;
+       self.weapon = weaponid;
+       if(weaponid)
+               WEPSET_COPY_EW(self, weaponid);
        self.flags = FL_ITEM | itemflags;
  
+       if(MUTATOR_CALLHOOK(FilterItem)) // error means we do not want the item
+       {
+               startitem_failed = TRUE;
+               remove(self);
+               return;
+       }
        // is it a dropped weapon?
        if (self.classname == "droppedweapon")
        {
                if(self.strength_finished || self.invincible_finished || self.superweapons_finished)
                /*
                if(self.items == 0)
-               if(self.weapons == (self.weapons & WEPBIT_SUPERWEAPONS)) // only superweapons
+               if(WEPSET_CONTAINS_ALL_AE(WEPBIT_SUPERWEAPONS, self)) // only superweapons
                if(self.ammo_nails == 0)
                if(self.ammo_cells == 0)
                if(self.ammo_rockets == 0)
        }
        else
        {
-               if(MUTATOR_CALLHOOK(FilterItem)) // error means we do not want the item
-               {
-                       startitem_failed = TRUE;
-                       remove(self);
-                       return;
-               }
                if(!have_pickup_item())
                {
                        startitem_failed = TRUE;
                        return;
                }
  
-               if(self.model != "")
-                       itemmodel = self.model;
-               if(self.item_pickupsound != "")
-                       pickupsound = self.item_pickupsound;
                self.reset = Item_Reset;
                // it's a level item
                if(self.spawnflags & 1)
                        self.is_item = TRUE;
                }
  
-               weaponsInMap |= weaponid;
+               WEPSET_OR_AW(weaponsInMap, weaponid);
  
-               precache_model (itemmodel);
-               precache_sound (pickupsound);
+               precache_model (self.model);
+               precache_sound (self.item_pickupsound);
  
                precache_sound ("misc/itemrespawncountdown.wav");
                if(!g_minstagib && itemid == IT_STRENGTH)
        self.bot_pickup = TRUE;
        self.bot_pickupevalfunc = pickupevalfunc;
        self.bot_pickupbasevalue = pickupbasevalue;
-       self.mdl = itemmodel;
-       self.item_pickupsound = pickupsound;
-       if(self.weapons)
-               self.weapon = WEP_FIRST + log2of(lowestbit(self.weapons));
-       else
-               self.weapon = 0;
-       // let mappers override respawntime
-       if(!self.respawntime) // both set
-       {
-               self.respawntime = defaultrespawntime;
-               self.respawntimejitter = defaultrespawntimejitter;
-       }
+       self.mdl = self.model;
        self.netname = itemname;
        self.touch = Item_Touch;
        setmodel (self, self.mdl); // precision set below
        }
        else
                Item_Reset();
 +
 +      // call this hook after everything else has been done
 +      if(MUTATOR_CALLHOOK(Item_Spawn))
 +      {
 +              startitem_failed = TRUE;
 +              remove(self);
 +              return;
 +      }
  }
  
  /* replace items in minstagib
@@@ -1013,8 -998,23 +1006,23 @@@ void weapon_defaultspawnfunc(float wpn
        if(self.classname != "droppedweapon" && self.classname != "replacedweapon")
        {
                e = get_weaponinfo(wpn);
-               s = cvar_string(strcat("g_weaponreplace_", e.netname));
-               if(s == "0")
+               if(e.spawnflags & WEP_FLAG_MUTATORBLOCKED)
+               {
+                       print("Attempted to spawn a mutator-blocked weapon; these guns will in the future require a mutator\n");
+                       /*
+                       objerror("Attempted to spawn a mutator-blocked weapon rejected");
+                       startitem_failed = TRUE;
+                       return;
+                       */
+               }
+               s = W_Apply_Weaponreplace(e.netname);
+               ret_string = s;
+               other = e;
+               MUTATOR_CALLHOOK(SetWeaponreplace);
+               s = ret_string;
+               if(s == "")
                {
                        remove(self);
                        startitem_failed = TRUE;
                        }
                        self = oldself;
                }
-               if(t >= 1)
+               if(t >= 1) // always the case!
                {
                        s = argv(0);
                        wpn = 0;
  
        if(!self.respawntime)
        {
-               if(e.weapons & WEPBIT_SUPERWEAPONS)
+               if(WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS))
                {
                        self.respawntime = g_pickup_respawntime_superweapon;
                        self.respawntimejitter = g_pickup_respawntimejitter_superweapon;
                }
        }
  
-       if(e.weapons & WEPBIT_SUPERWEAPONS)
+       if(WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS))
                if(!self.superweapons_finished)
                        self.superweapons_finished = autocvar_g_balance_superweapons_time;
  
        f = FL_WEAPON;
  
        // no weapon-stay on superweapons
-       if(e.weapons & WEPBIT_SUPERWEAPONS)
+       if(WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS))
                f |= FL_NO_WEAPON_STAY;
  
        // weapon stay isn't supported for teamed weapons
                if(self.ammo_cells)
                        self.ammo_cells = autocvar_g_minstagib_ammo_drop;
  
-       StartItem(e.model, "weapons/weaponpickup.wav", self.respawntime, self.respawntimejitter, e.message, 0, e.weapons, f, weapon_pickupevalfunc, e.bot_pickupbasevalue);
+       StartItem(e.model, "weapons/weaponpickup.wav", self.respawntime, self.respawntimejitter, e.message, 0, e.weapon, f, weapon_pickupevalfunc, e.bot_pickupbasevalue);
        if (self.modelindex) // don't precache if self was removed
                weapon_action(e.weapon, WR_PRECACHE);
  }
@@@ -1428,19 -1428,21 +1436,21 @@@ void spawnfunc_target_items (void
                        else if(argv(i) == "jetpack")                self.items |= IT_JETPACK;
                        else if(argv(i) == "fuel_regen")             self.items |= IT_FUEL_REGEN;
                        else
-                       for(j = WEP_FIRST; j <= WEP_LAST; ++j)
                        {
-                               e = get_weaponinfo(j);
-                               if(argv(i) == e.netname)
+                               for(j = WEP_FIRST; j <= WEP_LAST; ++j)
                                {
-                                       self.weapons |= e.weapons;
-                                       if(self.spawnflags == 0 || self.spawnflags == 2)
-                                               weapon_action(e.weapon, WR_PRECACHE);
-                                       break;
+                                       e = get_weaponinfo(j);
+                                       if(argv(i) == e.netname)
+                                       {
+                                               WEPSET_OR_EW(self, j);
+                                               if(self.spawnflags == 0 || self.spawnflags == 2)
+                                                       weapon_action(e.weapon, WR_PRECACHE);
+                                               break;
+                                       }
                                }
+                               if(j > WEP_LAST)
+                                       print("target_items: invalid item ", argv(i), "\n");
                        }
-                       if(j > WEP_LAST)
-                               print("target_items: invalid item ", argv(i), "\n");
                }
  
                string itemprefix, valueprefix;
                for(j = WEP_FIRST; j <= WEP_LAST; ++j)
                {
                        e = get_weaponinfo(j);
-                       if(e.weapons)
-                               self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.weapons & e.weapons), e.netname);
+                       if(e.weapon)
+                               self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, WEPSET_CONTAINS_EW(self, j), e.netname);
                }
        }
        self.netname = strzone(self.netname);
@@@ -1547,6 -1549,36 +1557,36 @@@ void spawnfunc_item_jetpack(void
  #define OP_PLUS 3
  #define OP_MINUS 4
  
+ float GiveWeapon(entity e, float wpn, float op, float val)
+ {
+       float v0, v1;
+       v0 = WEPSET_CONTAINS_EW(e, wpn);
+       switch(op)
+       {
+               case OP_SET:
+                       if(val > 0)
+                               WEPSET_OR_EW(e, wpn);
+                       else
+                               WEPSET_ANDNOT_EW(e, wpn);
+                       break;
+               case OP_MIN:
+               case OP_PLUS:
+                       if(val > 0)
+                               WEPSET_OR_EW(e, wpn);
+                       break;
+               case OP_MAX:
+                       if(val <= 0)
+                               WEPSET_ANDNOT_EW(e, wpn);
+                       break;
+               case OP_MINUS:
+                       if(val > 0)
+                               WEPSET_ANDNOT_EW(e, wpn);
+                       break;
+       }
+       v1 = WEPSET_CONTAINS_EW(e, wpn);
+       return (v0 != v1);
+ }
  float GiveBit(entity e, .float fld, float bit, float op, float val)
  {
        float v0, v1;
@@@ -1627,7 -1659,9 +1667,9 @@@ void GiveRot(entity e, float v0, float 
                e.regenfield = max(e.regenfield, time + regentime);
  }
  
+ #define PREGIVE_WEAPONS(e) WEPSET_DECLARE_A(save_weapons); WEPSET_COPY_AE(save_weapons, e)
  #define PREGIVE(e,f) float save_##f; save_##f = (e).f
+ #define POSTGIVE_WEAPON(e,b,snd_incr,snd_decr) GiveSound((e), WEPSET_CONTAINS_AW(save_weapons, b), WEPSET_CONTAINS_EW(e, b), 0, snd_incr, snd_decr)
  #define POSTGIVE_BIT(e,f,b,snd_incr,snd_decr) GiveSound((e), save_##f & (b), (e).f & (b), 0, snd_incr, snd_decr)
  #define POSTGIVE_VALUE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr)
  #define POSTGIVE_VALUE_ROT(e,f,t,rotfield,rottime,regenfield,regentime,snd_incr,snd_decr) GiveRot((e), save_##f, (e).f, rotfield, rottime, regenfield, regentime); GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr)
@@@ -1654,7 -1688,7 +1696,7 @@@ float GiveItems(entity e, float beginar
        e.superweapons_finished = max(0, e.superweapons_finished - time);
        
        PREGIVE(e, items);
-       PREGIVE(e, weapons);
+       PREGIVE_WEAPONS(e);
        PREGIVE(e, strength_finished);
        PREGIVE(e, invincible_finished);
        PREGIVE(e, superweapons_finished);
                                for(j = WEP_FIRST; j <= WEP_LAST; ++j)
                                {
                                        wi = get_weaponinfo(j);
-                                       if(wi.weapons)
-                                               got += GiveBit(e, weapons, wi.weapons, op, val);
+                                       if(wi.weapon)
+                                               if not(wi.spawnflags & WEP_FLAG_MUTATORBLOCKED)
+                                                       got += GiveWeapon(e, j, op, val);
                                }
                        case "allammo":
                                got += GiveValue(e, ammo_cells, op, val);
                                        wi = get_weaponinfo(j);
                                        if(cmd == wi.netname)
                                        {
-                                               got += GiveBit(e, weapons, wi.weapons, op, val);
+                                               got += GiveWeapon(e, j, op, val);
                                                break;
                                        }
                                }
        for(j = WEP_FIRST; j <= WEP_LAST; ++j)
        {
                wi = get_weaponinfo(j);
-               if(wi.weapons)
+               if(wi.weapon)
                {
-                       POSTGIVE_BIT(e, weapons, wi.weapons, "weapons/weaponpickup.wav", string_null);
-                       if not(save_weapons & wi.weapons)
-                               if(e.weapons & wi.weapons)
+                       POSTGIVE_WEAPON(e, j, "weapons/weaponpickup.wav", string_null);
+                       if not(WEPSET_CONTAINS_AW(save_weapons, j))
+                               if(WEPSET_CONTAINS_EW(e, j))
                                        weapon_action(wi.weapon, WR_PRECACHE);
                }
        }
        POSTGIVE_VALUE_ROT(e, health, 1, pauserothealth_finished, autocvar_g_balance_pause_health_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, "misc/megahealth.wav", string_null);
  
        if(e.superweapons_finished <= 0)
-               if(self.weapons & WEPBIT_SUPERWEAPONS)
+               if(WEPSET_CONTAINS_ANY_EA(self, WEPBIT_SUPERWEAPONS))
                        e.superweapons_finished = autocvar_g_balance_superweapons_time;
  
        if (g_minstagib)
        else
                e.superweapons_finished += time;
  
-       if not(e.weapons & W_WeaponBit(e.switchweapon))
+       if not(WEPSET_CONTAINS_EW(e, e.switchweapon))
                _switchweapon = TRUE;
        if(_switchweapon)
                W_SwitchWeapon_Force(e, w_getbestweapon(e));