Merge remote branch 'origin/master' into tzork/gm_nexball
authorJakob MG <jakob_mg@hotmail.com>
Thu, 29 Dec 2011 20:37:24 +0000 (21:37 +0100)
committerJakob MG <jakob_mg@hotmail.com>
Thu, 29 Dec 2011 20:37:24 +0000 (21:37 +0100)
Conflicts:
qcsrc/server/teamplay.qc

1  2 
defaultXonotic.cfg
qcsrc/server/cl_client.qc
qcsrc/server/defs.qh
qcsrc/server/mutators/gamemode_nexball.qc
qcsrc/server/progs.src
qcsrc/server/teamplay.qc

diff --combined defaultXonotic.cfg
@@@ -38,6 -38,16 +38,16 @@@ alias if_dedicated "${* asis}
  _if_dedicated alias if_client ""
  if_client alias if_dedicated ""
  
+ if_dedicated "alias" qc_cmd_svmenu "sv_cmd $$*"
+ if_client    "alias" qc_cmd_svmenu "menu_cmd $$*"
+ if_dedicated "alias" qc_cmd_svcl   "sv_cmd $$*"
+ if_client    "alias" qc_cmd_svcl   "cl_cmd $$*"
+ if_dedicated "alias" qc_cmd_svcmd  "sv_cmd $$*"
+ if_client    "alias" qc_cmd_svcmd  "cmd $$*"
+ // shorthand for the most usual case
+ alias qc_cmd "qc_cmd_svmenu $*"
  seta g_configversion 0        "Configuration file version (used to upgrade settings) 0: first run, or previous start was <2.4.1  Later, it's overridden by config.cfg, version ranges are defined in config_update.cfg"
  
  // say aliases
@@@ -606,8 -616,6 +616,6 @@@ seta fraglimit_override -1 "Frag limit 
  seta leadlimit_override -1    "Lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
  seta capturelimit_override -1 "Capture limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
  seta captureleadlimit_override -1     "Capture llead imit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
- seta g_ctf_capture_limit -1   "CTF capture limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
- seta g_ctf_capture_leadlimit -1       "CTF capture lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
  seta g_arena_point_limit -1   "Arena point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
  seta g_arena_point_leadlimit -1       "Arena point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
  seta g_domination_point_limit -1      "Domination point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
@@@ -619,15 -627,7 +627,14 @@@ seta g_keyhunt_point_leadlimit -1        "Keyh
  seta g_race_laps_limit -1     "Race laps limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
  seta g_nexball_goallimit -1 "Nexball goal limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
  seta g_nexball_goalleadlimit -1 "Nexball goal lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 +seta g_nexball_safepass_maxdist 5000    // Max distance to allow save fassping (0 to turn off safe passing)
 +seta g_nexball_safepass_turnrate 0.1    // How fast the safe-pass ball can habge direction
 +seta g_nexball_safepass_holdtime 0.75   // How long to remeber last teammate you pointed at
 +seta g_nexball_viewmodel_scale 0.25     // How large the ball for the carrier
 +seta g_nexball_viewmodel_offset "8 8 0" // Where the ball is located on carrier "forward right up"
 +seta g_nexball_tackling 1               // Allow ball theft?
 +
  
- seta g_ctf_win_mode 0 "0: captures only, 1: captures, then points, 2: points only"
  seta g_ctf_ignore_frags 0     "1: regular frags give no points"
  
  set g_freezetag 0 "Freeze Tag: Freeze the opposing team(s) to win, unfreeze teammates by standing next to them"
@@@ -1333,7 -1333,6 +1340,6 @@@ seta g_waypointsprites_turrets 1 "disab
  seta g_waypointsprites_turrets_maxdist 4000 "max distace for turret sprites"
  
  // command extension
- alias qc_cmd  "sv_cmd $*" // menu QC will override this to menu_cmd
  alias adminmsg        "sv_cmd adminmsg $*"
  alias teamstatus      "cmd teamstatus; sv_cmd teamstatus" // yes, it is broken on listen servers that way, but well, who cares :P
  alias printstats      "sv_cmd printstats" // print status on demand
@@@ -1407,14 -1406,8 +1413,8 @@@ alias unban "sv_cmd unban $*"     // us
  r_labelsprites_scale 0.40625 // labels sprites get displayed at 0.5x from 640x480 to 1280x1024, and at 1x from 1600x1200 onwards
  
  // settemp subsystem. Do not touch. Usage: settemp variable value, next map resets it.
- set settemp_list 0
- set settemp_idx 0
- set _settemp_var UNUSED
- alias settemp "_settemp_var \"_settemp_x$settemp_idx\"; qc_cmd rpn /settemp_idx settemp_idx 1 add def; _settemp \"$1\" \"$2\""
- alias _settemp "settemp_list \"1 $1 $_settemp_var $settemp_list\"; set $_settemp_var \"${$1}\"; $1 \"$2\""
- alias settemp_restore "_settemp_restore_${settemp_list asis}"
- alias _settemp_restore_0 "set settemp_var 0; set settemp_list 0"
- alias _settemp_restore_1 "$1 \"${$2}\"; _settemp_restore_${3- asis}"
+ alias settemp "qc_cmd_svcl settemp $$*"
+ alias settemp_restore "qc_cmd_svcl settemp_restore"
  
  // usercommands. These can be edited and bound by the menu.
  seta "userbind1_press" "say_team quad soon";  seta "userbind1_release" "";  seta "userbind1_description" "team: quad soon"
@@@ -1905,7 -1898,7 +1905,7 @@@ set sv_pitch_fixyaw 0 "workaround to fi
  
  set rescan_pending 0 "set to 1 to schedule a fs_rescan at the end of this match"
  
- seta g_mapinfo_allow_unsupported_modes_and_let_stuff_break "0" "set to 1 to be able to force game types using g_ cvars even if the map does not support them"
+ set g_mapinfo_allow_unsupported_modes_and_let_stuff_break "0" "set to 1 to be able to force game types using g_ cvars even if the map does not support them"
  
  // weapon accuracy stats
  set sv_accuracy_data_share 1 "1 send weapon accuracy data statistics to spectating clients, depends on cl_accuracy_data_share"
