Merge branch 'master' into Mario/wepent_experimental
authorMario <mario@smbclan.net>
Tue, 6 Dec 2016 15:01:32 +0000 (01:01 +1000)
committerMario <mario@smbclan.net>
Tue, 6 Dec 2016 15:01:32 +0000 (01:01 +1000)
23 files changed:
.gitlab-ci.yml
defaultXonotic.cfg
qcsrc/client/hud/panel/infomessages.qc
qcsrc/client/shownames.qc
qcsrc/common/ent_cs.qc
qcsrc/common/ent_cs.qh
qcsrc/common/items/item/armor.qh
qcsrc/common/items/item/health.qh
qcsrc/common/items/item/powerup.qh
qcsrc/common/mutators/mutator/buffs/sv_buffs.qc
qcsrc/common/mutators/mutator/buffs/sv_buffs.qh
qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc
qcsrc/menu/xonotic/credits.qc
qcsrc/menu/xonotic/dialog_multiplayer_create.qc
qcsrc/server/bot/default/bot.qc
qcsrc/server/bot/default/havocbot/havocbot.qc
qcsrc/server/bot/default/navigation.qc
qcsrc/server/command/cmd.qh
qcsrc/server/mutators/events.qh
qcsrc/server/mutators/mutator/gamemode_lms.qc
qcsrc/server/mutators/mutator/gamemode_lms.qh
qcsrc/server/player.qc
qcsrc/server/weapons/selection.qc

index 70b0200..63130f8 100644 (file)
@@ -30,7 +30,7 @@ test_sv_game:
     - wget -O data/maps/g-23.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/g-23.waypoints.cache
     - wget -O data/maps/g-23.waypoints.hardwired https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/g-23.waypoints.hardwired
     - make
-    - EXPECT=04d63df6a5d73bd335612543efd944d6
+    - EXPECT=b58f9c7587f1a14e5c52176d4e62a9fb
     - HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
       | tee /dev/stderr
       | grep '^:'
index 8ed6aa1..bf35a41 100644 (file)
@@ -389,10 +389,10 @@ set bot_ai_navigation_jetpack 0 "Enable bots to navigate maps using the jetpack"
 set bot_ai_navigation_jetpack_mindistance 3500 "Bots will try fly to objects located farther than this distance"
 // Better don't touch these, there are hard to tweak!
 set bot_ai_aimskill_order_mix_1st 0.01 "Amount of the 1st filter output to apply to the aiming angle"
-set bot_ai_aimskill_order_mix_2nd 0.1 "Amount of the 1st filter output to apply to the aiming angle"
-set bot_ai_aimskill_order_mix_3th 0.01 "Amount of the 1st filter output to apply to the aiming angle"
-set bot_ai_aimskill_order_mix_4th 0.05 "Amount of the 1st filter output to apply to the aiming angle"
-set bot_ai_aimskill_order_mix_5th 0.01 "Amount of the 1st filter output to apply to the aiming angle"
+set bot_ai_aimskill_order_mix_2nd 0.1 "Amount of the 2nd filter output to apply to the aiming angle"
+set bot_ai_aimskill_order_mix_3th 0.01 "Amount of the 3th filter output to apply to the aiming angle"
+set bot_ai_aimskill_order_mix_4th 0.05 "Amount of the 4th filter output to apply to the aiming angle"
+set bot_ai_aimskill_order_mix_5th 0.01 "Amount of the 5th filter output to apply to the aiming angle"
 set bot_ai_aimskill_order_filter_1st 0.4 "Position filter"
 set bot_ai_aimskill_order_filter_2nd 0.4 "Movement filter"
 set bot_ai_aimskill_order_filter_3th 0.2 "Acceleration filter"
index cd49f09..db102e6 100644 (file)
@@ -114,7 +114,7 @@ void HUD_InfoMessages()
                                InfoMessage(s);
                        }
 
-                       if(gametype == MAPINFO_TYPE_LMS)
+                       if(!warmup_stage && gametype == MAPINFO_TYPE_LMS)
                        {
                                entity sk;
                                sk = playerslots[player_localnum];
index 76125e6..e46a97c 100644 (file)
@@ -49,34 +49,41 @@ void Draw_ShowNames(entity this)
                hit = !(trace_fraction < 1 && (trace_networkentity != this.sv_entnum && trace_ent.entnum != this.sv_entnum));
        }
        // handle tag fading
