1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: cl_ctf.cpp 4542 2014-02-09 17:39:42Z dr_sean $
5 //
6 // Copyright (C) 2006-2014 by The Odamex Team.
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License
10 // as published by the Free Software Foundation; either version 2
11 // of the License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
17 //
18 // DESCRIPTION:
19 // Client-side CTF Implementation
20 //
21 //-----------------------------------------------------------------------------
22
23 #include "doomstat.h"
24 #include "c_console.h"
25 #include "c_dispatch.h"
26 #include "cl_main.h"
27 #include "w_wad.h"
28 #include "z_zone.h"
29 #include "v_video.h"
30 #include "p_local.h"
31 #include "p_inter.h"
32 #include "p_ctf.h"
33 #include "p_mobj.h"
34 #include "st_stuff.h"
35 #include "s_sound.h"
36 #include "v_text.h"
37
38 flagdata CTFdata[NUMFLAGS];
39 int TEAMpoints[NUMFLAGS];
40
41 static int tintglow = 0;
42
43 const char *team_names[NUMTEAMS + 2] =
44 {
45 "BLUE", "RED", "", ""
46 };
47
48 // denis - this is a lot clearer than doubly nested switches
49 static mobjtype_t flag_table[NUMFLAGS][NUMFLAGSTATES] =
50 {
51 {MT_BFLG, MT_BDWN, MT_BCAR},
52 {MT_RFLG, MT_RDWN, MT_RCAR}
53 };
54
55 EXTERN_CVAR (screenblocks)
EXTERN_CVAR(st_scale)56 EXTERN_CVAR (st_scale)
57 EXTERN_CVAR (hud_gamemsgtype)
58 EXTERN_CVAR (hud_heldflag)
59
60 //
61 // CTF_Connect
62 // Receive states of all flags
63 //
64 void CTF_Connect()
65 {
66 size_t i;
67
68 // clear player flags client may have imagined
69 for (Players::iterator it = players.begin();it != players.end();++it)
70 for(size_t j = 0; j < NUMFLAGS; j++)
71 it->flags[j] = false;
72
73 for(i = 0; i < NUMFLAGS; i++)
74 {
75 CTFdata[i].flagger = 0;
76 CTFdata[i].state = (flag_state_t)MSG_ReadByte();
77 byte flagger = MSG_ReadByte();
78
79 if(CTFdata[i].state == flag_carried)
80 {
81 player_t &player = idplayer(flagger);
82
83 if(validplayer(player))
84 CTF_CarryFlag(player, (flag_t)i);
85 }
86 }
87 }
88
89 //
90 // [Toke - CTF] CL_CTFEvent
91 // Deals with CTF specific network data
92 //
CL_CTFEvent(void)93 void CL_CTFEvent (void)
94 {
95 flag_score_t event = (flag_score_t)MSG_ReadByte();
96
97 if(event == SCORE_NONE) // CTF state refresh
98 {
99 CTF_Connect();
100 return;
101 }
102
103 flag_t flag = (flag_t)MSG_ReadByte();
104 player_t &player = idplayer(MSG_ReadByte());
105 int points = MSG_ReadLong();
106
107 if(validplayer(player))
108 player.points = points;
109
110 for(size_t i = 0; i < NUMFLAGS; i++)
111 TEAMpoints[i] = MSG_ReadLong ();
112
113 switch(event)
114 {
115 default:
116 case SCORE_NONE:
117 case SCORE_REFRESH:
118 case SCORE_KILL:
119 case SCORE_BETRAYAL:
120 case SCORE_CARRIERKILL:
121 break;
122
123 case SCORE_GRAB:
124 case SCORE_FIRSTGRAB:
125 case SCORE_MANUALRETURN:
126 if(validplayer(player))
127 {
128 CTF_CarryFlag(player, flag);
129 if (player.id == displayplayer().id)
130 player.bonuscount = BONUSADD;
131 }
132 break;
133
134 case SCORE_CAPTURE:
135 if (validplayer(player))
136 {
137 player.flags[flag] = 0;
138 }
139
140 CTFdata[flag].flagger = 0;
141 CTFdata[flag].state = flag_home;
142 if(CTFdata[flag].actor)
143 CTFdata[flag].actor->Destroy();
144 break;
145
146 case SCORE_RETURN:
147 if (validplayer(player))
148 {
149 player.flags[flag] = 0;
150 }
151
152 CTFdata[flag].flagger = 0;
153 CTFdata[flag].state = flag_home;
154 if(CTFdata[flag].actor)
155 CTFdata[flag].actor->Destroy();
156 break;
157
158 case SCORE_DROP:
159 if (validplayer(player))
160 {
161 player.flags[flag] = 0;
162 }
163
164 CTFdata[flag].flagger = 0;
165 CTFdata[flag].state = flag_dropped;
166 if(CTFdata[flag].actor)
167 CTFdata[flag].actor->Destroy();
168 break;
169 }
170
171 // [AM] Play CTF sound, moved from server.
172 CTF_Sound(flag, event);
173
174 // [AM] Show CTF message.
175 CTF_Message(flag, event);
176 }
177
178 // CTF_CheckFlags
179 // [Toke - CTF - carry]
180 // Checks player for flags
181 //
CTF_CheckFlags(player_t & player)182 void CTF_CheckFlags (player_t &player)
183 {
184 for(size_t i = 0; i < NUMFLAGS; i++)
185 {
186 if(player.flags[i])
187 {
188 player.flags[i] = false;
189 CTFdata[i].flagger = 0;
190 }
191 }
192 }
193
194 //
195 // CTF_TossFlag
196 // [Toke - CTF - Toss]
197 // Player tosses the flag
198 /* [ML] 04/4/06: Remove flagtossing, too buggy
199 void CTF_TossFlag (void)
200 {
201 MSG_WriteMarker (&net_buffer, clc_ctfcommand);
202
203 if (CTFdata.BlueScreen) CTFdata.BlueScreen = false;
204 if (CTFdata.RedScreen) CTFdata.RedScreen = false;
205 }
206
207 BEGIN_COMMAND (flagtoss)
208 {
209 CTF_TossFlag ();
210 }
211 END_COMMAND (flagtoss)
212 */
213
214 //
215 // [Toke - CTF] CTF_CarryFlag
216 // Spawns a flag on a players location and links the flag to the player
217 //
CTF_CarryFlag(player_t & player,flag_t flag)218 void CTF_CarryFlag (player_t &player, flag_t flag)
219 {
220 if (!validplayer(player))
221 return;
222
223 player.flags[flag] = true;
224 CTFdata[flag].flagger = player.id;
225 CTFdata[flag].state = flag_carried;
226
227 AActor *actor = new AActor(0, 0, 0, flag_table[flag][flag_carried]);
228 CTFdata[flag].actor = actor->ptr();
229
230 CTF_MoveFlags();
231 }
232
233 //
234 // [Toke - CTF] CTF_MoveFlag
235 // Moves the flag that is linked to a player
236 //
CTF_MoveFlags()237 void CTF_MoveFlags ()
238 {
239 // denis - flag is now a boolean
240 for(size_t i = 0; i < NUMFLAGS; i++)
241 {
242 if(CTFdata[i].flagger && CTFdata[i].actor)
243 {
244 player_t &player = idplayer(CTFdata[i].flagger);
245 AActor *flag = CTFdata[i].actor;
246
247 if (!validplayer(player) || !player.mo)
248 {
249 // [SL] 2012-12-13 - Remove a flag if it's being carried but
250 // there's not a valid player carrying it (should not happen)
251 CTFdata[i].flagger = 0;
252 CTFdata[i].state = flag_home;
253 if(CTFdata[i].actor)
254 CTFdata[i].actor->Destroy();
255 continue;
256 }
257
258 unsigned an = player.mo->angle >> ANGLETOFINESHIFT;
259 fixed_t x = (player.mo->x + FixedMul (-2*FRACUNIT, finecosine[an]));
260 fixed_t y = (player.mo->y + FixedMul (-2*FRACUNIT, finesine[an]));
261
262 CL_MoveThing(flag, x, y, player.mo->z);
263 }
264 else
265 {
266 // [AM] The flag isn't actually being held by anybody, so if
267 // anything is in CTFdata[i].actor it's a ghost and should
268 // be cleaned up.
269 if(CTFdata[i].actor)
270 CTFdata[i].actor->Destroy();
271 }
272 }
273 }
274
TintScreen(int color)275 void TintScreen(int color)
276 {
277 // draw border around the screen excluding the status bar
278 // NOTE: status bar is not currently drawn when spectating
279 if (screenblocks < 11 && !consoleplayer().spectator)
280 {
281 screen->Clear (0,
282 0,
283 screen->width / 100,
284 screen->height - ST_HEIGHT,
285 color);
286
287 screen->Clear (0,
288 0,
289 screen->width,
290 screen->height / 100,
291 color);
292
293 screen->Clear (screen->width - (screen->width / 100),
294 0,
295 screen->width,
296 screen->height - ST_HEIGHT,
297 color);
298
299 screen->Clear (0,
300 (screen->height - ST_HEIGHT) - (screen->height / 100),
301 screen->width,
302 screen->height - ST_HEIGHT,
303 color);
304 }
305
306 // if there's no status bar, draw border around the full screen
307 else
308 {
309 screen->Clear (0,
310 0,
311 screen->width / 100,
312 screen->height,
313 color);
314
315 screen->Clear (0,
316 0,
317 screen->width,
318 screen->height / 100,
319 color);
320
321 screen->Clear (screen->width - (screen->width / 100),
322 0,
323 screen->width,
324 screen->height,
325 color);
326
327 screen->Clear (0,
328 (screen->height) - (screen->height / 100),
329 screen->width,
330 screen->height,
331 color);
332 }
333 }
334
335 //
336 // [Toke - CTF] CTF_RunTics
337 // Runs once per gametic when ctf is enabled
338 //
CTF_RunTics(void)339 void CTF_RunTics (void)
340 {
341
342 // NES - Glowing effect on screen tint.
343 if (tintglow < 90)
344 tintglow++;
345 else
346 tintglow = 0;
347
348 // Move the physical clientside flag sprites
349 CTF_MoveFlags();
350
351 // Don't draw the flag the display player is carrying as it blocks the view.
352 for (size_t flag = 0; flag < NUMFLAGS; flag++)
353 {
354 if (!CTFdata[flag].actor)
355 continue;
356
357 if (CTFdata[flag].flagger == displayplayer().id &&
358 CTFdata[flag].state == flag_carried)
359 {
360 CTFdata[flag].actor->flags2 |= MF2_DONTDRAW;
361 }
362 else
363 {
364 CTFdata[flag].actor->flags2 &= ~MF2_DONTDRAW;
365 }
366 }
367 }
368
369 //
370 // [Toke - CTF - Hud] CTF_DrawHud
371 // Draws the CTF Hud, duH
372 //
CTF_DrawHud(void)373 void CTF_DrawHud (void)
374 {
375 int tintglowtype;
376 bool hasflag = false, hasflags[NUMFLAGS];
377
378 if(sv_gametype != GM_CTF)
379 return;
380
381 player_t &player = displayplayer();
382 for(size_t i = 0; i < NUMFLAGS; i++)
383 {
384 hasflags[i] = false;
385 if(CTFdata[i].state == flag_carried && CTFdata[i].flagger == player.id)
386 {
387 hasflag = true;
388 hasflags[i] = true;
389 }
390 }
391
392 if (!hud_heldflag)
393 return;
394
395 if (hasflag) {
396 palette_t *pal = GetDefaultPalette();
397
398 if (tintglow < 15)
399 tintglowtype = tintglow;
400 else if (tintglow < 30)
401 tintglowtype = 30 - tintglow;
402 else if (tintglow > 45 && tintglow < 60)
403 tintglowtype = tintglow - 45;
404 else if (tintglow >= 60 && tintglow < 75)
405 tintglowtype = 75 - tintglow;
406 else
407 tintglowtype = 0;
408
409 DWORD tintColor = 0;
410 if (hasflags[0] && hasflags[1])
411 {
412 if (tintglow < 15 || tintglow > 60)
413 tintColor = MAKERGB((int)(255/15)*tintglowtype, (int)(255/15)*tintglowtype, 255);
414 else
415 tintColor = MAKERGB(255, (int)(255/15)*tintglowtype, (int)(255/15)*tintglowtype);
416 }
417 else if (hasflags[0])
418 tintColor = MAKERGB((int)(255/15)*tintglowtype, (int)(255/15)*tintglowtype, 255);
419 else if (hasflags[1])
420 tintColor = MAKERGB(255, (int)(255/15)*tintglowtype, (int)(255/15)*tintglowtype);
421
422 if (tintColor != 0)
423 {
424 if (screen->is8bit())
425 TintScreen(BestColor2(pal->basecolors, tintColor, pal->numcolors));
426 else
427 TintScreen(tintColor);
428 }
429 }
430 }
431
operator <<(FArchive & arc,flagdata & flag)432 FArchive &operator<< (FArchive &arc, flagdata &flag)
433 {
434 int netid = flag.actor ? flag.actor->netid : 0;
435
436 arc << flag.flaglocated
437 << netid
438 << flag.flagger
439 << flag.pickup_time
440 << flag.x << flag.y << flag.z
441 << flag.timeout
442 << static_cast<byte>(flag.state)
443 << flag.sb_tick;
444
445 arc << 0;
446
447 return arc;
448 }
449
operator >>(FArchive & arc,flagdata & flag)450 FArchive &operator>> (FArchive &arc, flagdata &flag)
451 {
452 int netid;
453 byte state;
454 int dummy;
455
456 arc >> flag.flaglocated
457 >> netid
458 >> flag.flagger
459 >> flag.pickup_time
460 >> flag.x >> flag.y >> flag.z
461 >> flag.timeout
462 >> state
463 >> flag.sb_tick;
464
465 arc >> dummy;
466
467 flag.state = static_cast<flag_state_t>(state);
468 flag.actor = AActor::AActorPtr();
469
470 return arc;
471 }
472
473 // [AM] Clientside CTF sounds.
474 // 0: Team-agnostic SFX
475 // 1: Own team announcer
476 // 2: Enemy team announcer
477 // 3: Blue team announcer
478 // 4: Red team announcer
479 static const char *flag_sound[NUM_CTF_SCORE][6] = {
480 {"", "", "", "", "", ""}, // NONE
481 {"", "", "", "", "", ""}, // REFRESH
482 {"", "", "", "", "", ""}, // KILL
483 {"", "", "", "", "", ""}, // BETRAYAL
484 {"ctf/your/flag/take", "ctf/enemy/flag/take", "vox/your/flag/take", "vox/enemy/flag/take", "vox/blue/flag/take", "vox/red/flag/take"}, // GRAB
485 {"ctf/your/flag/take", "ctf/enemy/flag/take", "vox/your/flag/take", "vox/enemy/flag/take", "vox/blue/flag/take", "vox/red/flag/take"}, // FIRSTGRAB
486 {"", "", "", "", "", ""}, // CARRIERKILL
487 {"ctf/your/flag/return", "ctf/enemy/flag/return", "vox/your/flag/return", "vox/enemy/flag/return", "vox/blue/flag/return", "vox/red/flag/return"}, // RETURN
488 {"ctf/enemy/score", "ctf/your/score", "vox/enemy/score", "vox/your/score", "vox/red/score", "vox/blue/score"}, // CAPTURE
489 {"ctf/your/flag/drop", "ctf/enemy/flag/drop", "vox/your/flag/drop", "vox/enemy/flag/drop", "vox/blue/flag/drop", "vox/red/flag/drop"}, // DROP
490 {"ctf/your/flag/manualreturn", "ctf/enemy/flag/manualreturn", "vox/your/flag/manualreturn", "vox/enemy/flag/manualreturn", "vox/blue/flag/manualreturn", "vox/red/flag/manualreturn"}, // MANUALRETURN
491 };
492
493 EXTERN_CVAR(snd_voxtype)
EXTERN_CVAR(snd_gamesfx)494 EXTERN_CVAR(snd_gamesfx)
495
496 // [AM] Play appropriate sounds for CTF events.
497 void CTF_Sound(flag_t flag, flag_score_t event) {
498 if (flag >= NUMFLAGS) {
499 // Invalid team
500 return;
501 }
502
503 if (event >= NUM_CTF_SCORE) {
504 // Invalid CTF event
505 return;
506 }
507
508 if (strcmp(flag_sound[event][0], "") == 0) {
509 // No logical sound for this event
510 return;
511 }
512
513 // Play sound effect
514 if (snd_gamesfx) {
515 if (consoleplayer().spectator || consoleplayer().userinfo.team != (team_t)flag) {
516 // Enemy flag is being evented
517 if (S_FindSound(flag_sound[event][1]) != -1) {
518 S_Sound(CHAN_GAMEINFO, flag_sound[event][1], 1, ATTN_NONE);
519 }
520 } else {
521 // Your flag is being evented
522 if (S_FindSound(flag_sound[event][0]) != -1) {
523 S_Sound(CHAN_GAMEINFO, flag_sound[event][0], 1, ATTN_NONE);
524 }
525 }
526 }
527
528 // Play announcer sound
529 switch (snd_voxtype.asInt()) {
530 case 2:
531 // Possessive (yours/theirs)
532 if (!consoleplayer().spectator) {
533 if (consoleplayer().userinfo.team != (team_t)flag) {
534 // Enemy flag is being evented
535 if (S_FindSound(flag_sound[event][3]) != -1) {
536 S_Sound(CHAN_ANNOUNCER, flag_sound[event][3], 1, ATTN_NONE);
537 break;
538 }
539 } else {
540 // Your flag is being evented
541 if (S_FindSound(flag_sound[event][2]) != -1) {
542 S_Sound(CHAN_ANNOUNCER, flag_sound[event][2], 1, ATTN_NONE);
543 break;
544 }
545 }
546 }
547 // fallthrough
548 case 1:
549 // Team colors (red/blue)
550 if (S_FindSound(flag_sound[event][4 + flag]) != -1) {
551 if (consoleplayer().userinfo.team != (team_t)flag && !consoleplayer().spectator) {
552 S_Sound(CHAN_ANNOUNCER, flag_sound[event][4 + flag], 1, ATTN_NONE);
553 } else {
554 S_Sound(CHAN_ANNOUNCER, flag_sound[event][4 + flag], 1, ATTN_NONE);
555 }
556 break;
557 }
558 // fallthrough
559 default:
560 break;
561 }
562 }
563
564 static const char* flag_message[NUM_CTF_SCORE][4] = {
565 {"", "", "", ""}, // NONE
566 {"", "", "", ""}, // REFRESH
567 {"", "", "", ""}, // KILL
568 {"", "", "", ""}, // BETRAYAL
569 {"Your Flag Taken", "Enemy Flag Taken", "Blue Flag Taken", "Red Flag Taken"}, // GRAB
570 {"Your Flag Taken", "Enemy Flag Taken", "Blue Flag Taken", "Red Flag Taken"}, // FIRSTGRAB
571 {"", "", "", ""}, // CARRIERKILL
572 {"Your Flag Returned", "Enemy Flag Returned", "Blue Flag Returned", "Red Flag Returned"}, // RETURN
573 {"Enemy Team Scores", "Your Team Scores", "Red Team Scores", "Blue Team Scores"}, // CAPTURE
574 {"Your Flag Dropped", "Enemy Flag Dropped", "Blue Flag Dropped", "Red Flag Dropped"}, // DROP
575 {"Your Flag Returning", "Enemy Flag Returning", "Blue Flag Returning", "Red Flag Returning"} // MANUALRETURN*/
576 };
577
CTF_Message(flag_t flag,flag_score_t event)578 void CTF_Message(flag_t flag, flag_score_t event) {
579 // Invalid team
580 if (flag >= NUMFLAGS)
581 return;
582
583 // Invalid CTF event
584 if (event >= NUM_CTF_SCORE)
585 return;
586
587 // No message for this event
588 if (strcmp(flag_sound[event][0], "") == 0)
589 return;
590
591 int color = CR_GREY;
592
593 // Show message
594 switch (hud_gamemsgtype.asInt()) {
595 case 2:
596 // Possessive (yours/theirs)
597 if (!consoleplayer().spectator) {
598 if (consoleplayer().userinfo.team != (team_t)flag) {
599 // Enemy flag is being evented
600 if (event == SCORE_GRAB || event == SCORE_FIRSTGRAB || event == SCORE_CAPTURE)
601 color = CR_GREEN;
602 else
603 color = CR_BRICK;
604 C_GMidPrint(flag_message[event][1], color, 0);
605 } else {
606 // Friendly flag is being evented
607 if (event == SCORE_GRAB || event == SCORE_FIRSTGRAB || event == SCORE_CAPTURE)
608 color = CR_BRICK;
609 else
610 color = CR_GREEN;
611 C_GMidPrint(flag_message[event][0], color, 0);
612 }
613 break;
614 }
615 // fallthrough
616 case 1:
617 // Team colors (red/blue)
618 if (event == SCORE_CAPTURE) {
619 if (flag == it_blueflag)
620 color = CR_RED;
621 else
622 color = CR_BLUE;
623 } else {
624 if (flag == it_blueflag)
625 color = CR_BLUE;
626 else
627 color = CR_RED;
628 }
629
630 C_GMidPrint(flag_message[event][2 + flag], color, 0);
631 break;
632 default:
633 break;
634 }
635 }
636
637 VERSION_CONTROL (cl_ctf_cpp, "$Id: cl_ctf.cpp 4542 2014-02-09 17:39:42Z dr_sean $")
638