1 /*
2 	C-Dogs SDL
3 	A port of the legendary (and fun) action/arcade cdogs.
4 	Copyright (c) 2014-2016, 2018-2020 Cong Xu
5 	All rights reserved.
6 
7 	Redistribution and use in source and binary forms, with or without
8 	modification, are permitted provided that the following conditions are met:
9 
10 	Redistributions of source code must retain the above copyright notice, this
11 	list of conditions and the following disclaimer.
12 	Redistributions in binary form must reproduce the above copyright notice,
13 	this list of conditions and the following disclaimer in the documentation
14 	and/or other materials provided with the distribution.
15 
16 	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 	POSSIBILITY OF SUCH DAMAGE.
27 */
28 #include "player.h"
29 
30 #include "actors.h"
31 #include "events.h"
32 #include "log.h"
33 #include "net_client.h"
34 #include "player_template.h"
35 
36 CArray gPlayerDatas;
37 
PlayerDataInit(CArray * p)38 void PlayerDataInit(CArray *p)
39 {
40 	CArrayInit(p, sizeof(PlayerData));
41 }
42 
PlayerDataAddOrUpdate(const NPlayerData pd)43 void PlayerDataAddOrUpdate(const NPlayerData pd)
44 {
45 	PlayerData *p = PlayerDataGetByUID(pd.UID);
46 	if (p == NULL)
47 	{
48 		PlayerData pNew;
49 		memset(&pNew, 0, sizeof pNew);
50 		CArrayPushBack(&gPlayerDatas, &pNew);
51 		p = CArrayGet(&gPlayerDatas, (int)gPlayerDatas.size - 1);
52 
53 		// Set defaults
54 		p->ActorUID = -1;
55 		p->IsLocal =
56 			(int)pd.UID >= gNetClient.FirstPlayerUID &&
57 			(int)pd.UID < gNetClient.FirstPlayerUID + MAX_LOCAL_PLAYERS;
58 		CArrayInitFillZero(&p->ammo, sizeof(int), pd.Ammo_count);
59 		p->inputDevice = INPUT_DEVICE_UNSET;
60 
61 		p->Char.speed = 1;
62 
63 		LOG(LM_MAIN, LL_INFO, "add default player UID(%u) local(%s)", pd.UID,
64 			p->IsLocal ? "true" : "false");
65 	}
66 
67 	p->UID = pd.UID;
68 
69 	strcpy(p->name, pd.Name);
70 	p->Char.Class = StrCharacterClass(pd.CharacterClass);
71 	if (p->Char.Class == NULL)
72 	{
73 		p->Char.Class = StrCharacterClass("Jones");
74 	}
75 	CFREE(p->Char.Hair);
76 	p->Char.Hair = NULL;
77 	if (strlen(pd.Hair) > 0)
78 	{
79 		CSTRDUP(p->Char.Hair, pd.Hair);
80 	}
81 	p->Char.Colors = Net2CharColors(pd.Colors);
82 	for (int i = 0; i < (int)pd.Weapons_count; i++)
83 	{
84 		p->guns[i] = NULL;
85 		if (strlen(pd.Weapons[i]) > 0)
86 		{
87 			const WeaponClass *wc = StrWeaponClass(pd.Weapons[i]);
88 			p->guns[i] = wc;
89 		}
90 	}
91 	CArrayFillZero(&p->ammo);
92 	for (int i = 0; i < (int)pd.Ammo_count; i++)
93 	{
94 		CArraySet(&p->ammo, pd.Ammo[i].Id, &pd.Ammo[i].Amount);
95 	}
96 	p->Lives = pd.Lives;
97 	p->Stats = pd.Stats;
98 	p->Totals = pd.Totals;
99 	p->Char.maxHealth = pd.MaxHealth;
100 	p->lastMission = pd.LastMission;
101 
102 	// Ready players as well
103 	p->Ready = true;
104 
105 	LOG(LM_MAIN, LL_INFO, "update player UID(%d) maxHealth(%d)", p->UID,
106 		p->Char.maxHealth);
107 }
108 
109 static void PlayerTerminate(PlayerData *p);
PlayerRemove(const int uid)110 void PlayerRemove(const int uid)
111 {
112 	// Find the player so we can remove by index
113 	PlayerData *p = NULL;
114 	int i;
115 	for (i = 0; i < (int)gPlayerDatas.size; i++)
116 	{
117 		PlayerData *pi = CArrayGet(&gPlayerDatas, i);
118 		if (pi->UID == uid)
119 		{
120 			p = pi;
121 			break;
122 		}
123 	}
124 	if (p == NULL)
125 	{
126 		return;
127 	}
128 	if (p->ActorUID >= 0)
129 	{
130 		ActorDestroy(ActorGetByUID(p->ActorUID));
131 	}
132 	PlayerTerminate(p);
133 	CArrayDelete(&gPlayerDatas, i);
134 
135 	LOG(LM_MAIN, LL_INFO, "remove player UID(%d)", uid);
136 }
137 
PlayerTerminate(PlayerData * p)138 static void PlayerTerminate(PlayerData *p)
139 {
140 	CFREE(p->Char.bot);
141 }
142 
PlayerDataDefault(const int idx)143 NPlayerData PlayerDataDefault(const int idx)
144 {
145 	NPlayerData pd = NPlayerData_init_default;
146 
147 	pd.has_Colors = pd.has_Stats = pd.has_Totals = true;
148 
149 	// load from template if available
150 	const PlayerTemplate *t = PlayerTemplateGetById(&gPlayerTemplates, idx);
151 	if (t != NULL)
152 	{
153 		strcpy(pd.Name, t->name);
154 		strcpy(pd.CharacterClass, t->CharClassName);
155 		if (t->Hair != NULL)
156 		{
157 			strcpy(pd.Hair, t->Hair);
158 		}
159 		pd.Colors = CharColors2Net(t->Colors);
160 	}
161 	else
162 	{
163 		switch (idx)
164 		{
165 		case 0:
166 			strcpy(pd.Name, "Jones");
167 			strcpy(pd.CharacterClass, "Jones");
168 			pd.Colors.Skin = Color2Net(colorSkin);
169 			pd.Colors.Arms = Color2Net(colorLightBlue);
170 			pd.Colors.Body = Color2Net(colorLightBlue);
171 			pd.Colors.Legs = Color2Net(colorLightBlue);
172 			pd.Colors.Hair = Color2Net(colorLightBlue);
173 			pd.Colors.Feet = Color2Net(colorLightBlue);
174 			break;
175 		case 1:
176 			strcpy(pd.Name, "Ice");
177 			strcpy(pd.CharacterClass, "Jones");
178 			strcpy(pd.Hair, "shades");
179 			pd.Colors.Skin = Color2Net(colorDarkSkin);
180 			pd.Colors.Arms = Color2Net(colorRed);
181 			pd.Colors.Body = Color2Net(colorRed);
182 			pd.Colors.Legs = Color2Net(colorRed);
183 			pd.Colors.Hair = Color2Net(colorBlack);
184 			pd.Colors.Feet = Color2Net(colorRed);
185 			break;
186 		case 2:
187 			strcpy(pd.Name, "Warbaby");
188 			strcpy(pd.CharacterClass, "Jones");
189 			strcpy(pd.Hair, "beret");
190 			pd.Colors.Skin = Color2Net(colorSkin);
191 			pd.Colors.Arms = Color2Net(colorGreen);
192 			pd.Colors.Body = Color2Net(colorGreen);
193 			pd.Colors.Legs = Color2Net(colorGreen);
194 			pd.Colors.Hair = Color2Net(colorRed);
195 			pd.Colors.Feet = Color2Net(colorGreen);
196 			break;
197 		case 3:
198 			strcpy(pd.Name, "Han");
199 			strcpy(pd.CharacterClass, "Jones");
200 			strcpy(pd.Hair, "hogan");
201 			pd.Colors.Skin = Color2Net(colorAsianSkin);
202 			pd.Colors.Arms = Color2Net(colorYellow);
203 			pd.Colors.Body = Color2Net(colorYellow);
204 			pd.Colors.Legs = Color2Net(colorYellow);
205 			pd.Colors.Hair = Color2Net(colorYellow);
206 			pd.Colors.Feet = Color2Net(colorYellow);
207 			break;
208 		default:
209 			// Set up player N template
210 			sprintf(pd.Name, "Player %d", idx);
211 			strcpy(pd.CharacterClass, "Jones");
212 			pd.Colors.Skin = Color2Net(colorSkin);
213 			pd.Colors.Arms = Color2Net(colorLightBlue);
214 			pd.Colors.Body = Color2Net(colorLightBlue);
215 			pd.Colors.Legs = Color2Net(colorLightBlue);
216 			pd.Colors.Hair = Color2Net(colorLightBlue);
217 			pd.Colors.Feet = Color2Net(colorLightBlue);
218 			break;
219 		}
220 	}
221 
222 	pd.MaxHealth = 200;
223 
224 	return pd;
225 }
226 
PlayerDataMissionReset(const PlayerData * p)227 NPlayerData PlayerDataMissionReset(const PlayerData *p)
228 {
229 	NPlayerData pd = NMakePlayerData(p);
230 	pd.Lives = ModeLives(gCampaign.Entry.Mode);
231 
232 	memset(&pd.Stats, 0, sizeof pd.Stats);
233 
234 	pd.LastMission = gCampaign.MissionIndex;
235 	pd.MaxHealth = ModeMaxHealth(gCampaign.Entry.Mode);
236 	return pd;
237 }
238 
PlayerDataTerminate(CArray * p)239 void PlayerDataTerminate(CArray *p)
240 {
241 	for (int i = 0; i < (int)p->size; i++)
242 	{
243 		PlayerTerminate(CArrayGet(p, i));
244 	}
245 	CArrayTerminate(p);
246 }
247 
PlayerDataGetByUID(const int uid)248 PlayerData *PlayerDataGetByUID(const int uid)
249 {
250 	if (uid == -1)
251 	{
252 		return NULL;
253 	}
254 	CA_FOREACH(PlayerData, p, gPlayerDatas)
255 	if (p->UID == uid)
256 		return p;
257 	CA_FOREACH_END()
258 	return NULL;
259 }
260 
FindLocalPlayerIndex(const int uid)261 int FindLocalPlayerIndex(const int uid)
262 {
263 	const PlayerData *p = PlayerDataGetByUID(uid);
264 	if (p == NULL || !p->IsLocal)
265 	{
266 		// This update was for a non-local player; abort
267 		return -1;
268 	}
269 	// Note: player UIDs divided by MAX_LOCAL_PLAYERS per client
270 	return uid % MAX_LOCAL_PLAYERS;
271 }
272 
GetNumPlayers(const PlayerAliveOptions alive,const bool human,const bool local)273 int GetNumPlayers(
274 	const PlayerAliveOptions alive, const bool human, const bool local)
275 {
276 	int numPlayers = 0;
277 	CA_FOREACH(const PlayerData, p, gPlayerDatas)
278 	bool life = false;
279 	switch (alive)
280 	{
281 	case PLAYER_ANY:
282 		life = true;
283 		break;
284 	case PLAYER_ALIVE:
285 		life = IsPlayerAlive(p);
286 		break;
287 	case PLAYER_ALIVE_OR_DYING:
288 		life = IsPlayerAliveOrDying(p);
289 		break;
290 	}
291 	if (life && (!human || p->inputDevice != INPUT_DEVICE_AI) &&
292 		(!local || p->IsLocal))
293 	{
294 		numPlayers++;
295 	}
296 	CA_FOREACH_END()
297 	return numPlayers;
298 }
299 
AreAllPlayersDeadAndNoLives(void)300 bool AreAllPlayersDeadAndNoLives(void)
301 {
302 	CA_FOREACH(const PlayerData, p, gPlayerDatas)
303 	if (IsPlayerAlive(p) || p->Lives > 0)
304 	{
305 		return false;
306 	}
307 	CA_FOREACH_END()
308 	return true;
309 }
310 
GetFirstPlayer(const bool alive,const bool human,const bool local)311 const PlayerData *GetFirstPlayer(
312 	const bool alive, const bool human, const bool local)
313 {
314 	CA_FOREACH(const PlayerData, p, gPlayerDatas)
315 	if ((!alive || IsPlayerAliveOrDying(p)) &&
316 		(!human || p->inputDevice != INPUT_DEVICE_AI) &&
317 		(!local || p->IsLocal))
318 	{
319 		return p;
320 	}
321 	CA_FOREACH_END()
322 	return NULL;
323 }
324 
IsPlayerAlive(const PlayerData * player)325 bool IsPlayerAlive(const PlayerData *player)
326 {
327 	if (player == NULL || player->ActorUID == -1)
328 	{
329 		return false;
330 	}
331 	const TActor *p = ActorGetByUID(player->ActorUID);
332 	return !p->dead;
333 }
IsPlayerHuman(const PlayerData * player)334 bool IsPlayerHuman(const PlayerData *player)
335 {
336 	return player->inputDevice != INPUT_DEVICE_AI;
337 }
IsPlayerHumanAndAlive(const PlayerData * player)338 bool IsPlayerHumanAndAlive(const PlayerData *player)
339 {
340 	return IsPlayerAlive(player) && IsPlayerHuman(player);
341 }
IsPlayerAliveOrDying(const PlayerData * player)342 bool IsPlayerAliveOrDying(const PlayerData *player)
343 {
344 	if (player->ActorUID == -1)
345 	{
346 		return false;
347 	}
348 	const TActor *p = ActorGetByUID(player->ActorUID);
349 	const NamedSprites *deathSprites = CharacterClassGetDeathSprites(
350 		ActorGetCharacter(p)->Class, &gPicManager);
351 	return p->dead <= (int)deathSprites->pics.size;
352 }
IsPlayerScreen(const PlayerData * p)353 bool IsPlayerScreen(const PlayerData *p)
354 {
355 	const bool humanOnly = IsPVP(gCampaign.Entry.Mode) ||
356 						   !ConfigGetBool(&gConfig, "Interface.SplitscreenAI");
357 	const bool humanOrScreen = !humanOnly || p->inputDevice != INPUT_DEVICE_AI;
358 	return p->IsLocal && humanOrScreen && IsPlayerAliveOrDying(p);
359 }
360 
PlayersGetMidpoint(void)361 struct vec2 PlayersGetMidpoint(void)
362 {
363 	// for all surviving players, find bounding rectangle, and get center
364 	struct vec2 min, max;
365 	PlayersGetBoundingRectangle(&min, &max);
366 	return svec2_scale(svec2_add(min, max), 0.5f);
367 }
368 
PlayersGetBoundingRectangle(struct vec2 * min,struct vec2 * max)369 void PlayersGetBoundingRectangle(struct vec2 *min, struct vec2 *max)
370 {
371 	bool isFirst = true;
372 	*min = svec2_zero();
373 	*max = svec2_zero();
374 	const bool humansOnly =
375 		GetNumPlayers(PLAYER_ALIVE_OR_DYING, true, false) > 0;
376 	CA_FOREACH(const PlayerData, p, gPlayerDatas)
377 	if (!p->IsLocal)
378 	{
379 		continue;
380 	}
381 	if (humansOnly ? IsPlayerHumanAndAlive(p) : IsPlayerAlive(p))
382 	{
383 		const TActor *player = ActorGetByUID(p->ActorUID);
384 		const Thing *ti = &player->thing;
385 		if (isFirst)
386 		{
387 			*min = *max = ti->Pos;
388 		}
389 		else
390 		{
391 			min->x = MIN(ti->Pos.x, min->x);
392 			min->y = MIN(ti->Pos.y, min->y);
393 			max->x = MAX(ti->Pos.x, max->x);
394 			max->y = MAX(ti->Pos.y, max->y);
395 		}
396 		isFirst = false;
397 	}
398 	CA_FOREACH_END()
399 }
400 
401 // Get the number of players that use this ammo
PlayersNumUseAmmo(const int ammoId)402 int PlayersNumUseAmmo(const int ammoId)
403 {
404 	int numPlayersWithAmmo = 0;
405 	CA_FOREACH(const PlayerData, p, gPlayerDatas)
406 	if (!IsPlayerAlive(p))
407 	{
408 		continue;
409 	}
410 	const TActor *player = ActorGetByUID(p->ActorUID);
411 	for (int j = 0; j < MAX_WEAPONS; j++)
412 	{
413 		const Weapon *w = &player->guns[j];
414 		if (w->Gun == NULL)
415 			continue;
416 		for (int i = 0; i < WeaponClassNumBarrels(w->Gun); i++)
417 		{
418 			if (WC_BARREL_ATTR(*(w->Gun), AmmoId, i) == ammoId)
419 			{
420 				numPlayersWithAmmo++;
421 			}
422 		}
423 	}
424 	CA_FOREACH_END()
425 	return numPlayersWithAmmo;
426 }
427 
PlayerIsLocal(const int uid)428 bool PlayerIsLocal(const int uid)
429 {
430 	const PlayerData *p = PlayerDataGetByUID(uid);
431 	return p != NULL && p->IsLocal;
432 }
433 
PlayerScore(PlayerData * p,const int points)434 void PlayerScore(PlayerData *p, const int points)
435 {
436 	if (p == NULL)
437 	{
438 		return;
439 	}
440 	p->Stats.Score += points;
441 	p->Totals.Score += points;
442 }
443 
PlayerTrySetInputDevice(PlayerData * p,const input_device_e d,const int idx)444 bool PlayerTrySetInputDevice(
445 	PlayerData *p, const input_device_e d, const int idx)
446 {
447 	if (p->inputDevice == d && p->deviceIndex == idx)
448 	{
449 		return false;
450 	}
451 	p->inputDevice = d;
452 	p->deviceIndex = idx;
453 	LOG(LM_MAIN, LL_DEBUG, "playerUID(%d) assigned input device(%d %d)",
454 		p->UID, (int)d, idx);
455 	return true;
456 }
457 
PlayerTrySetUnusedInputDevice(PlayerData * p,const input_device_e d,const int idx)458 bool PlayerTrySetUnusedInputDevice(
459 	PlayerData *p, const input_device_e d, const int idx)
460 {
461 	// Check that player's input device is unassigned
462 	if (p->inputDevice != INPUT_DEVICE_UNSET)
463 		return false;
464 	// Check that no players use this input device
465 	CA_FOREACH(const PlayerData, pOther, gPlayerDatas)
466 	if (pOther->inputDevice == d && pOther->deviceIndex == idx)
467 	{
468 		return false;
469 	}
470 	CA_FOREACH_END()
471 	return PlayerTrySetInputDevice(p, d, idx);
472 }
473 
PlayerGetNumWeapons(const PlayerData * p)474 int PlayerGetNumWeapons(const PlayerData *p)
475 {
476 	int count = 0;
477 	for (int i = 0; i < MAX_WEAPONS; i++)
478 	{
479 		if (p->guns[i] != NULL)
480 		{
481 			count++;
482 		}
483 	}
484 	return count;
485 }
486 
PlayerHasGrenadeButton(const PlayerData * p)487 bool PlayerHasGrenadeButton(const PlayerData *p)
488 {
489 	return InputHasGrenadeButton(p->inputDevice, p->deviceIndex);
490 }
491