]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'Mario/item_wepammo_fix' into 'master'
authorMario <zacjardine@y7mail.com>
Fri, 7 Sep 2018 22:48:19 +0000 (22:48 +0000)
committerMario <zacjardine@y7mail.com>
Fri, 7 Sep 2018 22:48:19 +0000 (22:48 +0000)
Merge branch Mario/item_wepammo_fix (S merge request)

Closes #1920

See merge request xonotic/xonotic-data.pk3dir!587

24 files changed:
.gitlab-ci.yml
commands.cfg
notifications.cfg
qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qc
qcsrc/common/impulses/all.qh
qcsrc/common/minigames/sv_minigames.qc
qcsrc/common/notifications/all.inc
qcsrc/common/physics/player.qc
qcsrc/common/viewloc.qc
qcsrc/server/autocvars.qh
qcsrc/server/bot/default/waypoints.qc
qcsrc/server/client.qc
qcsrc/server/client.qh
qcsrc/server/clientkill.qc
qcsrc/server/command/cmd.qc
qcsrc/server/command/sv_cmd.qc
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/impulse.qc
qcsrc/server/player.qc
qcsrc/server/teamplay.qc
qcsrc/server/teamplay.qh
xonotic-client.cfg
xonotic-server.cfg

index a035335d536c951a4114dcb18436161a52af3ee2..9423d5d5dc081806a1dd2887200972f5ae5de2cd 100644 (file)
@@ -29,7 +29,7 @@ test_sv_game:
     - wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
     - wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
     - make
-    - EXPECT=f2d3077e76dd3519ed8f2ad80a337b5a
+    - EXPECT=dc1bc3ac45188576a9651c6b7a1535a8
     - HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
       | tee /dev/stderr
       | grep '^:'
index 3e5e78e0ef5d5581404ff021824f3bb741013776..0f2e5689253e3ef06acfc3c4b0cef282e7da7419 100644 (file)
@@ -175,6 +175,7 @@ alias spectate             "qc_cmd_cmd    spectate             ${* ?}" // Become
 alias suggestmap           "qc_cmd_cmd    suggestmap           ${* ?}" // Suggest a map to the mapvote at match end
 //alias tell               "qc_cmd_cmd    tell                 ${* ?}" // Send a message directly to a player
 alias voice                "qc_cmd_cmd    voice                ${* ?}" // Send voice message via sound
+alias wpeditor             "qc_cmd_cmd    wpeditor             ${* ?}" // Waypoint editor commands
 
 // other aliases for client-to-server commands
 alias autoswitch "set cl_autoswitch ${1 ?} ; cmd autoswitch ${1 ?}" // todo
index 143b5c0e25d71a270007f14e8864b6a6ad99fd9d..dc50883e0df2b5b0bfcc10211b55cddc32b705df 100644 (file)
@@ -261,7 +261,7 @@ seta notification_INFO_POWERUP_STRENGTH "1" "0 = off, 1 = print to console, 2 =
 seta notification_INFO_QUIT_DISCONNECT "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_QUIT_KICK_IDLING "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_QUIT_KICK_SPECTATING "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
-seta notification_INFO_QUIT_SPECTATE "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
+seta notification_INFO_QUIT_SPECTATE "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_RACE_ABANDONED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_RACE_FAIL_RANKED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_RACE_FAIL_UNRANKED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
@@ -281,7 +281,7 @@ seta notification_INFO_SUPERSPEC_MISSING_UID "2" "0 = off, 1 = print to console,
 seta notification_INFO_SUPERWEAPON_PICKUP "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_TEAMCHANGE_LARGERTEAM "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_TEAMCHANGE_NOTALLOWED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
-seta notification_INFO_VERSION_BETA "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
+seta notification_INFO_VERSION_BETA "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_VERSION_OLD "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_VERSION_OUTDATED "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_WATERMARK "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
index cce2ffb747076bf03485eb9fbee059037de3a3dc..f8456734caa0f528e93b7b0b6b50cfdbfae3ef75 100644 (file)
@@ -52,12 +52,6 @@ bool kh_no_radar_circles;
 .float siren_time;  //  time delay the siren
 //.float stuff_time;  //  time delay to stuffcmd a cvar
 
-int kh_keystatus[17];
-//kh_keystatus[0] = status of dropped keys, kh_keystatus[1 - 16] = player #
-//replace 17 with cvar("maxplayers") or similar !!!!!!!!!
-//for(i = 0; i < maxplayers; ++i)
-//     kh_keystatus[i] = "0";
-
 int kh_Team_ByID(int t)
 {
        if(t == 0) return NUM_TEAM_1;
@@ -340,18 +334,6 @@ void kh_Key_AssignTo(entity key, entity player)  // runs every time a key is pic
                if(key.kh_next)
                        key.kh_next.kh_prev = key;
 
-               float i;
-               i = kh_keystatus[key.owner.playerid];
-                       if(key.netname == "^1red key")
-                               i += 1;
-                       if(key.netname == "^4blue key")
-                               i += 2;
-                       if(key.netname == "^3yellow key")
-                               i += 4;
-                       if(key.netname == "^6pink key")
-                               i += 8;
-               kh_keystatus[key.owner.playerid] = i;
-
                kh_Key_Attach(key);
 
                if(key.kh_next == NULL)
index 45a8f1323b9f142bd02b5eae8ad61202a273ae63..8bd0c41dab29ec33e9f4f2877f00638e9578eb45 100644 (file)
@@ -186,20 +186,6 @@ LEGACY_IMPULSE(g_waypointsprite_clear_personal, 47, "waypoint_clear_personal")
 REGISTER_IMPULSE(waypoint_clear, 48)
 LEGACY_IMPULSE(g_waypointsprite_clear, 48, "waypoint_clear")
 
-REGISTER_IMPULSE(navwaypoint_spawn, 103)
-LEGACY_IMPULSE(g_waypointeditor_spawn, 103, "navwaypoint_spawn")
-
-REGISTER_IMPULSE(navwaypoint_remove, 104)
-LEGACY_IMPULSE(g_waypointeditor_remove, 104, "navwaypoint_remove")
-
-REGISTER_IMPULSE(navwaypoint_relink, 105)
-LEGACY_IMPULSE(g_waypointeditor_relinkall, 105, "navwaypoint_relink")
-
-REGISTER_IMPULSE(navwaypoint_save, 106)
-LEGACY_IMPULSE(g_waypointeditor_saveall, 106, "navwaypoint_save")
-
-REGISTER_IMPULSE(navwaypoint_unreachable, 107)
-LEGACY_IMPULSE(g_waypointeditor_unreachable, 107, "navwaypoint_unreachable")
 
 #define CHIMPULSE(id, n) _CHIMPULSE(CHIMPULSE_##id, n)
 #define _CHIMPULSE(id, n) \
index 5c6af26615dc5f71363dfb9286eab23da294a859..af74e6a5a6c601902c1da414b0a24d8d46f356b4 100644 (file)
@@ -9,7 +9,7 @@ void player_clear_minigame(entity player)
                set_movetype(player, MOVETYPE_WALK);
        else
                set_movetype(player, MOVETYPE_FLY_WORLDONLY);
-       player.team_forced = 0;
+       Player_SetForcedTeamIndex(player, TEAM_FORCE_DEFAULT);
 }
 
 void minigame_rmplayer(entity minigame_session, entity player)
@@ -150,7 +150,7 @@ int minigame_addplayer(entity minigame_session, entity player)
                        PutObserverInServer(player);
                }
                if ( autocvar_sv_minigames_observer == 2 )
-                       player.team_forced = -1;
+                       Player_SetForcedTeamIndex(player, TEAM_FORCE_SPECTATOR);
 
                minigame_resend(minigame_session);
        }
index a01718afdf2ecd5115c2496f040dc78af2c83295..7cb218412f8897dec276c561b06ce5bbd0b188bf 100644 (file)
     MSG_INFO_NOTIF(QUIT_KICK_IDLING,                        N_CHATCON,  1, 0, "s1", "",         "",             _("^BG%s^F3 was kicked for idling"), "")
     MSG_INFO_NOTIF(QUIT_KICK_SPECTATING,                    N_CONSOLE,  0, 0, "", "",           "",             _("^F2You were kicked from the server because you are a spectator and spectators aren't allowed at the moment."), "")
     MSG_INFO_NOTIF(QUIT_KICK_TEAMKILL,                      N_CHATCON,  1, 0, "s1", "",         "",             _("^BG%s^F3 was kicked for excessive teamkilling"), "")