-       bool overlap = false;
+       int overlap = -1;
        vector o = project_3d_to_2d(this.origin + eZ * autocvar_hud_shownames_offset);
+       if (autocvar_hud_shownames_crosshairdistance)
+       {
+               float d = autocvar_hud_shownames_crosshairdistance;
+               float w = o.x - vid_conwidth / 2;
+               float h = o.y - vid_conheight / 2;
+               if (d * d > w * w + h * h) this.pointtime = time;
+               if (this.pointtime + autocvar_hud_shownames_crosshairdistance_time <= time)
+                       overlap = 1;
+               else if(!autocvar_hud_shownames_crosshairdistance_antioverlap)
+                       overlap = 0;
+       }
+
        float dist = vlen(this.origin - view_origin);
-       if (autocvar_hud_shownames_antioverlap)
+       if (overlap == -1 && autocvar_hud_shownames_antioverlap)
        {
                // fade tag out if another tag that is closer to you overlaps
-               LL_EACH(shownames_ent, it != this && entcs_receiver(i), {
+               entity entcs = NULL;
+               LL_EACH(shownames_ent, it != this, {
+                       entcs = entcs_receiver(i);
+                       if (!(entcs && entcs.has_sv_origin))
+                               continue;
                        vector eo = project_3d_to_2d(it.origin);
                        if (eo.z < 0 || eo.x < 0 || eo.y < 0 || eo.x > vid_conwidth || eo.y > vid_conheight) continue;
                        eo.z = 0;
                        if (vdist(((eX * o.x + eY * o.y) - eo), <, autocvar_hud_shownames_antioverlap_distance)
                            && vdist((it.origin - view_origin), <, dist))
                        {
-                               overlap = true;
+                               overlap = 1;
                                break;
                        }
                });
        }
        bool onscreen = (o.z >= 0 && o.x >= 0 && o.y >= 0 && o.x <= vid_conwidth && o.y <= vid_conheight);
-       if (autocvar_hud_shownames_crosshairdistance)
-       {
-               float d = autocvar_hud_shownames_crosshairdistance;
-               float w = o.x - vid_conwidth / 2;
-               float h = o.y - vid_conheight / 2;
-               if (d * d > w * w + h * h) this.pointtime = time;
-               if (this.pointtime + autocvar_hud_shownames_crosshairdistance_time <= time) overlap = true;
-               else overlap = (autocvar_hud_shownames_crosshairdistance_antioverlap ? overlap : false); // override what antioverlap says unless allowed by cvar.
-       }
        if (!this.fadedelay) this.fadedelay = time + SHOWNAMES_FADEDELAY;
        if (this.csqcmodel_isdead)                                                                   // dead player, fade out slowly
        {
@@ -87,7 +94,7 @@ void Draw_ShowNames(entity this)
                this.alpha = max(0, this.alpha - SHOWNAMES_FADESPEED * frametime);
                this.fadedelay = 0;                         // reset fade in delay, enemy has left the view
        }
-       else if (overlap)                               // tag overlap detected, fade out
+       else if (overlap > 0) // tag overlap detected, fade out
        {
                this.alpha = max(0, this.alpha - SHOWNAMES_FADESPEED * frametime);
        }
@@ -193,8 +200,6 @@ void Draw_ShowNames_All()
                        it.sameteam = false;
                }
                bool dead = entcs_IsDead(i) || entcs_IsSpectating(i);
-               if(gametype == MAPINFO_TYPE_CA)
-                       dead = (dead || entcs_IsEliminated(i));
                if (!it.csqcmodel_isdead) setorigin(it, entcs.origin);
                it.csqcmodel_isdead = dead;
                Draw_ShowNames(it);
index da53f68..71052bd 100644 (file)
@@ -73,6 +73,11 @@ MACRO_END
                entity player = this.owner;
                sf |= BIT(0); // assume private
                do {
+                       if (!(IS_PLAYER(player)))
+                       {
+                               sf &= ENTCS_PUBLICMASK; // no private updates
+                               break;
+                       }
                        if (radar_showennemies) break;
                        if (SAME_TEAM(to, player)) break;
                        if (!(IS_PLAYER(to) || to.caplayer) && time > game_starttime) break;
index ac06dc4..65cdd83 100644 (file)
@@ -69,17 +69,6 @@ REGISTER_NET_TEMP(CLIENT_ENTCS)
        /**
      * @param i zero indexed player
      */
-    .int frags;
-       bool entcs_IsEliminated(int i)
-       {
-               bool unconnected = !playerslots[i].gotscores;
-               entity e = entcs_receiver(i);
-               return unconnected || ((e) ? e.frags : stof(getplayerkeyvalue(i, "frags"))) == FRAGS_LMS_LOSER;
-       }
-
-       /**
-     * @param i zero indexed player
-     */
        int entcs_GetClientColors(int i)
        {
                entity e = entcs_receiver(i);
index 7946fb7..0a52754 100644 (file)
@@ -91,6 +91,7 @@ REGISTER_ITEM(ArmorMega, Armor) {
     this.m_waypoint             =   _("Mega armor");
     this.m_waypointblink        =   2;
 #ifdef SVQC
+    this.m_maxs                 =   '16 16 70';
     this.m_botvalue             =   BOT_PICKUP_RATING_HIGH;
     this.m_itemid               =   IT_ARMOR;
     this.m_respawntime          =   GET(g_pickup_respawntime_long);
index 1597ba6..3717bf5 100644 (file)
@@ -91,6 +91,7 @@ REGISTER_ITEM(HealthMega, Health) {
     this.m_waypoint             =   _("Mega health");
     this.m_waypointblink        =   2;
 #ifdef SVQC
+    this.m_maxs                 =   '16 16 70';
     this.m_botvalue             =   BOT_PICKUP_RATING_HIGH;
     this.m_itemid               =   IT_HEALTH;
     this.m_respawntime          =   GET(g_pickup_respawntime_long);
index 26d649d..df9315e 100644 (file)
@@ -9,7 +9,7 @@
 CLASS(Powerup, Pickup)
 #ifdef SVQC
     ATTRIB(Powerup, m_mins, vector, '-16 -16 0');
-    ATTRIB(Powerup, m_maxs, vector, '16 16 48');
+    ATTRIB(Powerup, m_maxs, vector, '16 16 80');
     ATTRIB(Powerup, m_botvalue, int, 100000);
     ATTRIB(Powerup, m_itemflags, int, FL_POWERUP);
     ATTRIB(Powerup, m_respawntime, float(), GET(g_pickup_respawntime_powerup));
index 21ce55b..a5d3490 100644 (file)
@@ -231,6 +231,7 @@ void buff_Think(entity this)
                this.skin = buff.m_skin;
 
                setmodel(this, MDL_BUFF);
+               setsize(this, BUFF_MIN, BUFF_MAX);
 
                if(this.buff_waypoint)
                {
index b9fc1e4..5b14c39 100644 (file)
@@ -70,8 +70,8 @@ float autocvar_g_buffs_luck_damagemultiplier = 3;
 .int oldbuffs; // for updating effects
 .entity buff_model; // controls effects (TODO: make csqc)
 
-const vector BUFF_MIN = ('-16 -16 -20');
-const vector BUFF_MAX = ('16 16 20');
+const vector BUFF_MIN = ('-16 -16 0');
+const vector BUFF_MAX = ('16 16 60');
 
 // client side options
 .float cvar_cl_buffs_autoreplace;
index e4b7843..5f11777 100644 (file)
@@ -87,6 +87,8 @@ MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn)
                int tested = 0;
                FOREACH_CLIENT_RANDOM(IS_PLAYER(it), LAMBDA(
                        if (autocvar_g_spawn_near_teammate_ignore_spawnpoint_max && tested >= autocvar_g_spawn_near_teammate_ignore_spawnpoint_max) break;
+
+                       if (PHYS_INPUT_BUTTON_CHAT(it)) continue;
                        if (!SAME_TEAM(player, it)) continue;
                        if (autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health && it.health < autocvar_g_balance_health_regenstable) continue;
                        if (IS_DEAD(it)) continue;
index d30ab3d..816120b 100644 (file)
@@ -402,7 +402,6 @@ void XonoticCreditsList_resizeNotify(entity me, vector relOrigin, vector relSize
 }
 void XonoticCreditsList_drawListBoxItem(entity me, int i, vector absSize, bool isSelected, bool isFocused)
 {
-       // layout: Ping, Credits name, Map name, NP, TP, MP
        string s;
        float theAlpha;
        vector theColor;
index 85e0e9e..4819142 100644 (file)
@@ -58,6 +58,27 @@ void GameType_ConfigureSliders(entity me, string pLabel, float pMin, float pMax,
        t.configureXonoticTextSliderValues(t);
 }
 
+void GameType_ConfigureSliders_for_CurrentGametype(entity me)
+{
+       switch(MapInfo_CurrentGametype())
+       {
+               case MAPINFO_TYPE_CA:              GameType_ConfigureSliders(me, _("Frag limit:"),      5,  100,  5, "fraglimit_override",        "g_ca_teams_override",          _("The amount of frags needed before the match will end")); break;
+               case MAPINFO_TYPE_FREEZETAG:       GameType_ConfigureSliders(me, _("Frag limit:"),      5,  100,  5, "fraglimit_override",        "g_freezetag_teams_override",   _("The amount of frags needed before the match will end")); break;
+               case MAPINFO_TYPE_CTF:             GameType_ConfigureSliders(me, _("Capture limit:"),   1,   20,  1, "capturelimit_override",     string_null,                    _("The amount of captures needed before the match will end")); break;
+               case MAPINFO_TYPE_DOMINATION:      GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, "g_domination_point_limit",  "g_domination_teams_override",  _("The amount of points needed before the match will end")); break;
+               case MAPINFO_TYPE_KEYHUNT:         GameType_ConfigureSliders(me, _("Point limit:"),   200, 1500, 50, "g_keyhunt_point_limit",     "g_keyhunt_teams_override",     _("The amount of points needed before the match will end")); break;
+               case MAPINFO_TYPE_LMS:             GameType_ConfigureSliders(me, _("Lives:"),           3,   50,  1, "g_lms_lives_override",      string_null,                    string_null); break;
+               case MAPINFO_TYPE_RACE:            GameType_ConfigureSliders(me, _("Laps:"),            1,   25,  1, "g_race_laps_limit",         string_null,                    string_null); break;
+               case MAPINFO_TYPE_NEXBALL:         GameType_ConfigureSliders(me, _("Goals:"),           1,   50,  1, "g_nexball_goallimit",       string_null,                    _("The amount of goals needed before the match will end")); break;
+               case MAPINFO_TYPE_ASSAULT:         GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
+               case MAPINFO_TYPE_ONSLAUGHT:       GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
+               case MAPINFO_TYPE_CTS:             GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
+               case MAPINFO_TYPE_INVASION:        GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
+               case MAPINFO_TYPE_TEAM_DEATHMATCH: GameType_ConfigureSliders(me, _("Point limit:"),     5,  100,  5, "g_tdm_point_limit",         "g_tdm_teams_override",         _("The amount of points needed before the match will end")); break;
+               default:                           GameType_ConfigureSliders(me, _("Frag limit:"),      5,  100,  5, "fraglimit_override",        string_null,                    _("The amount of frags needed before the match will end")); break;
+       }
+}
+
 entity makeXonoticServerCreateTab()
 {
        entity me;
@@ -210,28 +231,12 @@ void XonoticServerCreateTab_fill(entity me)
                        e.onClickEntity = me.mapListBox;
                        me.mapListBox.startButton = e;
 
-       me.gameTypeChangeNotify(me);
+       GameType_ConfigureSliders_for_CurrentGametype(me);
 }
 
 void XonoticServerCreateTab_gameTypeChangeNotify(entity me)
 {
-       switch(MapInfo_CurrentGametype())
-       {
-               case MAPINFO_TYPE_CA:              GameType_ConfigureSliders(me, _("Frag limit:"),      5,  100,  5, "fraglimit_override",        "g_ca_teams_override",          _("The amount of frags needed before the match will end")); break;
-               case MAPINFO_TYPE_FREEZETAG:       GameType_ConfigureSliders(me, _("Frag limit:"),      5,  100,  5, "fraglimit_override",        "g_freezetag_teams_override",   _("The amount of frags needed before the match will end")); break;
-               case MAPINFO_TYPE_CTF:             GameType_ConfigureSliders(me, _("Capture limit:"),   1,   20,  1, "capturelimit_override",     string_null,                    _("The amount of captures needed before the match will end")); break;
-               case MAPINFO_TYPE_DOMINATION:      GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, "g_domination_point_limit",  "g_domination_teams_override",  _("The amount of points needed before the match will end")); break;
-               case MAPINFO_TYPE_KEYHUNT:         GameType_ConfigureSliders(me, _("Point limit:"),   200, 1500, 50, "g_keyhunt_point_limit",     "g_keyhunt_teams_override",     _("The amount of points needed before the match will end")); break;
-               case MAPINFO_TYPE_LMS:             GameType_ConfigureSliders(me, _("Lives:"),           3,   50,  1, "g_lms_lives_override",      string_null,                    string_null); break;
-               case MAPINFO_TYPE_RACE:            GameType_ConfigureSliders(me, _("Laps:"),            1,   25,  1, "g_race_laps_limit",         string_null,                    string_null); break;
-               case MAPINFO_TYPE_NEXBALL:         GameType_ConfigureSliders(me, _("Goals:"),           1,   50,  1, "g_nexball_goallimit",       string_null,                    _("The amount of goals needed before the match will end")); break;
-               case MAPINFO_TYPE_ASSAULT:         GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
-               case MAPINFO_TYPE_ONSLAUGHT:       GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
-               case MAPINFO_TYPE_CTS:             GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
-               case MAPINFO_TYPE_INVASION:        GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
-               case MAPINFO_TYPE_TEAM_DEATHMATCH: GameType_ConfigureSliders(me, _("Point limit:"),     5,  100,  5, "g_tdm_point_limit",         "g_tdm_teams_override",         _("The amount of points needed before the match will end")); break;
-               default:                           GameType_ConfigureSliders(me, _("Frag limit:"),      5,  100,  5, "fraglimit_override",        string_null,                    _("The amount of frags needed before the match will end")); break;
-       }
+       GameType_ConfigureSliders_for_CurrentGametype(me);
 
        me.mapListBox.refilter(me.mapListBox);
 }
index 2a7478d..9d612ea 100644 (file)
@@ -72,6 +72,7 @@ void bot_think(entity this)
 
        if (!IS_PLAYER(this) || (autocvar_g_campaign && !campaign_bots_may_start))
        {
+               this.movement = '0 0 0';
                this.bot_nextthink = time + 0.5;
                return;
        }
@@ -118,6 +119,7 @@ void bot_think(entity this)
        // if dead, just wait until we can respawn
        if (IS_DEAD(this))
        {
+               this.movement = '0 0 0';
                if (this.deadflag == DEAD_DEAD)
                {
                        PHYS_INPUT_BUTTON_JUMP(this) = true; // press jump to respawn
index d768656..9f3e075 100644 (file)
@@ -49,7 +49,7 @@ void havocbot_ai(entity this)
 
                // TODO: tracewalk() should take care of this job (better path finding under water)
                // if we don't have a goal and we're under water look for a waypoint near the "shore" and push it
-               if(IS_DEAD(this))
+               if(!(IS_DEAD(this)))
                if(!this.goalcurrent)
                if(this.waterlevel == WATERLEVEL_SWIMMING || (this.aistatus & AI_STATUS_OUT_WATER))
                {
index 7c71720..5092a65 100644 (file)
@@ -1032,7 +1032,7 @@ void navigation_unstuck(entity this)
 
        if (!bot_waypoint_queue_owner)
        {
-               LOG_DEBUG(this.netname, " sutck, taking over the waypoints queue");
+               LOG_DEBUG(this.netname, " stuck, taking over the waypoints queue");
                bot_waypoint_queue_owner = this;
                bot_waypoint_queue_bestgoal = NULL;
                bot_waypoint_queue_bestgoalrating = 0;
@@ -1044,7 +1044,7 @@ void navigation_unstuck(entity this)
        if (bot_waypoint_queue_goal)
        {
                // evaluate the next goal on the queue
-               float d = vlen(this.origin - bot_waypoint_queue_goal.origin);
+               float d = vlen2(this.origin - bot_waypoint_queue_goal.origin);
                LOG_DEBUG(this.netname, " evaluating ", bot_waypoint_queue_goal.classname, " with distance ", ftos(d));
                if(tracewalk(bot_waypoint_queue_goal, this.origin, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), bot_waypoint_queue_goal.origin, bot_navigation_movemode))
                {
index 5f2c86e..3f8e4c4 100644 (file)
@@ -2,7 +2,6 @@
 
 .float cmd_floodtime;
 .float cmd_floodcount;
-.float lms_spectate_warning;
 
 string MapVote_Suggest(entity this, string m);
 
index 9170662..3b33b02 100644 (file)
@@ -894,3 +894,18 @@ MUTATOR_HOOKABLE(ForbidWeaponUse, EV_ForbidWeaponUse);
     /** keepvelocity? */        i(bool, MUTATOR_ARGV_2_bool) \
     /**/
 MUTATOR_HOOKABLE(CopyBody, EV_CopyBody);
+
+/** called when sending a chat message, ret argument can be changed to prevent the message */
+#define EV_ChatMessage(i, o) \
+    /** sender */ i(entity, MUTATOR_ARGV_0_entity) \
+    /** ret */ i(int, MUTATOR_ARGV_1_int) \
+    /**/ o(int, MUTATOR_ARGV_1_int) \
+    /**/
+MUTATOR_HOOKABLE(ChatMessage, EV_ChatMessage);
+
+/** return true to prevent sending a chat (private, team or regular) message from reaching a certain player */
+#define EV_ChatMessageTo(i, o) \
+    /** destination player */ i(entity, MUTATOR_ARGV_0_entity) \
+    /** sender */ i(entity, MUTATOR_ARGV_1_entity) \
+    /**/
+MUTATOR_HOOKABLE(ChatMessageTo, EV_ChatMessageTo);
index 608517f..c325a35 100644 (file)
@@ -72,7 +72,10 @@ int WinningCondition_LMS()
                                // a winner!
                                // and assign him his first place
                                PlayerScore_Add(head, SP_LMS_RANK, 1);
-                               return WINNING_YES;
+                               if(warmup_stage)
+                                       return WINNING_NO;
+                               else
+                                       return WINNING_YES;
                        }
                }
        }
@@ -110,26 +113,50 @@ int WinningCondition_LMS()
 MUTATOR_HOOKFUNCTION(lms, reset_map_global)
 {
        lms_lowest_lives = 999;
-       lms_next_place = player_count;
 }
 
 MUTATOR_HOOKFUNCTION(lms, reset_map_players)
 {
-       if(restart_mapalreadyrestarted || (time < game_starttime))
-       FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(PlayerScore_Add(it, SP_LMS_LIVES, LMS_NewPlayerLives())));
+       FOREACH_CLIENT(true, {
+               TRANSMUTE(Player, it);
+               it.frags = FRAGS_PLAYER;
+               PlayerScore_Add(it, SP_LMS_LIVES, LMS_NewPlayerLives());
+               PutClientInServer(it);
+       });
 }
 
 MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
 {
        entity player = M_ARGV(0, entity);
 
-       // player is dead and becomes observer
-       // FIXME fix LMS scoring for new system
-       if(PlayerScore_Add(player, SP_LMS_RANK, 0) > 0)
-       {
+       if(player.frags == FRAGS_SPECTATOR)
                TRANSMUTE(Observer, player);
+       else
+       {
+               float tl = PlayerScore_Add(player, SP_LMS_LIVES, 0);
+               if(tl < lms_lowest_lives)
+                       lms_lowest_lives = tl;
+               if(tl <= 0)
+                       TRANSMUTE(Observer, player);
+               if(warmup_stage)
+                       PlayerScore_Add(player, SP_LMS_RANK, -PlayerScore_Add(player, SP_LMS_RANK, 0));
+       }
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(warmup_stage)
+               return false;
+       if(player.frags == FRAGS_SPECTATOR)
+               return true;
+       if(PlayerScore_Add(player, SP_LMS_LIVES, 0) <= 0)
+       {
                Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES);
+               return true;
        }
+       return false;
 }
 
 MUTATOR_HOOKFUNCTION(lms, PlayerDies)
@@ -141,11 +168,47 @@ MUTATOR_HOOKFUNCTION(lms, PlayerDies)
 
 void lms_RemovePlayer(entity player)
 {
-       // Only if the player cannot play at all
-       if(PlayerScore_Add(player, SP_LMS_RANK, 0) == 666)
-               player.frags = FRAGS_SPECTATOR;
-       else
-               player.frags = FRAGS_LMS_LOSER;
+       static int quitters = 0;
+       float player_rank = PlayerScore_Add(player, SP_LMS_RANK, 0);
+       if (!player_rank)
+       {
+               int pl_cnt = 0;
+               FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
+               if (player.lms_spectate_warning != 2)
+               {
+                       player.frags = FRAGS_LMS_LOSER;
+                       PlayerScore_Add(player, SP_LMS_RANK, pl_cnt + 1);
+               }
+               else
+               {
+                       lms_lowest_lives = 999;
+                       FOREACH_CLIENT(true, {
+                               if (it.frags == FRAGS_LMS_LOSER)
+                               {
+                                       float it_rank = PlayerScore_Add(it, SP_LMS_RANK, 0);
+                                       if (it_rank > player_rank && it_rank <= 256)
+                                               PlayerScore_Add(it, SP_LMS_RANK, -1);
+                                       lms_lowest_lives = 0;
+                               }
+                               else if (it.frags != FRAGS_SPECTATOR)
+                               {
+                                       float tl = PlayerScore_Add(it, SP_LMS_LIVES, 0);
+                                       if(tl < lms_lowest_lives)
+                                               lms_lowest_lives = tl;
+                               }
+                       });
+                       PlayerScore_Add(player, SP_LMS_RANK, 665 - quitters); // different from 666
+                       if(!warmup_stage)
+                       {
+                               PlayerScore_Add(player, SP_LMS_LIVES, -PlayerScore_Add(player, SP_LMS_LIVES, 0));
+                               ++quitters;
+                       }
+                       player.frags = FRAGS_LMS_LOSER;
+                       TRANSMUTE(Observer, player);
+               }
+               if (pl_cnt == 2 && !warmup_stage) // a player is forfeiting leaving only one player
+                       lms_lowest_lives = 0; // end the game now!
+       }
 
        if(player.killcount != FRAGS_SPECTATOR)
                if(PlayerScore_Add(player, SP_LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
@@ -178,7 +241,7 @@ MUTATOR_HOOKFUNCTION(lms, ClientConnect)
 
        if(PlayerScore_Add(player, SP_LMS_LIVES, LMS_NewPlayerLives()) <= 0)
        {
-               PlayerScore_Add(player, SP_LMS_RANK, 666);
+               PlayerScore_Add(player, SP_LMS_RANK, 666); // mark as forced spectator for the hud code
                player.frags = FRAGS_SPECTATOR;
        }
 }
@@ -208,21 +271,21 @@ MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
 {
        entity frag_target = M_ARGV(1, entity);
 
-       // remove a life
-       float tl;
-       tl = PlayerScore_Add(frag_target, SP_LMS_LIVES, -1);
-       if(tl < lms_lowest_lives)
-               lms_lowest_lives = tl;
-       if(tl <= 0)
+       if (!warmup_stage)
        {
-               if(!lms_next_place)
-                       lms_next_place = player_count;
-               else
-                       lms_next_place = min(lms_next_place, player_count);
-               PlayerScore_Add(frag_target, SP_LMS_RANK, lms_next_place); // won't ever spawn again
-               --lms_next_place;
+               // remove a life
+               int tl = PlayerScore_Add(frag_target, SP_LMS_LIVES, -1);
+               if(tl < lms_lowest_lives)
+                       lms_lowest_lives = tl;
+               if(tl <= 0)
+               {
+                       int pl_cnt = 0;
+                       FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
+                       frag_target.frags = FRAGS_LMS_LOSER;
+                       PlayerScore_Add(frag_target, SP_LMS_RANK, pl_cnt);
+               }
        }
-       M_ARGV(2, float) = 0;
+       M_ARGV(2, float) = 0; // frag score
 
        return true;
 }
@@ -301,8 +364,8 @@ MUTATOR_HOOKFUNCTION(lms, ItemTouch)
 MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
 {
        FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(
-               ++M_ARGV(0, int);
-               ++M_ARGV(1, int);
+               ++M_ARGV(0, int); // activerealplayers
+               ++M_ARGV(1, int); // realplayers
        ));
 
        return true;
@@ -312,17 +375,18 @@ MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
 {
     entity player = M_ARGV(0, entity);
 
-       if(player.lms_spectate_warning)
+       if(warmup_stage || player.lms_spectate_warning)
        {
                // for the forfeit message...
                player.lms_spectate_warning = 2;
-               // mark player as spectator
-               PlayerScore_Add(player, SP_LMS_RANK, 666 - PlayerScore_Add(player, SP_LMS_RANK, 0));
        }
        else
        {
-               player.lms_spectate_warning = 1;
-               sprint(player, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n");
+               if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER)
+               {
+                       player.lms_spectate_warning = 1;
+                       sprint(player, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n");
+               }
                return MUT_SPECCMD_RETURN;
        }
        return MUT_SPECCMD_CONTINUE;
@@ -363,7 +427,6 @@ void lms_ScoreRules()
 void lms_Initialize()
 {
        lms_lowest_lives = 9999;
-       lms_next_place = 0;
 
        lms_ScoreRules();
 }
index 7bf0126..1f1d2d4 100644 (file)
@@ -2,6 +2,7 @@
 
 #include "../gamemode.qh"
 
+.float lms_spectate_warning;
 #define autocvar_g_lms_lives_override cvar("g_lms_lives_override")
 void lms_Initialize();
 
@@ -34,5 +35,4 @@ REGISTER_MUTATOR(lms, false)
 
 // lives related defs
 float lms_lowest_lives;
-float lms_next_place;
 float LMS_NewPlayerLives();
index ce71f6b..0454731 100644 (file)
@@ -913,6 +913,9 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
                ret = 1;
        }
 
+       MUTATOR_CALLHOOK(ChatMessage, source, ret);
+       ret = M_ARGV(1, int);
+
        if(sourcemsgstr != "" && ret != 0)
        {
                if(ret < 0) // faked message, because the player is muted
@@ -924,16 +927,19 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
                else if(privatesay) // private message, between 2 people only
                {
                        sprint(source, sourcemsgstr);
-                       sprint(privatesay, msgstr);
                        if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled
-                       if(cmsgstr != "")
-                               centerprint(privatesay, cmsgstr);
+                       if(!MUTATOR_CALLHOOK(ChatMessageTo, privatesay, source))
+                       {
+                               sprint(privatesay, msgstr);
+                               if(cmsgstr != "")
+                                       centerprint(privatesay, cmsgstr);
+                       }
                }
                else if ( teamsay && source.active_minigame )
                {
                        sprint(source, sourcemsgstr);
                        dedicated_print(msgstr); // send to server console too
-                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && it.active_minigame == source.active_minigame, sprint(it, msgstr));
+                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && it.active_minigame == source.active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
                }
                else if(teamsay > 0) // team message, only sent to team mates
                {
@@ -941,7 +947,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, {
+                       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
                                sprint(it, msgstr);
                                if(cmsgstr != "")
                                        centerprint(it, cmsgstr);
@@ -951,7 +957,7 @@ 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, sprint(it, msgstr));
+                       FOREACH_CLIENT(!IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
                }
                else
                {
@@ -960,7 +966,7 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
                 dedicated_print(msgstr); // send to server console too
                 MX_Say(strcat(playername(source), "^7: ", msgin));
             }
-            FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source, sprint(it, msgstr));
+            FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
         }
        }
 
index d7d8fc9..7abe976 100644 (file)
@@ -23,7 +23,7 @@ void Send_WeaponComplain(entity e, float wpn, float type)
 void Weapon_whereis(Weapon this, entity cl)
 {
        if (!autocvar_g_showweaponspawns) return;
-       IL_EACH(g_items, it.weapon == this.m_id,
+       IL_EACH(g_items, it.weapon == this.m_id && (it.ItemStatus & ITS_AVAILABLE),
        {
                if (it.classname == "droppedweapon" && autocvar_g_showweaponspawns < 2)
                        continue;