1 /*
2     C-Dogs SDL
3     A port of the legendary (and fun) action/arcade cdogs.
4     Copyright (c) 2013-2019 Cong Xu
5 
6     Redistribution and use in source and binary forms, with or without
7     modification, are permitted provided that the following conditions are met:
8 
9     Redistributions of source code must retain the above copyright notice, this
10     list of conditions and the following disclaimer.
11     Redistributions in binary form must reproduce the above copyright notice,
12     this list of conditions and the following disclaimer in the documentation
13     and/or other materials provided with the distribution.
14 
15     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16     AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18     ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19     LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20     CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21     SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22     INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25     POSSIBILITY OF SUCH DAMAGE.
26 */
27 #include "camera.h"
28 
29 #include "actors.h"
30 #include "draw/draw.h"
31 #include "draw/drawtools.h"
32 #include "events.h"
33 #include "font.h"
34 #include "log.h"
35 #include "los.h"
36 #include "player.h"
37 
38 
39 #define PAN_SPEED 4
40 
CameraInit(Camera * camera)41 void CameraInit(Camera *camera)
42 {
43 	memset(camera, 0, sizeof *camera);
44 	CameraReset(camera);
45 	camera->lastPosition = svec2_zero();
46 	HUDInit(&camera->HUD, &gGraphicsDevice, &gMission);
47 	camera->shake = ScreenShakeZero();
48 }
49 
CameraReset(Camera * camera)50 void CameraReset(Camera *camera)
51 {
52 	DrawBufferTerminate(&camera->Buffer);
53 	DrawBufferInit(
54 		&camera->Buffer, svec2i(X_TILES, Y_TILES), &gGraphicsDevice);
55 }
56 
CameraTerminate(Camera * camera)57 void CameraTerminate(Camera *camera)
58 {
59 	DrawBufferTerminate(&camera->Buffer);
60 	HUDTerminate(&camera->HUD);
61 }
62 
CameraInput(Camera * camera,const int cmd,const int lastCmd)63 void CameraInput(Camera *camera, const int cmd, const int lastCmd)
64 {
65 	// Control the camera
66 	if (camera->spectateMode == SPECTATE_NONE)
67 	{
68 		return;
69 	}
70 	// Arrows: pan camera
71 	// CMD1/2: choose next player to follow
72 	if (CMD_HAS_DIRECTION(cmd))
73 	{
74 		camera->spectateMode = SPECTATE_FREE;
75 		const int pan = PAN_SPEED;
76 		if (cmd & CMD_LEFT)			camera->lastPosition.x -= pan;
77 		else if (cmd & CMD_RIGHT)	camera->lastPosition.x += pan;
78 		if (cmd & CMD_UP)			camera->lastPosition.y -= pan;
79 		else if (cmd & CMD_DOWN)	camera->lastPosition.y += pan;
80 	}
81 	else if ((AnyButton(cmd) && !AnyButton(lastCmd)) ||
82 		camera->FollowNextPlayer)
83 	{
84 		// Can't follow if there are no players
85 		if (GetNumPlayers(PLAYER_ALIVE_OR_DYING, false, false) == 0)
86 		{
87 			return;
88 		}
89 		camera->spectateMode = SPECTATE_FOLLOW;
90 		// Find index of player
91 		int playerIndex = -1;
92 		CA_FOREACH(const PlayerData, p, gPlayerDatas)
93 			if (p->UID == camera->FollowActorUID)
94 			{
95 				playerIndex = _ca_index;
96 				break;
97 			}
98 		CA_FOREACH_END()
99 		// Get the next player by index that has an actor in the game
100 		const int d = (cmd & CMD_BUTTON1) ? 1 : -1;
101 		for (int i = playerIndex + d;; i += d)
102 		{
103 			i = CLAMP_OPPOSITE(i, 0, (int)gPlayerDatas.size - 1);
104 			// Check if clamping made us hit the termination condition
105 			if (i == playerIndex) break;
106 			const PlayerData *p = CArrayGet(&gPlayerDatas, i);
107 			if (IsPlayerAliveOrDying(p))
108 			{
109 				// Follow this player
110 				camera->FollowActorUID = p->ActorUID;
111 				camera->FollowNextPlayer = false;
112 				break;
113 			}
114 		}
115 	}
116 }
117 
118 static struct vec2 GetFollowPlayerPos(
119 	const struct vec2 lastPos, const PlayerData *p);
CameraUpdate(Camera * camera,const int ticks,const int ms)120 void CameraUpdate(Camera *camera, const int ticks, const int ms)
121 {
122 	camera->HUD.DrawData = HUDGetDrawData();
123 	if (camera->HUD.DrawData.NumScreens == 0)
124 	{
125 		// Try to spectate if there are other players alive
126 		if (camera->spectateMode == SPECTATE_NONE)
127 		{
128 			// Enter spectator mode
129 			// Free-look mode
130 			camera->spectateMode = SPECTATE_FREE;
131 			// If there are other players alive, follow them
132 			CA_FOREACH(const PlayerData, p, gPlayerDatas)
133 				if (IsPlayerAliveOrDying(p))
134 				{
135 					camera->spectateMode = SPECTATE_FOLLOW;
136 					camera->FollowActorUID = p->ActorUID;
137 					break;
138 				}
139 			CA_FOREACH_END()
140 		}
141 	}
142 	else
143 	{
144 		// Don't spectate
145 		camera->spectateMode = SPECTATE_NONE;
146 	}
147 	if (camera->spectateMode == SPECTATE_FOLLOW)
148 	{
149 		const TActor *a = ActorGetByUID(camera->FollowActorUID);
150 		if (a != NULL)
151 		{
152 			camera->HUD.DrawData.Players[0] = PlayerDataGetByUID(a->PlayerUID);
153 			if (camera->HUD.DrawData.Players[0] != NULL)
154 			{
155 				camera->HUD.DrawData.NumScreens = 1;
156 				camera->lastPosition = GetFollowPlayerPos(
157 					camera->lastPosition, camera->HUD.DrawData.Players[0]);
158 			}
159 		}
160 	}
161 
162 	HUDUpdate(&camera->HUD, ms);
163 	camera->shake = ScreenShakeUpdate(camera->shake, ticks);
164 
165 	// Determine how many camera views to use, and set camera position
166 	if (camera->HUD.DrawData.NumScreens == 0)
167 	{
168 		camera->NumViews = 1;
169 		SoundSetEars(camera->lastPosition);
170 	}
171 	else
172 	{
173 		const bool onePlayer = camera->HUD.DrawData.NumScreens == 1;
174 		const bool singleScreen = CameraIsSingleScreen();
175 		if (onePlayer || singleScreen)
176 		{
177 			// Single camera screen
178 			if (onePlayer)
179 			{
180 				const PlayerData *firstPlayer =
181 					camera->HUD.DrawData.Players[0];
182 				const TActor *p = ActorGetByUID(firstPlayer->ActorUID);
183 				camera->lastPosition = p->thing.Pos;
184 			}
185 			else if (singleScreen)
186 			{
187 				// One screen
188 				camera->lastPosition = PlayersGetMidpoint();
189 			}
190 
191 			// Special case: if map is smaller than screen, center the camera
192 			// However, it is important to keep the ear positions unmodified
193 			// so that sounds don't get muffled just because there's a wall
194 			// between player and camera center
195 			const struct vec2 earPos = camera->lastPosition;
196 			if (gMap.Size.x * TILE_WIDTH < gGraphicsDevice.cachedConfig.Res.x)
197 			{
198 				camera->lastPosition.x = (float)gMap.Size.x * TILE_WIDTH / 2;
199 			}
200 			if (gMap.Size.y * TILE_HEIGHT < gGraphicsDevice.cachedConfig.Res.y)
201 			{
202 				camera->lastPosition.y = (float)gMap.Size.y * TILE_HEIGHT / 2;
203 			}
204 
205 			SoundSetEars(earPos);
206 
207 			camera->NumViews = 1;
208 		}
209 		else if (camera->HUD.DrawData.NumScreens == 2)
210 		{
211 			// side-by-side split
212 			for (int i = 0; i < camera->HUD.DrawData.NumScreens; i++)
213 			{
214 				const PlayerData *p = camera->HUD.DrawData.Players[i];
215 				const TActor *a = ActorGetByUID(p->ActorUID);
216 				camera->lastPosition = a->thing.Pos;
217 				SoundSetEarsSide(i == 0, camera->lastPosition);
218 			}
219 
220 			camera->NumViews = 2;
221 		}
222 		else if (camera->HUD.DrawData.NumScreens >= 3 &&
223 			camera->HUD.DrawData.NumScreens <= 4)
224 		{
225 			// 4 player split screen
226 			bool isLocalPlayerAlive[MAX_LOCAL_PLAYERS];
227 			memset(isLocalPlayerAlive, 0, sizeof isLocalPlayerAlive);
228 			for (int i = 0; i < camera->HUD.DrawData.NumScreens; i++)
229 			{
230 				const PlayerData *p = camera->HUD.DrawData.Players[i];
231 				isLocalPlayerAlive[i] = IsPlayerAliveOrDying(p);
232 				if (!isLocalPlayerAlive[i])
233 				{
234 					continue;
235 				}
236 				const TActor *a = ActorGetByUID(p->ActorUID);
237 				camera->lastPosition = a->thing.Pos;
238 
239 				// Set the sound "ears"
240 				const bool isLeft = i == 0 || i == 2;
241 				const bool isUpper = i <= 2;
242 				SoundSetEar(isLeft, isUpper ? 0 : 1, camera->lastPosition);
243 				// If any player is dead, that ear reverts to the other ear
244 				// of the same side of the remaining player
245 				const int otherIdxOnSameSide = i ^ 2;
246 				if (!isLocalPlayerAlive[otherIdxOnSameSide])
247 				{
248 					SoundSetEar(
249 						isLeft, !isUpper ? 0 : 1, camera->lastPosition);
250 				}
251 				else if (!isLocalPlayerAlive[3 - i] &&
252 					!isLocalPlayerAlive[3 - otherIdxOnSameSide])
253 				{
254 					// If both players of one side are dead,
255 					// those ears revert to any of the other remaining
256 					// players
257 					SoundSetEarsSide(!isLeft, camera->lastPosition);
258 				}
259 			}
260 
261 			camera->NumViews = 4;
262 		}
263 		else
264 		{
265 			CASSERT(false, "not implemented yet");
266 		}
267 	}
268 }
269 // Try to follow a player
GetFollowPlayerPos(const struct vec2 lastPos,const PlayerData * p)270 static struct vec2 GetFollowPlayerPos(
271 	const struct vec2 lastPos, const PlayerData *p)
272 {
273 	const TActor *a = ActorGetByUID(p->ActorUID);
274 	if (a == NULL) return lastPos;
275 	return a->Pos;
276 }
277 
278 static void DoBuffer(
279 	DrawBuffer *b, const struct vec2 center, const int w, const struct vec2 noise,
280 	const struct vec2i offset);
CameraDraw(Camera * camera,const HUDDrawData drawData)281 void CameraDraw(Camera *camera, const HUDDrawData drawData)
282 {
283 	const struct vec2i centerOffset = svec2i(-4, -8);
284 	const int w = gGraphicsDevice.cachedConfig.Res.x;
285 	const int h = gGraphicsDevice.cachedConfig.Res.y;
286 
287 	const struct vec2 noise = camera->shake.Delta;
288 
289 	GraphicsResetClip(gGraphicsDevice.gameWindow.renderer);
290 	if (drawData.NumScreens == 0)
291 	{
292 		DoBuffer(
293 			&camera->Buffer,
294 			camera->lastPosition,
295 			X_TILES, noise, centerOffset);
296 	}
297 	else
298 	{
299 		// Redo LOS if PVP, so that each split screen has its own LOS
300 		if (IsPVP(gCampaign.Entry.Mode) && drawData.NumScreens > 0)
301 		{
302 			LOSReset(&gMap.LOS);
303 		}
304 		if (camera->NumViews == 1)
305 		{
306 			// Single camera screen
307 
308 			// Redo LOS for every local human player
309 			if (IsPVP(gCampaign.Entry.Mode))
310 			{
311 				CA_FOREACH(const PlayerData, p, gPlayerDatas)
312 					if (!p->IsLocal || !IsPlayerAliveOrDying(p) ||
313 						!IsPlayerHuman(p))
314 					{
315 						continue;
316 					}
317 					const TActor *a = ActorGetByUID(p->ActorUID);
318 					LOSCalcFrom(&gMap, Vec2ToTile(a->thing.Pos), false);
319 				CA_FOREACH_END()
320 			}
321 
322 			DoBuffer(
323 				&camera->Buffer,
324 				camera->lastPosition,
325 				X_TILES, noise, centerOffset);
326 		}
327 		else if (drawData.NumScreens == 2)
328 		{
329 			// side-by-side split
330 			for (int i = 0; i < drawData.NumScreens; i++)
331 			{
332 				const PlayerData *p = drawData.Players[i];
333 				if (!IsPlayerAliveOrDying(p))
334 				{
335 					continue;
336 				}
337 				const TActor *a = ActorGetByUID(p->ActorUID);
338 				if (a == NULL)
339 				{
340 					continue;
341 				}
342 				camera->lastPosition = a->thing.Pos;
343 				struct vec2i centerOffsetPlayer = centerOffset;
344 				const Rect2i clip = Rect2iNew(
345 					svec2i((i & 1) ? w / 2 : 0, 0), svec2i(w / 2, h));
346 				GraphicsSetClip(gGraphicsDevice.gameWindow.renderer, clip);
347 				if (i == 1)
348 				{
349 					centerOffsetPlayer.x += w / 2 - centerOffset.x;
350 				}
351 
352 				LOSCalcFrom(&gMap, Vec2ToTile(camera->lastPosition), false);
353 				DoBuffer(
354 					&camera->Buffer,
355 					camera->lastPosition,
356 					X_TILES_HALF, noise, centerOffsetPlayer);
357 			}
358 			Draw_Line(w / 2 - 1, 0, w / 2 - 1, h - 1, colorBlack);
359 			Draw_Line(w / 2, 0, w / 2, h - 1, colorBlack);
360 		}
361 		else if (drawData.NumScreens >= 3 && drawData.NumScreens <= 4)
362 		{
363 			// 4 player split screen
364 			for (int i = 0; i < drawData.NumScreens; i++)
365 			{
366 				const PlayerData *p = drawData.Players[i];
367 				if (!IsPlayerAliveOrDying(p))
368 				{
369 					continue;
370 				}
371 				const TActor *a = ActorGetByUID(p->ActorUID);
372 				if (a == NULL)
373 				{
374 					continue;
375 				}
376 				camera->lastPosition = a->thing.Pos;
377 				struct vec2i centerOffsetPlayer = centerOffset;
378 				const Rect2i clip = Rect2iNew(
379 					svec2i((i & 1) ? w / 2 : 0, (i < 2) ? 0 : h / 2 - 1),
380 					svec2i(w / 2, h / 2));
381 				GraphicsSetClip(gGraphicsDevice.gameWindow.renderer, clip);
382 				if (i & 1)
383 				{
384 					centerOffsetPlayer.x += w / 2 - centerOffset.x;
385 				}
386 				if (i < 2)
387 				{
388 					centerOffsetPlayer.y -= h / 4 + centerOffset.y;
389 				}
390 				else
391 				{
392 					centerOffsetPlayer.y += h / 4 - centerOffset.y;
393 				}
394 				LOSCalcFrom(&gMap, Vec2ToTile(camera->lastPosition), false);
395 				DoBuffer(
396 					&camera->Buffer,
397 					camera->lastPosition,
398 					X_TILES_HALF, noise, centerOffsetPlayer);
399 			}
400 			Draw_Line(w / 2 - 1, 0, w / 2 - 1, h - 1, colorBlack);
401 			Draw_Line(w / 2, 0, w / 2, h - 1, colorBlack);
402 			Draw_Line(0, h / 2 - 1, w - 1, h / 2 - 1, colorBlack);
403 			Draw_Line(0, h / 2, w - 1, h / 2, colorBlack);
404 		}
405 		else
406 		{
407 			assert(0 && "not implemented yet");
408 		}
409 	}
410 	GraphicsResetClip(gGraphicsDevice.gameWindow.renderer);
411 }
DoBuffer(DrawBuffer * b,const struct vec2 center,const int w,const struct vec2 noise,const struct vec2i offset)412 static void DoBuffer(
413 	DrawBuffer *b, const struct vec2 center, const int w, const struct vec2 noise,
414 	const struct vec2i offset)
415 {
416 	DrawBufferSetFromMap(b, &gMap, svec2_add(center, noise), w);
417 	if (gPlayerDatas.size > 0)
418 	{
419 		DrawBufferFix(b);
420 	}
421 	DrawBufferDraw(b, offset, NULL);
422 }
423 
CameraDrawMode(const Camera * camera)424 void CameraDrawMode(const Camera *camera)
425 {
426 	// Draw camera mode
427 	char cameraNameBuf[256];
428 	bool drawCameraMode = false;
429 	switch (camera->spectateMode)
430 	{
431 	case SPECTATE_NONE:
432 		// do nothing
433 		break;
434 	case SPECTATE_FOLLOW:
435 		{
436 			const TActor *a = ActorGetByUID(camera->FollowActorUID);
437 			if (a == NULL || !a->isInUse) break;
438 			const PlayerData *p = PlayerDataGetByUID(a->PlayerUID);
439 			if (p == NULL) break;
440 			sprintf(cameraNameBuf, "Following %s", p->name);
441 			drawCameraMode = true;
442 		}
443 		break;
444 	case SPECTATE_FREE:
445 		strcpy(cameraNameBuf, "Free-look Mode");
446 		drawCameraMode = true;
447 		break;
448 	default:
449 		CASSERT(false, "Unknown spectate mode");
450 		break;
451 	}
452 	if (!drawCameraMode)
453 	{
454 		return;
455 	}
456 
457 	const int w = gGraphicsDevice.cachedConfig.Res.x;
458 	const int h = gGraphicsDevice.cachedConfig.Res.y;
459 
460 	// Draw the message centered at the bottom
461 	FontStrMask(
462 		cameraNameBuf,
463 		svec2i((w - FontStrW(cameraNameBuf)) / 2, h - FontH() * 2),
464 		colorYellow);
465 
466 	// Show camera controls
467 	const PlayerData *p = GetFirstPlayer(false, true, true);
468 	// Use default keyboard controls
469 	input_device_e inputDevice = INPUT_DEVICE_KEYBOARD;
470 	int deviceIndex = 0;
471 	if (p != NULL)
472 	{
473 		inputDevice = p->inputDevice;
474 		deviceIndex = p->deviceIndex;
475 	}
476 	struct vec2i pos = svec2i(
477 		(w - FontStrW("foo/bar to follow player, baz to free-look")) / 2,
478 		h - FontH());
479 	char buf[256];
480 	color_t c = colorYellow;
481 	InputGetButtonNameColor(
482 		inputDevice, deviceIndex, CMD_BUTTON1, buf, &c);
483 	pos = FontStrMask(buf, pos, c);
484 	pos = FontStrMask("/", pos, colorYellow);
485 	c = colorYellow;
486 	InputGetButtonNameColor(
487 		inputDevice, deviceIndex, CMD_BUTTON2, buf, &c);
488 	pos = FontStrMask(buf, pos, c);
489 	pos = FontStrMask(" to follow player, ", pos, colorYellow);
490 	InputGetDirectionNames(buf, inputDevice, deviceIndex);
491 	pos = FontStrMask(buf, pos, colorYellow);
492 	FontStrMask(" to free-look", pos, colorYellow);
493 }
494 
CameraIsSingleScreen(void)495 bool CameraIsSingleScreen(void)
496 {
497 	if (ConfigGetEnum(&gConfig, "Interface.Splitscreen") == SPLITSCREEN_ALWAYS)
498 	{
499 		return false;
500 	}
501 	// Do split screen for PVP, unless whole map fits on camera, or there
502 	// are no human players alive or dying, then just do single screen
503 	const bool mapFitsInScreen =
504 		gMap.Size.x * TILE_WIDTH < gGraphicsDevice.cachedConfig.Res.x &&
505 		gMap.Size.y * TILE_HEIGHT < gGraphicsDevice.cachedConfig.Res.y;
506 	if (IsPVP(gCampaign.Entry.Mode) &&
507 		!mapFitsInScreen &&
508 		GetNumPlayers(PLAYER_ALIVE_OR_DYING, true, true) > 0)
509 	{
510 		return false;
511 	}
512 	// Otherwise, if we are forcing never splitscreen, use single screen
513 	// regardless of whether the players are within camera range
514 	if (ConfigGetEnum(&gConfig, "Interface.Splitscreen") == SPLITSCREEN_NEVER)
515 	{
516 		return true;
517 	}
518 	// Finally, use split screen if players don't fit on camera
519 	struct vec2 min, max;
520 	PlayersGetBoundingRectangle(&min, &max);
521 	return
522 		max.x - min.x < gGraphicsDevice.cachedConfig.Res.x - CAMERA_SPLIT_PADDING &&
523 		max.y - min.y < gGraphicsDevice.cachedConfig.Res.y - CAMERA_SPLIT_PADDING;
524 }
525