-    MSG_INFO_NOTIF(QUIT_SPECTATE,                           N_CONSOLE,  1, 0, "s1", "",         "",             _("^BG%s^F3 is now spectating"), "")
+    MSG_INFO_NOTIF(QUIT_SPECTATE,                           N_CHATCON,  1, 0, "s1", "",         "",             _("^BG%s^F3 is now spectating"), "")
 
     MSG_INFO_NOTIF(RACE_ABANDONED,                          N_CONSOLE,  1, 0, "s1", "",                                                                     "",                         _("^BG%s^BG has abandoned the race"), "")
     MSG_INFO_NOTIF(RACE_FAIL_RANKED,                        N_CONSOLE,  1, 3, "s1 race_col f1ord race_col f3race_time race_diff", "s1 f3race_time",         "race_newfail",             _("^BG%s^BG couldn't break their %s%s^BG place record of %s%s %s"), "")
     MSG_INFO_NOTIF(TEAMCHANGE_LARGERTEAM,                   N_CONSOLE,  0, 0, "", "",           "",                     _("^BGYou cannot change to a larger team"), "")
     MSG_INFO_NOTIF(TEAMCHANGE_NOTALLOWED,                   N_CONSOLE,  0, 0, "", "",           "",                     _("^BGYou are not allowed to change teams"), "")
 
-    MSG_INFO_NOTIF(VERSION_BETA,                            N_CHATCON,  2, 0, "s1 s2", "",      "",                     _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s (beta)^BG, you have ^F2Xonotic %s"), "")
+    MSG_INFO_NOTIF(VERSION_BETA,                            N_CONSOLE,  2, 0, "s1 s2", "",      "",                     _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s (beta)^BG, you have ^F2Xonotic %s"), "")
     MSG_INFO_NOTIF(VERSION_OLD,                             N_CHATCON,  2, 0, "s1 s2", "",      "",                     _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s^BG, you have ^F2Xonotic %s"), "")
     MSG_INFO_NOTIF(VERSION_OUTDATED,                        N_CHATCON,  2, 0, "s1 s2", "",      "",                     _("^F4NOTE: ^F1Xonotic %s^BG is out, and you still have ^F2Xonotic %s^BG - get the update from ^F3http://www.xonotic.org/^BG!"), "")
 
index f6ab29715a298c07a7bc9c317d0a69d4d9636298..572bee349f205ca81caac2d23d0ff6da1d839696 100644 (file)
@@ -119,6 +119,8 @@ void PM_ClientMovement_UpdateStatus(entity this)
                }
        }
        bool do_crouch = PHYS_INPUT_BUTTON_CROUCH(this);
+       if(this.viewloc && !(this.viewloc.spawnflags & VIEWLOC_FREEMOVE) && PHYS_CS(this).movement.x < 0)
+               do_crouch = true;
        if (have_hook) {
                do_crouch = false;
        //} else if (this.waterlevel >= WATERLEVEL_SWIMMING) {
index f2dab7bfd22cd7c8620f3b69e7c10d9f37ece242..e1be43af0d41f9826993c9b9a7e1dceb247b9e16 100644 (file)
@@ -48,7 +48,7 @@ void viewloc_PlayerPhysics(entity this)
                        if(PHYS_CS(this).movement_x > 0) // right
                                this.angles_y = forward.y;
                }
-
+       #if 0
                //if(!PHYS_INPUT_BUTTON_CROUCH(this) && !IS_DUCKED(this))
                if(!(this.viewloc.spawnflags & VIEWLOC_FREEMOVE))
                {
@@ -65,6 +65,7 @@ void viewloc_PlayerPhysics(entity this)
                        //else { input_buttons &= ~16; this.flags &= ~FL_DUCKED; }
 #endif
                }
+       #endif
        }
 }
 
index 7d73a73a8b7e65086756c1e261a1b9edcacd3b90..8a19f06c6e19bb68026cc95a8bba27fcfe97eeb7 100644 (file)
@@ -88,9 +88,6 @@ float autocvar_g_balance_powerup_strength_selfforce;
 //float autocvar_g_balance_powerup_strength_time;
 float autocvar_g_balance_superweapons_time;
 float autocvar_g_balance_selfdamagepercent;
-bool autocvar_g_balance_teams;
-bool autocvar_g_balance_teams_prevent_imbalance;
-//float autocvar_g_balance_teams_scorefactor;
 float autocvar_g_ballistics_density_corpse;
 float autocvar_g_ballistics_density_player;
 float autocvar_g_ballistics_mindistance;
@@ -109,7 +106,6 @@ bool autocvar_g_campaign;
 #define autocvar_g_campaign_forceteam cvar("g_campaign_forceteam")
 int autocvar_g_campaign_skill;
 int autocvar_g_casings;
-bool autocvar_g_changeteam_banned;
 float autocvar_g_chat_flood_burst;
 float autocvar_g_chat_flood_burst_team;
 float autocvar_g_chat_flood_burst_tell;
@@ -124,11 +120,7 @@ int autocvar_g_chat_nospectators;
 bool autocvar_g_chat_teamcolors;
 bool autocvar_g_chat_tellprivacy;
 bool autocvar_g_forced_respawn;
-string autocvar_g_forced_team_blue;
-string autocvar_g_forced_team_otherwise;
-string autocvar_g_forced_team_pink;
-string autocvar_g_forced_team_red;
-string autocvar_g_forced_team_yellow;
+string autocvar_g_forced_team_otherwise; // TODO: Move to teamplay.qc
 #define autocvar_g_friendlyfire cvar("g_friendlyfire")
 #define autocvar_g_friendlyfire_virtual cvar("g_friendlyfire_virtual")
 #define autocvar_g_friendlyfire_virtual_force cvar("g_friendlyfire_virtual_force")
@@ -366,8 +358,6 @@ bool autocvar_sv_vote_gamestart;
 string autocvar_sv_weaponstats_file;
 float autocvar_sv_gibhealth;
 float autocvar_sys_ticrate;
-bool autocvar_teamplay_lockonrestart;
-int autocvar_teamplay_mode;
 #define autocvar_timelimit cvar("timelimit")
 #define autocvar_timelimit_override cvar("timelimit_override")
 float autocvar_timelimit_increment;
index f75829aecd8595d8a69087e9a035708d53cf14ef..2ea30b50fe31ab5f742df66155f60c327ce3e073 100644 (file)
@@ -128,7 +128,7 @@ void waypoint_unreachable(entity pl)
        if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", j);
 }
 
