]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator/gamemode_infection.qc
Merge branch 'master' into TimePath/gametypes/infection
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator / gamemode_infection.qc
1 #ifdef IMPLEMENTATION
2
3 float autocvar_g_infection_round_timelimit;
4 float autocvar_g_infection_warmup;
5 int autocvar_g_infection_teams;
6 bool autocvar_g_infection_conversions;
7
8 int infection_players_count;
9
10 .int infectioncolor;
11 .int infectioncolor_original;
12
13 const int INFECTIONTEAM_NONE = -1;
14 const int INFECTIONTEAM_UNDEFINED = -2;
15
16 // safe team comparisons
17 #define INF_SAMETEAM(a, b) (a.infectioncolor == b.infectioncolor)
18 #define INF_DIFFTEAM(a, b) (a.infectioncolor != b.infectioncolor)
19
20 void infection_SetColor(entity e, int _color)
21 {
22         e.infectioncolor = _color;
23         setcolor(e, (_color << 4) | _color);
24 }
25
26 string color_owner_green, color_owner_red;
27 // find whose color a player is carrying, true if it's his own, otherwise set color_owner_* to the other owner
28 void infection_GetColorOwner(entity me)
29 {
30         if (me.infectioncolor == me.infectioncolor_original)
31         {
32                 color_owner_green = "^2your own";
33                 color_owner_red = "^1their own";
34                 return;
35         }
36         FOREACH_CLIENT(IS_PLAYER(it) && me.infectioncolor == it.infectioncolor_original, {
37         color_owner_green = sprintf("%s^2's", it.netname);
38         color_owner_red = sprintf("%s^1's", it.netname);
39         break;
40         });
41 }
42
43 void infection_Assign(bool late)
44 {
45     SELFPARAM();
46         if (!late)
47         {
48                 int infection_coloridx = 0;
49                 FOREACH_CLIENT(IS_PLAYER(it), {
50                         if (infection_coloridx < autocvar_g_infection_teams)  // Limit alphas
51                                 it.infectioncolor_original = infection_coloridx;
52                         infection_SetColor(it, infection_coloridx++ % bound(0, autocvar_g_infection_teams, 15));
53                 });
54         }
55         else
56         {
57                 // Spawn too late, give player a random color
58                 int color = 15;
59                 int skip = rint(random() * (infection_players_count - 1));  // Ignore self
60                 entity e = NULL;
61                 FOREACH_CLIENT(IS_PLAYER(it), {
62                         if (it == this || IS_OBSERVER(it)) continue;
63                         if (!skip-- > 0) {
64                             e = it;
65                 break;
66                         }
67                 });
68                 LOG_DEBUGF("[INFECTION] copying %s's color", e.netname);
69                 color = e.infectioncolor;
70                 this.infectioncolor_original = INFECTIONTEAM_NONE;  // Can't win if player didn't spawn during the round delay
71                 infection_SetColor(this, color);
72         }
73 }
74
75 bool infection_CheckTeams()
76 {
77         return infection_players_count > 1;
78 }
79
80 bool infection_CheckWinner()
81 {
82         if (infection_players_count <= 1) return false;                           // There can be no winner
83
84         if (0 < round_handler_GetEndTime() && round_handler_GetEndTime() <= time) // Round over
85         {
86                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
87                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
88                 round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit);
89                 return true;
90         }
91
92         // Check if only one color remains
93         int previnfectioncolor = -1;
94         bool we_have_a_winner = true;  // until loop below proves us wrong
95         FOREACH_CLIENT(IS_PLAYER(it), {
96                 // All infection colors are the same if we have a winner
97                 if (previnfectioncolor != -1 && previnfectioncolor != it.infectioncolor)
98                 {
99                         // In this case we still have more than one color alive
100                         we_have_a_winner = false;
101                         break;
102                 }
103                 previnfectioncolor = it.infectioncolor;
104         });
105
106         if (!we_have_a_winner) return false;
107
108         // Who is it?
109         FOREACH_CLIENT(IS_PLAYER(it) && it.infectioncolor == it.infectioncolor_original, {
110         UpdateFrags(it, 10);  // Bonus points
111         Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, it.netname);
112         Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, it.netname);
113         });
114
115         round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit);
116         return true;
117 }
118
119 void infection_RoundStart()
120 {
121         infection_Assign(false);
122         infection_CheckWinner();
123 }
124
125 MUTATOR_HOOKFUNCTION(inf, PlayerDies)
126 {
127         infection_CheckWinner();
128         return true;
129 }
130
131 bool inf_RemovePlayer();
132 MUTATOR_HOOKFUNCTION(inf, MakePlayerObserver)
133 {
134         return inf_RemovePlayer();
135 }
136 MUTATOR_HOOKFUNCTION(inf, ClientDisconnect)
137 {
138         return inf_RemovePlayer();
139 }
140 bool inf_RemovePlayer()
141 {
142     SELFPARAM();
143         if (!IS_PLAYER(this)) return true;  // Wasn't playing
144
145         infection_players_count--;
146
147         this.infectioncolor_original = INFECTIONTEAM_UNDEFINED;
148
149         // Grant alpha status to next of kin
150         FOREACH_CLIENT(IS_PLAYER(it) && it.infectioncolor == this.infectioncolor_original, {
151         it.infectioncolor_original = this.infectioncolor;
152         centerprint(it, "^2You are now an alpha.\n");
153         break;
154         });
155
156         infection_CheckWinner();
157         return true;
158 }
159
160 MUTATOR_HOOKFUNCTION(inf, PlayerSpawn)
161 {
162     SELFPARAM();
163         if (this.infectioncolor_original != INFECTIONTEAM_UNDEFINED) return true;  // Wasn't observing
164         infection_players_count++;
165
166         infection_Assign(true);
167
168         return true;
169 }
170
171 MUTATOR_HOOKFUNCTION(inf, GiveFragsForKill, CBC_ORDER_FIRST)
172 {
173         frag_score = 0;
174         infection_GetColorOwner(frag_attacker);
175         // If this is the first time we die... (our infectioncolor remained unchanged)
176         if (autocvar_g_infection_conversions && frag_target.infectioncolor == frag_target.infectioncolor_original)
177         {
178             // check other players and see if they have our original infection color
179                 FOREACH_CLIENT(IS_PLAYER(it) && INF_SAMETEAM(it, frag_target), {
180             // If so, remove it, our infection color has now "died out" from this round and we can not win anymore.
181             // The attacker will "summon" all of our previously fragged targets, and also us.
182             centerprint(it, sprintf("^1Your alpha ^7%s^1 was infected by ^7%s^1 with ^7%s^1 color.\n",
183                 frag_target.netname, frag_attacker.netname, color_owner_red));
184             infection_SetColor(it, frag_attacker.infectioncolor);
185             frag_score++;
186                 });
187         }
188         else
189         {
190                 infection_SetColor(frag_target, frag_attacker.infectioncolor);
191                 frag_score++;
192         }
193
194         string target = frag_target.netname, attacker = frag_attacker.netname;
195
196         centerprint(frag_attacker, sprintf("^2You infected ^7%s^2 with ^7%s^2 color.\n", target, color_owner_green));
197         centerprint(frag_target, sprintf("^1You were infected by ^7%s^1 with ^7%s^1 color.\n", attacker, color_owner_red));
198
199         bprint(sprintf("^7%s^1 was infected by ^7%s^1 with ^7%s^1 color.\n", frag_target.netname, attacker,
200                 color_owner_red));
201
202         return true;
203 }
204
205 MUTATOR_HOOKFUNCTION(inf, PlayerPreThink, CBC_ORDER_FIRST)
206 {
207     SELFPARAM();
208         if (IS_PLAYER(this))
209         {
210                 // Prevent cheating by changing player colors
211                 infection_SetColor(this, round_handler_IsRoundStarted()
212                         ? this.infectioncolor
213                         : 15);
214         }
215         return true;
216 }
217
218 MUTATOR_HOOKFUNCTION(inf, PlayerDamage_Calculate)
219 {
220         if (IS_PLAYER(frag_attacker)                    // Allow environment damage
221             && frag_attacker != frag_target             // Allow self damage
222             && INF_SAMETEAM(frag_attacker, frag_target) // Block friendly fire
223            )
224         {
225                 frag_damage = 0;
226                 frag_force = '0 0 0';
227         }
228         return false;
229 }
230
231 MUTATOR_HOOKFUNCTION(inf, BotShouldAttack)
232 {
233     SELFPARAM();
234         return INF_SAMETEAM(checkentity, this);
235 }
236
237 MUTATOR_HOOKFUNCTION(inf, ClientConnect)
238 {
239     SELFPARAM();
240         this.infectioncolor_original = INFECTIONTEAM_UNDEFINED;
241         stuffcmd(this, "settemp cl_forceplayercolors 0\n");
242
243         return false;
244 }
245
246 REGISTER_MUTATOR(inf, false)
247 {
248         MUTATOR_ONADD
249         {
250                 if (time > 1)  // game loads at time 1
251                         error("This is a game type and it cannot be added at runtime.");
252                 infection_players_count = 0;
253                 round_handler_Spawn(infection_CheckTeams, infection_CheckWinner, infection_RoundStart);
254                 round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit);
255         }
256
257         MUTATOR_ONROLLBACK_OR_REMOVE
258         {
259                 // we actually cannot roll back inf_Initialize here
260                 // BUT: we don't need to! If this gets called, adding always
261                 // succeeds.
262         }
263
264         MUTATOR_ONREMOVE
265         {
266                 print("This is a game type and it cannot be removed at runtime.");
267                 return -1;
268         }
269
270         return 0;
271 }
272
273 #endif