]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/mutators/mutator/gamemode_ctf.qc
Implement symmetrical editing of waypoints for ctf maps with rotational symmetry...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator / gamemode_ctf.qc
index 534b41618f81f946c667a0409dd9618318ffe889..4690cd8f5820dedbe682d8bc1a7c0ab036eabcef 100644 (file)
@@ -479,6 +479,7 @@ void ctf_Handle_Throw(entity player, entity receiver, int droptype)
        flag.solid = SOLID_TRIGGER;
        flag.ctf_dropper = player;
        flag.ctf_droptime = time;
+       navigation_dynamicgoal_set(flag);
 
        flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
 
@@ -569,6 +570,7 @@ void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
 
        if(!player) { return; } // without someone to give the reward to, we can't possibly cap
        if(CTF_DIFFTEAM(player, flag)) { return; }
+       if((flag.cnt || enemy_flag.cnt) && flag.cnt != enemy_flag.cnt) { return; } // this should catch some edge cases (capturing grouped flag at ungrouped flag disallowed etc)
 
        if(ctf_oneflag)
        for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
@@ -596,8 +598,14 @@ void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
        }
 
        // scoring
-       PlayerTeamScore_AddScore(player, ((flag.score_capture) ? flag.score_capture : autocvar_g_ctf_score_capture));
-       PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
+       float pscore = 0;
+       if(enemy_flag.score_capture || flag.score_capture)
+               pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
+       PlayerTeamScore_AddScore(player, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
+       float capscore = 0;
+       if(enemy_flag.score_team_capture || flag.score_team_capture)
+               capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
+       PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, ((capscore) ? capscore : 1));
 
        old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
        new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
@@ -615,7 +623,7 @@ void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
                if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
 
                if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