-vector waypoint_getSymmetricalOrigin(vector org, int ctf_flags)
+vector waypoint_getSymmetricalPoint(vector org, int ctf_flags)
 {
        vector new_org = org;
        if (fabs(autocvar_g_waypointeditor_symmetrical) == 1)
@@ -281,7 +281,7 @@ void waypoint_spawn_fromeditor(entity pl)
        bprint(strcat("Waypoint spawned at ", vtos(e.origin), "\n"));
        if(sym)
        {
-               org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
+               org = waypoint_getSymmetricalPoint(e.origin, ctf_flags);
                if (vdist(org - pl.origin, >, 32))
                {
                        if(wp_num > 2)
@@ -329,7 +329,7 @@ void waypoint_remove_fromeditor(entity pl)
        entity wp_sym = NULL;
        if (sym)
        {
-               vector org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
+               vector org = waypoint_getSymmetricalPoint(e.origin, ctf_flags);
                FOREACH_ENTITY_CLASS("waypoint", !(it.wpflags & WAYPOINTFLAG_GENERATED), {
                        if(vdist(org - it.origin, <, 3))
                        {
@@ -1016,6 +1016,7 @@ void waypoint_save_links()
                        entity link = waypoint_get_link(it, j);
                        if(link)
                        {
+                               // NOTE: vtos rounds vector components to 1 decimal place
                                string s = strcat(vtos(it.origin), "*", vtos(link.origin), "\n");
                                fputs(file, s);
                                ++c;
@@ -1088,6 +1089,7 @@ void waypoint_saveall()
                        continue;
 
                string s;
+               // NOTE: vtos rounds vector components to 1 decimal place
                s = strcat(vtos(it.origin + it.mins), "\n");
                s = strcat(s, vtos(it.origin + it.maxs));
                s = strcat(s, "\n");
index 85514bdf9e0b02c553e3def6a750ad84b5043e49..8e6a26961b02c6b845103254f56222496d24a10e 100644 (file)
@@ -285,25 +285,11 @@ void PutObserverInServer(entity this)
 
        WaypointSprite_PlayerDead(this);
 
-       if (mutator_returnvalue) {
-           // mutator prevents resetting teams+score
-       } else {
-               Player_SetTeamIndex(this, -1);
-               this.frags = FRAGS_SPECTATOR;
-        PlayerScore_Clear(this);  // clear scores when needed
-    }
-
        if (CS(this).killcount != FRAGS_SPECTATOR)
        {
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, this.netname);
                if(!game_stopped)
                if(autocvar_g_chat_nospectators == 1 || (!warmup_stage && autocvar_g_chat_nospectators == 2))
                        Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS);
-
-               if(!CS(this).just_joined)
-                       LogTeamchange(this.playerid, -1, TEAM_CHANGE_SPECTATOR);
-               else
-                       CS(this).just_joined = false;
        }
 
        accuracy_resend(this);
@@ -388,6 +374,16 @@ void PutObserverInServer(entity this)
                if(axh.owner == this && axh != NULL && !wasfreed(axh))
                        delete(axh);
        }
+       
+       if (mutator_returnvalue)
+       {
+               // mutator prevents resetting teams+score
+       }
+       else
+       {
+               SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR);
+               this.frags = FRAGS_SPECTATOR;
+    }
 }
 
 int player_getspecies(entity this)
@@ -1070,38 +1066,7 @@ void ClientConnect(entity this)
 
        bot_clientconnect(this);
 
-       // identify the right forced team
-       if (autocvar_g_campaign)
-       {
-               if (IS_REAL_CLIENT(this)) // only players, not bots
-               {
-                       switch (autocvar_g_campaign_forceteam)
-                       {
-                               case 1: this.team_forced = NUM_TEAM_1; break;
-                               case 2: this.team_forced = NUM_TEAM_2; break;
-                               case 3: this.team_forced = NUM_TEAM_3; break;
-                               case 4: this.team_forced = NUM_TEAM_4; break;
-                               default: this.team_forced = 0;
-                       }
-               }
-       }
-       else if (PlayerInList(this, autocvar_g_forced_team_red))    this.team_forced = NUM_TEAM_1;
-       else if (PlayerInList(this, autocvar_g_forced_team_blue))   this.team_forced = NUM_TEAM_2;
-       else if (PlayerInList(this, autocvar_g_forced_team_yellow)) this.team_forced = NUM_TEAM_3;
-       else if (PlayerInList(this, autocvar_g_forced_team_pink))   this.team_forced = NUM_TEAM_4;
-       else switch (autocvar_g_forced_team_otherwise)
-       {
-               default: this.team_forced = 0; break;
-               case "red": this.team_forced = NUM_TEAM_1; break;
-               case "blue": this.team_forced = NUM_TEAM_2; break;
-               case "yellow": this.team_forced = NUM_TEAM_3; break;
-               case "pink": this.team_forced = NUM_TEAM_4; break;
-               case "spectate":
-               case "spectator":
-                       this.team_forced = -1;
-                       break;
-       }
-       if (!teamplay && this.team_forced > 0) this.team_forced = 0;
+       Player_DetermineForcedTeam(this);
 
        TRANSMUTE(Observer, this);
 
@@ -1647,7 +1612,7 @@ void GetPressedKeys(entity this)
        keys = BITSET(keys, KEY_LEFT,           CS(this).movement.y < 0);
 
        keys = BITSET(keys, KEY_JUMP,           PHYS_INPUT_BUTTON_JUMP(this));
-       keys = BITSET(keys, KEY_CROUCH,         PHYS_INPUT_BUTTON_CROUCH(this));
+       keys = BITSET(keys, KEY_CROUCH,         IS_DUCKED(this)); // workaround: player can't un-crouch until their path is clear, so we keep the button held here
        keys = BITSET(keys, KEY_ATCK,           PHYS_INPUT_BUTTON_ATCK(this));
        keys = BITSET(keys, KEY_ATCK2,          PHYS_INPUT_BUTTON_ATCK2(this));
        CS(this).pressedkeys = keys; // store for other users
@@ -1905,7 +1870,7 @@ void ShowRespawnCountdown(entity this)
 .bool team_selected;
 bool ShowTeamSelection(entity this)
 {
-       if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || this.team_forced > 0)
+       if (!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || Player_HasRealForcedTeam(this))
                return false;
        stuffcmd(this, "menu_showteamselect\n");
        return true;
@@ -1928,7 +1893,6 @@ void Join(entity this)
        if(IS_PLAYER(this))
        if(teamplay && this.team != -1)
        {
-               //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_PLAY_TEAM), this.netname);
        }
        else
                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
@@ -1953,7 +1917,7 @@ int nJoinAllowed(entity this, entity ignore)
                        return 0;
        }
 
-       if(this && this.team_forced < 0)
+       if(this && (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
                return 0; // forced spectators can never join
 
        // TODO simplify this
@@ -2505,7 +2469,7 @@ void PlayerPreThink (entity this)
                // don't do this in ClientConnect
                // many things can go wrong if a client is spawned as player on connection
                if (MUTATOR_CALLHOOK(AutoJoinOnConnection, this)
-                       || (!(autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0)
+                       || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
                                && (!teamplay || autocvar_g_balance_teams)))
                {
                        campaign_bots_may_start = true;
index efb61a436d8db7021028b58c8b60424b6bdfb67a..099fac9f541f5ae71358f2fad6f2e0894a978e14 100644 (file)
@@ -226,6 +226,8 @@ METHOD(Client, m_unwind, bool(Client this))
     return false;
 }
 
+bool PlayerInList(entity player, string list);
+
 /// \brief Print the string to the client's chat.
 /// \param[in] client Client to print to.
 /// \param[in] text Text to print.
index 0bb761483b19bb8433c8bf968dad5fa3679b8f19..8d7293bb066214c1f5e47535be2ab93453f47c46 100644 (file)
@@ -22,7 +22,10 @@ void ClientKill_Now_TeamChange(entity this)
                PutObserverInServer(this);
        }
        else
-               SV_ChangeTeam(this, this.killindicator_teamchange - 1);
+       {
+               Player_SetTeamIndexChecked(this, Team_TeamToIndex(
+                       this.killindicator_teamchange));
+       }
        this.killindicator_teamchange = 0;
 }
 
index 353d81395b0f568acb931cc517219bc0c8d0e139..915fea7ee0ca2ca02ffb14d9341d539b4b4a7d14 100644 (file)
@@ -8,6 +8,8 @@
 #include "common.qh"
 #include "vote.qh"
 
+#include "../bot/api.qh"
+
 #include "../campaign.qh"
 #include "../cheats.qh"
 #include "../client.qh"
@@ -119,7 +121,7 @@ void ClientCommand_clientversion(entity caller, int request, int argc)  // inter
                                        {
                                                // JoinBestTeam(caller, false, true);
                                        }
-                                       else if (teamplay && !autocvar_sv_spectate && !(caller.team_forced > 0))
+                                       else if (teamplay && !autocvar_sv_spectate && !(Player_GetForcedTeamIndex(caller) > 0))
                                        {
                                                TRANSMUTE(Observer, caller);  // really?
                                                stuffcmd(caller, "menu_showteamselect\n");
@@ -166,6 +168,61 @@ void ClientCommand_mv_getpicture(entity caller, int request, int argc)  // inter
        }
 }
 
+void ClientCommand_wpeditor(entity caller, int request, int argc)
+{
+       switch (request)
+       {
+               case CMD_REQUEST_COMMAND:
+               {
+                       if (!autocvar_g_waypointeditor)
+                       {
+                               sprint(caller, "ERROR: this command works only if the waypoint editor is on\n");
+                               return;
+                       }
+
+                       if (argv(1) != "")
+                       {
+                               if (argv(1) == "spawn")
+                               {
+                                       if (!IS_PLAYER(caller))
+                                               sprint(caller, "ERROR: this command works only if you are player\n");
+                                       else
+                                               waypoint_spawn_fromeditor(caller);
+                               }
+                               else if (argv(1) == "remove")
+                               {
+                                       if (!IS_PLAYER(caller))
+                                               sprint(caller, "ERROR: this command works only if you are player\n");
+                                       else
+                                               waypoint_remove_fromeditor(caller);
+                               }
+                               else if (argv(1) == "unreachable")
+                               {
+                                       if (!IS_PLAYER(caller))
+                                               sprint(caller, "ERROR: this command works only if you are player\n");
+                                       else
+                                               waypoint_unreachable(caller);
+                               }
+                               else if (argv(1) == "saveall")
+                                       waypoint_saveall();
+                               else if (argv(1) == "relinkall")
+                                       waypoint_schedulerelinkall();
+
+                               return;
+                       }
+               }
+
+               default:
+                       sprint(caller, "Incorrect parameters for ^2wpeditor^7\n");
+               case CMD_REQUEST_USAGE:
+               {
+                       sprint(caller, "\nUsage:^3 cmd wpeditor action\n");
+                       sprint(caller, "  Where 'action' can be: spawn, remove, unreachable, saveall, relinkall\n");
+                       return;
+               }
+       }
+}
+
 void ClientCommand_join(entity caller, int request)
 {
        switch (request)
@@ -306,19 +363,7 @@ void ClientCommand_say_team(entity caller, int request, int argc, string command
                case CMD_REQUEST_COMMAND:
                {
                        if (argc >= 2)
-                       {
-                               string _classname = string_null;
-                               if (!IS_PLAYER(caller) && caller.caplayer)
-                               {
-                                       // CA: make work team chat for killed players
-                                       _classname = caller.classname;
-                                       caller.classname = STR_PLAYER;
-                               }
-
                                Say(caller, true, NULL, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
-                               if (_classname)
-                                       caller.classname = _classname;
-                       }
                        return;  // never fall through to usage
                }
 
@@ -352,7 +397,7 @@ void ClientCommand_selectteam(entity caller, int request, int argc)
                                sprint(caller, "^7selectteam can only be used in teamgames\n");
                                return;
                        }
-                       if (caller.team_forced > 0)
+                       if (Player_GetForcedTeamIndex(caller) > 0)
                        {
                                sprint(caller, "^7selectteam can not be used as your team is forced\n");
                                return;
@@ -698,6 +743,7 @@ void ClientCommand_(entity caller, int request)
        CLIENT_COMMAND("suggestmap", ClientCommand_suggestmap(ent, request, arguments), "Suggest a map to the mapvote at match end") \
        CLIENT_COMMAND("tell", ClientCommand_tell(ent, request, arguments, command), "Send a message directly to a player") \
        CLIENT_COMMAND("voice", ClientCommand_voice(ent, request, arguments, command), "Send voice message via sound") \
+       CLIENT_COMMAND("wpeditor", ClientCommand_wpeditor(ent, request, arguments), "Waypoint editor commands") \
        /* nothing */
 
 void ClientCommand_macro_help(entity caller)
@@ -772,6 +818,7 @@ void SV_ParseClientCommand(entity this, string command)
                case "begin": break;                               // handled by engine in host_cmd.c
                case "download": break;                            // handled by engine in cl_parse.c
                case "mv_getpicture": break;                       // handled by server in this file
+               case "wpeditor": break;                            // handled by server in this file
                case "pause": break;                               // handled by engine in host_cmd.c
                case "prespawn": break;                            // handled by engine in host_cmd.c
                case "sentcvar": break;                            // handled by server in this file
index c917874c358f5d345cd740fcffa194033f9f54c7..c3e5bf9015293c712bb59f4e1d05c6c71f626804 100644 (file)
@@ -1052,8 +1052,8 @@ void GameCommand_moveplayer(int request, int argc)
                                                        {
                                                                // set up
                                                                float team_id;
-                                                               float save = client.team_forced;
-                                                               client.team_forced = 0;
+                                                               int save = Player_GetForcedTeamIndex(client);
+                                                               Player_SetForcedTeamIndex(client, TEAM_FORCE_DEFAULT);
 
                                                                // find the team to move the player to
                                                                team_id = Team_ColorToTeam(destination);
@@ -1073,7 +1073,7 @@ void GameCommand_moveplayer(int request, int argc)
                                                                {
                                                                        balance = TeamBalance_CheckAllowedTeams(client);
                                                                }
-                                                               client.team_forced = save;
+                                                               Player_SetForcedTeamIndex(client, save);
 
                                                                // Check to see if the destination team is even available
                                                                switch (team_id)
@@ -1130,7 +1130,7 @@ void GameCommand_moveplayer(int request, int argc)
                                                                }
 
                                                                // If so, lets continue and finally move the player
-                                                               client.team_forced = 0;
+                                                               Player_SetForcedTeamIndex(client, TEAM_FORCE_DEFAULT);
                                                                if (MoveToTeam(client, Team_TeamToIndex(team_id), 6))
                                                                {
                                                                        successful = strcat(successful, (successful ? ", " : ""), playername(client, false));
@@ -1312,7 +1312,7 @@ void GameCommand_shuffleteams(int request)
                        }
 
                        FOREACH_CLIENT(IS_PLAYER(it) || it.caplayer, {
-                               if (it.team_forced) {
+                               if (Player_HasRealForcedTeam(it)) {
                                        // we could theoretically assign forced players to their teams
                                        // and shuffle the rest to fill the empty spots but in practise
                                        // either all players or none are gonna have forced teams
index 173c2d5caec0dbb7b62093a9240aa661938ad9f6..58111fb12bcfea9482a8d89b6d0dbceaf31ab42e 100644 (file)
@@ -351,8 +351,6 @@ const int ACTIVE_TOGGLE     = 3;
 
 //float serverflags;
 
-.int team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator
-
 .bool player_blocked;
 
 .float revival_time; // time at which player was last revived
index a25ae5bec9f878d9ba5feca5a6bb2f7e7c55bae6..4964bf91e71014cea100bed1b76e5c784d8e91c4 100644 (file)
@@ -4,6 +4,7 @@
 #include "bot/api.qh"
 #include "g_hook.qh"
 #include <server/mutators/_mod.qh>
+#include "teamplay.qh"
 #include "scores.qh"
 #include "spawnpoints.qh"
 #include "../common/state.qh"
index 5d71b95afa829d84ba7885516b458a92d40a3dd2..cce5963990893d7a4178e8b7d5e0ddd7895b886e 100644 (file)
@@ -1,8 +1,6 @@
 #include "impulse.qh"
 #include "round_handler.qh"
 
-#include "bot/api.qh"
-
 #include "weapons/throwing.qh"
 #include "command/common.qh"
 #include "cheats.qh"
@@ -572,33 +570,3 @@ IMPULSE(waypoint_clear)
        }
        sprint(this, "all waypoints cleared\n");
 }
-
-IMPULSE(navwaypoint_spawn)
-{
-       if (!autocvar_g_waypointeditor) return;
-       waypoint_spawn_fromeditor(this);
-}
-
-IMPULSE(navwaypoint_remove)
-{
-       if (!autocvar_g_waypointeditor) return;
-       waypoint_remove_fromeditor(this);
-}
-
-IMPULSE(navwaypoint_relink)
-{
-       if (!autocvar_g_waypointeditor) return;
-       waypoint_schedulerelinkall();
-}
-
-IMPULSE(navwaypoint_save)
-{
-       if (!autocvar_g_waypointeditor) return;
-       waypoint_saveall();
-}
-
-IMPULSE(navwaypoint_unreachable)
-{
-       if (!autocvar_g_waypointeditor) return;
-       waypoint_unreachable(this);
-}
index 5d69c0aa1a4b88dde931bd63ec6f967dd525cdad..6c64f2feabed42452d713e933b50357d23a20488 100644 (file)
@@ -551,8 +551,8 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
                if(this.classname != "body")
                        Obituary (attacker, inflictor, this, deathtype, weaponentity);
 
-        // increment frag counter for used weapon type
-        Weapon w = DEATH_WEAPONOF(deathtype);
+               // increment frag counter for used weapon type
+               Weapon w = DEATH_WEAPONOF(deathtype);
                if(w != WEP_Null && accuracy_isgooddamage(attacker, this))
                        CS(attacker).accuracy.(accuracy_frags[w.m_id-1]) += 1;
 
@@ -686,13 +686,13 @@ bool PlayerHeal(entity targ, entity inflictor, float amount, float limit)
 int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol)
 {
        if (!teamsay && !privatesay && substring(msgin, 0, 1) == " ")
-        msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!)
+               msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!)
 
-    if(source)
+       if (source)
                msgin = formatmessage(source, msgin);
 
-    string colorstr;
-       if (!IS_PLAYER(source))
+       string colorstr;
+       if (!(IS_PLAYER(source) || source.caplayer))
                colorstr = "^0"; // black for spectators
        else if(teamplay)
                colorstr = Team_ColorCode(source.team);
@@ -705,10 +705,10 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
        if(game_stopped)
                teamsay = false;
 
-    if (!source) {
+       if (!source) {
                colorstr = "";
                teamsay = false;
-    }
+       }
 
        if(msgin != "")
                msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
@@ -724,18 +724,17 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
        }
        */
 
-    string namestr = "";
-    if (source)
-        namestr = playername(source, autocvar_g_chat_teamcolors);
+       string namestr = "";
+       if (source)
+               namestr = playername(source, autocvar_g_chat_teamcolors);
 
-    string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7";
+       string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7";
 
-    string msgstr, cmsgstr;
-    string privatemsgprefix = string_null;
-    int privatemsgprefixlen = 0;
-       if (msgin == "") {
-        msgstr = cmsgstr = "";
-       } else {
+       string msgstr = "", cmsgstr = "";
+       string privatemsgprefix = string_null;
+       int privatemsgprefixlen = 0;
+       if (msgin != "")
+       {
                if(privatesay)
                {
                        msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
@@ -767,10 +766,10 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
                                msgstr = strcat("\{1}^4* ", "^7", msgin);
                        }
                        else {
-                msgstr = "\{1}";
-                msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7");
-                msgstr = strcat(msgstr, msgin);
-            }
+                               msgstr = "\{1}";
+                               msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7");
+                               msgstr = strcat(msgstr, msgin);
+                       }
                        cmsgstr = "";
                }
                msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
@@ -853,7 +852,7 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
                        source.(flood_field) = flood = 0;
        }
 
-    string sourcemsgstr, sourcecmsgstr;
+       string sourcemsgstr, sourcecmsgstr;
        if(flood == 2) // cannot happen for empty msgstr
        {
                if(autocvar_g_chat_flood_notify_flooder)
@@ -874,7 +873,7 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
                sourcecmsgstr = cmsgstr;
        }
 
-       if (!privatesay && source && !IS_PLAYER(source))
+       if (!privatesay && source && !(IS_PLAYER(source) || source.caplayer))
        {
                if (!game_stopped)
                if (teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage))
@@ -888,7 +887,7 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
        if(privatesay)
                sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
 
-    int ret;
+       int ret;
        if(source && CS(source).muted)
        {
                // always fake the message
@@ -909,10 +908,10 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
                ret = 1;
        }
 
-       if (privatesay && source && !IS_PLAYER(source))
+       if (privatesay && source && !(IS_PLAYER(source) || source.caplayer))
        {
                if (!game_stopped)
-               if ((privatesay && IS_PLAYER(privatesay)) && ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage)))
+               if ((privatesay && (IS_PLAYER(privatesay) || privatesay.caplayer)) && ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage)))
                        ret = -1; // just hide the message completely
        }
 
