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