-                       { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, ((flag.score_assist) ? flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
+                       { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
        }
 
        // reset the flag
@@ -914,11 +922,11 @@ void ctf_FlagThink(entity this)
                FOREACH_CLIENT(true, LAMBDA(ctf_CaptureShield_Update(it, 1))); // release shield only
 
        // sanity checks
-       if(this.mins != CTF_FLAG.m_mins || this.maxs != CTF_FLAG.m_maxs) { // reset the flag boundaries in case it got squished
+       if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
                LOG_TRACE("wtf the flag got squashed?");
-               tracebox(this.origin, CTF_FLAG.m_mins, CTF_FLAG.m_maxs, this.origin, MOVE_NOMONSTERS, this);
+               tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
                if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
-                       setsize(this, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
+                       setsize(this, this.m_mins, this.m_maxs);
        }
 
        // main think method
@@ -1190,6 +1198,7 @@ void ctf_RespawnFlag(entity flag)
        flag.ctf_pickuptime = 0;
        flag.ctf_droptime = 0;
        flag.ctf_flagdamaged_byworld = false;
+       navigation_dynamicgoal_unset(flag);
 
        ctf_CheckStalemate();
 }
@@ -1204,7 +1213,12 @@ void ctf_Reset(entity this)
 
 bool ctf_FlagBase_Customize(entity this, entity client)
 {
-       if(client.flagcarried && CTF_SAMETEAM(client, client.flagcarried))
+       entity e = WaypointSprite_getviewentity(client);
+       entity wp_owner = this.owner;
+       entity flag = e.flagcarried;
+       if(flag && CTF_SAMETEAM(e, flag))
+               return false;
+       if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
                return false;
        return true;
 }
@@ -1213,8 +1227,7 @@ void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map
 {
        // bot waypoints
        waypoint_spawnforitem_force(this, this.origin);
-       this.nearestwaypointtimeout = 0; // activate waypointing again
-       this.bot_basewaypoint = this.nearestwaypoint;
+       navigation_dynamicgoal_init(this, true);
 
        // waypointsprites
        entity basename;
@@ -1275,7 +1288,7 @@ void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag e
 
        // crudely force them all to 0
        if(autocvar_g_ctf_score_ignore_fields)
-               flag.score_assist = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
+               flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
 
        string teamname = Static_Team_ColorName_Lower(teamnumber);
        // appearence
@@ -1305,7 +1318,9 @@ void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag e
 
        // appearence
        _setmodel(flag, flag.model); // precision set below
-       setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
+       setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
+       flag.m_mins = flag.mins; // store these for squash checks
+       flag.m_maxs = flag.maxs;
        setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
 
        if(autocvar_g_ctf_flag_glowtrails)
@@ -1360,7 +1375,7 @@ void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag e
 
 // NOTE: LEGACY CODE, needs to be re-written!
 
-void havocbot_calculate_middlepoint()
+void havocbot_ctf_calculate_middlepoint()
 {
        entity f;
        vector s = '0 0 0';
@@ -1377,8 +1392,23 @@ void havocbot_calculate_middlepoint()
        }
        if(!n)
                return;
-       havocbot_ctf_middlepoint = s / n;
-       havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
+
+       havocbot_middlepoint = s / n;
+       havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
+
+       havocbot_symmetryaxys_equation = '0 0 0';
+       if(n == 2)
+       {
+               // for symmetrical editing of waypoints
+               entity f1 = ctf_worldflaglist;
+               entity f2 = f1.ctf_worldflagnext;
+               float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
+               float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
+               havocbot_symmetryaxys_equation.x = m;
+               havocbot_symmetryaxys_equation.y = q;
+       }
+       // store number of flags in this otherwise unused vector component
+       havocbot_symmetryaxys_equation.z = n;
 }
 
 
@@ -1439,6 +1469,8 @@ int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
        return c;
 }
 
+// unused
+#if 0
 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
 {
        entity head;
@@ -1452,6 +1484,7 @@ void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
        if (head)
                navigation_routerating(this, head, ratingscale, 10000);
 }
+#endif
 
 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
 {
@@ -1460,7 +1493,15 @@ void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
        while (head)
        {
                if (CTF_SAMETEAM(this, head))
+               {
+                       if (this.flagcarried)
+                       if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
+                       {
+                               head = head.ctf_worldflagnext; // skip base if it has a different group
+                               continue;
+                       }
                        break;
+               }
                head = head.ctf_worldflagnext;
        }
        if (!head)
@@ -1576,9 +1617,6 @@ void havocbot_ctf_reset_role(entity this)
        if(IS_DEAD(this))
                return;
 
-       if(havocbot_ctf_middlepoint == '0 0 0')
-               havocbot_calculate_middlepoint();
-
        // Check ctf flags
        if (this.flagcarried)
        {
@@ -1615,13 +1653,13 @@ void havocbot_ctf_reset_role(entity this)
 
        // Evaluate best position to take
        // Count mates on middle position
-       cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
+       cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
 
        // Count mates on defense position
-       cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
+       cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
 
        // Count mates on offense position
-       coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
+       coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
 
        if(cdefense<=coffense)
                havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
@@ -1660,7 +1698,7 @@ void havocbot_role_ctf_carrier(entity this)
 
                navigation_goalrating_end(this);
 
-               if (this.navigation_hasgoals)
+               if (this.goalentity)
                        this.havocbot_cantfindflag = time + 10;
                else if (time > this.havocbot_cantfindflag)
                {
@@ -1903,15 +1941,15 @@ void havocbot_role_ctf_middle(entity this)
        {
                vector org;
 
-               org = havocbot_ctf_middlepoint;
+               org = havocbot_middlepoint;
                org.z = this.origin.z;
 
                this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
                havocbot_goalrating_ctf_ourstolenflag(this, 50000);
                havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
-               havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
+               havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
                havocbot_goalrating_items(this, 2500, this.origin, 10000);
                havocbot_goalrating_ctf_enemybase(this, 2500);
                navigation_goalrating_end(this);
@@ -1952,11 +1990,7 @@ void havocbot_role_ctf_defense(entity this)
        }
        if (this.bot_strategytime < time)
        {
-               float mp_radius;
-               vector org;
-
-               org = mf.dropped_origin;
-               mp_radius = havocbot_ctf_middlepoint_radius;
+               vector org = mf.dropped_origin;
 
                this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
                navigation_goalrating_start(this);
@@ -1980,9 +2014,9 @@ void havocbot_role_ctf_defense(entity this)
                        havocbot_goalrating_ctf_ourbase(this, 30000);
 
                havocbot_goalrating_ctf_ourstolenflag(this, 20000);
-               havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
-               havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
-               havocbot_goalrating_items(this, 10000, org, mp_radius);
+               havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
+               havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
+               havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
                havocbot_goalrating_items(this, 5000, this.origin, 10000);
                navigation_goalrating_end(this);
        }
@@ -2043,6 +2077,7 @@ MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
        entity player = M_ARGV(0, entity);
 
        int t = 0, t2 = 0, t3 = 0;
+       bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC)
 
        // initially clear items so they can be set as necessary later.
        player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING                | CTF_RED_FLAG_TAKEN            | CTF_RED_FLAG_LOST
@@ -2055,11 +2090,11 @@ MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
        // scan through all the flags and notify the client about them
        for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
        {
-               if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING;                t2 = CTF_RED_FLAG_TAKEN;                t3 = CTF_RED_FLAG_LOST; }
-               if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING;               t2 = CTF_BLUE_FLAG_TAKEN;               t3 = CTF_BLUE_FLAG_LOST; }
-               if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING;     t2 = CTF_YELLOW_FLAG_TAKEN;             t3 = CTF_YELLOW_FLAG_LOST; }
-               if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING;               t2 = CTF_PINK_FLAG_TAKEN;               t3 = CTF_PINK_FLAG_LOST; }
-               if(flag.team == 0)                      { t = CTF_NEUTRAL_FLAG_CARRYING;        t2 = CTF_NEUTRAL_FLAG_TAKEN;    t3 = CTF_NEUTRAL_FLAG_LOST; player.ctf_flagstatus |= CTF_FLAG_NEUTRAL; }
+               if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING;              t2 = CTF_RED_FLAG_TAKEN;                t3 = CTF_RED_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING;             t2 = CTF_BLUE_FLAG_TAKEN;               t3 = CTF_BLUE_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING;   t2 = CTF_YELLOW_FLAG_TAKEN;             t3 = CTF_YELLOW_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING;             t2 = CTF_PINK_FLAG_TAKEN;               t3 = CTF_PINK_FLAG_LOST; }
+               if(flag.team == 0 && !b5)                  { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING;  t2 = CTF_NEUTRAL_FLAG_TAKEN;    t3 = CTF_NEUTRAL_FLAG_LOST; player.ctf_flagstatus |= CTF_FLAG_NEUTRAL; }
 
                switch(flag.ctf_status)
                {
@@ -2695,6 +2730,8 @@ void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait f
                if(tmp_entity.team == 0) { ctf_oneflag = true; }
        }
 
+       havocbot_ctf_calculate_middlepoint();
+
        if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
        {
                ctf_teams = 0; // so set the default red and blue teams