@@ -942,7 +941,9 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
                {
                        sprint(source, sourcemsgstr);
                        dedicated_print(msgstr); // send to server console too
-                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && CS(it).active_minigame == CS(source).active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
+                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && CS(it).active_minigame == CS(source).active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+                               sprint(it, msgstr);
+                       });
                }
                else if(teamsay > 0) // team message, only sent to team mates
                {
@@ -950,7 +951,7 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
                        dedicated_print(msgstr); // send to server console too
                        if(sourcecmsgstr != "")
                                centerprint(source, sourcecmsgstr);
-                       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+                       FOREACH_CLIENT((IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
                                sprint(it, msgstr);
                                if(cmsgstr != "")
                                        centerprint(it, cmsgstr);
@@ -960,17 +961,21 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
                {
                        sprint(source, sourcemsgstr);
                        dedicated_print(msgstr); // send to server console too
-                       FOREACH_CLIENT(!IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
+                       FOREACH_CLIENT(!(IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+                               sprint(it, msgstr);
+                       });
                }
                else
                {
-            if (source) {
-                sprint(source, sourcemsgstr);
-                dedicated_print(msgstr); // send to server console too
-                MX_Say(strcat(playername(source, true), "^7: ", msgin));
-            }
-            FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
-        }
+                       if (source) {
+                               sprint(source, sourcemsgstr);
+                               dedicated_print(msgstr); // send to server console too
+                               MX_Say(strcat(playername(source, true), "^7: ", msgin));
+                       }
+                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+                               sprint(it, msgstr);
+                       });
+               }
        }
 
        return ret;
