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

@@@ -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"
Simple merge
Simple merge
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;
 +}
Simple merge
@@@ -358,21 -257,15 +256,16 @@@ void InitGameplayMode(
  
        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);
        }