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