index 2c41ae18bdfc77b22113f5f36f40df8cb5d7ba23..abc5df1d726a0e730e2c55402690112ad2cd9fc4 100644 (file)
@@ -28,6 +28,8 @@ enum
 /// \brief Indicates that the player is not allowed to join a team.
 const int TEAM_NOT_ALLOWED = -1;
 
+.float team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator
+
 .int m_team_balance_state; ///< Holds the state of the team balance entity.
 .entity m_team_balance_team[NUM_TEAMS]; ///< ???
 
@@ -37,6 +39,11 @@ const int TEAM_NOT_ALLOWED = -1;
 .int m_num_players_alive; ///< Number of alive players in a team.
 .int m_num_control_points; ///< Number of control points owned by a team.
 
+string autocvar_g_forced_team_red;
+string autocvar_g_forced_team_blue;
+string autocvar_g_forced_team_yellow;
+string autocvar_g_forced_team_pink;
+
 entity g_team_entities[NUM_TEAMS]; ///< Holds global team entities.
 
 STATIC_INIT(g_team_entities)
@@ -204,22 +211,92 @@ bool SetPlayerTeam(entity player, int team_index, int type)
        {
                return false;
        }
-       LogTeamchange(player.playerid, player.team, type);
+       LogTeamChange(player.playerid, player.team, type);
        if (team_index != old_team_index)
        {
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(player.team,
-                       INFO_JOIN_PLAY_TEAM), player.netname);
+               PlayerScore_Clear(player);
+               if (team_index != -1)
+               {
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(
+                               player.team, INFO_JOIN_PLAY_TEAM), player.netname);
+               }
+               else
+               {
+                       if (!CS(player).just_joined)
+                       {
+                               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE,
+                                       player.netname);
+                       }
+                       else
+                       {
+                               CS(player).just_joined = false;
+                       }
+               }
                KillPlayerForTeamChange(player);
+               if (!IS_BOT_CLIENT(player))
+               {
+                       TeamBalance_AutoBalanceBots();
+               }
        }
        return true;
 }
 
+void Player_SetTeamIndexChecked(entity player, int team_index)
+{
+       if (!teamplay)
+       {
+               return;
+       }
+       if (!Team_IsValidIndex(team_index))
+       {
+               return;
+       }
+       if ((autocvar_g_campaign) || (autocvar_g_changeteam_banned &&
+               CS(player).wasplayer))
+       {
+               Send_Notification(NOTIF_ONE, player, MSG_INFO,
+                       INFO_TEAMCHANGE_NOTALLOWED);
+               return;
+       }
+       entity balance = TeamBalance_CheckAllowedTeams(player);
+       if (team_index == 1 && !TeamBalance_IsTeamAllowedInternal(balance, 1))
+       {
+               team_index = 4;
+       }
+       if (team_index == 4 && !TeamBalance_IsTeamAllowedInternal(balance, 4))
+       {
+               team_index = 3;
+       }
+       if (team_index == 3 && !TeamBalance_IsTeamAllowedInternal(balance, 3))
+       {
+               team_index = 2;
+       }
+       if (team_index == 2 && !TeamBalance_IsTeamAllowedInternal(balance, 2))
+       {
+               team_index = 1;
+       }
+       // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless
+       if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
+       {
+               TeamBalance_GetTeamCounts(balance, player);
+               if ((Team_IndexToBit(team_index) & TeamBalance_FindBestTeams(balance,
+                       player, false)) == 0)
+               {
+                       Send_Notification(NOTIF_ONE, player, MSG_INFO,
+                               INFO_TEAMCHANGE_LARGERTEAM);
+                       TeamBalance_Destroy(balance);
+                       return;
+               }
+       }
+       TeamBalance_Destroy(balance);
+       SetPlayerTeam(player, team_index, TEAM_CHANGE_MANUAL);
+}
+
 bool MoveToTeam(entity client, int team_index, int type)
 {
        //PrintToChatAll(sprintf("MoveToTeam: %s, %f", client.netname, team_index));
        int lockteams_backup = lockteams;  // backup any team lock
        lockteams = 0;  // disable locked teams
-       PlayerScore_Clear(client);
        if (!SetPlayerTeam(client, team_index, type))
        {
                lockteams = lockteams_backup;  // restore the team lock
@@ -229,29 +306,150 @@ bool MoveToTeam(entity client, int team_index, int type)
        return true;
 }
 
-void KillPlayerForTeamChange(entity player)
+bool Player_HasRealForcedTeam(entity player)
 {
-       if (IS_DEAD(player))
+       return player.team_forced > TEAM_FORCE_DEFAULT;
+}
+
+int Player_GetForcedTeamIndex(entity player)
+{
+       return player.team_forced;
+}
+
+void Player_SetForcedTeamIndex(entity player, int team_index)
+{
+       switch (team_index)
        {
-               return;
+               case TEAM_FORCE_SPECTATOR:
+               case TEAM_FORCE_DEFAULT:
+               {
+                       player.team_forced = team_index;
+                       break;
+               }
+               default:
+               {
+                       if (!Team_IsValidIndex(team_index))
+                       {
+                               LOG_FATAL("Player_SetForcedTeamIndex: Invalid team index.");
+                       }
+                       else
+                       {
+                               player.team_forced = team_index;
+                               break;
+                       }
+               }
        }
-       if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true)
+}
+
+void Player_DetermineForcedTeam(entity player)
+{
+       if (autocvar_g_campaign)
        {
-               return;
+               if (IS_REAL_CLIENT(player)) // only players, not bots
+               {
+                       if (Team_IsValidIndex(autocvar_g_campaign_forceteam))
+                       {
+                               player.team_forced = autocvar_g_campaign_forceteam;
+                       }
+                       else
+                       {
+                               player.team_forced = TEAM_FORCE_DEFAULT;
+                       }
+               }
+       }
+       else if (PlayerInList(player, autocvar_g_forced_team_red))
+       {
+               player.team_forced = 1;
+       }
+       else if (PlayerInList(player, autocvar_g_forced_team_blue))
+       {
+               player.team_forced = 2;
+       }
+       else if (PlayerInList(player, autocvar_g_forced_team_yellow))
+       {
+               player.team_forced = 3;
+       }
+       else if (PlayerInList(player, autocvar_g_forced_team_pink))
+       {
+               player.team_forced = 4;
+       }
+       else
+       {
+               switch (autocvar_g_forced_team_otherwise)
+               {
+                       case "red":
+                       {
+                               player.team_forced = 1;
+                               break;
+                       }
+                       case "blue":
+                       {
+                               player.team_forced = 2;
+                               break;
+                       }
+                       case "yellow":
+                       {
+                               player.team_forced = 3;
+                               break;
+                       }
+                       case "pink":
+                       {
+                               player.team_forced = 4;
+                               break;
+                       }
+                       case "spectate":
+                       case "spectator":
+                       {
+                               player.team_forced = TEAM_FORCE_SPECTATOR;
+                               break;
+                       }
+                       default:
+                       {
+                               player.team_forced = TEAM_FORCE_DEFAULT;
+                               break;
+                       }
+               }
+       }
+       if (!teamplay && Player_HasRealForcedTeam(player))
+       {
+               player.team_forced = TEAM_FORCE_DEFAULT;
        }
-       Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, DMG_NOWEP,
-               player.origin, '0 0 0');
 }
 
