Xonotic
sv_nexball.qc
Go to the documentation of this file.
1 #include "sv_nexball.qh"
2 
3 #include <server/client.qh>
4 #include <server/command/vote.qh>
5 #include <server/gamelog.qh>
6 #include <server/world.qh>
7 #include <common/ent_cs.qh>
10 
11 .entity ballcarried;
12 
15 
33 
40 
49 
50 void basketball_touch(entity this, entity toucher);
51 void football_touch(entity this, entity toucher);
52 void ResetBall(entity this);
53 const int NBM_NONE = 0;
54 const int NBM_FOOTBALL = 2;
55 const int NBM_BASKETBALL = 4;
57 
58 float OtherTeam(float t) //works only if there are two teams on the map!
59 {
60  entity e;
61  e = find(NULL, classname, "nexball_team");
62  if(e.team == t)
63  e = find(e, classname, "nexball_team");
64  return e.team;
65 }
66 
67 const int ST_NEXBALL_GOALS = 1;
69 {
70  GameRules_scoring(teams, 0, 0, {
71  field_team(ST_NEXBALL_GOALS, "goals", SFL_SORT_PRIO_PRIMARY);
72  field(SP_NEXBALL_GOALS, "goals", SFL_SORT_PRIO_PRIMARY);
73  field(SP_NEXBALL_FAULTS, "faults", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER);
74  });
75 }
76 
77 void LogNB(string mode, entity actor)
78 {
79  string s;
81  return;
82  s = strcat(":nexball:", mode);
83  if(actor != NULL)
84  s = strcat(s, ":", ftos(actor.playerid));
85  GameLogEcho(s);
86 }
87 
88 void ball_restart(entity this)
89 {
90  if(this.owner)
91  DropBall(this, this.owner.origin, '0 0 0');
92  ResetBall(this);
93 }
94 
96 {
97  STAT(OBJECTIVE_STATUS, this) &= ~NB_CARRYING;
98  if(this.ballcarried)
99  {
100  if(this.ballcarried.lifetime && (this.ballcarried.lifetime < time))
101  {
102  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_NEXBALL_RETURN_HELD));
103  entity e = this.ballcarried;
104  DropBall(this.ballcarried, this.ballcarried.owner.origin, '0 0 0');
105  ResetBall(e);
106  }
107  else
108  STAT(OBJECTIVE_STATUS, this) |= NB_CARRYING;
109  }
110 }
111 
113 {
114  tracebox(this.origin, BALL_MINS, BALL_MAXS, this.origin, true, this);
115  if(trace_startsolid)
116  {
117  vector o = this.origin;
118  if (!move_out_of_solid(this)) {
119  objerror(this, "could not get out of solid at all!");
120  }
121  LOG_INFOF(
122  "^1NOTE: this map needs FIXING. %s at %s needs to be moved out of solid, e.g. by %s",
123  this.classname,
124  vtos(o - '0 0 1'),
125  vtos(this.origin - o)
126  );
127  this.origin = o;
128  }
129 }
130 
131 void DropOwner(entity this)
132 {
133  entity ownr;
134  ownr = this.owner;
135  DropBall(this, ownr.origin, ownr.velocity);
136  makevectors(ownr.v_angle.y * '0 1 0');
137  ownr.velocity += ('0 0 0.75' - v_forward) * 1000;
138  UNSET_ONGROUND(ownr);
139 }
140 
141 void GiveBall(entity plyr, entity ball)
142 {
143  .entity weaponentity = weaponentities[0]; // TODO: find ballstealer
144  entity ownr = ball.owner;
145  if(ownr)
146  {
148  ownr.ballcarried = NULL;
149  GameRules_scoring_vip(ownr, false);
150  if(STAT(NB_METERSTART, ownr))
151  {
152  STAT(NB_METERSTART, ownr) = 0;
153  ownr.(weaponentity).state = WS_READY;
154  }
155  WaypointSprite_Kill(ownr.waypointsprite_attachedforcarrier);
156  }
157  else
158  {
159  WaypointSprite_Kill(ball.waypointsprite_attachedforcarrier);
160  }
161 
162  //setattachment(ball, plyr, "");
163  setorigin(ball, plyr.origin + plyr.view_ofs);
164 
165  if(ball.team != plyr.team)
167 
168  ball.owner = ball.pusher = plyr; //"owner" is set to the player carrying, "pusher" to the last player who touched it
169  ball.weaponentity_fld = weaponentity;
170  ball.team = plyr.team;
171  plyr.ballcarried = ball;
172  GameRules_scoring_vip(plyr, true);
173  ball.nb_dropper = plyr;
174 
177 
178  ball.velocity = '0 0 0';
180  settouch(ball, func_null);
181  ball.effects |= EF_NOSHADOW;
182  ball.scale = 1; // scale down.
183 
184  WaypointSprite_AttachCarrier(WP_NbBall, plyr, RADARICON_FLAGCARRIER);
185  WaypointSprite_UpdateRule(plyr.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
186 
188  {
189  setthink(ball, DropOwner);
191  }
192 
193  STAT(WEAPONS, plyr.(weaponentity)) = STAT(WEAPONS, plyr);
194  plyr.m_switchweapon = plyr.(weaponentity).m_weapon;
195  STAT(WEAPONS, plyr) = WEPSET(NEXBALL);
196  Weapon w = WEP_NEXBALL;
197  w.wr_resetplayer(w, plyr);
198  plyr.(weaponentity).m_switchweapon = WEP_NEXBALL;
199  W_SwitchWeapon(plyr, WEP_NEXBALL, weaponentity);
200 }
201 
202 void DropBall(entity ball, vector org, vector vel)
203 {
205  ball.effects &= ~EF_NOSHADOW;
206  ball.owner.effects &= ~autocvar_g_nexball_basketball_effects_default;
207 
208  setattachment(ball, NULL, "");
209  setorigin(ball, org);
211  UNSET_ONGROUND(ball);
212  ball.scale = ball_scale;
213  ball.velocity = vel;
214  ball.nb_droptime = time;
215  settouch(ball, basketball_touch);
216  setthink(ball, ResetBall);
217  ball.nextthink = min(time + autocvar_g_nexball_delay_idle, ball.lifetime);
218 
219  if(STAT(NB_METERSTART, ball.owner))
220  {
221  STAT(NB_METERSTART, ball.owner) = 0;
222  .entity weaponentity = ball.weaponentity_fld;
223  ball.owner.(weaponentity).state = WS_READY;
224  }
225 
226  WaypointSprite_Kill(ball.owner.waypointsprite_attachedforcarrier);
227  WaypointSprite_Spawn(WP_NbBall, 0, 0, ball, '0 0 64', NULL, ball.team, ball, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER); // no health bar please
228  WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
229 
230  entity e = ball.owner; ball.owner = NULL;
231  e.ballcarried = NULL;
232  GameRules_scoring_vip(e, false);
233 }
234 
235 void InitBall(entity this)
236 {
237  if(game_stopped) return;
238  UNSET_ONGROUND(this);
240  if(this.classname == "nexball_basketball")
241  settouch(this, basketball_touch);
242  else if(this.classname == "nexball_football")
243  settouch(this, football_touch);
244  this.cnt = 0;
245  setthink(this, ResetBall);
247  this.lifetime = 0;
248  this.pusher = NULL;
249  this.team = false;
250  _sound(this, CH_TRIGGER, this.noise1, VOL_BASE, ATTEN_NORM);
251  WaypointSprite_Ping(this.waypointsprite_attachedforcarrier);
252  LogNB("init", NULL);
253 }
254 
255 void ResetBall(entity this)
256 {
257  if(this.cnt < 2) // step 1
258  {
259  if(time == this.lifetime)
260  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_NEXBALL_RETURN_HELD));
261 
262  settouch(this, func_null);
264  this.velocity = '0 0 0'; // just in case?
265  if(!this.cnt)
266  LogNB("resetidle", NULL);
267  this.cnt = 2;
268  this.nextthink = time;
269  }
270  else if(this.cnt < 4) // step 2 and 3
271  {
272 // dprint("Step ", ftos(this.cnt), ": Calculated velocity: ", vtos(this.spawnorigin - this.origin), ", time: ", ftos(time), "\n");
273  this.velocity = (this.spawnorigin - this.origin) * (this.cnt - 1); // 1 or 0.5 second movement
274  this.nextthink = time + 0.5;
275  this.cnt += 1;
276  }
277  else // step 4
278  {
279 // dprint("Step 4: time: ", ftos(time), "\n");
280  if(vdist(this.origin - this.spawnorigin, >, 10)) // should not happen anymore
281  LOG_TRACE("The ball moved too far away from its spawn origin.\nOffset: ",
282  vtos(this.origin - this.spawnorigin), " Velocity: ", vtos(this.velocity), "\n");
283  this.velocity = '0 0 0';
284  setorigin(this, this.spawnorigin); // make sure it's positioned correctly anyway
286  setthink(this, InitBall);
287  this.nextthink = max(time, game_starttime) + autocvar_g_nexball_delay_start;
288  }
289 }
290 
291 void football_touch(entity this, entity toucher)
292 {
293  if(toucher.solid == SOLID_BSP)
294  {
295  if(time > this.lastground + 0.1)
296  {
297  _sound(this, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
298  this.lastground = time;
299  }
300  if(this.velocity && !this.cnt)
302  return;
303  }
304  if (!IS_PLAYER(toucher) && !IS_VEHICLE(toucher))
305  return;
306  if(GetResource(toucher, RES_HEALTH) < 1)
307  return;
308  if(!this.cnt)
310 
311  this.pusher = toucher;
312  this.team = toucher.team;
313 
314  if(autocvar_g_nexball_football_physics == -1) // MrBougo try 1, before decompiling Rev's original
315  {
316  if(toucher.velocity)
317  this.velocity = toucher.velocity * 1.5 + '0 0 1' * autocvar_g_nexball_football_boost_up;
318  }
319  else if(autocvar_g_nexball_football_physics == 1) // MrBougo's modded Rev style: partially independant of the height of the aiming point
320  {
321  makevectors(toucher.v_angle);
323  }
324  else if(autocvar_g_nexball_football_physics == 2) // 2nd mod try: totally independant. Really playable!
325  {
326  makevectors(toucher.v_angle.y * '0 1 0');
328  }
329  else // Revenant's original style (from the original mod's disassembly, acknowledged by Revenant)
330  {
331  makevectors(toucher.v_angle);
333  }
334  this.avelocity = -250 * v_forward; // maybe there is a way to make it look better?
335 }
336 
337 void basketball_touch(entity this, entity toucher)
338 {
339  if(toucher.ballcarried)
340  {
341  football_touch(this, toucher);
342  return;
343  }
344  if(!this.cnt && IS_PLAYER(toucher) && !STAT(FROZEN, toucher) && !IS_DEAD(toucher) && (toucher != this.nb_dropper || time > this.nb_droptime + autocvar_g_nexball_delay_collect))
345  {
346  if(GetResource(toucher, RES_HEALTH) < 1)
347  return;
348  LogNB("caught", toucher);
349  GiveBall(toucher, this);
350  }
351  else if(toucher.solid == SOLID_BSP)
352  {
353  _sound(this, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
354  if(this.velocity && !this.cnt)
356  }
357 }
358 
359 void GoalTouch(entity this, entity toucher)
360 {
361  entity ball;
362  float isclient, pscore, otherteam;
363  string pname;
364 
365  if(game_stopped) return;
366  if((this.spawnflags & GOAL_TOUCHPLAYER) && toucher.ballcarried)
367  ball = toucher.ballcarried;
368  else
369  ball = toucher;
370  if(ball.classname != "nexball_basketball")
371  if(ball.classname != "nexball_football")
372  return;
373  if((!ball.pusher && this.team != GOAL_OUT) || ball.cnt)
374  return;
375  EXACTTRIGGER_TOUCH(this, toucher);
376 
377 
378  if(NumTeams(nb_teams) == 2)
379  otherteam = OtherTeam(ball.team);
380  else
381  otherteam = 0;
382 
383  if((isclient = IS_CLIENT(ball.pusher)))
384  pname = ball.pusher.netname;
385  else
386  pname = "Someone (?)";
387 
388  if(ball.team == this.team) //owngoal (regular goals)
389  {
390  LogNB("owngoal", ball.pusher);
391  bprint("Boo! ", pname, "^7 scored a goal against their own team!\n");
392  pscore = -1;
393  }
394  else if(this.team == GOAL_FAULT)
395  {
396  LogNB("fault", ball.pusher);
397  if(NumTeams(nb_teams) == 2)
398  bprint(Team_ColoredFullName(otherteam), " gets a point due to ", pname, "^7's silliness.\n");
399  else
400  bprint(Team_ColoredFullName(ball.team), " loses a point due to ", pname, "^7's silliness.\n");
401  pscore = -1;
402  }
403  else if(this.team == GOAL_OUT)
404  {
405  LogNB("out", ball.pusher);
406  if((this.spawnflags & GOAL_TOUCHPLAYER) && ball.owner)
407  bprint(pname, "^7 went out of bounds.\n");
408  else
409  bprint("The ball was returned.\n");
410  pscore = 0;
411  }
412  else //score
413  {
414  LogNB(strcat("goal:", ftos(this.team)), ball.pusher);
415  bprint("Goaaaaal! ", pname, "^7 scored a point for the ", Team_ColoredFullName(ball.team), ".\n");
416  pscore = 1;
417  }
418 
419  _sound(ball, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NONE);
420 
421  if(ball.team && pscore)
422  {
423  if(NumTeams(nb_teams) == 2 && pscore < 0)
424  TeamScore_AddToTeam(otherteam, ST_NEXBALL_GOALS, -pscore);
425  else
426  TeamScore_AddToTeam(ball.team, ST_NEXBALL_GOALS, pscore);
427  }
428  if(isclient)
429  {
430  if(pscore > 0)
431  GameRules_scoring_add(ball.pusher, NEXBALL_GOALS, pscore);
432  else if(pscore < 0)
433  GameRules_scoring_add(ball.pusher, NEXBALL_FAULTS, -pscore);
434  }
435 
436  if(ball.owner) // Happens on spawnflag GOAL_TOUCHPLAYER
437  DropBall(ball, ball.owner.origin, ball.owner.velocity);
438 
439  WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier);
440 
441  ball.cnt = 1;
442  setthink(ball, ResetBall);
443  if(ball.classname == "nexball_basketball")
444  settouch(ball, football_touch); // better than func_null: football control until the ball gets reset
445  ball.nextthink = time + autocvar_g_nexball_delay_goal * (this.team != GOAL_OUT);
446 }
447 
448 //=======================//
449 // team ents //
450 //=======================//
451 spawnfunc(nexball_team)
452 {
453  if(!g_nexball)
454  {
455  delete(this);
456  return;
457  }
458  this.team = this.cnt + 1;
459 }
460 
461 void nb_spawnteam(string teamname, float teamcolor)
462 {
463  LOG_TRACE("^2spawned team ", teamname);
464  entity e = new_pure(nexball_team);
465  e.netname = teamname;
466  e.cnt = teamcolor;
467  e.team = e.cnt + 1;
468  //nb_teams += 1;
469 }
470 
472 {
473  bool t_red = false, t_blue = false, t_yellow = false, t_pink = false;
474  entity e;
475  for(e = NULL; (e = find(e, classname, "nexball_goal"));)
476  {
477  switch(e.team)
478  {
479  case NUM_TEAM_1:
480  if(!t_red)
481  {
482  nb_spawnteam("Red", e.team-1) ;
483  nb_teams |= BIT(0);
484  t_red = true;
485  }
486  break;
487  case NUM_TEAM_2:
488  if(!t_blue)
489  {
490  nb_spawnteam("Blue", e.team-1) ;
491  t_blue = true;
492  nb_teams |= BIT(1);
493  }
494  break;
495  case NUM_TEAM_3:
496  if(!t_yellow)
497  {
498  nb_spawnteam("Yellow", e.team-1);
499  t_yellow = true;
500  nb_teams |= BIT(2);
501  }
502  break;
503  case NUM_TEAM_4:
504  if(!t_pink)
505  {
506  nb_spawnteam("Pink", e.team-1) ;
507  t_pink = true;
508  nb_teams |= BIT(3);
509  }
510  break;
511  }
512  }
513 }
514 
516 {
517  if(find(NULL, classname, "nexball_team") == NULL)
518  nb_spawnteams();
520 }
521 
522 
523 //=======================//
524 // spawnfuncs //
525 //=======================//
526 
527 void SpawnBall(entity this)
528 {
529  if(!g_nexball) { delete(this); return; }
530 
531 // balls += 4; // using the remaining bits to count balls will leave more than the max edict count, so it's fine
532 
533  if(this.model == "")
534  {
535  this.model = "models/nexball/ball.md3";
536  this.scale = 1.3;
537  }
538 
539  precache_model(this.model);
540  _setmodel(this, this.model);
541  setsize(this, BALL_MINS, BALL_MAXS);
542  ball_scale = this.scale;
543 
544  relocate_nexball(this);
545  this.spawnorigin = this.origin;
546 
547  this.effects = this.effects | EF_LOWPRECISION;
548 
549  if(cvar(strcat("g_", this.classname, "_trail"))) //nexball_basketball :p
550  {
552  this.glow_trail = true;
553  }
554 
555  set_movetype(this, MOVETYPE_FLY);
556 
559 
561  this.noise = "";
562  else if(this.noise == "")
563  this.noise = strzone(SND(NB_BOUNCE));
564  //bounce sound placeholder (FIXME)
565  if(this.noise1 == "")
566  this.noise1 = strzone(SND(NB_DROP));
567  //ball drop sound placeholder (FIXME)
568  if(this.noise2 == "")
569  this.noise2 = strzone(SND(NB_STEAL));
570  //stealing sound placeholder (FIXME)
571  if(this.noise) precache_sound(this.noise);
572  precache_sound(this.noise1);
573  precache_sound(this.noise2);
574 
575  WaypointSprite_AttachCarrier(WP_NbBall, this, RADARICON_FLAGCARRIER); // the ball's team is not set yet, no rule update needed
576 
577  this.reset = ball_restart;
578  setthink(this, InitBall);
579  this.nextthink = game_starttime + autocvar_g_nexball_delay_start;
580 }
581 
582 spawnfunc(nexball_basketball)
583 {
585  if (!(balls & BALL_BASKET))
586  {
587  /*
588  CVTOV(g_nexball_basketball_effects_default);
589  CVTOV(g_nexball_basketball_delay_hold);
590  CVTOV(g_nexball_basketball_delay_hold_forteam);
591  CVTOV(g_nexball_basketball_teamsteal);
592  */
594  }
595  if(!this.effects)
597  this.solid = SOLID_TRIGGER;
599  balls |= BALL_BASKET;
602  SpawnBall(this);
603 }
604 
605 spawnfunc(nexball_football)
606 {
608  this.solid = SOLID_TRIGGER;
609  balls |= BALL_FOOT;
613  SpawnBall(this);
614 }
615 
616 bool nb_Goal_Customize(entity this, entity client)
617 {
618  entity e = WaypointSprite_getviewentity(client);
619  entity wp_owner = this.owner;
620  if(SAME_TEAM(e, wp_owner)) { return false; }
621 
622  return true;
623 }
624 
625 void SpawnGoal(entity this)
626 {
627  if(!g_nexball) { delete(this); return; }
628 
630 
631  if(this.team != GOAL_OUT && Team_IsValidTeam(this.team))
632  {
633  entity wp = WaypointSprite_SpawnFixed(WP_NbGoal, (this.absmin + this.absmax) * 0.5, this, sprite, RADARICON_NONE);
634  wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 0.5 0');
635  setcefc(this.sprite, nb_Goal_Customize);
636  }
637 
638  this.classname = "nexball_goal";
639  if(this.noise == "")
640  this.noise = "ctf/respawn.wav";
641  precache_sound(this.noise);
642  settouch(this, GoalTouch);
643 }
644 
645 spawnfunc(nexball_redgoal)
646 {
647  this.team = NUM_TEAM_1;
648  SpawnGoal(this);
649 }
650 spawnfunc(nexball_bluegoal)
651 {
652  this.team = NUM_TEAM_2;
653  SpawnGoal(this);
654 }
655 spawnfunc(nexball_yellowgoal)
656 {
657  this.team = NUM_TEAM_3;
658  SpawnGoal(this);
659 }
660 spawnfunc(nexball_pinkgoal)
661 {
662  this.team = NUM_TEAM_4;
663  SpawnGoal(this);
664 }
665 
666 spawnfunc(nexball_fault)
667 {
668  this.team = GOAL_FAULT;
669  if(this.noise == "")
670  this.noise = strzone(SND(TYPEHIT));
671  SpawnGoal(this);
672 }
673 
674 spawnfunc(nexball_out)
675 {
676  this.team = GOAL_OUT;
677  if(this.noise == "")
678  this.noise = strzone(SND(TYPEHIT));
679  SpawnGoal(this);
680 }
681 
682 //
683 //Spawnfuncs preserved for compatibility
684 //
685 
687 {
688  spawnfunc_nexball_football(this);
689 }
690 spawnfunc(ball_football)
691 {
692  spawnfunc_nexball_football(this);
693 }
694 spawnfunc(ball_basketball)
695 {
696  spawnfunc_nexball_basketball(this);
697 }
698 // The "red goal" is defended by blue team. A ball in there counts as a point for red.
699 spawnfunc(ball_redgoal)
700 {
701  spawnfunc_nexball_bluegoal(this); // I blame Revenant
702 }
703 spawnfunc(ball_bluegoal)
704 {
705  spawnfunc_nexball_redgoal(this); // but he didn't mean to cause trouble :p
706 }
707 spawnfunc(ball_fault)
708 {
709  spawnfunc_nexball_fault(this);
710 }
711 spawnfunc(ball_bound)
712 {
713  spawnfunc_nexball_out(this);
714 }
715 
716 bool ball_customize(entity this, entity client)
717 {
718  if(!this.owner)
719  {
720  this.effects &= ~EF_FLAME;
721  this.scale = 1;
722  setcefc(this, func_null);
723  return true;
724  }
725 
726  if(client == this.owner)
727  {
729  if(this.enemy)
730  this.effects |= EF_FLAME;
731  else
732  this.effects &= ~EF_FLAME;
733  }
734  else
735  {
736  this.effects &= ~EF_FLAME;
737  this.scale = 1;
738  }
739 
740  return true;
741 }
742 
743 void nb_DropBall(entity player)
744 {
745  if(player.ballcarried && g_nexball)
746  DropBall(player.ballcarried, player.origin, player.velocity);
747 }
748 
750 {
751  entity player = M_ARGV(0, entity);
752 
753  nb_DropBall(player);
754 }
755 
756 MUTATOR_HOOKFUNCTION(nb, PlayerDies)
757 {
758  entity frag_target = M_ARGV(2, entity);
759 
760  nb_DropBall(frag_target);
761 }
762 
763 MUTATOR_HOOKFUNCTION(nb, MakePlayerObserver)
764 {
765  entity player = M_ARGV(0, entity);
766 
767  nb_DropBall(player);
768  return false;
769 }
770 
772 {
773  entity player = M_ARGV(0, entity);
774 
775  makevectors(player.v_angle);
777  {
778  if(player.ballcarried)
779  {
780  // 'view ball'
781  player.ballcarried.velocity = player.velocity;
782  setcefc(player.ballcarried, ball_customize);
783 
784  vector org = player.origin + player.view_ofs +
788  setorigin(player.ballcarried, org);
789 
790  // 'safe passing'
792  {
793  if(player.ballcarried.wait < time && player.ballcarried.enemy)
794  {
795  //centerprint(player, sprintf("Lost lock on %s", player.ballcarried.enemy.netname));
796  player.ballcarried.enemy = NULL;
797  }
798 
799 
800  //tracebox(player.origin + player.view_ofs, '-2 -2 -2', '2 2 2', player.origin + player.view_ofs + v_forward * autocvar_g_nexball_safepass_maxdist);
801  crosshair_trace(player);
802  if( trace_ent &&
803  IS_CLIENT(trace_ent) &&
804  !IS_DEAD(trace_ent) &&
805  trace_ent.team == player.team &&
806  vdist(trace_ent.origin - player.origin, <=, autocvar_g_nexball_safepass_maxdist) )
807  {
808 
809  //if(player.ballcarried.enemy != trace_ent)
810  // centerprint(player, sprintf("Locked to %s", trace_ent.netname));
811  player.ballcarried.enemy = trace_ent;
812  player.ballcarried.wait = time + autocvar_g_nexball_safepass_holdtime;
813 
814 
815  }
816  }
817  }
818  else
819  {
820  for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
821  {
822  .entity weaponentity = weaponentities[slot];
823 
824  if(STAT(WEAPONS, player.(weaponentity)))
825  {
826  STAT(WEAPONS, player) = STAT(WEAPONS, player.(weaponentity));
827  Weapon w = WEP_NEXBALL;
828  w.wr_resetplayer(w, player);
829  player.(weaponentity).m_switchweapon = player.m_switchweapon;
830  W_SwitchWeapon(player, player.(weaponentity).m_switchweapon, weaponentity);
831 
832  STAT(WEAPONS, player.(weaponentity)) = '0 0 0';
833  }
834  }
835  }
836 
837  }
838 
839  nexball_setstatus(player);
840 }
841 
843 {
844  entity spectatee = M_ARGV(0, entity);
845  entity client = M_ARGV(1, entity);
846 
847  STAT(NB_METERSTART, client) = STAT(NB_METERSTART, spectatee);
848 }
849 
850 MUTATOR_HOOKFUNCTION(nb, PlayerSpawn)
851 {
852  entity player = M_ARGV(0, entity);
853 
854  STAT(NB_METERSTART, player) = 0;
855  for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
856  {
857  .entity weaponentity = weaponentities[slot];
858  STAT(WEAPONS, player.(weaponentity)) = '0 0 0';
859  }
860 
862  STAT(WEAPONS, player) |= WEPSET(NEXBALL);
863  else
864  STAT(WEAPONS, player) = '0 0 0';
865 
866  return false;
867 }
868 
869 MUTATOR_HOOKFUNCTION(nb, PlayerPhysics_UpdateStats)
870 {
871  entity player = M_ARGV(0, entity);
872  // these automatically reset, no need to worry
873 
874  if(player.ballcarried)
875  STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_nexball_basketball_carrier_highspeed;
876 }
877 
878 MUTATOR_HOOKFUNCTION(nb, ForbidThrowCurrentWeapon)
879 {
880  //entity player = M_ARGV(0, entity);
881  entity wepent = M_ARGV(1, entity);
882 
883  return wepent.m_weapon == WEP_NEXBALL;
884 }
885 
886 MUTATOR_HOOKFUNCTION(nb, ForbidDropCurrentWeapon)
887 {
888  //entity player = M_ARGV(0, entity);
889  int wep = M_ARGV(1, int);
890 
891  return wep == WEP_MORTAR.m_id; // TODO: what is this for?
892 }
893 
894 MUTATOR_HOOKFUNCTION(nb, FilterItem)
895 {
896  entity item = M_ARGV(0, entity);
897 
898  if(Item_IsLoot(item))
899  if(item.weapon == WEP_NEXBALL.m_id)
900  return true;
901 
902  return false;
903 }
904 
905 MUTATOR_HOOKFUNCTION(nb, ItemTouch)
906 {
907  entity item = M_ARGV(0, entity);
908  entity toucher = M_ARGV(1, entity);
909 
910  if(item.weapon && toucher.ballcarried)
911  return MUT_ITEMTOUCH_RETURN; // no new weapons for you, mister!
912 
913  return MUT_ITEMTOUCH_CONTINUE;
914 }
915 
917 {
918  M_ARGV(1, string) = "nexball_team";
919  return true;
920 }
921 
922 MUTATOR_HOOKFUNCTION(nb, WantWeapon)
923 {
924  M_ARGV(1, float) = 0; // weapon is set a few lines later, apparently
925  return true;
926 }
927 
928 MUTATOR_HOOKFUNCTION(nb, DropSpecialItems)
929 {
930  entity frag_target = M_ARGV(0, entity);
931 
932  if(frag_target.ballcarried)
933  DropBall(frag_target.ballcarried, frag_target.origin, frag_target.velocity);
934 
935  return false;
936 }
937 
938 MUTATOR_HOOKFUNCTION(nb, SendWaypoint)
939 {
940  M_ARGV(2, int) &= ~0x80;
941 }
942 
944 {
946  if(g_nexball_meter_period <= 0)
947  g_nexball_meter_period = 2; // avoid division by zero etc. due to silly users
948  g_nexball_meter_period = rint(g_nexball_meter_period * 32) / 32; //Round to 1/32ths to send as a byte multiplied by 32
949 
950  // General settings
951  /*
952  CVTOV(g_nexball_football_boost_forward); //100
953  CVTOV(g_nexball_football_boost_up); //200
954  CVTOV(g_nexball_delay_idle); //10
955  CVTOV(g_nexball_football_physics); //0
956  */
957  radar_showenemies = autocvar_g_nexball_radar_showallplayers;
958 
959  GameRules_teams(true);
962 
963  InitializeEntity(NULL, nb_delayedinit, INITPRIO_GAMETYPE);
964 }
void InitBall(entity this)
Definition: sv_nexball.qc:235
float state
Definition: subs.qh:32
float autocvar_g_nexball_football_boost_up
Definition: sv_nexball.qh:38
#define WEPSET(id)
Definition: all.qh:37
#define APP_TEAM_NUM(num, prefix)
Definition: all.qh:85
#define GameRules_scoring(teams, spprio, stprio, fields)
Definition: sv_rules.qh:53
float autocvar_g_balance_nexball_secondary_speed
Definition: sv_nexball.qc:48
float MOVETYPE_NONE
Definition: progsdefs.qc:246
#define g_nexball
Definition: nexball.qh:33
spawnfunc(nexball_team)
Definition: sv_nexball.qc:451
void SpectateCopy(entity this, entity spectatee)
Definition: client.qc:1713
void DropBall(entity ball, vector org, vector vel)
Definition: sv_nexball.qc:202
entity sprite
Definition: sv_assault.qc:11
const int NUM_TEAM_2
Definition: teams.qh:19
void ball_restart(entity this)
Definition: sv_nexball.qc:88
void relocate_nexball(entity this)
Definition: sv_nexball.qc:112
float autocvar_g_nexball_football_boost_forward
Definition: sv_nexball.qh:37
#define SND(id)
Definition: all.qh:35
float g_nexball_meter_period
Definition: sv_nexball.qh:54
#define EXACTTRIGGER_INIT
Definition: common.qh:117
int NumTeams(int teams)
Definition: scores_rules.qc:17
string noise
Definition: progsdefs.qc:209
vector autocvar_g_nexball_viewmodel_offset
Definition: sv_nexball.qc:39
const float EF_FLAME
Definition: csprogsdefs.qc:302
void football_touch(entity this, entity toucher)
Definition: sv_nexball.qc:291
const float GOAL_OUT
Definition: sv_nexball.qh:34
const int SPRITERULE_DEFAULT
#define IS_CLIENT(v)
Definition: utils.qh:13
bool pushable
Definition: items.qc:15
int team
Definition: main.qh:157
vector Team_ColorRGB(int teamid)
Definition: teams.qh:76
#define EXACTTRIGGER_TOUCH(e, t)
Definition: common.qh:116
float TeamScore_AddToTeam(int t, float scorefield, float score)
Adds a score to the given team.
Definition: scores.qc:108
void GameRules_scoring_vip(entity player, bool value)
Mark a player as being &#39;important&#39; (flag carrier, ball carrier, etc)
Definition: sv_rules.qc:98
const int SFL_SORT_PRIO_SECONDARY
Scoring priority (NOTE: PRIMARY is used for fraglimit)
Definition: scores.qh:126
entity() spawn
const int NBM_FOOTBALL
Definition: sv_nexball.qc:54
const vector BALL_MAXS
Definition: sv_nexball.qh:26
void nb_Initialize()
Definition: sv_nexball.qc:943
const float ATTEN_NONE
Definition: sound.qh:27
#define GameRules_scoring_add(client, fld, value)
Definition: sv_rules.qh:78
bool autocvar_g_nexball_basketball_meter
Definition: sv_nexball.qc:20
void SpawnBall(entity this)
Definition: sv_nexball.qc:527
void GameRules_limit_score(int limit)
Definition: sv_rules.qc:34
float lastground
Definition: player.qh:61
bool autocvar_g_nexball_basketball_jumppad
Definition: sv_nexball.qc:16
const int SFL_SORT_PRIO_PRIMARY
Definition: scores.qh:127
float autocvar_g_nexball_basketball_meter_maxpower
Definition: sv_nexball.qc:21
void basketball_touch(entity this, entity toucher)
Definition: sv_nexball.qc:337
string noise2
Definition: progsdefs.qc:209
float autocvar_g_nexball_tackling
Definition: sv_nexball.qc:38
float ball_scale
Definition: sv_nexball.qh:48
void GiveBall(entity plyr, entity ball)
Definition: sv_nexball.qc:141
float DPCONTENTS_PLAYERCLIP
float bouncestop
float autocvar_g_nexball_delay_idle
Definition: sv_nexball.qh:40
float autocvar_g_balance_nexball_secondary_animtime
Definition: sv_nexball.qc:44
void nb_spawnteams()
Definition: sv_nexball.qc:471
origin
Definition: ent_cs.qc:114
string classname
Definition: csprogsdefs.qc:107
vector avelocity
Definition: csprogsdefs.qc:105
float MOVETYPE_BOUNCE
Definition: progsdefs.qc:256
float autocvar_g_nexball_basketball_meter_minpower
Definition: sv_nexball.qc:22
const int WS_READY
idle frame
Definition: weapon.qh:38
bool autocvar_g_nexball_playerclip_collisions
Definition: sv_nexball.qc:32
#define UNSET_ONGROUND(s)
Definition: movetypes.qh:18
void PlayerPreThink(entity this)
Definition: client.qc:2402
#define move_out_of_solid(e)
Definition: common.qh:110
float effects
Definition: csprogsdefs.qc:111
const float EF_NOSHADOW
Definition: csprogsdefs.qc:306
float spawnflags
Definition: progsdefs.qc:191
void SpawnGoal(entity this)
Definition: sv_nexball.qc:625
entity owner
Definition: main.qh:73
ClientDisconnect(this)
string model
Definition: csprogsdefs.qc:108
entity pusher
Definition: teleporters.qh:13
const int SFL_LOWER_IS_BETTER
Lower scores are better (e.g.
Definition: scores.qh:98
float autocvar_g_balance_nexball_secondary_force
Definition: sv_nexball.qc:45
void crosshair_trace(entity pl)
Definition: tracing.qc:508
entity trace_ent
Definition: csprogsdefs.qc:40
bool ball_customize(entity this, entity client)
Definition: sv_nexball.qc:716
const int NBM_BASKETBALL
Definition: sv_nexball.qc:55
int autocvar_g_nexball_goalleadlimit
Definition: sv_nexball.qc:13
entity teams
Definition: main.qh:44
float nb_droptime
Definition: sv_nexball.qh:52
float autocvar_g_nexball_safepass_turnrate
Definition: sv_nexball.qc:34
const int ST_NEXBALL_GOALS
Definition: sv_nexball.qc:67
vector absmax
Definition: csprogsdefs.qc:92
RES_HEALTH
Definition: ent_cs.qc:126
const float GOAL_FAULT
Definition: sv_nexball.qh:33
float autocvar_g_nexball_viewmodel_scale
Definition: sv_nexball.qc:37
float autocvar_g_nexball_safepass_maxdist
Definition: sv_nexball.qc:35
float autocvar_g_nexball_basketball_effects_default
Definition: sv_nexball.qh:43
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition: bits.qh:8
bool autocvar_g_nexball_radar_showallplayers
Definition: sv_nexball.qc:29
int autocvar_g_nexball_trail_color
Definition: sv_nexball.qc:31
float bouncefactor
float autocvar_g_balance_nexball_primary_refire
Definition: sv_nexball.qc:42
float lifetime
Definition: powerups.qc:23
entity enemy
Definition: sv_ctf.qh:143
bool autocvar_g_nexball_sound_bounce
Definition: sv_nexball.qc:30
float cnt
Definition: powerups.qc:24
float autocvar_g_balance_nexball_primary_speed
Definition: sv_nexball.qc:43
const float BALL_FOOT
Definition: sv_nexball.qh:28
void GameRules_teams(bool value)
Definition: sv_rules.qc:6
float autocvar_g_balance_nexball_secondary_lifetime
Definition: sv_nexball.qc:46
#define LOG_INFOF(...)
Definition: log.qh:71
vector v_up
Definition: csprogsdefs.qc:31
entity nb_dropper
Definition: sv_nexball.qh:51
float autocvar_g_nexball_delay_collect
Definition: sv_nexball.qc:23
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 f1 s1 strcat(_("Level %s: "), "^BG%s\3\, _("^BGPress ^F2%s^BG to enter the game"))
const int MAX_WEAPONSLOTS
Definition: weapon.qh:13
const int CH_TRIGGER
Definition: sound.qh:12
float autocvar_g_nexball_basketball_bouncefactor
Definition: sv_nexball.qc:17
string noise1
Definition: progsdefs.qc:209
void nb_DropBall(entity player)
Definition: sv_nexball.qc:743
#define NULL
Definition: post.qh:17
bool autocvar_g_nexball_football_jumppad
Definition: sv_nexball.qc:26
float DPCONTENTS_SOLID
float autocvar_g_nexball_delay_goal
Definition: sv_nexball.qc:24
bool nb_Goal_Customize(entity this, entity client)
Definition: sv_nexball.qc:616
const float VOL_BASE
Definition: sound.qh:36
void nb_spawnteam(string teamname, float teamcolor)
Definition: sv_nexball.qc:461
void DropOwner(entity this)
Definition: sv_nexball.qc:131
void GameLogEcho(string s)
Definition: gamelog.qc:12
#define SAME_TEAM(a, b)
Definition: teams.qh:239
#define M_ARGV(x, type)
Definition: events.qh:17
#define IS_DEAD(s)
Definition: utils.qh:26
float glow_color
const float ATTEN_NORM
Definition: sound.qh:30
float nextthink
Definition: csprogsdefs.qc:121
float autocvar_g_nexball_basketball_carrier_highspeed
Definition: sv_nexball.qc:19
float scale
Definition: projectile.qc:14
const float GOAL_TOUCHPLAYER
Definition: sv_nexball.qh:31
vector(float skel, float bonenum) _skel_get_boneabs_hidden
#define IS_VEHICLE(v)
Definition: utils.qh:22
const int NUM_TEAM_4
Definition: teams.qh:21
float glow_trail
void nexball_setstatus(entity this)
Definition: sv_nexball.qc:95
MUTATOR_HOOKFUNCTION(nb, ClientDisconnect)
Definition: sv_nexball.qc:749
float autocvar_g_balance_nexball_secondary_refire
Definition: sv_nexball.qc:47
float MOVETYPE_NOCLIP
Definition: progsdefs.qc:254
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
Definition: cl_resources.qc:10
const float SOLID_BSP
Definition: csprogsdefs.qc:248
float DPCONTENTS_BODY
void InitializeEntity(entity e, void(entity this) func, int order)
Definition: world.qc:2146
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition: vector.qh:8
#define LOG_TRACE(...)
Definition: log.qh:81
const int NBM_NONE
Definition: sv_nexball.qc:53
const float BALL_EFFECTMASK
Definition: sv_nexball.qh:24
void nb_ScoreRules(int teams)
Definition: sv_nexball.qc:68
const float BALL_BASKET
Definition: sv_nexball.qh:29
float balls
Definition: sv_nexball.qh:47
float autocvar_g_nexball_basketball_bouncestop
Definition: sv_nexball.qc:18
#define _sound(e, c, s, v, a)
Definition: sound.qh:50
void ResetBall(entity this)
Definition: sv_nexball.qc:255
const float SOLID_TRIGGER
Definition: csprogsdefs.qc:245
bool W_SwitchWeapon(entity this, Weapon w,.entity weaponentity)
Definition: selection.qc:272
void GameRules_limit_lead(int limit)
Definition: sv_rules.qc:44
vector v_right
Definition: csprogsdefs.qc:31
float OtherTeam(float t)
Definition: sv_nexball.qc:58
entity weaponentities[MAX_WEAPONSLOTS]
Definition: weapon.qh:14
#define new_pure(class)
purely logical entities (.origin doesn&#39;t work)
Definition: oo.qh:62
setorigin(ent, v)
bool Item_IsLoot(entity item)
Returns whether the item is loot.
Definition: spawning.qc:121
float dphitcontentsmask
void LogNB(string mode, entity actor)
Definition: sv_nexball.qc:77
#define setthink(e, f)
float autocvar_g_nexball_basketball_delay_hold
Definition: sv_nexball.qh:41
void nb_delayedinit(entity this)
Definition: sv_nexball.qc:515
float nb_teams
Definition: sv_nexball.qh:49
const vector BALL_MINS
Definition: sv_nexball.qh:25
const int NUM_TEAM_1
Definition: teams.qh:18
float trace_startsolid
Definition: csprogsdefs.qc:35
entity TeamBalance_CheckAllowedTeams(entity for_whom)
Checks whether the player can join teams according to global configuration and mutator settings...
Definition: teamplay.qc:487
vector absmin
Definition: csprogsdefs.qc:92
bool autocvar_sv_eventlog
Definition: gamelog.qh:3
fields which are explicitly/manually set are marked with "M", fields set automatically are marked wit...
Definition: weapon.qh:41
float autocvar_g_nexball_meter_period
Definition: sv_nexball.qh:45
float time
Definition: csprogsdefs.qc:16
vector velocity
Definition: csprogsdefs.qc:103
float nexball_mode
Definition: sv_nexball.qc:56
float autocvar_g_balance_nexball_primary_animtime
Definition: sv_nexball.qc:41
#define Team_ColoredFullName(teamid)
Definition: teams.qh:230
float autocvar_g_nexball_delay_start
Definition: sv_nexball.qc:25
#define makevectors
Definition: post.qh:21
float autocvar_g_nexball_goallimit
Definition: sv_nexball.qc:14
bool Team_IsValidTeam(int team_num)
Returns whether team value is valid.
Definition: teams.qh:133
void set_movetype(entity this, int mt)
float autocvar_g_nexball_football_bouncefactor
Definition: sv_nexball.qc:27
float MOVETYPE_FLY
Definition: progsdefs.qc:251
#define IS_PLAYER(v)
Definition: utils.qh:9
float autocvar_g_nexball_football_physics
Definition: sv_nexball.qh:39
float autocvar_g_nexball_safepass_holdtime
Definition: sv_nexball.qc:36
float EF_LOWPRECISION
var void func_null()
float autocvar_g_nexball_football_bouncestop
Definition: sv_nexball.qc:28
const int NUM_TEAM_3
Definition: teams.qh:20
entity ballcarried
Definition: sv_nexball.qc:11
vector v_forward
Definition: csprogsdefs.qc:31
float solid
Definition: csprogsdefs.qc:99
void GoalTouch(entity this, entity toucher)
Definition: sv_nexball.qc:359
float autocvar_g_nexball_basketball_delay_hold_forteam
Definition: sv_nexball.qh:42