@@@ -421,6 -421,9 +421,6 @@@ void PutObserverInServer (void
        if(self.flagcarried)
                DropFlag(self.flagcarried, world, world);
  
 -      if(self.ballcarried && g_nexball)
 -              DropBall(self.ballcarried, self.origin + self.ballcarried.origin, self.velocity);
 -
        WaypointSprite_PlayerDead();
  
        if not(g_ca)  // don't reset teams when moving a ca player to the spectators
@@@ -1501,8 -1504,6 +1501,6 @@@ void ClientConnect (void
        else
                stuffcmd(self, "set _teams_available 0\n");
  
-       stuffcmd(self, strcat("set gametype ", ftos(game), "\n"));
        if(g_arena || g_ca)
        {
                self.classname = "observer";
@@@ -1647,6 -1648,8 +1645,6 @@@ void ClientDisconnect (void
        RemoveGrapplingHook(self);
        if(self.flagcarried)
                DropFlag(self.flagcarried, world, world);
 -      if(self.ballcarried && g_nexball)
 -              DropBall(self.ballcarried, self.origin + self.ballcarried.origin, self.velocity);
  
        // Here, everything has been done that requires this player to be a client.
  
@@@ -2918,10 -2921,8 +2916,8 @@@ void PlayerPostThink (void
                stuffcmd(self, strcat("name ", self.netname, substring(ftos(random()), 2, -1), "\n"));
        }
  
-       if(sv_maxidle && frametime)
+       if(sv_maxidle && frametime) // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero).
        {
-               // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero).
-               float timeleft;
                if (time - self.parm_idlesince < 1) // instead of (time == self.parm_idlesince) to support sv_maxidle <= 10
                {
                        if(self.idlekick_lasttimeleft)
                                Send_CSQC_Centerprint_Generic_Expire(self, CPID_DISCONNECT_IDLING);
                                self.idlekick_lasttimeleft = 0;
                        }
-                       return;
-               }
-               timeleft = ceil(sv_maxidle - (time - self.parm_idlesince));
-               if(timeleft == min(10, sv_maxidle - 1)) // - 1 to support sv_maxidle <= 10
-               {
-                       if(!self.idlekick_lasttimeleft)
-                               Send_CSQC_Centerprint_Generic(self, CPID_DISCONNECT_IDLING, "^3Stop idling!\n^3Disconnecting in %d seconds...", 1, timeleft);
-               }
-               if(timeleft <= 0)
-               {
-                       bprint("^3", self.netname, "^3 was kicked for idling.\n");
-                       AnnounceTo(self, "terminated");
-                       dropclient(self);
-                       return;
                }
-               else if(timeleft <= 10)
+               else
                {
-                       if(timeleft != self.idlekick_lasttimeleft)
-                               AnnounceTo(self, ftos(timeleft));
-                       self.idlekick_lasttimeleft = timeleft;
+                       float timeleft;
+                       timeleft = ceil(sv_maxidle - (time - self.parm_idlesince));
+                       if(timeleft == min(10, sv_maxidle - 1)) // - 1 to support sv_maxidle <= 10
+                       {
+                               if(!self.idlekick_lasttimeleft)
+                                       Send_CSQC_Centerprint_Generic(self, CPID_DISCONNECT_IDLING, "^3Stop idling!\n^3Disconnecting in %d seconds...", 1, timeleft);
+                       }
+                       if(timeleft <= 0)
+                       {
+                               bprint("^3", self.netname, "^3 was kicked for idling.\n");
+                               AnnounceTo(self, "terminated");
+                               dropclient(self);
+                               return;
+                       }
+                       else if(timeleft <= 10)
+                       {
+                               if(timeleft != self.idlekick_lasttimeleft)
+                                       AnnounceTo(self, ftos(timeleft));
+                               self.idlekick_lasttimeleft = timeleft;
+                       }
                }
        }
  
  #ifdef TETRIS
        if(self.impulse == 100)
                ImpulseCommands();
-       if (TetrisPostFrame())
-               return;
+       if (!TetrisPostFrame())
+       {
  #endif
  
        CheatFrame();
                //do nothing
        }
        
+ #ifdef TETRIS
+       }
+ #endif
        /*
        float i;
        for(i = 0; i < 1000; ++i)
diff --combined qcsrc/server/defs.qh
@@@ -18,12 -18,10 +18,10 @@@ noref float require_spawnfunc_prefix; /
  
  float ctf_score_value(string parameter);
  
- float g_dm, g_domination, g_ctf, g_tdm, g_keyhunt, g_onslaught, g_assault, g_arena, g_ca, g_lms, g_runematch, g_race, g_nexball, g_cts, g_freezetag, g_keepaway;
  float g_cloaked, g_footsteps, g_jump_grunt, g_grappling_hook, g_midair, g_minstagib, g_pinata, g_norecoil, g_minstagib_invis_alpha, g_bloodloss;
  float g_warmup_limit;
  float g_warmup_allguns;
  float g_warmup_allow_timeout;
- float g_ctf_win_mode;
  float g_ctf_ignore_frags;
  float g_ctf_reverse;
  float g_race_qualifying;
@@@ -363,6 -361,7 +361,6 @@@ string gamemode_name
  float startitem_failed;
  
  void DropFlag(entity flag, entity penalty_receiver, entity attacker);
 -void DropBall(entity ball, vector org, vector vel);
  void DropAllRunes(entity pl);
  
  
index 198ee9c,0000000..8554a12
mode 100644,000000..100644
--- /dev/null
@@@ -1,983 -1,0 +1,983 @@@
-         self.weapons = 0; //W_WeaponBit(WEP_PORTO);
 +float autocvar_g_nexball_safepass_turnrate;
 +float autocvar_g_nexball_safepass_maxdist;
 +float autocvar_g_nexball_safepass_holdtime;
 +float autocvar_g_nexball_viewmodel_scale;
 +float autocvar_g_nexball_tackling;
 +vector autocvar_g_nexball_viewmodel_offset;
 +
 +void basketball_touch();
 +void football_touch();
 +void ResetBall();
 +#define NBM_NONE 0
 +#define NBM_FOOTBALL 2
 +#define NBM_BASKETBALL 4
 +float nexball_mode;
 +
 +float OtherTeam(float t)  //works only if there are two teams on the map!
 +{
 +      entity e;
 +      e = find(world, classname, "nexball_team");
 +      if(e.team == t)
 +              e = find(e, classname, "nexball_team");
 +      return e.team;
 +}
 +
 +
 +void LogNB(string mode, entity actor)
 +{
 +      string s;
 +      if(!autocvar_sv_eventlog)
 +              return;
 +      s = strcat(":nexball:", mode);
 +      if(actor != world)
 +              s = strcat(s, ":", ftos(actor.playerid));
 +      GameLogEcho(s);
 +}
 +
 +void ball_restart(void)
 +{
 +      if(self.owner)
 +              DropBall(self, self.owner.origin, '0 0 0');
 +      ResetBall();
 +}
 +
 +void nexball_setstatus(void)
 +{
 +      entity oldself;
 +      self.items &~= IT_KEY1;
 +      if(self.ballcarried)
 +      {
 +              if(self.ballcarried.teamtime && (self.ballcarried.teamtime < time))
 +              {
 +                      bprint("The ", ColoredTeamName(self.team), " held the ball for too long.\n");
 +                      oldself = self;
 +                      self = self.ballcarried;
 +                      DropBall(self, self.owner.origin, '0 0 0');
 +                      ResetBall();
 +                      self = oldself;
 +              }
 +              else
 +                      self.items |= IT_KEY1;
 +      }
 +}
 +
 +void relocate_nexball(void)
 +{
 +      tracebox(self.origin, BALL_MINS, BALL_MAXS, self.origin, TRUE, self);
 +      if(trace_startsolid)
 +      {
 +              vector o;
 +              o = self.origin;
 +              if(!move_out_of_solid(self))
 +                      objerror("could not get out of solid at all!");
 +              print("^1NOTE: this map needs FIXING. ", self.classname, " at ", vtos(o - '0 0 1'));
 +              print(" needs to be moved out of solid, e.g. by '", ftos(self.origin_x - o_x));
 +              print(" ", ftos(self.origin_y - o_y));
 +              print(" ", ftos(self.origin_z - o_z), "'\n");
 +              self.origin = o;
 +      }
 +}
 +
 +void DropOwner(void)
 +{
 +      entity ownr;
 +      ownr = self.owner;
 +      DropBall(self, ownr.origin, ownr.velocity);
 +      makevectors(ownr.v_angle_y * '0 1 0');
 +      ownr.velocity += ('0 0 0.75' - v_forward) * 1000;
 +      ownr.flags &~= FL_ONGROUND;
 +}
 +
 +void GiveBall(entity plyr, entity ball)
 +{
 +      entity ownr;
 +
 +      if((ownr = ball.owner))
 +      {
 +              ownr.effects &~= autocvar_g_nexball_basketball_effects_default;
 +              ownr.ballcarried = world;
 +              if(ownr.metertime)
 +              {
 +                      ownr.metertime = 0;
 +                      ownr.weaponentity.state = WS_READY;
 +              }
 +              WaypointSprite_Kill(ownr.waypointsprite_attachedforcarrier);
 +      }
 +      else
 +      {
 +              WaypointSprite_Kill(ball.waypointsprite_attachedforcarrier);
 +      }
 +      
 +      //setattachment(ball, plyr, "");
 +      setorigin(ball, plyr.origin + plyr.view_ofs);
 +
 +      if(ball.team != plyr.team)
 +              ball.teamtime = time + autocvar_g_nexball_basketball_delay_hold_forteam;
 +
 +      ball.owner = ball.pusher = plyr; //"owner" is set to the player carrying, "pusher" to the last player who touched it
 +      ball.team = plyr.team;
 +      plyr.ballcarried = ball;
 +      ball.dropperid = plyr.playerid;
 +
 +      plyr.effects |= autocvar_g_nexball_basketball_effects_default;
 +      ball.effects &~= autocvar_g_nexball_basketball_effects_default;
 +
 +      ball.velocity = '0 0 0';
 +      ball.movetype = MOVETYPE_NONE;
 +      ball.touch = SUB_Null;
 +      ball.effects |= EF_NOSHADOW;
 +      ball.scale = 1; // scale down.
 +
 +      WaypointSprite_AttachCarrier("nb-ball", plyr, RADARICON_FLAGCARRIER, BALL_SPRITECOLOR);
 +      WaypointSprite_UpdateRule(plyr.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
 +
 +      if(autocvar_g_nexball_basketball_delay_hold)
 +      {
 +              ball.think = DropOwner;
 +              ball.nextthink = time + autocvar_g_nexball_basketball_delay_hold;
 +      }
 +      
 +    ownr = self;
 +    self = plyr;    
 +    self.weaponentity.weapons = self.weapons;
 +    self.weaponentity.switchweapon = self.weapon;
 +    self.weapons = W_WeaponBit(WEP_PORTO);      
 +    weapon_action(WEP_PORTO, WR_RESETPLAYER);
 +    self.switchweapon = WEP_PORTO;
 +    W_SwitchWeapon(WEP_PORTO);
 +    self = ownr;
 +}
 +
 +void DropBall(entity ball, vector org, vector vel)
 +{
 +      ball.effects |= autocvar_g_nexball_basketball_effects_default;
 +      ball.effects &~= EF_NOSHADOW;
 +      ball.owner.effects &~= autocvar_g_nexball_basketball_effects_default;
 +
 +      setattachment(ball, world, "");
 +      setorigin(ball, org);
 +      ball.movetype = MOVETYPE_BOUNCE;
 +      ball.flags &~= FL_ONGROUND;
 +      ball.scale = ball_scale;
 +      ball.velocity = vel;
 +      ball.ctf_droptime = time;
 +      ball.touch = basketball_touch;
 +      ball.think = ResetBall;
 +      ball.nextthink = min(time + autocvar_g_nexball_delay_idle, ball.teamtime);
 +
 +      if(ball.owner.metertime)
 +      {
 +              ball.owner.metertime = 0;
 +              ball.owner.weaponentity.state = WS_READY;
 +      }
 +
 +      WaypointSprite_Kill(ball.owner.waypointsprite_attachedforcarrier);
 +      WaypointSprite_Spawn("nb-ball", 0, 0, ball, '0 0 64', world, ball.team, ball, waypointsprite_attachedforcarrier, FALSE, RADARICON_FLAGCARRIER, BALL_SPRITECOLOR); // no health bar please
 +      WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
 +
 +      ball.owner.ballcarried = world;
 +      ball.owner = world;
 +}
 +
 +void InitBall(void)
 +{
 +      if(gameover) return;
 +      self.flags &~= FL_ONGROUND;
 +      self.movetype = MOVETYPE_BOUNCE;
 +      if(self.classname == "nexball_basketball")
 +              self.touch = basketball_touch;
 +      else if(self.classname == "nexball_football")
 +              self.touch = football_touch;
 +      self.cnt = 0;
 +      self.think = ResetBall;
 +      self.nextthink = time + autocvar_g_nexball_delay_idle + 3;
 +      self.teamtime = 0;
 +      self.pusher = world;
 +      self.team = FALSE;
 +      sound(self, CH_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
 +      WaypointSprite_Ping(self.waypointsprite_attachedforcarrier);
 +      LogNB("init", world);
 +}
 +
 +void ResetBall(void)
 +{
 +      if(self.cnt < 2)    // step 1
 +      {
 +              if(time == self.teamtime)
 +                      bprint("The ", ColoredTeamName(self.team), " held the ball for too long.\n");
 +              self.touch = SUB_Null;
 +              self.movetype = MOVETYPE_NOCLIP;
 +              self.velocity = '0 0 0'; // just in case?
 +              if(!self.cnt)
 +                      LogNB("resetidle", world);
 +              self.cnt = 2;
 +              self.nextthink = time;
 +      }
 +      else if(self.cnt < 4)      // step 2 and 3
 +      {
 +//            dprint("Step ", ftos(self.cnt), ": Calculated velocity: ", vtos(self.spawnorigin - self.origin), ", time: ", ftos(time), "\n");
 +              self.velocity = (self.spawnorigin - self.origin) * (self.cnt - 1); // 1 or 0.5 second movement
 +              self.nextthink = time + 0.5;
 +              self.cnt += 1;
 +      }
 +      else     // step 4
 +      {
 +//            dprint("Step 4: time: ", ftos(time), "\n");
 +              if(vlen(self.origin - self.spawnorigin) > 10)  // should not happen anymore
 +                      dprint("The ball moved too far away from its spawn origin.\nOffset: ",
 +                                 vtos(self.origin - self.spawnorigin), " Velocity: ", vtos(self.velocity), "\n");
 +              self.velocity = '0 0 0';
 +              setorigin(self, self.spawnorigin); // make sure it's positioned correctly anyway
 +              self.movetype = MOVETYPE_NONE;
 +              self.think = InitBall;
 +              self.nextthink = max(time, game_starttime) + autocvar_g_nexball_delay_start;
 +      }
 +}
 +
 +void football_touch(void)
 +{
 +      if(other.solid == SOLID_BSP)
 +      {
 +              if(time > self.lastground + 0.1)
 +              {
 +                      sound(self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
 +                      self.lastground = time;
 +              }
 +              if(vlen(self.velocity) && !self.cnt)
 +                      self.nextthink = time + autocvar_g_nexball_delay_idle;
 +              return;
 +      }
 +      if(other.classname != "player")
 +              return;
 +      if(other.health < 1)
 +              return;
 +      if(!self.cnt)
 +              self.nextthink = time + autocvar_g_nexball_delay_idle;
 +
 +      self.pusher = other;
 +      self.team = other.team;
 +
 +      if(autocvar_g_nexball_football_physics == -1)    // MrBougo try 1, before decompiling Rev's original
 +      {
 +              if(vlen(other.velocity))
 +                      self.velocity = other.velocity * 1.5 + '0 0 1' * autocvar_g_nexball_football_boost_up;
 +      }
 +      else if(autocvar_g_nexball_football_physics == 1)      // MrBougo's modded Rev style: partially independant of the height of the aiming point
 +      {
 +              makevectors(other.v_angle);
 +              self.velocity = other.velocity + v_forward * autocvar_g_nexball_football_boost_forward + '0 0 1' * autocvar_g_nexball_football_boost_up;
 +      }
 +      else if(autocvar_g_nexball_football_physics == 2)      // 2nd mod try: totally independant. Really playable!
 +      {
 +              makevectors(other.v_angle_y * '0 1 0');
 +              self.velocity = other.velocity + v_forward * autocvar_g_nexball_football_boost_forward + v_up * autocvar_g_nexball_football_boost_up;
 +      }
 +      else     // Revenant's original style (from the original mod's disassembly, acknowledged by Revenant)
 +      {
 +              makevectors(other.v_angle);
 +              self.velocity = other.velocity + v_forward * autocvar_g_nexball_football_boost_forward + v_up * autocvar_g_nexball_football_boost_up;
 +      }
 +      self.avelocity = -250 * v_forward;  // maybe there is a way to make it look better?
 +}
 +
 +void basketball_touch(void)
 +{
 +      if(other.ballcarried)
 +      {
 +              football_touch();
 +              return;
 +      }
 +      if(!self.cnt && other.classname == "player" && (other.playerid != self.dropperid || time > self.ctf_droptime + autocvar_g_nexball_delay_collect))
 +      {
 +              if(other.health <= 0)
 +                      return;
 +              LogNB("caught", other);
 +              GiveBall(other, self);
 +      }
 +      else if(other.solid == SOLID_BSP)
 +      {
 +              sound(self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
 +              if(vlen(self.velocity) && !self.cnt)
 +                      self.nextthink = min(time + autocvar_g_nexball_delay_idle, self.teamtime);
 +      }
 +}
 +
 +void GoalTouch(void)
 +{
 +      entity ball;
 +      float isclient, pscore, otherteam;
 +      string pname;
 +
 +      if(gameover) return;
 +      if((self.spawnflags & GOAL_TOUCHPLAYER) && other.ballcarried)
 +              ball = other.ballcarried;
 +      else
 +              ball = other;
 +      if(ball.classname != "nexball_basketball")
 +              if(ball.classname != "nexball_football")
 +                      return;
 +      if((!ball.pusher && self.team != GOAL_OUT) || ball.cnt)
 +              return;
 +      EXACTTRIGGER_TOUCH;
 +
 +
 +      if(nb_teams == 2)
 +              otherteam = OtherTeam(ball.team);
 +
 +      if((isclient = ball.pusher.flags & FL_CLIENT))
 +              pname = ball.pusher.netname;
 +      else
 +              pname = "Someone (?)";
 +
 +      if(ball.team == self.team)         //owngoal (regular goals)
 +      {
 +              LogNB("owngoal", ball.pusher);
 +              bprint("Boo! ", pname, "^7 scored a goal against their own team!\n");
 +              pscore = -1;
 +      }
 +      else if(self.team == GOAL_FAULT)
 +      {
 +              LogNB("fault", ball.pusher);
 +              if(nb_teams == 2)
 +                      bprint(ColoredTeamName(otherteam), " gets a point due to ", pname, "^7's silliness.\n");
 +              else
 +                      bprint(ColoredTeamName(ball.team), " loses a point due to ", pname, "^7's silliness.\n");
 +              pscore = -1;
 +      }
 +      else if(self.team == GOAL_OUT)
 +      {
 +              LogNB("out", ball.pusher);
 +              if((self.spawnflags & GOAL_TOUCHPLAYER) && ball.owner)
 +                      bprint(pname, "^7 went out of bounds.\n");
 +              else
 +                      bprint("The ball was returned.\n");
 +              pscore = 0;
 +      }
 +      else                               //score
 +      {
 +              LogNB(strcat("goal:", ftos(self.team)), ball.pusher);
 +              bprint("Goaaaaal! ", pname, "^7 scored a point for the ", ColoredTeamName(ball.team), ".\n");
 +              pscore = 1;
 +      }
 +
 +      sound(ball, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NONE);
 +
 +      if(ball.team && pscore)
 +      {
 +              if(nb_teams == 2 && pscore < 0)
 +                      TeamScore_AddToTeam(otherteam, ST_NEXBALL_GOALS, -pscore);
 +              else
 +                      TeamScore_AddToTeam(ball.team, ST_NEXBALL_GOALS, pscore);
 +      }
 +      if(isclient)
 +      {
 +              if(pscore > 0)
 +                      PlayerScore_Add(ball.pusher, SP_NEXBALL_GOALS, pscore);
 +              else if(pscore < 0)
 +                      PlayerScore_Add(ball.pusher, SP_NEXBALL_FAULTS, -pscore);
 +      }
 +
 +      if(ball.owner)  // Happens on spawnflag GOAL_TOUCHPLAYER
 +              DropBall(ball, ball.owner.origin, ball.owner.velocity);
 +
 +      WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier);
 +
 +      ball.cnt = 1;
 +      ball.think = ResetBall;
 +      if(ball.classname == "nexball_basketball")
 +              ball.touch = football_touch; // better than SUB_Null: football control until the ball gets reset
 +      ball.nextthink = time + autocvar_g_nexball_delay_goal * (self.team != GOAL_OUT);
 +}
 +
 +//=======================//
 +//       team ents       //
 +//=======================//
 +void spawnfunc_nexball_team(void)
 +{
 +      if(!g_nexball)
 +      {
 +              remove(self);
 +              return;
 +      }
 +      self.team = self.cnt + 1;
 +}
 +
 +void nb_spawnteam(string teamname, float teamcolor)
 +{
 +      dprint("^2spawned team ", teamname, "\n");
 +      entity e;
 +      e = spawn();
 +      e.classname = "nexball_team";
 +      e.netname = teamname;
 +      e.cnt = teamcolor;
 +      e.team = e.cnt + 1;
 +      nb_teams += 1;
 +}
 +
 +void nb_spawnteams(void)
 +{
 +      float t_r, t_b, t_y, t_p;
 +      entity e;
 +      for(e = world; (e = find(e, classname, "nexball_goal"));)
 +      {
 +              switch(e.team)
 +              {
 +              case COLOR_TEAM1:
 +                      if(!t_r)
 +                      {
 +                              nb_spawnteam("Red", e.team-1)   ;
 +                              t_r = 1;
 +                      }
 +                      break;
 +              case COLOR_TEAM2:
 +                      if(!t_b)
 +                      {
 +                              nb_spawnteam("Blue", e.team-1)  ;
 +                              t_b = 1;
 +                      }
 +                      break;
 +              case COLOR_TEAM3:
 +                      if(!t_y)
 +                      {
 +                              nb_spawnteam("Yellow", e.team-1);
 +                              t_y = 1;
 +                      }
 +                      break;
 +              case COLOR_TEAM4:
 +                      if(!t_p)
 +                      {
 +                              nb_spawnteam("Pink", e.team-1)  ;
 +                              t_p = 1;
 +                      }
 +                      break;
 +              }
 +      }
 +}
 +
 +void nb_delayedinit(void)
 +{
 +      if(find(world, classname, "nexball_team") == world)
 +              nb_spawnteams();
 +      ScoreRules_nexball(nb_teams);
 +}
 +
 +
 +//=======================//
 +//      spawnfuncs       //
 +//=======================//
 +
 +void SpawnBall(void)
 +{
 +      if(!g_nexball)
 +      {
 +              remove(self);
 +              return;
 +      }
 +
 +//    balls += 4; // using the remaining bits to count balls will leave more than the max edict count, so it's fine
 +
 +      if(!self.model)
 +      {
 +              self.model = "models/nexball/ball.md3";
 +              self.scale = 1.3;
 +      }
 +
 +      precache_model(self.model);
 +      setmodel(self, self.model);
 +      setsize(self, BALL_MINS, BALL_MAXS);
 +      ball_scale = self.scale;
 +
 +      relocate_nexball();
 +      self.spawnorigin = self.origin;
 +
 +      self.effects = self.effects | EF_LOWPRECISION;
 +
 +      if(cvar(strcat("g_", self.classname, "_trail")))  //nexball_basketball :p
 +      {
 +              self.glow_color = autocvar_g_nexball_trail_color;
 +              self.glow_trail = TRUE;
 +      }
 +
 +      self.movetype = MOVETYPE_FLY;
 +
 +      if(!autocvar_g_nexball_sound_bounce)
 +              self.noise = "";
 +      else if(!self.noise)
 +              self.noise = "sound/nexball/bounce.wav";
 +      //bounce sound placeholder (FIXME)
 +      if(!self.noise1)
 +              self.noise1 = "sound/nexball/drop.wav";
 +      //ball drop sound placeholder (FIXME)
 +      if(!self.noise2)
 +              self.noise2 = "sound/nexball/steal.wav";
 +      //stealing sound placeholder (FIXME)
 +      if(self.noise) precache_sound(self.noise);
 +      precache_sound(self.noise1);
 +      precache_sound(self.noise2);
 +
 +      WaypointSprite_AttachCarrier("nb-ball", self, RADARICON_FLAGCARRIER, BALL_SPRITECOLOR); // the ball's team is not set yet, no rule update needed
 +
 +      self.reset = ball_restart;
 +      self.think = InitBall;
 +      self.nextthink = game_starttime + autocvar_g_nexball_delay_start;
 +}
 +
 +void spawnfunc_nexball_basketball(void)
 +{
 +    nexball_mode |= NBM_BASKETBALL;
 +      self.classname = "nexball_basketball";
 +      if not(balls & BALL_BASKET)
 +      {
 +              /*
 +              CVTOV(g_nexball_basketball_effects_default);
 +              CVTOV(g_nexball_basketball_delay_hold);
 +              CVTOV(g_nexball_basketball_delay_hold_forteam);
 +              CVTOV(g_nexball_basketball_teamsteal);
 +              */
 +              autocvar_g_nexball_basketball_effects_default = autocvar_g_nexball_basketball_effects_default & BALL_EFFECTMASK;
 +      }
 +      if(!self.effects)
 +              self.effects = autocvar_g_nexball_basketball_effects_default;
 +      self.solid = SOLID_TRIGGER;
 +      balls |= BALL_BASKET;
 +      self.bouncefactor = autocvar_g_nexball_basketball_bouncefactor;
 +      self.bouncestop = autocvar_g_nexball_basketball_bouncestop;
 +      SpawnBall();
 +}
 +
 +void spawnfunc_nexball_football(void)
 +{
 +    nexball_mode |= NBM_FOOTBALL;
 +      self.classname = "nexball_football";
 +      self.solid = SOLID_TRIGGER;
 +      balls |= BALL_FOOT;
 +      self.bouncefactor = autocvar_g_nexball_football_bouncefactor;
 +      self.bouncestop = autocvar_g_nexball_football_bouncestop;
 +      SpawnBall();
 +}
 +
 +void SpawnGoal(void)
 +{
 +      if(!g_nexball)
 +      {
 +              remove(self);
 +              return;
 +      }
 +      EXACTTRIGGER_INIT;
 +      self.classname = "nexball_goal";
 +      if(!self.noise)
 +              self.noise = "ctf/respawn.wav";
 +      precache_sound(self.noise);
 +      self.touch = GoalTouch;
 +}
 +
 +void spawnfunc_nexball_redgoal(void)
 +{
 +      self.team = COLOR_TEAM1;
 +      SpawnGoal();
 +}
 +void spawnfunc_nexball_bluegoal(void)
 +{
 +      self.team = COLOR_TEAM2;
 +      SpawnGoal();
 +}
 +void spawnfunc_nexball_yellowgoal(void)
 +{
 +      self.team = COLOR_TEAM3;
 +      SpawnGoal();
 +}
 +void spawnfunc_nexball_pinkgoal(void)
 +{
 +      self.team = COLOR_TEAM4;
 +      SpawnGoal();
 +}
 +
 +void spawnfunc_nexball_fault(void)
 +{
 +      self.team = GOAL_FAULT;
 +      if(!self.noise)
 +              self.noise = "misc/typehit.wav";
 +      SpawnGoal();
 +}
 +
 +void spawnfunc_nexball_out(void)
 +{
 +      self.team = GOAL_OUT;
 +      if(!self.noise)
 +              self.noise = "misc/typehit.wav";
 +      SpawnGoal();
 +}
 +
 +//
 +//Spawnfuncs preserved for compatibility
 +//
 +
 +void spawnfunc_ball(void)
 +{
 +      spawnfunc_nexball_football();
 +}
 +void spawnfunc_ball_football(void)
 +{
 +      spawnfunc_nexball_football();
 +}
 +void spawnfunc_ball_basketball(void)
 +{
 +      spawnfunc_nexball_basketball();
 +}
 +// The "red goal" is defended by blue team. A ball in there counts as a point for red.
 +void spawnfunc_ball_redgoal(void)
 +{
 +      spawnfunc_nexball_bluegoal();    // I blame Revenant
 +}
 +void spawnfunc_ball_bluegoal(void)
 +{
 +      spawnfunc_nexball_redgoal();    // but he didn't mean to cause trouble :p
 +}
 +void spawnfunc_ball_fault(void)
 +{
 +      spawnfunc_nexball_fault();
 +}
 +void spawnfunc_ball_bound(void)
 +{
 +      spawnfunc_nexball_out();
 +}
 +
 +//=======================//
 +//      Weapon code      //
 +//=======================//
 +
 +
 +void W_Nexball_Think()
 +{
 +    //dprint("W_Nexball_Think\n");
 +    //vector new_dir = steerlib_arrive(self.enemy.origin, 2500);
 +    vector new_dir = normalize(self.enemy.origin - self.origin);
 +    vector old_dir = normalize(self.velocity);     
 +    float _speed = vlen(self.velocity);    
 +    vector new_vel = normalize(old_dir + (new_dir * autocvar_g_nexball_safepass_turnrate)) * _speed;
 +    //vector new_vel = (new_dir * autocvar_g_nexball_safepass_turnrate
 +    
 +    self.velocity = new_vel;
 +    
 +    self.nextthink = time;
 +}
 +
 +void W_Nexball_Touch(void)
 +{
 +      entity ball, attacker;
 +      attacker = self.owner;
 +    //self.think = SUB_Null;
 +    //self.enemy = world;
 +    
 +      PROJECTILE_TOUCH;
 +      if(attacker.team != other.team || autocvar_g_nexball_basketball_teamsteal)
 +              if((ball = other.ballcarried) && (attacker.classname == "player"))
 +              {
 +                      other.velocity = other.velocity + normalize(self.velocity) * other.damageforcescale * autocvar_g_balance_nexball_secondary_force;
 +                      other.flags &~= FL_ONGROUND;
 +                      if(!attacker.ballcarried)
 +                      {
 +                              LogNB("stole", attacker);
 +                              sound(other, CH_TRIGGER, ball.noise2, VOL_BASE, ATTN_NORM);
 +
 +                              if(attacker.team == other.team && time > attacker.teamkill_complain)
 +                              {
 +                                      attacker.teamkill_complain = time + 5;
 +                                      attacker.teamkill_soundtime = time + 0.4;
 +                                      attacker.teamkill_soundsource = other;
 +                              }
 +
 +                              GiveBall(attacker, other.ballcarried);
 +                      }
 +              }
 +      remove(self);
 +}
 +
 +void W_Nexball_Attack(float t)
 +{
 +      entity ball;
 +      float mul, mi, ma;
 +      if(!(ball = self.ballcarried))
 +              return;
 +
 +      W_SetupShot(self, FALSE, 4, "nexball/shoot1.wav", CH_WEAPON_A, 0);
 +      tracebox(w_shotorg, BALL_MINS, BALL_MAXS, w_shotorg, MOVE_WORLDONLY, world);
 +      if(trace_startsolid)
 +      {
 +              if(self.metertime)
 +                      self.metertime = 0; // Shot failed, hide the power meter
 +              return;
 +      }
 +
 +      //Calculate multiplier
 +      if(t < 0)
 +              mul = 1;
 +      else
 +      {
 +              mi = autocvar_g_nexball_basketball_meter_minpower;
 +              ma = max(mi, autocvar_g_nexball_basketball_meter_maxpower); // avoid confusion
 +              //One triangle wave period with 1 as max
 +              mul = 2 * mod(t, g_nexball_meter_period) / g_nexball_meter_period;
 +              if(mul > 1)
 +                      mul = 2 - mul;
 +              mul = mi + (ma - mi) * mul; // range from the minimal power to the maximal power
 +      }
 +      
 +    DropBall(ball, w_shotorg, W_CalculateProjectileVelocity(self.velocity, w_shotdir * autocvar_g_balance_nexball_primary_speed * mul, FALSE));
 +      
 +
 +      //TODO: use the speed_up cvar too ??
 +}
 +
 +void W_Nexball_Attack2(void)
 +{
 +      if(self.ballcarried.enemy)
 +      {
 +          entity _ball = self.ballcarried;
 +        W_SetupShot(self, FALSE, 4, "nexball/shoot1.wav", CH_WEAPON_A, 0);
 +          DropBall(_ball, w_shotorg, trigger_push_calculatevelocity(_ball.origin, _ball.enemy, 32));
 +        _ball.think = W_Nexball_Think;
 +        _ball.nextthink = time;
 +          return;
 +      }
 +    
 +    if(!autocvar_g_nexball_tackling)
 +        return;
 +      
 +      entity missile;
 +      if(!(balls & BALL_BASKET))
 +              return;
 +      W_SetupShot(self, FALSE, 2, "nexball/shoot2.wav", CH_WEAPON_A, 0);
 +//    pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
 +      missile = spawn();
 +
 +      missile.owner = self;
 +      missile.classname = "ballstealer";
 +
 +      missile.movetype = MOVETYPE_FLY;
 +      PROJECTILE_MAKETRIGGER(missile);
 +
 +      setmodel(missile, "models/elaser.mdl");  // precision set below
 +      setsize(missile, '0 0 0', '0 0 0');
 +      setorigin(missile, w_shotorg);
 +
 +      W_SetupProjectileVelocity(missile, autocvar_g_balance_nexball_secondary_speed, 0);
 +      missile.angles = vectoangles(missile.velocity);
 +      missile.touch = W_Nexball_Touch;
 +      missile.think = SUB_Remove;
 +      missile.nextthink = time + autocvar_g_balance_nexball_secondary_lifetime; //FIXME: use a distance instead?
 +
 +      missile.effects = EF_BRIGHTFIELD | EF_LOWPRECISION;
 +      missile.flags = FL_PROJECTILE;
 +}
 +
 +var const float() nullfunc;
 +float ball_customize()
 +{
 +    if(!self.owner)
 +    {
 +        self.effects &~= EF_FLAME;
 +        self.scale = 1;
 +        self.customizeentityforclient = nullfunc;
 +        return TRUE;
 +    }        
 +    
 +    if(other == self.owner)
 +    {
 +        self.scale = autocvar_g_nexball_viewmodel_scale;
 +        if(self.enemy)
 +            self.effects |= EF_FLAME;
 +        else
 +            self.effects &~= EF_FLAME;
 +    }    
 +    else
 +    {
 +        self.effects &~= EF_FLAME;
 +        self.scale = 1;
 +    }
 +        
 +    return TRUE;
 +}
 +
 +float w_nexball_weapon(float req)
 +{
 +      if(req == WR_THINK)
 +      {
 +              if(self.BUTTON_ATCK)
 +                      if(weapon_prepareattack(0, autocvar_g_balance_nexball_primary_refire))
 +                              if(autocvar_g_nexball_basketball_meter)
 +                              {
 +                                      if(self.ballcarried && !self.metertime)
 +                                              self.metertime = time;
 +                                      else
 +                                              weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_nexball_primary_animtime, w_ready);
 +                              }
 +                              else
 +                              {
 +                                      W_Nexball_Attack(-1);
 +                                      weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_nexball_primary_animtime, w_ready);
 +                              }
 +              if(self.BUTTON_ATCK2)
 +                      if(weapon_prepareattack(1, autocvar_g_balance_nexball_secondary_refire))
 +                      {
 +                              W_Nexball_Attack2();
 +                              weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_nexball_secondary_animtime, w_ready);
 +                      }
 +
 +              if(!self.BUTTON_ATCK && self.metertime && self.ballcarried)
 +              {
 +                      W_Nexball_Attack(time - self.metertime);
 +                      // DropBall or stealing will set metertime back to 0
 +                      weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_nexball_primary_animtime, w_ready);
 +              }
 +      }
 +      else if(req == WR_PRECACHE)
 +      {
 +              precache_model("models/weapons/g_porto.md3");
 +              precache_model("models/weapons/v_porto.md3");
 +              precache_model("models/weapons/h_porto.iqm");
 +              precache_model("models/elaser.mdl");
 +              precache_sound("nexball/shoot1.wav");
 +              precache_sound("nexball/shoot2.wav");
 +              precache_sound("misc/typehit.wav");
 +      }
 +      else if(req == WR_SETUP)
 +              weapon_setup(WEP_PORTO);
 +      else if(req == WR_SUICIDEMESSAGE)
 +      {
 +              w_deathtypestring = "is a weirdo";
 +      }
 +      else if(req == WR_KILLMESSAGE)
 +      {
 +              w_deathtypestring = "got killed by #'s black magic";
 +      }
 +      // No need to check WR_CHECKAMMO* or WR_AIM, it should always return TRUE
 +      return TRUE;
 +}
 +
 +MUTATOR_HOOKFUNCTION(nexball_BallDrop)
 +{
 +      if(self.ballcarried && g_nexball)
 +              DropBall(self.ballcarried, self.origin, self.velocity);
 +
 +      return 0;
 +}
 +
 +MUTATOR_HOOKFUNCTION(nexball_BuildMutatorsString)
 +{
 +      ret_string = strcat(ret_string, ":NB");
 +      return 0;
 +}
 +
 +MUTATOR_HOOKFUNCTION(nexball_BuildMutatorsPrettyString)
 +{
 +      ret_string = strcat(ret_string, ", NexBall");
 +      return 0;
 +}
 +
 +MUTATOR_HOOKFUNCTION(nexball_PlayerPreThink)
 +{
 +    makevectors(self.v_angle);
 +    if(nexball_mode & NBM_BASKETBALL)
 +    {        
 +        if(self.ballcarried)
 +        {
 +            // 'view ball'
 +            self.ballcarried.velocity = self.velocity;            
 +            self.ballcarried.customizeentityforclient = ball_customize;
 +            
 +            setorigin(self.ballcarried, self.origin + self.view_ofs + 
 +                      v_forward * autocvar_g_nexball_viewmodel_offset_x + 
 +                      v_right * autocvar_g_nexball_viewmodel_offset_y + 
 +                      v_up * autocvar_g_nexball_viewmodel_offset_z);    
 +                      
 +            // 'safe passing'
 +            if(autocvar_g_nexball_safepass_maxdist)
 +            {
 +                if(self.ballcarried.wait < time && self.ballcarried.enemy)
 +                {
 +                    //centerprint(self, sprintf("Lost lock on %s", self.ballcarried.enemy.netname));
 +                    self.ballcarried.enemy = world;
 +                }
 +                    
 +                
 +                //tracebox(self.origin + self.view_ofs, '-2 -2 -2', '2 2 2', self.origin + self.view_ofs + v_forward * autocvar_g_nexball_safepass_maxdist);
 +                crosshair_trace(self);
 +                if( trace_ent && 
 +                    trace_ent.flags & FL_CLIENT &&
 +                    trace_ent.deadflag == DEAD_NO &&
 +                    trace_ent.team == self.team &&
 +                    vlen(trace_ent.origin - self.origin) <= autocvar_g_nexball_safepass_maxdist )
 +                {
 +                    
 +                    //if(self.ballcarried.enemy != trace_ent)
 +                    //    centerprint(self, sprintf("Locked to %s", trace_ent.netname));
 +                    self.ballcarried.enemy = trace_ent;
 +                    self.ballcarried.wait = time + autocvar_g_nexball_safepass_holdtime;
 +                    
 +                    
 +                }
 +            }
 +        }
 +        else
 +        {            
 +            if(self.weaponentity.weapons)
 +            {
 +                self.weapons = self.weaponentity.weapons;        
 +                weapon_action(WEP_PORTO, WR_RESETPLAYER);
 +                self.switchweapon = self.weaponentity.switchweapon;
 +                W_SwitchWeapon(self.switchweapon);
 +                
 +                self.weaponentity.weapons = 0;
 +            }
 +        }
 +        
 +    }
 +    return FALSE;
 +}
 +
 +MUTATOR_HOOKFUNCTION(nexball_PlayerSpawn)
 +{    
 +    self.weaponentity.weapons = 0;
 +    
 +    if(nexball_mode & NBM_BASKETBALL)
 +        self.weapons |= W_WeaponBit(WEP_PORTO);
 +    else
++        self.weapons = 0; //    W_WeaponBit(WEP_PORTO);
 +
 +    return FALSE;
 +}
 +
 +MUTATOR_DEFINITION(gamemode_nexball)
 +{
 +      MUTATOR_HOOK(PlayerDies, nexball_BallDrop, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(MakePlayerObserver, nexball_BallDrop, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(ClientDisconnect, nexball_BallDrop, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(BuildMutatorsPrettyString, nexball_BuildMutatorsPrettyString, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(BuildMutatorsString, nexball_BuildMutatorsString, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(PlayerSpawn, nexball_PlayerSpawn, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(PlayerPreThink, nexball_PlayerPreThink, CBC_ORDER_ANY);
 +
 +      MUTATOR_ONADD
 +      {
 +          g_nexball = 1;
 +              g_nexball_meter_period = autocvar_g_nexball_meter_period;
 +              if(g_nexball_meter_period <= 0)
 +                      g_nexball_meter_period = 2; // avoid division by zero etc. due to silly users
 +              g_nexball_meter_period = rint(g_nexball_meter_period * 32) / 32; //Round to 1/32ths to send as a byte multiplied by 32
 +              addstat(STAT_NB_METERSTART, AS_FLOAT, metertime);
 +
 +              // General settings
 +              /*
 +              CVTOV(g_nexball_football_boost_forward);   //100
 +              CVTOV(g_nexball_football_boost_up);        //200
 +              CVTOV(g_nexball_delay_idle);               //10
 +              CVTOV(g_nexball_football_physics);         //0
 +              */
 +              radar_showennemies = autocvar_g_nexball_radar_showallplayers;
 +
 +              InitializeEntity(world, nb_delayedinit, INITPRIO_GAMETYPE);
 +      }
 +
 +      return 0;
 +}
diff --combined qcsrc/server/progs.src
@@@ -25,7 -25,6 +25,7 @@@ defs.qh               // Should rename this, it has 
  mutators/base.qh
  mutators/mutators.qh
  mutators/gamemode_keyhunt.qh // TODO fix this
 +mutators/gamemode_nexball.qh 
  mutators/mutator_dodging.qh
  
  //// tZork Turrets ////
@@@ -35,7 -34,6 +35,6 @@@ vehicles/vehicles_def.q
  campaign.qh
  ../common/campaign_common.qh
  ../common/mapinfo.qh
- ../common/util.qc
  
  accuracy.qh
  csqcprojectile.qh
@@@ -131,7 -129,7 +130,7 @@@ antilag.q
  ctf.qc
  domination.qc
  mode_onslaught.qc
 -nexball.qc
 +//nexball.qc
  g_hook.qc
  
  t_swamp.qc
@@@ -192,7 -190,6 +191,7 @@@ playerstats.q
  ../common/explosion_equation.qc
  
  mutators/base.qc
 +mutators/gamemode_nexball.qc
  mutators/gamemode_keyhunt.qc
  mutators/gamemode_freezetag.qc
  mutators/gamemode_keepaway.qc
@@@ -210,4 -207,6 +209,6 @@@ mutators/sandbox.q
  ../warpzonelib/util_server.qc
  ../warpzonelib/server.qc
  
+ ../common/util.qc
  ../common/if-this-file-errors-scroll-up-and-fix-the-warnings.fteqccfail
diff --combined qcsrc/server/teamplay.qc
@@@ -71,6 -71,7 +71,6 @@@ void dom_init()
  void ctf_init();
  void runematch_init();
  void tdm_init();
 -void nb_init();
  void entcs_init();
  
  void LogTeamchange(float player_id, float team_number, float type)
        GameLogEcho(strcat(":team:", ftos(player_id), ":", ftos(team_number), ":", ftos(type)));
  }
  
- void WriteGameCvars()
- {
-       cvar_set("g_dm", ftos(g_dm));
-       cvar_set("g_tdm", ftos(g_tdm));
-       cvar_set("g_domination", ftos(g_domination));
-       cvar_set("g_ctf", ftos(g_ctf));
-       cvar_set("g_runematch", ftos(g_runematch));
-       cvar_set("g_lms", ftos(g_lms));
-       cvar_set("g_arena", ftos(g_arena));
-       cvar_set("g_ca", ftos(g_ca));
-       cvar_set("g_keyhunt", ftos(g_keyhunt));
-       cvar_set("g_assault", ftos(g_assault));
-       cvar_set("g_onslaught", ftos(g_onslaught));
-       cvar_set("g_race", ftos(g_race));
-       cvar_set("g_nexball", ftos(g_nexball));
-       cvar_set("g_cts", ftos(g_cts));
-       cvar_set("g_freezetag", ftos(g_freezetag));
-       cvar_set("g_keepaway", ftos(g_keepaway));
- }
- void ReadGameCvars()
- {
-       float found;
-       float prev;
-       float i;
-       found = 0;
-       prev = autocvar_gamecfg;
-       for(i = 0; i < 2; ++i)
-       {
- //#NO AUTOCVARS START
-               found += (g_dm = (!found && (prev != GAME_DEATHMATCH) && cvar("g_dm")));
-               found += (g_tdm = (!found && (prev != GAME_TEAM_DEATHMATCH) && cvar("g_tdm")));
-               found += (g_domination = (!found && (prev != GAME_DOMINATION) && cvar("g_domination")));
-               found += (g_ctf = (!found && (prev != GAME_CTF) && cvar("g_ctf")));
-               found += (g_runematch = (!found && (prev != GAME_RUNEMATCH) && cvar("g_runematch")));
-               found += (g_lms = (!found && (prev != GAME_LMS) && cvar("g_lms")));
-               found += (g_arena = (!found && (prev != GAME_ARENA) && cvar("g_arena")));
-               found += (g_ca = (!found && (prev != GAME_CA) && cvar("g_ca")));
-               found += (g_keyhunt = (!found && (prev != GAME_KEYHUNT) && cvar("g_keyhunt")));
-               found += (g_assault = (!found && (prev != GAME_ASSAULT) && cvar("g_assault")));
-               found += (g_onslaught = (!found && (prev != GAME_ONSLAUGHT) && cvar("g_onslaught")));
-               found += (g_race = (!found && (prev != GAME_RACE) && cvar("g_race")));
-               found += (g_nexball = (!found && (prev != GAME_NEXBALL) && cvar("g_nexball")));
-               found += (g_cts = (!found && (prev != GAME_CTS) && cvar("g_cts")));
-               found += (g_freezetag = (!found && (prev != GAME_FREEZETAG) && cvar("g_freezetag")));
-               found += (g_keepaway = (!found && (prev != GAME_KEEPAWAY) && cvar("g_keepaway")));
- //#NO AUTOCVARS END
-               if(found)
-                       break;
-               prev = -1; // second attempt takes place WITHOUT prev set
-       }
-       if(!found)
-               g_dm = 1;
-       teamplay = 0;
-       serverflags &~= SERVERFLAG_TEAMPLAY;
- }
  void default_delayedinit()
  {
        if(!scores_initialized)
@@@ -166,16 -105,14 +104,14 @@@ void InitGameplayMode(
  
        VoteReset();
  
-       // make sure only ONE type is selected
-       ReadGameCvars();
-       WriteGameCvars();
        // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
        get_mi_min_max(1);
        world.mins = mi_min;
        world.maxs = mi_max;
  
        MapInfo_LoadMapSettings(mapname);
+       teamplay = 0;
+       serverflags &~= SERVERFLAG_TEAMPLAY;
  
        if not(cvar_value_issafe(world.fog))
        {
  
        MapInfo_ClearTemps();
  
-       // in case mapinfo switched the type
-       ReadGameCvars();
        // set both here, gamemode can override it later
        timelimit_override = autocvar_timelimit_override;
        fraglimit_override = autocvar_fraglimit_override;
        leadlimit_override = autocvar_leadlimit_override;
+       gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype);
  
        if(g_dm)
        {
-               game = GAME_DEATHMATCH;
-               gamemode_name = "Deathmatch";
        }
  
        if(g_tdm)
        {
-               game = GAME_TEAM_DEATHMATCH;
-               gamemode_name = "Team Deathmatch";
                ActivateTeamplay();
                tdm_init();
                if(autocvar_g_tdm_team_spawns)
  
        if(g_domination)
        {
-               game = GAME_DOMINATION;
-               gamemode_name = "Domination";
                ActivateTeamplay();
                fraglimit_override = autocvar_g_domination_point_limit;
                leadlimit_override = autocvar_g_domination_point_leadlimit;
  
        if(g_ctf)
        {
-               game = GAME_CTF;
-               gamemode_name = "Capture the Flag";
                ActivateTeamplay();
                g_ctf_ignore_frags = autocvar_g_ctf_ignore_frags;
-               if(g_ctf_win_mode == 2)
-               {
-                       fraglimit_override = autocvar_g_ctf_capture_limit;
-                       leadlimit_override = autocvar_g_ctf_capture_leadlimit;
-               }
-               else
-               {
-                       fraglimit_override = autocvar_capturelimit_override;
-                       leadlimit_override = autocvar_captureleadlimit_override;
-               }
+               fraglimit_override = autocvar_capturelimit_override;
+               leadlimit_override = autocvar_captureleadlimit_override;
                ctf_init();
                have_team_spawns = -1; // request team spawns
        }
  
        if(g_runematch)
        {
-               game = GAME_RUNEMATCH;
-               gamemode_name = "Rune Match";
                // ActivateTeamplay();
                fraglimit_override = autocvar_g_runematch_point_limit;
                leadlimit_override = autocvar_g_runematch_point_leadlimit;
  
        if(g_lms)
        {
-               game = GAME_LMS;
-               gamemode_name = "Last Man Standing";
                fraglimit_override = autocvar_g_lms_lives_override;
                leadlimit_override = 0; // not supported by LMS
                if(fraglimit_override == 0)
  
        if(g_arena)
        {
-               game = GAME_ARENA;
-               gamemode_name = "Arena";
                fraglimit_override = autocvar_g_arena_point_limit;
                leadlimit_override = autocvar_g_arena_point_leadlimit;
                maxspawned = autocvar_g_arena_maxspawned;
  
        if(g_ca)
        {
-               game = GAME_CA;
-               gamemode_name = "Clan Arena";
                ActivateTeamplay();
                fraglimit_override = autocvar_g_ca_point_limit;
                leadlimit_override = autocvar_g_ca_point_leadlimit;
        }
        if(g_keyhunt)
        {
-               game = GAME_KEYHUNT;
-               gamemode_name = "Key Hunt";
                ActivateTeamplay();
                fraglimit_override = autocvar_g_keyhunt_point_limit;
                leadlimit_override = autocvar_g_keyhunt_point_leadlimit;
  
        if(g_freezetag)
        {
-               game = GAME_FREEZETAG;
-               gamemode_name = "Freeze Tag";
                ActivateTeamplay();
                fraglimit_override = autocvar_g_freezetag_point_limit;
                leadlimit_override = autocvar_g_freezetag_point_leadlimit;
  
        if(g_assault)
        {
-               game = GAME_ASSAULT;
-               gamemode_name = "Assault";
                ActivateTeamplay();
                ScoreRules_assault();
                have_team_spawns = -1; // request team spawns
  
        if(g_onslaught)
        {
-               game = GAME_ONSLAUGHT;
-               gamemode_name = "Onslaught";
                ActivateTeamplay();
                have_team_spawns = -1; // request team spawns
        }
  
        if(g_race)
        {
-               game = GAME_RACE;
-               gamemode_name = "Race";
  
                if(autocvar_g_race_teams)
                {
  
        if(g_cts)
        {
-               game = GAME_CTS;
-               gamemode_name = "CTS";
                g_race_qualifying = 1;
                fraglimit_override = 0;
                leadlimit_override = 0;
  
        if(g_nexball)
        {
-         game = GAME_NEXBALL;
-         gamemode_name = "Nexball";
-         fraglimit_override = autocvar_g_nexball_goallimit;
-         leadlimit_override = autocvar_g_nexball_goalleadlimit;
-         ActivateTeamplay();
-         have_team_spawns = -1; // request team spawns
-           MUTATOR_ADD(gamemode_nexball);
++
+               fraglimit_override = autocvar_g_nexball_goallimit;
+               leadlimit_override = autocvar_g_nexball_goalleadlimit;
+               ActivateTeamplay();
 -              nb_init();
+               have_team_spawns = -1; // request team spawns
++        MUTATOR_ADD(gamemode_nexball);
        }
 -
 +        
        if(g_keepaway)
        {
-               game = GAME_KEEPAWAY;
-               gamemode_name = "Keepaway";
                MUTATOR_ADD(gamemode_keepaway);
        }
  
        if(teamplay)
                entcs_init();
  
-       // save it (for the next startup)
-       cvar_set("gamecfg", ftos(game));
        cache_mutatormsg = strzone("");
        cache_lastmutatormsg = strzone("");