-void LogTeamchange(float player_id, float team_number, int type)
+void TeamBalance_JoinBestTeam(entity player)
 {
-       if(!autocvar_sv_eventlog)
+       //PrintToChatAll(sprintf("TeamBalance_JoinBestTeam: %s", player.netname));
+       if (!teamplay)
+       {
                return;
-
-       if(player_id < 1)
+       }
+       if (player.bot_forced_team)
+       {
                return;
-
-       GameLogEcho(strcat(":team:", ftos(player_id), ":", ftos(team_number), ":", ftos(type)));
+       }
+       entity balance = TeamBalance_CheckAllowedTeams(player);
+       if (Player_HasRealForcedTeam(player))
+       {
+               int forced_team_index = player.team_forced;
+               bool is_team_allowed = TeamBalance_IsTeamAllowedInternal(balance,
+                       forced_team_index);
+               TeamBalance_Destroy(balance);
+               if (!is_team_allowed)
+               {
+                       return;
+               }
+               if (!SetPlayerTeam(player, forced_team_index, TEAM_CHANGE_AUTO))
+               {
+                       return;
+               }
+               return;
+       }
+       int best_team_index = TeamBalance_FindBestTeam(balance, player, true);
+       TeamBalance_Destroy(balance);
+       if (!SetPlayerTeam(player, best_team_index, TEAM_CHANGE_AUTO))
+       {
+               return;
+       }
 }
 
 entity TeamBalance_CheckAllowedTeams(entity for_whom)
@@ -383,7 +581,7 @@ entity TeamBalance_CheckAllowedTeams(entity for_whom)
        // if player has a forced team, ONLY allow that one
        for (int i = 1; i <= NUM_TEAMS; ++i)
        {
-               if (for_whom.team_forced == Team_IndexToTeam(i) &&
+               if (for_whom.team_forced == i &&
                        TeamBalance_IsTeamAllowedInternal(balance, i))
                {
                        TeamBalance_BanTeamsExcept(balance, i);
@@ -483,13 +681,15 @@ void TeamBalance_GetTeamCounts(entity balance, entity ignore)
                                continue;
                        }
                        int team_num;
-                       if (IS_PLAYER(it) || it.caplayer)
+                       // TODO: Reconsider when the player is truly on the team.
+                       if (IS_CLIENT(it) || (it.caplayer))
                        {
                                team_num = it.team;
                        }
-                       else if (it.team_forced > 0)
+                       else if (Player_HasRealForcedTeam(it))
                        {
-                               team_num = it.team_forced; // reserve the spot
+                               // Do we really need this? Probably not.
+                               team_num = Team_IndexToTeam(it.team_forced); // reserve the spot
                        }
                        else
                        {
@@ -631,52 +831,6 @@ int TeamBalance_FindBestTeams(entity balance, entity player, bool use_score)
        return team_bits;
 }
 
-void TeamBalance_JoinBestTeam(entity this)
-{
-       //PrintToChatAll(sprintf("JoinBestTeam: %s", this.netname));
-       if (!teamplay)
-       {
-               return;
-       }
-       if (this.bot_forced_team)
-       {
-               return;
-       }
-       int old_team_index = Team_TeamToIndex(this.team);
-       entity balance = TeamBalance_CheckAllowedTeams(this);
-       if (this.team_forced > 0)
-       {
-               int forced_team_index = Team_TeamToIndex(this.team_forced);
-               bool is_team_allowed = TeamBalance_IsTeamAllowedInternal(balance,
-                       forced_team_index);
-               TeamBalance_Destroy(balance);
-               if (!is_team_allowed)
-               {
-                       return;
-               }
-               if (!SetPlayerTeam(this, forced_team_index, TEAM_CHANGE_AUTO))
-               {
-                       return;
-               }
-               if ((old_team_index != -1) && !IS_BOT_CLIENT(this))
-               {
-                       TeamBalance_AutoBalanceBots(forced_team_index, old_team_index);
-               }
-               return;
-       }
-       int best_team_index = TeamBalance_FindBestTeam(balance, this, true);
-       TeamBalance_Destroy(balance);
-       PlayerScore_Clear(this);
-       if (!SetPlayerTeam(this, best_team_index, TEAM_CHANGE_AUTO))
-       {
-               return;
-       }
-       if ((old_team_index != -1) && !IS_BOT_CLIENT(this))
-       {
-               TeamBalance_AutoBalanceBots(best_team_index, old_team_index);
-       }
-}
-
 int TeamBalance_CompareTeams(entity balance, int team_index_a, int team_index_b,
        entity player, bool use_score)
 {
@@ -708,74 +862,172 @@ int TeamBalance_CompareTeams(entity balance, int team_index_a, int team_index_b,
        return TeamBalance_CompareTeamsInternal(team_a, team_b, player, use_score);
 }
 
-void TeamBalance_AutoBalanceBots(int source_team_index,
-       int destination_team_index)
+void TeamBalance_AutoBalanceBots()
 {
-       if (!Team_IsValidIndex(source_team_index))
-       {
-               LOG_WARNF("TeamBalance_AutoBalanceBots: "
-                       "Source team index is invalid: %f", source_team_index);
-               return;
-       }
-       if (!Team_IsValidIndex(destination_team_index))
-       {
-               LOG_WARNF("TeamBalance_AutoBalanceBots: "
-                       "Destination team index is invalid: %f", destination_team_index);
-               return;
-       }
        if (!autocvar_g_balance_teams ||
                !autocvar_g_balance_teams_prevent_imbalance)
        {
                return;
        }
+       //PrintToChatAll("TeamBalance_AutoBalanceBots");
        entity balance = TeamBalance_CheckAllowedTeams(NULL);
        TeamBalance_GetTeamCounts(balance, NULL);
-       entity source_team = TeamBalance_GetTeamFromIndex(balance,
-               source_team_index);
-       entity destination_team = TeamBalance_GetTeamFromIndex(balance,
-               destination_team_index);
-       if ((source_team.m_num_bots == 0) || (source_team.m_num_players <=
-               destination_team.m_num_players))
+       int smallest_team_index = 0;
+       int smallest_team_player_count = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
        {
-               TeamBalance_Destroy(balance);
-               return;
+               entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
+               if (!TeamBalanceTeam_IsAllowed(team_))
+               {
+                       continue;
+               }
+               int player_count = TeamBalanceTeam_GetNumberOfPlayers(team_);
+               if (smallest_team_index == 0)
+               {
+                       smallest_team_index = i;
+                       smallest_team_player_count = player_count;
+               }
+               else if (player_count < smallest_team_player_count)
+               {
+                       smallest_team_index = i;
+                       smallest_team_player_count = player_count;
+               }
+       }
+       //PrintToChatAll(sprintf("Smallest team: %f", smallest_team_index));
+       //PrintToChatAll(sprintf("Smallest team players: %f", smallest_team_player_count));
+       entity switchable_bot = NULL;
+       int teams = BITS(NUM_TEAMS);
+       while (teams != 0)
+       {
+               int largest_team_index = TeamBalance_GetLargestTeamIndex(balance,
+                       teams);
+               if (smallest_team_index == largest_team_index)
+               {
+                       TeamBalance_Destroy(balance);
+                       return;
+               }
+               entity largest_team = TeamBalance_GetTeamFromIndex(balance,
+                       largest_team_index);
+               int largest_team_player_count = TeamBalanceTeam_GetNumberOfPlayers(
+                       largest_team);
+               if (largest_team_player_count - smallest_team_player_count < 2)
+               {
+                       TeamBalance_Destroy(balance);
+                       return;
+               }
+               //PrintToChatAll(sprintf("Largest team: %f", largest_team_index));
+               //PrintToChatAll(sprintf("Largest team players: %f", largest_team_player_count));
+               switchable_bot = TeamBalance_GetPlayerForTeamSwitch(largest_team_index,
+                       smallest_team_index, true);
+               if (switchable_bot != NULL)
+               {
+                       break;
+               }
+               teams &= ~Team_IndexToBit(largest_team_index);
        }
        TeamBalance_Destroy(balance);
-       entity lowest_bot = NULL;
+       if (switchable_bot == NULL)
+       {
+               //PrintToChatAll("No bot found after searching through all the teams");
+               return;
+       }
+       SetPlayerTeam(switchable_bot, smallest_team_index, TEAM_CHANGE_AUTO);
+}
+
+int TeamBalance_GetLargestTeamIndex(entity balance, int teams)
+{
+       int largest_team_index = 0;
+       int largest_team_player_count = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               if (!(Team_IndexToBit(i) & teams))
+               {
+                       continue;
+               }
+               entity team_ = TeamBalance_GetTeamFromIndex(balance, i);
+               if (!TeamBalanceTeam_IsAllowed(team_))
+               {
+                       continue;
+               }
+               int player_count = TeamBalanceTeam_GetNumberOfPlayers(team_);
+               if (largest_team_index == 0)
+               {
+                       largest_team_index = i;
+                       largest_team_player_count = player_count;
+               }
+               else if (player_count > largest_team_player_count)
+               {
+                       largest_team_index = i;
+                       largest_team_player_count = player_count;
+               }
+       }
+       return largest_team_index;
+}
+
+entity TeamBalance_GetPlayerForTeamSwitch(int source_team_index,
+       int destination_team_index, bool is_bot)
+{
        if (MUTATOR_CALLHOOK(TeamBalance_GetPlayerForTeamSwitch, source_team_index,
-               destination_team_index, true))
+               destination_team_index, is_bot))
        {
-               lowest_bot = M_ARGV(3, entity);
+               return M_ARGV(3, entity);
        }
-       else
+       entity lowest_player = NULL;
+       float lowest_score = FLOAT_MAX;
+       FOREACH_CLIENT(Entity_GetTeamIndex(it) == source_team_index,
        {
-               float lowest_score = FLOAT_MAX;
-               FOREACH_CLIENT(IS_BOT_CLIENT(it) && (Entity_GetTeamIndex(it) ==
-                       source_team_index),
+               if (IS_BOT_CLIENT(it) != is_bot)
                {
-                       float temp_score = PlayerScore_Get(it, SP_SCORE);
-                       if (temp_score >= lowest_score)
-                       {
-                               continue;
-                       }
-                       balance = TeamBalance_CheckAllowedTeams(it);
-                       if (TeamBalance_IsTeamAllowed(balance, destination_team_index))
-                       {
-                               lowest_bot = it;
-                               lowest_score = temp_score;
-                       }
-                       TeamBalance_Destroy(balance);
-               });
+                       continue;
+               }
+               float temp_score = PlayerScore_Get(it, SP_SCORE);
+               if (temp_score >= lowest_score)
+               {
+                       continue;
+               }
+               //PrintToChatAll(sprintf(
+               //      "Found %s with lowest score, checking allowed teams", it.netname));
+               entity balance = TeamBalance_CheckAllowedTeams(it);
+               if (TeamBalance_IsTeamAllowed(balance, source_team_index))
+               {
+                       //PrintToChatAll("Allowed");
+                       lowest_player = it;
+                       lowest_score = temp_score;
+               }
+               else
+               {
+                       //PrintToChatAll("Not allowed");
+               }
+               TeamBalance_Destroy(balance);
+       });
+       return lowest_player;
+}
+
+void LogTeamChange(float player_id, float team_number, int type)
+{
+       if (!autocvar_sv_eventlog)
+       {
+               return;
+       }
+       if (player_id < 1)
+       {
+               return;
        }
-       if (lowest_bot == NULL)
+       GameLogEcho(sprintf(":team:%f:%f:%f", player_id, team_number, type));
+}
+
+void KillPlayerForTeamChange(entity player)
+{
+       if (IS_DEAD(player))
        {
                return;
        }
-       if (!Player_SetTeamIndex(lowest_bot, destination_team_index))
+       if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true)
        {
                return;
        }
-       KillPlayerForTeamChange(lowest_bot);
+       Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, DMG_NOWEP,
+               player.origin, '0 0 0');
 }
 
 bool TeamBalance_IsTeamAllowedInternal(entity balance, int index)
@@ -866,101 +1118,24 @@ int TeamBalance_CompareTeamsInternal(entity team_a, entity team_b,
        return TEAMS_COMPARE_EQUAL;
 }
 
-// Called when the player connects or when they change their color with "color"
-// command.
-void SV_ChangeTeam(entity this, float _color)
+void SV_ChangeTeam(entity player, int new_color)
 {
-       //PrintToChatAll(sprintf("SV_ChangeTeam: %s, %f", this.netname, _color));
-
-       // in normal deathmatch we can just apply the color and we're done
-       if(!teamplay)
-               SetPlayerColors(this, _color);
-
-       if(!IS_CLIENT(this))
-       {
-               // since this is an engine function, and gamecode doesn't have any calls earlier than this, do the connecting message here
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CONNECTING, this.netname);
-               return;
-       }
-
-       if(!teamplay)
-               return;
-
-       int source_color, destination_color;
-       int source_team_index, destination_team_index;
-
-       source_color = this.clientcolors & 0x0F;
-       destination_color = _color & 0x0F;
-
-       source_team_index = Team_TeamToIndex(source_color + 1);
-       destination_team_index = Team_TeamToIndex(destination_color + 1);
-
-       if (destination_team_index == -1)
-       {
-               return;
-       }
-
-       entity balance = TeamBalance_CheckAllowedTeams(this);
-
-       if (destination_team_index == 1 && !TeamBalance_IsTeamAllowedInternal(
-               balance, 1))
-       {
-               destination_team_index = 4;
-       }
-       if (destination_team_index == 4 && !TeamBalance_IsTeamAllowedInternal(
-               balance, 4))
-       {
-               destination_team_index = 3;
-       }
-       if (destination_team_index == 3 && !TeamBalance_IsTeamAllowedInternal(
-               balance, 3))
-       {
-               destination_team_index = 2;
-       }
-       if (destination_team_index == 2 && !TeamBalance_IsTeamAllowedInternal(
-               balance, 2))
-       {
-               destination_team_index = 1;
-       }
-
-       // not changing teams
-       if (source_color == destination_color)
-       {
-               SetPlayerTeam(this, destination_team_index, TEAM_CHANGE_MANUAL);
-               TeamBalance_Destroy(balance);
-               return;
-       }
-
-       if((autocvar_g_campaign) || (autocvar_g_changeteam_banned && CS(this).wasplayer)) {
-               Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_NOTALLOWED);
-               return; // changing teams is not allowed
-       }
-
-       // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless
-       if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
-       {
-               TeamBalance_GetTeamCounts(balance, this);
-               if ((Team_IndexToBit(destination_team_index) &
-                       TeamBalance_FindBestTeams(balance, this, false)) == 0)
-               {
-                       Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
-                       TeamBalance_Destroy(balance);
-                       return;
-               }
-       }
-       TeamBalance_Destroy(balance);
-       if (IS_PLAYER(this) && source_team_index != destination_team_index)
+       if (!teamplay)
        {
-               // reduce frags during a team change
-               PlayerScore_Clear(this);
+               SetPlayerColors(player, new_color);
        }
-       if (!SetPlayerTeam(this, destination_team_index, TEAM_CHANGE_MANUAL))
+       // TODO: Should we really bother with this?
+       if(!IS_CLIENT(player))
        {
+               // since this is an engine function, and gamecode doesn't have any calls earlier than this, do the connecting message here
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CONNECTING,
+                       player.netname);
                return;
        }
-       if (source_team_index == -1)
+       if (!teamplay)
        {
                return;
        }
-       TeamBalance_AutoBalanceBots(destination_team_index, source_team_index);
+       Player_SetTeamIndexChecked(player, Team_TeamToIndex((new_color & 0x0F) +
+               1));
 }
index 776a80d329cf91d5917f1af4e1ba7275eb18a731..33f9d02d7e6b01da87c6dd86e6217b3ab546aa95 100644 (file)
@@ -1,5 +1,13 @@
 #pragma once
 
+int autocvar_teamplay_mode;
+
+bool autocvar_g_changeteam_banned;
+bool autocvar_teamplay_lockonrestart;
+
+bool autocvar_g_balance_teams;
+bool autocvar_g_balance_teams_prevent_imbalance;
+
 bool lockteams;
 
 // ========================== Global teams API ================================
@@ -83,13 +91,25 @@ void SetPlayerColors(entity player, float _color);
 /// \return True if team switch was successful, false otherwise.
 bool Player_SetTeamIndex(entity player, int index);
 
+enum
+{
+       TEAM_CHANGE_AUTO = 2, ///< The team was selected by autobalance.
+       TEAM_CHANGE_MANUAL = 3, ///< Player has manually selected their team.
+       TEAM_CHANGE_SPECTATOR = 4 ///< Player is joining spectators. //TODO: Remove?
+};
+
 /// \brief Sets the team of the player.
 /// \param[in,out] player Player to adjust.
 /// \param[in] team_index Index of the team to set.
-/// \param[in] type ???
+/// \param[in] type Type of the team change. See TEAM_CHANGE constants.
 /// \return True if team switch was successful, false otherwise.
 bool SetPlayerTeam(entity player, int team_index, int type);
 
+/// \brief Sets the team of the player with all sanity checks.
+/// \param[in,out] player Player to adjust.
+/// \param[in] team_index Index of the team to set.
+void Player_SetTeamIndexChecked(entity player, int team_index);
+
 /// \brief Moves player to the specified team.
 /// \param[in,out] client Client to move.
 /// \param[in] team_index Index of the team.
@@ -97,21 +117,39 @@ bool SetPlayerTeam(entity player, int team_index, int type);
 /// \return True on success, false otherwise.
 bool MoveToTeam(entity client, int team_index, int type);
 
-/// \brief Kills player as a result of team change.
-/// \param[in,out] player Player to kill.
-void KillPlayerForTeamChange(entity player);
-
 enum
 {
-       TEAM_CHANGE_AUTO = 2,
-       TEAM_CHANGE_MANUAL = 3,
-       TEAM_CHANGE_SPECTATOR = 4
+       TEAM_FORCE_SPECTATOR = -1, ///< Force the player to spectator team.
+       TEAM_FORCE_DEFAULT = 0 ///< Don't force any team.
 };
 
-void LogTeamchange(float player_id, float team_number, int type);
+/// \brief Returns whether player has real forced team. Spectator team is
+/// ignored.
+/// \param[in] player Player to check.
+/// \return True if player has real forced team, false otherwise.
+bool Player_HasRealForcedTeam(entity player);
+
+/// \brief Returns the index of the forced team of the given player.
+/// \param[in] player Player to check.
+/// \return Index of the forced team.
+int Player_GetForcedTeamIndex(entity player);
+
+/// \brief Sets the index of the forced team of the given player.
+/// \param[in,out] player Player to adjust.
+/// \param[in] team_index Index of the team to set.
+void Player_SetForcedTeamIndex(entity player, int team_index);
+
+/// \brief Determines the forced team of the player using current global config.
+/// \param[in,out] player Player to adjust.
+void Player_DetermineForcedTeam(entity player);
 
 // ========================= Team balance API =================================
 
+/// \brief Assigns the given player to a team that will make the game most
+/// balanced.
+/// \param[in,out] player Player to assign.
+void TeamBalance_JoinBestTeam(entity player);
+
 /// \brief Checks whether the player can join teams according to global
 /// configuration and mutator settings.
 /// \param[in] for_whom Player to check for. Pass NULL for global rules.
@@ -174,8 +212,6 @@ int TeamBalance_FindBestTeam(entity balance, entity player, bool ignore_player);
 /// function.
 int TeamBalance_FindBestTeams(entity balance, entity player, bool use_score);
 
-void TeamBalance_JoinBestTeam(entity this);
-
 /// \brief Describes the result of comparing teams.
 enum
 {
@@ -198,13 +234,33 @@ int TeamBalance_CompareTeams(entity balance, int team_index_a, int team_index_b,
        entity player, bool use_score);
 
 /// \brief Switches a bot from one team to another if teams are not balanced.
-/// \param[in] source_team_index Index of the team to switch from.
+void TeamBalance_AutoBalanceBots();
+
+/// \brief Returns the index of the team with most players that is contained in
+/// the given bitmask of teams.
+/// \param[in] balance Team balance entity.
+/// \param[in] teams Bitmask of teams to search in.
+/// \return Index of the team with most players.
+int TeamBalance_GetLargestTeamIndex(entity balance, int teams);
+
+/// \brief Returns the player who is the most suitable for switching between
+/// the given teams.
+/// \param[in] source_team_index Index of the team to search in.
 /// \param[in] destination_team_index Index of the team to switch to.
-void TeamBalance_AutoBalanceBots(int source_team_index,
-       int destination_team_index);
+/// \param[in] is_bot True to search for bot, false for human.
+/// \return Player who is the most suitable for switching between the given
+/// teams or NULL if not found.
+entity TeamBalance_GetPlayerForTeamSwitch(int source_team_index,
+       int destination_team_index, bool is_bot);
 
 // ============================ Internal API ==================================
 
+void LogTeamChange(float player_id, float team_number, int type);
+
+/// \brief Kills player as a result of team change.
+/// \param[in,out] player Player to kill.
+void KillPlayerForTeamChange(entity player);
+
 /// \brief Returns whether the team change to the specified team is allowed.
 /// \param[in] balance Team balance entity.
 /// \param[in] index Index of the team.
@@ -262,3 +318,9 @@ int TeamBalanceTeam_GetNumberOfBots(entity team_ent);
 /// function.
 int TeamBalance_CompareTeamsInternal(entity team_a, entity team_index_b,
        entity player, bool use_score);
+
+/// \brief Called when the player connects or when they change their color with
+/// the "color" command.
+/// \param[in,out] player Player that requested a new color.
+/// \param[in] new_color Requested color.
+void SV_ChangeTeam(entity player, int new_color);
index 8517818d6406c902715a967507f55b6eed28e669..548616908e06c893763a09fa3ea99ed248162a6c 100644 (file)
@@ -229,11 +229,17 @@ cl_movement 1
 cl_movement_track_canjump 0
 cl_stairsmoothspeed 200
 
-alias g_waypointeditor_spawn "impulse 103"
-alias g_waypointeditor_remove "impulse 104"
-alias g_waypointeditor_relinkall "impulse 105"
-alias g_waypointeditor_saveall "impulse 106"
-alias g_waypointeditor_unreachable "impulse 107"
+alias g_waypointeditor_spawn         "wpeditor spawn"
+alias g_waypointeditor_remove        "wpeditor remove"
+alias g_waypointeditor_relinkall     "wpeditor relinkall"
+alias g_waypointeditor_saveall       "wpeditor saveall"
+alias g_waypointeditor_unreachable   "wpeditor unreachable"
+
+alias navwaypoint_relink        g_waypointeditor_spawn
+alias navwaypoint_remove        g_waypointeditor_remove
+alias navwaypoint_save          g_waypointeditor_relinkall
+alias navwaypoint_spawn         g_waypointeditor_saveall
+alias navwaypoint_unreachable   g_waypointeditor_unreachable
 
 seta menu_sandbox_spawn_model ""
 seta menu_sandbox_attach_bone ""
index 4898cb6afcae4c7e3aac4726d479fab8c3645536..efba68a908c55b9c6451b08f62e2a83db9432f5f 100644 (file)
@@ -258,9 +258,7 @@ set g_teamdamage_resetspeed 20      "for teamplay_mode 4: how fast player's team
 
 set g_balance_teams 1  "automatically balance out players entering instead of asking them for their preferred team"
 set g_balance_teams_prevent_imbalance  1       "prevent players from changing to larger teams"
-set g_balance_teams_scorefactor 0.25 "at the end of the game, take score into account instead of team size by this amount (beware: values over 0.5 mean that a x:0 score imbalance will cause ALL new players to prefer the losing team at the end, despite numbers)"
 set g_changeteam_banned 0      "not allowed to change team"
-set g_changeteam_fragtransfer 0        "% of frags you get to keep when you change teams (rounded down)"
 
 set sv_teamnagger 1 "enable a nag message when the teams are unbalanced"