1 /**
2 * @file
3 * @brief Main part of the game logic.
4 *
5 * @section Connection
6 *
7 * In case the connection is established (@c G_ClientConnect), the client
8 * state is @c cs_connected. The client will send SV_STATE_NEW, @c SV_New_f sets the
9 * state to @c cs_spawning, and asks the client to precache the data after
10 * sending the configstrings. After the client is done with it, it will send
11 * "begin" to the server. @c SV_Begin_f will now set the client state to @c cs_began
12 * and calls @c G_ClientBegin. The server will ask the client to execute
13 * "spawnsoldiers" now. The client is answering with @c clc_teaminfo and the server
14 * will call @c G_ClientTeamInfo. The client state is now @c cs_spawned. Last but not
15 * least the server informs the client that the match can now get started by
16 * asking to execute "startmatch". The client answers with NET_STATE_STARTMATCH and
17 * @c G_ClientStartMatch is executed.
18 */
19
20 /*
21 Copyright (C) 2002-2013 UFO: Alien Invasion.
22
23 This program is free software; you can redistribute it and/or
24 modify it under the terms of the GNU General Public License
25 as published by the Free Software Foundation; either version 2
26 of the License, or (at your option) any later version.
27
28 This program is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
31
32 See the GNU General Public License for more details.
33
34 You should have received a copy of the GNU General Public License
35 along with this program; if not, write to the Free Software
36 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
37
38 */
39
40 #include "g_client.h"
41 #include "g_actor.h"
42 #include "g_combat.h"
43 #include "g_edicts.h"
44 #include "g_inventory.h"
45 #include "g_match.h"
46 #include "g_move.h"
47 #include "g_reaction.h"
48 #include "g_utils.h"
49 #include "g_vis.h"
50
51 static chrScoreMission_t scoreMission[MAX_EDICTS];
52 static int scoreMissionNum = 0;
53
54 /**
55 * @brief Iterate through the list of players
56 * @param lastPlayer The player found in the previous iteration; if nullptr, we start at the beginning
57 */
G_PlayerGetNextHuman(Player * lastPlayer)58 Player *G_PlayerGetNextHuman (Player *lastPlayer)
59 {
60 Player *endOfPlayers = &game.players[game.sv_maxplayersperteam];
61 Player *player;
62
63 if (!game.sv_maxplayersperteam)
64 return nullptr;
65
66 if (!lastPlayer)
67 return game.players;
68 assert(lastPlayer >= game.players);
69 assert(lastPlayer < endOfPlayers);
70
71 player = lastPlayer;
72
73 player++;
74 if (player >= endOfPlayers)
75 return nullptr;
76 else
77 return player;
78 }
79
80 /**
81 * @brief Iterate through the list of players
82 * @param lastPlayer The player found in the previous iteration; if nullptr, we start at the beginning
83 */
G_PlayerGetNextAI(Player * lastPlayer)84 Player *G_PlayerGetNextAI (Player *lastPlayer)
85 {
86 Player *endOfPlayers = &game.players[game.sv_maxplayersperteam * 2];
87 Player *player;
88
89 if (!game.sv_maxplayersperteam)
90 return nullptr;
91
92 if (!lastPlayer)
93 return &game.players[game.sv_maxplayersperteam];
94 assert(lastPlayer >= &game.players[game.sv_maxplayersperteam]);
95 assert(lastPlayer < endOfPlayers);
96
97 player = lastPlayer;
98
99 player++;
100 if (player >= endOfPlayers)
101 return nullptr;
102 else
103 return player;
104 }
105
106 /**
107 * @brief Iterate through the list of players
108 * @param lastPlayer The player found in the previous iteration; if nullptr, we start at the beginning
109 */
G_PlayerGetNextActiveHuman(Player * lastPlayer)110 Player *G_PlayerGetNextActiveHuman (Player *lastPlayer)
111 {
112 Player *player = lastPlayer;
113
114 while ((player = G_PlayerGetNextHuman(player))) {
115 if (player->isInUse())
116 return player;
117 }
118
119 return nullptr;
120 }
121
122 /**
123 * @brief Iterate through the list of players
124 * @param lastPlayer The player found in the previous iteration; if nullptr, we start at the beginning
125 */
G_PlayerGetNextActiveAI(Player * lastPlayer)126 Player *G_PlayerGetNextActiveAI (Player *lastPlayer)
127 {
128 Player *player = lastPlayer;
129
130 while ((player = G_PlayerGetNextAI(player))) {
131 if (player->isInUse())
132 return player;
133 }
134
135 return nullptr;
136 }
137
138 /**
139 * @brief Generates the player bit mask for a given team
140 * @param[in] team The team to create the player bit mask for
141 * @note E.g. multiplayer team play can have more than one human player on the
142 * same team.
143 */
G_TeamToPM(int team)144 playermask_t G_TeamToPM (int team)
145 {
146 playermask_t playerMask = 0;
147 Player *p = nullptr;
148
149 /* don't handle the ai players, here */
150 while ((p = G_PlayerGetNextHuman(p))) {
151 if (p->isInUse() && team == p->getTeam())
152 playerMask |= G_PlayerToPM(*p);
153 }
154
155 return playerMask;
156 }
157
158 /**
159 * @brief Converts player mask to vis mask
160 * @param[in] playerMask The player bit mask (contains the player numbers) that
161 * is converted to a vis mask
162 * @return Returns a vis mask for all the teams of the connected players that
163 * are marked in the given @c playerMask.
164 */
G_PMToVis(playermask_t playerMask)165 teammask_t G_PMToVis (playermask_t playerMask)
166 {
167 teammask_t teamMask = 0;
168 Player *p = nullptr;
169
170 /* don't handle the ai players, here */
171 while ((p = G_PlayerGetNextActiveHuman(p))) {
172 if (playerMask & G_PlayerToPM(*p))
173 teamMask |= G_TeamToVisMask(p->getTeam());
174 }
175
176 return teamMask;
177 }
178
179 /**
180 * @brief Converts vis mask to player mask
181 * @param[in] teamMask The visibility bit mask (contains the team numbers) that
182 * is converted to a player mask
183 * @return Returns a playermask for all the teams of the connected players that
184 * are marked in the given @c vis_mask.
185 */
G_VisToPM(teammask_t teamMask)186 playermask_t G_VisToPM (teammask_t teamMask)
187 {
188 playermask_t playerMask = 0;
189 Player *p = nullptr;
190
191 /* don't handle the ai players, here */
192 while ((p = G_PlayerGetNextActiveHuman(p))) {
193 if (teamMask & G_TeamToVisMask(p->getTeam()))
194 playerMask |= G_PlayerToPM(*p);
195 }
196
197 return playerMask;
198 }
199
200 /**
201 * Send messages to human players
202 * @param player A player (AI players are ignored here)
203 * @param printLevel A numeric value to restrict and channel the printing (CONSOLE, HUD, CHAT...)
204 * @param fmt A format string as in printf
205 */
G_ClientPrintf(const Player & player,int printLevel,const char * fmt,...)206 void G_ClientPrintf (const Player &player, int printLevel, const char* fmt, ...)
207 {
208 va_list ap;
209
210 /* there is no client for an AI controlled player on the server where we
211 * could send the message to */
212 if (G_IsAIPlayer(&player))
213 return;
214
215 va_start(ap, fmt);
216 gi.PlayerPrintf(&player, printLevel, fmt, ap);
217 va_end(ap);
218 }
219
220 /**
221 * @brief Network function to update the time units (TUs) for each team-member.
222 * @param[in] team The index of the team to update the values for.
223 * @sa G_SendStats
224 */
G_GiveTimeUnits(int team)225 void G_GiveTimeUnits (int team)
226 {
227 Edict* ent = nullptr;
228
229 while ((ent = G_EdictsGetNextLivingActorOfTeam(ent, team))) {
230 G_ActorGiveTimeUnits(ent);
231 G_SendStats(*ent);
232 }
233 }
234
235 /**
236 * @brief Send the appear or perish event to the affected clients
237 * @param[in] playerMask These are the affected players or clients
238 * In case of e.g. teamplay there can be more than one client affected - thus
239 * this is a player mask
240 * @param[in] appear Is this event about an appearing actor (or a perishing one)
241 * @param[in] check The edict we are talking about (that appears or perishes)
242 * @param[in] ent The edict that was responsible for letting the check edict appear
243 * or perish. Might be @c nullptr.
244 * @sa CL_ActorAppear
245 */
G_AppearPerishEvent(playermask_t playerMask,bool appear,Edict & check,const Edict * ent)246 void G_AppearPerishEvent (playermask_t playerMask, bool appear, Edict &check, const Edict* ent)
247 {
248 teammask_t teamMaskDiff;
249
250 /* test for pointless player mask */
251 if (!playerMask)
252 return;
253
254 teamMaskDiff = G_PMToVis(playerMask);
255 G_VisFlagsSwap(check, teamMaskDiff);
256
257 if (appear) {
258 /* appear */
259 switch (check.type) {
260 case ET_ACTOR:
261 case ET_ACTOR2x2:
262 G_EventActorAppear(playerMask, check, ent);
263 break;
264
265 case ET_CAMERA:
266 G_EventCameraAppear(playerMask, check);
267 break;
268
269 case ET_ITEM:
270 G_EventEdictAppear(playerMask, check);
271 G_SendInventory(playerMask, check);
272 break;
273
274 case ET_PARTICLE:
275 G_EventEdictAppear(playerMask, check);
276 G_EventSendParticle(playerMask, check);
277 break;
278
279 case ET_TRIGGER_RESCUE:
280 G_EventAddBrushModel(playerMask, check);
281 break;
282
283 default:
284 if (G_IsVisibleOnBattlefield(&check))
285 gi.Error("Missing edict type %i in G_AppearPerishEvent", check.type);
286 break;
287 }
288 } else if (G_IsVisibleOnBattlefield(&check)) {
289 G_EventEdictPerish(playerMask, check);
290 }
291 }
292
293 /**
294 * @brief This function sends all the actors to the client that are not visible
295 * initially - this is needed because an actor can e.g. produce sounds that are
296 * send over the net. And the client can only handle them if he knows the
297 * @c le_t (local entity) already
298 * @note Call this for the first @c G_CheckVis call for every new
299 * actor or player
300 * @sa G_CheckVis
301 * @sa CL_ActorAdd
302 */
G_SendInvisible(const Player & player)303 void G_SendInvisible (const Player &player)
304 {
305 const int team = player.getTeam();
306
307 assert(team != TEAM_NO_ACTIVE);
308 if (!level.num_alive[team])
309 return;
310
311 Edict* ent = nullptr;
312 /* check visibility */
313 while ((ent = G_EdictsGetNextActor(ent))) {
314 if (ent->team == team)
315 continue;
316 /* not visible for this team - so add the le only */
317 if (!G_IsVisibleForTeam(ent, team)) {
318 G_EventActorAdd(G_PlayerToPM(player), *ent);
319 }
320 }
321 }
322
323 /**
324 * @brief Returns the current active team to the server
325 * @par If this has the value @c TEAM_NO_ACTIVE there is either no
326 * living actor for any player left, or the turn wasn't started yet.
327 */
G_GetActiveTeam(void)328 int G_GetActiveTeam (void)
329 {
330 return level.activeTeam;
331 }
332
333 /**
334 * @brief Checks whether the requested action is possible
335 * @param[in] player Which player (human player) is trying to do the action
336 * @param[in] ent Which of his units is trying to do the action.
337 */
G_ActionCheck(const Player & player,Edict * ent)338 static bool G_ActionCheck (const Player &player, Edict* ent)
339 {
340 if (!ent || !ent->inuse) {
341 G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - object not present!"));
342 return false;
343 }
344
345 if (ent->type != ET_ACTOR && ent->type != ET_ACTOR2x2) {
346 G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - not an actor!"));
347 return false;
348 }
349
350 if (G_IsStunned(ent)) {
351 G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - actor is stunned!"));
352 return false;
353 }
354
355 if (G_IsDead(ent)) {
356 G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - actor is dead!"));
357 return false;
358 }
359
360 if (ent->team != player.getTeam()) {
361 G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - not on same team!"));
362 return false;
363 }
364
365 if (ent->pnum != player.getNum()) {
366 G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - no control over allied actors!"));
367 return false;
368 }
369
370 /* could be possible */
371 return true;
372 }
373
374 /**
375 * @brief Checks whether the requested action is possible for the current active team
376 * @param[in] player Which player (human player) is trying to do the action
377 * @param[in] ent Which of his units is trying to do the action.
378 * @param[in] TU The time units to check against the ones ent has.
379 * the action with
380 */
G_ActionCheckForCurrentTeam(const Player & player,Edict * ent,int TU)381 bool G_ActionCheckForCurrentTeam (const Player &player, Edict* ent, int TU)
382 {
383 /* a generic tester if an action could be possible */
384 if (level.activeTeam != player.getTeam()) {
385 G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - it is not your turn!"));
386 return false;
387 }
388
389 if (TU > G_ActorUsableTUs(ent)) {
390 return false;
391 }
392
393 return G_ActionCheck(player, ent);
394 }
395
396 /**
397 * @brief Checks whether the requested action is possible
398 * @param[in] player Which player (human player) is trying to do the action
399 * @param[in] ent Which of his units is trying to do the action.
400 * @param[in] TU The time units to check against the ones ent has.
401 * the action with
402 * @sa G_ActionCheck
403 */
G_ActionCheckForReaction(const Player & player,Edict * ent,int TU)404 bool G_ActionCheckForReaction (const Player &player, Edict* ent, int TU)
405 {
406 if (TU > ent->TU) {
407 return false;
408 }
409
410 return G_ActionCheck(player, ent);
411 }
412
413 /**
414 * @brief Sends the actual actor turn event over the netchannel
415 */
G_ClientTurn(Player & player,Edict * ent,dvec_t dvec)416 static void G_ClientTurn (Player &player, Edict* ent, dvec_t dvec)
417 {
418 const int dir = getDVdir(dvec);
419
420 /* check if action is possible */
421 if (!G_ActionCheckForCurrentTeam(player, ent, TU_TURN))
422 return;
423
424 /* check if we're already facing that direction */
425 if (ent->dir == dir)
426 return;
427
428 /* do the turn */
429 G_ActorDoTurn(ent, dir);
430 G_ActorUseTU(ent, TU_TURN);
431
432 /* send the turn */
433 G_EventActorTurn(*ent);
434
435 /* send the new TUs */
436 G_SendStats(*ent);
437
438 /* end the event */
439 G_EventEnd();
440 }
441
442 /**
443 * @brief After an actor changed his state, he might get visible for other
444 * players. Check the vis here and send the state change to the clients that
445 * are seeing him already.
446 * @param ent The actor edict
447 */
G_ClientStateChangeUpdate(Edict & ent)448 static void G_ClientStateChangeUpdate (Edict &ent)
449 {
450 /* Send the state change. */
451 G_EventSendState(G_VisToPM(ent.visflags), ent);
452
453 /* Check if the player appears/perishes, seen from other teams. */
454 G_CheckVis(&ent);
455
456 /* Calc new vis for this player. */
457 G_CheckVisTeamAll(ent.team, 0, &ent);
458
459 /* Send the new TUs. */
460 G_SendStats(ent);
461
462 /* End the event. */
463 G_EventEnd();
464 }
465
466 /**
467 * @brief Changes the state of a player/soldier.
468 * @param[in,out] player The player who controlls the actor
469 * @param[in] ent the edict to perform the state change for
470 * @param[in] reqState The bit-map of the requested state change
471 * @param[in] checkaction only activate the events - network stuff is handled in the calling function
472 * don't even use the G_ActionCheckForCurrentTeam function
473 * @note Use checkaction true only for e.g. spawning values
474 */
G_ClientStateChange(const Player & player,Edict * ent,int reqState,bool checkaction)475 void G_ClientStateChange (const Player &player, Edict* ent, int reqState, bool checkaction)
476 {
477 /* Check if any action is possible. */
478 if (checkaction && !G_ActionCheckForCurrentTeam(player, ent, 0))
479 return;
480
481 if (!reqState)
482 return;
483
484 switch (reqState) {
485 case STATE_CROUCHED: /* Toggle between crouch/stand. */
486 /* Check if player has enough TUs (TU_CROUCH TUs for crouch/uncrouch). */
487 if (!checkaction || G_ActionCheckForCurrentTeam(player, ent, TU_CROUCH)) {
488 if (G_IsCrouched(ent)) {
489 if (!gi.CanActorStandHere(ent->fieldSize, ent->pos))
490 break;
491 }
492 G_ToggleCrouched(ent);
493 G_ActorUseTU(ent, TU_CROUCH);
494 G_ActorSetMaxs(ent);
495 }
496 break;
497 case ~STATE_REACTION: /* Request to turn off reaction fire. */
498 if (G_IsReaction(ent)) {
499 if (G_IsShaken(ent) && G_ReactionFireSettingsReserveTUs(ent)) {
500 G_ClientPrintf(player, PRINT_HUD, _("Currently shaken, won't let their guard down."));
501 } else {
502 /* Turn off reaction fire. */
503 G_RemoveReaction(ent);
504 G_ActorReserveTUs(ent, 0, ent->chr.reservedTus.shot, ent->chr.reservedTus.crouch);
505 if (!G_IsAI(ent))
506 G_EventReactionFireChange(*ent);
507 }
508 }
509 break;
510 /* Request to turn on multi- or single-reaction fire mode. */
511 case STATE_REACTION:
512 /* Disable reaction fire. */
513 G_RemoveReaction(ent);
514
515 if (G_ReactionFireSettingsReserveTUs(ent)) {
516 /* Enable requested reaction fire. */
517 G_SetState(ent, reqState);
518 }
519 if (!G_IsAI(ent))
520 G_EventReactionFireChange(*ent);
521 break;
522 default:
523 gi.DPrintf("G_ClientStateChange: unknown request %i, ignoring\n", reqState);
524 return;
525 }
526
527 /* Only activate the events - network stuff is handled in the calling function */
528 if (!checkaction)
529 return;
530
531 G_ClientStateChangeUpdate(*ent);
532 }
533
534 /**
535 * @brief Returns true if actor can reload weapon
536 * @sa AI_ActorThink
537 */
G_ClientCanReload(Edict * ent,containerIndex_t containerID)538 bool G_ClientCanReload (Edict* ent, containerIndex_t containerID)
539 {
540 const objDef_t* weapon;
541
542 if (ent->getContainer(containerID)) {
543 weapon = ent->getContainer(containerID)->def();
544 } else if (containerID == CID_LEFT && ent->getRightHandItem()->isHeldTwoHanded()) {
545 /* Check for two-handed weapon */
546 weapon = ent->getRightHandItem()->def();
547 } else
548 return false;
549
550 assert(weapon);
551
552 /* also try the temp containers */
553 const Container* cont = nullptr;
554 while ((cont = ent->chr.inv.getNextCont(cont, true))) {
555 Item* item = nullptr;
556 while ((item = cont->getNextItem(item))) {
557 if (item->def()->isLoadableInWeapon(weapon))
558 return true;
559 }
560 }
561 return false;
562 }
563
564 /**
565 * @brief Retrieve or collect weapon from any linked container for the actor's right hand
566 * @note This function will also collect items from floor containers when the actor
567 * is standing on a given point.
568 * @sa AI_ActorThink
569 */
G_ClientGetWeaponFromInventory(Edict * ent)570 void G_ClientGetWeaponFromInventory (Edict* ent)
571 {
572 /* e.g. bloodspiders are not allowed to carry or collect weapons */
573 if (!ent->chr.teamDef->weapons)
574 return;
575
576 /* search for weapons and select the one that is available easily */
577 const invDef_t* bestContainer = nullptr;
578 Item* theWeapon = nullptr;
579 int tu = 100;
580 const Container* cont = nullptr;
581 while ((cont = ent->chr.inv.getNextCont(cont, true))) {
582 if (cont->def()->out >= tu)
583 continue;
584 Item* item = nullptr;
585 while ((item = cont->getNextItem(item))) {
586 /* We are looking for the *fastest* way to get a weapon,
587 * no matter what kind of weapon it is. */
588 assert(item->def());
589 if (item->isWeapon() && !item->mustReload()) {
590 theWeapon = item;
591 bestContainer = cont->def();
592 tu = bestContainer->out;
593 break;
594 }
595 }
596 }
597
598 /* send request */
599 const invDef_t* invDef = INVDEF(CID_RIGHT);
600 if (bestContainer)
601 G_ActorInvMove(ent, bestContainer, theWeapon, invDef, 0, 0, true);
602 }
603
604 /**
605 * @brief This function 'uses' the edict. E.g. it opens the door when the player wants it to open
606 * @sa PA_USE_DOOR
607 * @param[in] player The player is trying to activate the door
608 * @param[in,out] actor The actor the player is using to activate the entity
609 * @param[in,out] edict The entity that is to be used
610 * @todo Do we have to change the trigger position here, too? I don't think this is really needed.
611 * @sa CL_ActorUse
612 * @sa G_UseEdict
613 */
G_ClientUseEdict(const Player & player,Edict * actor,Edict * edict)614 bool G_ClientUseEdict (const Player &player, Edict* actor, Edict* edict)
615 {
616 /* check whether the actor has sufficient TUs to 'use' this edicts */
617 if (!G_ActionCheckForCurrentTeam(player, actor, edict->TU))
618 return false;
619
620 if (!G_UseEdict(edict, actor))
621 return false;
622
623 /* using a group of edicts only costs TUs once (for the master) */
624 G_ActorUseTU(actor, edict->TU);
625 /* send the new TUs */
626 G_SendStats(*actor);
627
628 G_EventEnd();
629
630 return true;
631 }
632
633 /**
634 * @brief The client sent us a message that he did something. We now execute the related function(s) and notify him if necessary.
635 * @param[in] player The player to execute the action for (the actor belongs to this player)
636 * @note a client action will also send the server side edict number to determine the actor
637 */
G_ClientAction(Player & player)638 int G_ClientAction (Player &player)
639 {
640 player_action_t action;
641 int num;
642 pos3_t pos;
643 int i;
644 fireDefIndex_t firemode;
645 int from, fx, fy, to, tx, ty;
646 actorHands_t hand;
647 int fmIdx, objIdx;
648 int resCrouch, resShot;
649 Edict* ent;
650 const char* format;
651
652 /* read the header */
653 action = (player_action_t)gi.ReadByte();
654 num = gi.ReadShort();
655
656 ent = G_EdictsGetByNum(num);
657 if (ent == nullptr)
658 return action;
659
660 format = pa_format[action];
661
662 switch (action) {
663 case PA_NULL:
664 /* do nothing on a null action */
665 break;
666
667 case PA_TURN:
668 gi.ReadFormat(format, &i);
669 G_ClientTurn(player, ent, (dvec_t) i);
670 break;
671
672 case PA_MOVE:
673 gi.ReadFormat(format, &pos);
674 G_ClientMove(player, player.getTeam(), ent, pos);
675 break;
676
677 case PA_STATE:
678 gi.ReadFormat(format, &i);
679 G_ClientStateChange(player, ent, i, true);
680 break;
681
682 case PA_SHOOT:
683 gi.ReadFormat(format, &pos, &i, &firemode, &from);
684 G_ClientShoot(player, ent, pos, i, firemode, nullptr, true, from);
685 break;
686
687 case PA_INVMOVE:
688 gi.ReadFormat(format, &from, &fx, &fy, &to, &tx, &ty);
689
690 if (!isValidContId(from) || !isValidContId(to)) {
691 gi.DPrintf("G_ClientAction: PA_INVMOVE Container index out of range. (from: %i, to: %i)\n", from, to);
692 } else {
693 const invDef_t* fromPtr = INVDEF(from);
694 const invDef_t* toPtr = INVDEF(to);
695 Item* fromItem = ent->chr.inv.getItemAtPos(fromPtr, fx, fy);
696 if (fromItem)
697 G_ActorInvMove(ent, fromPtr, fromItem, toPtr, tx, ty, true);
698 }
699 break;
700
701 case PA_USE:
702 if (ent->clientAction) {
703 Edict* actionEnt;
704
705 /* read the door the client wants to open */
706 gi.ReadFormat(format, &i);
707
708 /* get the door edict */
709 actionEnt = G_EdictsGetByNum(i);
710
711 /* maybe the door is no longer 'alive' because it was destroyed */
712 if (actionEnt && ent->clientAction == actionEnt) {
713 if (G_IsDoor(actionEnt)) {
714 G_ActorUseDoor(ent, actionEnt);
715 }
716 }
717 }
718 break;
719
720 case PA_REACT_SELECT:
721 gi.ReadFormat(format, &hand, &fmIdx, &objIdx);
722 G_ReactionFireSettingsUpdate(ent, fmIdx, hand, INVSH_GetItemByIDX(objIdx));
723 break;
724
725 case PA_RESERVE_STATE:
726 gi.ReadFormat(format, &resShot, &resCrouch);
727
728 G_ActorReserveTUs(ent, ent->chr.reservedTus.reaction, resShot, resCrouch);
729 break;
730
731 default:
732 gi.Error("G_ClientAction: Unknown action!\n");
733 }
734 return action;
735 }
736
737 /**
738 * @brief Sets the teamnum var for this match
739 * @param[in] player Pointer to connected player
740 * @todo Check whether there are enough free spawnpoints in all cases
741 */
G_GetTeam(Player & player)742 static void G_GetTeam (Player &player)
743 {
744 /* player has already a team */
745 if (player.getTeam() > 0) {
746 Com_DPrintf(DEBUG_GAME, "Player %s is already on team %i\n", player.pers.netname, player.getTeam());
747 return;
748 }
749
750 /* number of currently connected players (no ai players) */
751 int playersInGame = 0;
752 Player *p = nullptr;
753 while ((p = G_PlayerGetNextActiveHuman(p)))
754 playersInGame++;
755
756 /* randomly assign a teamnumber in deathmatch games */
757 if (playersInGame <= 1 && G_IsMultiPlayer() && !sv_teamplay->integer) {
758 int spawnCheck[MAX_TEAMS];
759 int spawnSpots = 0;
760 int randomSpot;
761 int i;
762 /* skip civilian teams */
763 for (i = TEAM_PHALANX; i < MAX_TEAMS; i++) {
764 spawnCheck[i] = 0;
765 /* check whether there are spawnpoints for this team */
766 if (level.num_spawnpoints[i])
767 spawnCheck[spawnSpots++] = i;
768 }
769 /* we need at least 2 different team spawnpoints for multiplayer in a death match game */
770 if (spawnSpots < 2)
771 gi.Error("G_GetTeam: Not enough spawn spots in map!");
772
773 /* assign random valid team number */
774 i = spawnSpots;
775 randomSpot = rand() % spawnSpots;
776 for (;;) {
777 const int team = spawnCheck[randomSpot];
778 if (i == 0)
779 gi.Error("G_GetTeam: Could not assign a team!");
780 if (G_SetTeamForPlayer(player, team)) {
781 gi.DPrintf("%s has been randomly assigned to team %i\n",
782 player.pers.netname, G_ClientGetTeamNum(player));
783 break;
784 }
785 i--;
786 randomSpot = (randomSpot + 1) % spawnSpots;
787 }
788 return;
789 }
790
791 /* find a team */
792 if (G_IsSinglePlayer()) {
793 G_SetTeamForPlayer(player, TEAM_PHALANX);
794 } else if (sv_teamplay->integer) {
795 /* set the team specified in the userinfo */
796 const int i = G_ClientGetTeamNumPref(player);
797 gi.DPrintf("Get a team for teamplay for %s\n", player.pers.netname);
798 /* civilians are at team zero */
799 if (i > TEAM_CIVILIAN && sv_maxteams->integer >= i) {
800 G_SetTeamForPlayer(player, i);
801 gi.BroadcastPrintf(PRINT_CONSOLE, "serverconsole: %s has chosen team %i\n", player.pers.netname, i);
802 } else {
803 gi.DPrintf("Team %i is not valid - choose a team between 1 and %i\n", i, sv_maxteams->integer);
804 G_SetTeamForPlayer(player, TEAM_DEFAULT);
805 }
806 } else {
807 int i;
808 /* search team */
809 gi.DPrintf("Getting a multiplayer team for %s\n", player.pers.netname);
810 for (i = TEAM_CIVILIAN + 1; i < MAX_TEAMS; i++) {
811 if (level.num_spawnpoints[i]) {
812 bool teamAvailable = true;
813
814 p = nullptr;
815 /* check if team is in use (only human controlled players) */
816 while ((p = G_PlayerGetNextActiveAI(p))) {
817 if (p->getTeam() == i) {
818 Com_DPrintf(DEBUG_GAME, "Team %i is already in use\n", i);
819 /* team already in use */
820 teamAvailable = false;
821 break;
822 }
823 }
824 if (teamAvailable)
825 break;
826 }
827 }
828
829 /* set the team */
830 if (i < MAX_TEAMS) {
831 /* remove ai player */
832 p = nullptr;
833 while ((p = G_PlayerGetNextActiveHuman(p))) {
834 if (p->getTeam() == i) {
835 gi.BroadcastPrintf(PRINT_CONSOLE, "Removing ai player...");
836 p->setInUse(false);
837 break;
838 }
839 }
840 Com_DPrintf(DEBUG_GAME, "Assigning %s to team %i\n", player.pers.netname, i);
841 G_SetTeamForPlayer(player, i);
842 } else {
843 gi.DPrintf("No free team - disconnecting '%s'\n", player.pers.netname);
844 G_ClientDisconnect(player);
845 }
846 }
847 }
848
849 /**
850 * @brief Set the used team for the given player
851 * @param[out] player The player the team should be set for
852 * @param[in] team The team to set for the given player
853 * @return <code>true</code> if the team was set successfully, <code>false</code> otherwise.
854 */
G_SetTeamForPlayer(Player & player,const int team)855 bool G_SetTeamForPlayer (Player &player, const int team)
856 {
857 assert(team >= TEAM_NO_ACTIVE && team < MAX_TEAMS);
858
859 if (G_IsAIPlayer(&player)) {
860 if (team != TEAM_ALIEN && team != TEAM_CIVILIAN)
861 return false;
862 } else {
863 if (!sv_teamplay->integer) {
864 Player *p = nullptr;
865 while ((p = G_PlayerGetNextHuman(p)) != nullptr) {
866 if (p->getTeam() == team)
867 return false;
868 }
869 }
870 }
871
872 player.setTeam(team);
873
874 /* if we started in dev mode, we maybe don't have a
875 * starting position in this map */
876 if (!g_nospawn->integer) {
877 if (team >= 0 && team < MAX_TEAMS) {
878 if (!level.num_spawnpoints[team])
879 gi.Error("No spawnpoints for team %i", team);
880 }
881 }
882
883 if (!G_IsAIPlayer(&player))
884 Info_SetValueForKeyAsInteger(player.pers.userinfo, sizeof(player.pers.userinfo), "cl_team", team);
885
886 return true;
887 }
888
889 /**
890 * @brief Returns the assigned team number of the player
891 */
G_ClientGetTeamNum(const Player & player)892 int G_ClientGetTeamNum (const Player &player)
893 {
894 return player.getTeam();
895 }
896
897 /**
898 * @brief Returns the preferred team number for the player
899 */
G_ClientGetTeamNumPref(const Player & player)900 int G_ClientGetTeamNumPref (const Player &player)
901 {
902 return Info_IntegerForKey(player.pers.userinfo, "cl_teamnum");
903 }
904
905 /**
906 * @return @c true if the player is for starting the multiplayer match
907 */
G_ClientIsReady(const Player * player)908 bool G_ClientIsReady (const Player *player)
909 {
910 assert(player);
911 return player->isReady();
912 }
913
914 /**
915 * @brief Chose a team that should start the match
916 * @param[in] player In singleplayer mode the team of this player will get the first turn
917 * @sa SVCmd_StartGame_f
918 */
G_GetStartingTeam(const Player & player)919 static void G_GetStartingTeam (const Player &player)
920 {
921 int teamCount;
922 int playerCount;
923 int knownTeams[MAX_TEAMS];
924 Player *p;
925
926 /* return with no action if activeTeam already assigned or if are in single-player mode */
927 if (G_MatchIsRunning())
928 return;
929
930 if (G_IsSinglePlayer()) {
931 level.activeTeam = player.getTeam();
932 level.teamOfs = MAX_TEAMS - level.activeTeam;
933 return;
934 }
935
936 /* count number of currently connected unique teams and players (human controlled players only) */
937 p = nullptr;
938 teamCount = 0;
939 playerCount = 0;
940 while ((p = G_PlayerGetNextActiveHuman(p))) {
941 int j;
942 playerCount++;
943 for (j = 0; j < teamCount; j++) {
944 if (p->getTeam() == knownTeams[j])
945 break;
946 }
947 if (j == teamCount)
948 knownTeams[teamCount++] = p->getTeam();
949 }
950
951 if (teamCount) {
952 const int teamIndex = (int) (frand() * (teamCount - 1) + 0.5);
953 G_PrintStats("Starting new game: %s with %i teams", level.mapname, teamCount);
954 level.activeTeam = knownTeams[teamIndex];
955 level.teamOfs = MAX_TEAMS - level.activeTeam;
956 p = nullptr;
957 while ((p = G_PlayerGetNextActiveHuman(p)))
958 if (p->getTeam() != level.activeTeam)
959 p->roundDone = true;
960 }
961 }
962
963 /**
964 * @brief Find valid actor spawn fields for this player.
965 * @note Already used spawn-point are not found because ent->type is changed in G_ClientTeamInfo.
966 * @param[in] player The player to spawn the actors for.
967 * @param[in] spawnType The type of spawn-point so search for (ET_ACTORSPAWN or ET_ACTOR2x2SPAWN)
968 * @return A pointer to a found spawn point or nullptr if nothing was found or on error.
969 */
G_ClientGetFreeSpawnPoint(const Player & player,int spawnType)970 static Edict* G_ClientGetFreeSpawnPoint (const Player &player, int spawnType)
971 {
972 Edict* ent = nullptr;
973
974 /* Abort for non-spawnpoints */
975 assert(spawnType == ET_ACTORSPAWN || spawnType == ET_ACTOR2x2SPAWN);
976
977 if (level.noRandomSpawn) {
978 while ((ent = G_EdictsGetNextInUse(ent)))
979 if (ent->type == spawnType && player.getTeam() == ent->team) {
980 if (G_EdictsGetLivingActorFromPos(ent->pos))
981 continue;
982 return ent;
983 }
984 } else {
985 Edict* list[MAX_EDICTS];
986 int count = 0;
987 while ((ent = G_EdictsGetNextInUse(ent)))
988 if (ent->type == spawnType && player.getTeam() == ent->team) {
989 if (G_EdictsGetLivingActorFromPos(ent->pos))
990 continue;
991 list[count++] = ent;
992 }
993
994 if (count)
995 return list[rand() % count];
996 }
997
998 return nullptr;
999 }
1000
1001 /**
1002 * @brief Checks whether the spawn of an actor is allowed for the current running match.
1003 * @note Don't allow spawning of soldiers for multiplayer if:
1004 * + the sv_maxsoldiersperplayer limit is hit (e.g. the assembled team is bigger than the allowed number of soldiers)
1005 * + the team already hit the max allowed amount of soldiers
1006 * @param num The nth actor the player want to spawn in the game.
1007 * @param team The team the player is part of.
1008 * @return @c true if spawn is allowed, @c false otherwise.
1009 */
G_ActorSpawnIsAllowed(const int num,const int team)1010 static inline bool G_ActorSpawnIsAllowed (const int num, const int team)
1011 {
1012 if (G_IsSinglePlayer())
1013 return true;
1014
1015 return num < sv_maxsoldiersperplayer->integer && level.num_spawned[team] < sv_maxsoldiersperteam->integer;
1016 }
1017
1018 /**
1019 * @brief Think function for actors that spawn dead
1020 * @param ent The actor
1021 */
G_ThinkActorDieAfterSpawn(Edict * ent)1022 static void G_ThinkActorDieAfterSpawn (Edict* ent)
1023 {
1024 G_ActorDieOrStun(ent, nullptr);
1025 ent->think = nullptr;
1026 }
1027
1028 /**
1029 * @brief Think function for actors that spawn crouched
1030 * @param ent The actor
1031 */
G_ThinkActorGoCrouch(Edict * ent)1032 static void G_ThinkActorGoCrouch (Edict* ent)
1033 {
1034 G_ClientStateChange(ent->getPlayer(), ent, STATE_CROUCHED, true);
1035 ent->think = nullptr;
1036 }
1037
1038 /**
1039 * @brief Searches a free spawning point for a given actor size and turns it into an actor
1040 * @param[in] player The player to get the free spawn points for
1041 * @param[in] actorSize The actor size to get a spawning point for
1042 * @return An actor edict or @c nullptr if no free spawning point was found
1043 */
G_ClientGetFreeSpawnPointForActorSize(const Player & player,const actorSizeEnum_t actorSize)1044 Edict* G_ClientGetFreeSpawnPointForActorSize (const Player &player, const actorSizeEnum_t actorSize)
1045 {
1046 Edict* ent;
1047
1048 if (actorSize == ACTOR_SIZE_NORMAL) {
1049 /* Find valid actor spawn fields for this player. */
1050 ent = G_ClientGetFreeSpawnPoint(player, ET_ACTORSPAWN);
1051 if (ent) {
1052 Edict* copy = G_EdictDuplicate(ent);
1053 if (copy != nullptr)
1054 copy->type = ET_ACTOR;
1055 ent = copy;
1056 }
1057 } else if (actorSize == ACTOR_SIZE_2x2) {
1058 /* Find valid actor spawn fields for this player. */
1059 ent = G_ClientGetFreeSpawnPoint(player, ET_ACTOR2x2SPAWN);
1060 if (ent) {
1061 Edict* copy = G_EdictDuplicate(ent);
1062 if (copy != nullptr) {
1063 copy->type = ET_ACTOR2x2;
1064 copy->morale = 100;
1065 }
1066 ent = copy;
1067 }
1068 } else {
1069 gi.Error("G_ClientGetFreeSpawnPointForActorSize: unknown fieldSize for actor edict (actorSize: %i)\n",
1070 actorSize);
1071 }
1072
1073 if (!ent)
1074 return nullptr;
1075
1076 level.num_spawned[ent->team]++;
1077 ent->pnum = player.getNum();
1078 ent->chr.fieldSize = actorSize;
1079 ent->fieldSize = ent->chr.fieldSize;
1080 ent->flags |= FL_DESTROYABLE;
1081 G_VisFlagsReset(*ent);
1082 gi.LinkEdict(ent);
1083
1084 if (ent->spawnflags & STATE_CROUCHED) {
1085 ent->think = G_ThinkActorGoCrouch;
1086 ent->nextthink = 1;
1087 }
1088
1089 if (ent->spawnflags & STATE_STUN) {
1090 if (ent->spawnflags & STATE_DEAD)
1091 ent->HP = 0;
1092 ent->think = G_ThinkActorDieAfterSpawn;
1093 ent->nextthink = 1;
1094 }
1095
1096 G_ActorModifyCounters(nullptr, ent, 1, 0, 0);
1097
1098 G_ReactionFireTargetsCreate(ent);
1099
1100 return ent;
1101 }
1102
1103 /**
1104 * @brief Read the inventory from the clients team data
1105 * @param ent The actor's entity that should receive the items.
1106 */
G_ClientReadInventory(Edict * ent)1107 static void G_ClientReadInventory (Edict* ent)
1108 {
1109 /* inventory */
1110 int nr = gi.ReadShort();
1111
1112 for (; nr-- > 0;) {
1113 const invDef_t* container;
1114 Item item;
1115 int x, y;
1116 G_ReadItem(&item, &container, &x, &y);
1117 if (container->temp)
1118 gi.Error("G_ClientReadInventory failed, tried to add '%s' to a temp container %i", item.def()->id, container->id);
1119 /* ignore the overload for now */
1120 if (!ent->chr.inv.canHoldItemWeight(CID_EQUIP, container->id, item, ent->chr.score.skills[ABILITY_POWER]))
1121 Com_Printf("G_ClientReadInventory: Item %s exceeds ent %i weight capacity\n", item.def()->id, ent->number);
1122 if (!level.noEquipment && game.i.addToInventory(&ent->chr.inv, &item, container, x, y, 1) == nullptr)
1123 gi.Error("G_ClientReadInventory failed, could not add item '%s' to container %i (x:%i,y:%i)",
1124 item.def()->id, container->id, x, y);
1125 }
1126 }
1127
1128 /**
1129 * @brief Reads the character data from the netchannel that is needed to spawn an actor.
1130 * @param ent The actor entity to save the read data in.
1131 */
G_ClientReadCharacter(Edict * ent)1132 static void G_ClientReadCharacter (Edict* ent)
1133 {
1134 int k;
1135 int teamDefIdx;
1136
1137 ent->chr.init();
1138 /* model */
1139 ent->chr.ucn = gi.ReadShort();
1140 gi.ReadString(ent->chr.name, sizeof(ent->chr.name));
1141 gi.ReadString(ent->chr.path, sizeof(ent->chr.path));
1142 gi.ReadString(ent->chr.body, sizeof(ent->chr.body));
1143 gi.ReadString(ent->chr.head, sizeof(ent->chr.head));
1144 ent->chr.bodySkin = gi.ReadByte();
1145 ent->chr.headSkin = gi.ReadByte();
1146
1147 ent->chr.HP = gi.ReadShort();
1148 ent->chr.minHP = ent->chr.HP;
1149 ent->chr.maxHP = gi.ReadShort();
1150 teamDefIdx = gi.ReadByte();
1151 if (teamDefIdx < 0 || teamDefIdx >= MAX_TEAMDEFS)
1152 gi.Error("Invalid team definition index given: %i", teamDefIdx);
1153 ent->chr.teamDef = &gi.csi->teamDef[teamDefIdx];
1154
1155 ent->chr.gender = gi.ReadByte();
1156 ent->chr.STUN = gi.ReadByte();
1157 ent->chr.morale = gi.ReadByte();
1158
1159 for (k = 0; k < ent->chr.teamDef->bodyTemplate->numBodyParts(); ++k)
1160 ent->chr.wounds.treatmentLevel[k] = gi.ReadByte();
1161
1162 for (k = 0; k < SKILL_NUM_TYPES + 1; k++) /* new attributes */
1163 ent->chr.score.experience[k] = gi.ReadLong();
1164 for (k = 0; k < SKILL_NUM_TYPES; k++) /* new attributes */
1165 ent->chr.score.skills[k] = gi.ReadByte();
1166 for (k = 0; k < KILLED_NUM_TYPES; k++)
1167 ent->chr.score.kills[k] = gi.ReadShort();
1168 for (k = 0; k < KILLED_NUM_TYPES; k++)
1169 ent->chr.score.stuns[k] = gi.ReadShort();
1170 ent->chr.score.assignedMissions = gi.ReadShort();
1171 }
1172
1173 /**
1174 * @brief Call this if you want to skip some actor netchannel data
1175 * @note The fieldsize is not skipped
1176 * @sa G_ClientTeamInfo
1177 */
G_ClientSkipActorInfo(void)1178 static void G_ClientSkipActorInfo (void)
1179 {
1180 int i, n;
1181 Edict ent;
1182 invDef_t container;
1183 Item item;
1184 int x, y;
1185 const invDef_t* c = &container;
1186
1187 G_ClientReadCharacter(&ent);
1188
1189 /* skip inventory */
1190 n = gi.ReadShort();
1191 for (i = 0; i < n; i++) {
1192 G_ReadItem(&item, &c, &x, &y);
1193 }
1194 }
1195
1196 /**
1197 * @brief Used after spawning an actor to set some default values that are not read from the
1198 * network event.
1199 * @param ent The actor edict to set the values for.
1200 */
G_ClientAssignDefaultActorValues(Edict * ent)1201 static void G_ClientAssignDefaultActorValues (Edict* ent)
1202 {
1203 /* Mission Scores */
1204 scoreMission[scoreMissionNum].init();
1205 ent->chr.scoreMission = &scoreMission[scoreMissionNum];
1206 scoreMissionNum++;
1207
1208 /* set initial vital statistics */
1209 ent->HP = ent->chr.HP;
1210 ent->morale = ent->chr.morale;
1211
1212 /** @todo for now, heal fully upon entering mission */
1213 ent->morale = GET_MORALE(ent->chr.score.skills[ABILITY_MIND]);
1214
1215 /* set models */
1216 ent->body = gi.ModelIndex(CHRSH_CharGetBody(&ent->chr));
1217 ent->head = gi.ModelIndex(CHRSH_CharGetHead(&ent->chr));
1218
1219 ent->chr.scoreMission->carriedWeight = ent->chr.inv.getWeight();
1220 }
1221
1222 /**
1223 * @brief This is called after the actors are spawned and will set actor states without consuming TUs
1224 * @param player The player to perform the action for
1225 */
G_ClientInitActorStates(const Player & player)1226 void G_ClientInitActorStates (const Player &player)
1227 {
1228 const int length = gi.ReadByte(); /* Get the actor amount that the client sent. */
1229 int i;
1230
1231 for (i = 0; i < length; i++) {
1232 const int ucn = gi.ReadShort();
1233 int saveTU;
1234 actorHands_t hand;
1235 int fmIdx, objIdx;
1236 Edict* ent = G_EdictsGetActorByUCN(ucn, player.getTeam());
1237 if (!ent)
1238 gi.Error("Could not find character on team %i with unique character number %i", player.getTeam(), ucn);
1239
1240 /* these state changes are not consuming any TUs */
1241 saveTU = ent->TU;
1242 G_ClientStateChange(player, ent, gi.ReadShort(), false);
1243 hand = (actorHands_t)gi.ReadShort();
1244 fmIdx = gi.ReadShort();
1245 objIdx = gi.ReadShort();
1246 G_ActorSetTU(ent, saveTU);
1247 if (objIdx != NONE) {
1248 G_ReactionFireSettingsUpdate(ent, fmIdx, hand, INVSH_GetItemByIDX(objIdx));
1249 }
1250 G_ClientStateChangeUpdate(*ent);
1251 }
1252 }
1253
1254 /**
1255 * @brief The client lets the server spawn the actors for a given player by sending their information (models, inventory, etc..) over the network.
1256 * @param[in] player The player to spawn the actors for.
1257 * @sa GAME_SendCurrentTeamSpawningInfo
1258 * @sa clc_teaminfo
1259 */
G_ClientTeamInfo(const Player & player)1260 void G_ClientTeamInfo (const Player &player)
1261 {
1262 const int length = gi.ReadByte(); /* Get the actor amount that the client sent. */
1263 int i;
1264
1265 for (i = 0; i < length; i++) {
1266 const actorSizeEnum_t actorFieldSize = gi.ReadByte();
1267 /* Search for a spawn point for each entry the client sent */
1268 if (player.getTeam() == TEAM_NO_ACTIVE || !G_ActorSpawnIsAllowed(i, player.getTeam()))
1269 G_ClientSkipActorInfo();
1270 else {
1271 Edict* ent = G_ClientGetFreeSpawnPointForActorSize(player, actorFieldSize);
1272 if (ent) {
1273 Com_DPrintf(DEBUG_GAME, "Player: %i - team %i - size: %i\n", player.getNum(), ent->team, ent->fieldSize);
1274
1275 G_ClientReadCharacter(ent);
1276 G_ClientReadInventory(ent);
1277 G_ClientAssignDefaultActorValues(ent);
1278 G_ActorGiveTimeUnits(ent);
1279 G_TouchTriggers(ent);
1280 ent->contentFlags = G_ActorGetContentFlags(ent->origin);
1281 } else {
1282 gi.DPrintf("Not enough spawn points for team %i (actorsize: %i)\n", player.getTeam(), actorFieldSize);
1283
1284 G_ClientSkipActorInfo();
1285 }
1286 }
1287 }
1288
1289 Com_Printf("Used inventory slots client %s spawn: %i\n", player.pers.netname, game.i.GetUsedSlots());
1290 }
1291
1292 /**
1293 * @brief Send brush models for entities like func_breakable and func_door and triggers
1294 * with their bounding boxes to the client and let him know about them.
1295 * There are also entities that are announced here, but fully handled clientside - like
1296 * func_rotating.
1297 * @sa CL_AddBrushModel
1298 * @sa EV_ADD_BRUSH_MODEL
1299 * @param[in] player The player the edicts are send to
1300 */
G_ClientSendEdictsAndBrushModels(const Player & player)1301 static void G_ClientSendEdictsAndBrushModels (const Player &player)
1302 {
1303 const int mask = G_PlayerToPM(player);
1304 /* skip the world */
1305 Edict* ent = G_EdictsGetFirst();
1306
1307 /* make SOLID_BSP edicts visible to the client */
1308 while ((ent = G_EdictsGetNextInUse(ent))) {
1309 /* brush models that have a type - not the world - keep in
1310 * mind that there are several world edicts in the list in case of
1311 * a map assembly */
1312 if (ent->solid != SOLID_BSP)
1313 continue;
1314
1315 /* skip the world(s) in case of map assembly */
1316 if (ent->type > ET_NULL) {
1317 G_EventAddBrushModel(mask, *ent);
1318 G_VisFlagsAdd(*ent, ~ent->visflags);
1319 }
1320 }
1321 }
1322
1323 /**
1324 * @brief This functions starts the client
1325 * @sa G_ClientStartMatch
1326 * @sa CL_StartGame
1327 */
G_ClientBegin(Player & player)1328 bool G_ClientBegin (Player &player)
1329 {
1330 player.began = true;
1331 level.numplayers++;
1332
1333 /* find a team */
1334 G_GetTeam(player);
1335 if (!player.began)
1336 return false;
1337
1338 gi.ConfigString(CS_PLAYERCOUNT, "%i", level.numplayers);
1339
1340 /* spawn camera (starts client rendering) */
1341 G_EventStart(player, sv_teamplay->integer);
1342
1343 /* send things like doors and breakables */
1344 G_ClientSendEdictsAndBrushModels(player);
1345
1346 /* ensure that the start event is send */
1347 G_EventEnd();
1348
1349 /* set the net name */
1350 gi.ConfigString(CS_PLAYERNAMES + player.getNum(), "%s", player.pers.netname);
1351
1352 /* inform all clients */
1353 gi.BroadcastPrintf(PRINT_CONSOLE, "%s has joined team %i\n", player.pers.netname, player.getTeam());
1354
1355 return true;
1356 }
1357
1358 /**
1359 * @brief Sets the team, init the TU and sends the player stats.
1360 * @sa G_SendPlayerStats
1361 * @sa G_GetTeam
1362 * @sa G_GiveTimeUnits
1363 * @sa G_ClientBegin
1364 * @sa CL_Reset
1365 */
G_ClientStartMatch(Player & player)1366 void G_ClientStartMatch (Player &player)
1367 {
1368 G_GetStartingTeam(player);
1369
1370 /* do all the init events here... */
1371 /* reset the data */
1372 G_EventReset(player, level.activeTeam);
1373
1374 /* show visible actors and add invisible actor */
1375 G_VisFlagsClear(player.getTeam());
1376 G_CheckVisPlayer(player, false);
1377 G_SendInvisible(player);
1378
1379 /* submit stats */
1380 G_SendPlayerStats(player);
1381
1382 /* ensure that the last event is send, too */
1383 G_EventEnd();
1384
1385 if (G_IsMultiPlayer()) {
1386 /* ensure that we restart the round time limit */
1387 sv_roundtimelimit->modified = true;
1388 }
1389
1390 /* inform all clients */
1391 gi.BroadcastPrintf(PRINT_CONSOLE, "%s has taken control over team %i.\n", player.pers.netname, player.getTeam());
1392 }
1393
1394 /**
1395 * @brief called whenever the player updates a userinfo variable.
1396 * @note The game can override any of the settings in place (forcing skins or names, etc) before copying it off.
1397 */
G_ClientUserinfoChanged(Player & player,const char * userinfo)1398 void G_ClientUserinfoChanged (Player &player, const char* userinfo)
1399 {
1400 const bool alreadyReady = player.isReady();
1401 const int oldTeamnum = Info_IntegerForKey(player.pers.userinfo, "cl_teamnum");
1402
1403 /* check for malformed or illegal info strings */
1404 if (!Info_Validate(userinfo))
1405 userinfo = "\\cl_name\\badinfo";
1406
1407 /* set name */
1408 Q_strncpyz(player.pers.netname, Info_ValueForKey(userinfo, "cl_name"), sizeof(player.pers.netname));
1409 Q_strncpyz(player.pers.userinfo, userinfo, sizeof(player.pers.userinfo));
1410 player.autostand = Info_IntegerForKey(userinfo, "cl_autostand");
1411 player.setReady(Info_IntegerForKey(userinfo, "cl_ready"));
1412
1413 /* send the updated config string */
1414 gi.ConfigString(CS_PLAYERNAMES + player.getNum(), "%s", player.pers.netname);
1415
1416 /* try to update to the preferred team */
1417 if (!G_MatchIsRunning() && oldTeamnum != Info_IntegerForKey(userinfo, "cl_teamnum")) {
1418 /* if the player is marked as ready he can't change his team */
1419 if (!alreadyReady || !player.isReady()) {
1420 player.setTeam(TEAM_NO_ACTIVE);
1421 G_GetTeam(player);
1422 } else {
1423 Com_DPrintf(DEBUG_GAME, "G_ClientUserinfoChanged: player %s is already marked as being ready\n",
1424 player.pers.netname);
1425 }
1426 }
1427 }
1428
1429 /**
1430 * @brief Checks whether the connection is valid or invalid and set some
1431 * user info keys.
1432 * @param[in,out] player The player that is trying to connect to the game
1433 * @param[in,out] userinfo The userinfo data that is checked and changed
1434 * @param[in] userinfoSize The size of the userinfo buffer
1435 * @sa G_ClientDisconnect
1436 * @sa CL_ConnectionlessPacket
1437 * @todo Check if the teamnum preference has already reached maxsoldiers
1438 * and reject connection if so
1439 * @return @c false if the connection is refused, @c true otherwise
1440 */
G_ClientConnect(Player * player,char * userinfo,size_t userinfoSize)1441 bool G_ClientConnect (Player *player, char* userinfo, size_t userinfoSize)
1442 {
1443 const char* value;
1444
1445 value = Info_ValueForKey(userinfo, "ip");
1446
1447 Com_Printf("connection attempt from %s\n", value);
1448
1449 /* check to see if they are on the banned IP list */
1450 if (SV_FilterPacket(value)) {
1451 Info_SetValueForKey(userinfo, userinfoSize, "rejmsg", REJ_BANNED);
1452 return false;
1453 }
1454
1455 if (!G_PlayerToPM(*player)) {
1456 Info_SetValueForKey(userinfo, userinfoSize, "rejmsg", REJ_SERVER_FULL);
1457 return false;
1458 }
1459
1460 value = Info_ValueForKey(userinfo, "password");
1461 if (password->string[0] != '\0' && !Q_streq(password->string, "none") && !Q_streq(password->string, value)) {
1462 Info_SetValueForKey(userinfo, userinfoSize, "rejmsg", REJ_PASSWORD_REQUIRED_OR_INCORRECT);
1463 return false;
1464 }
1465
1466 /* fix for fast reconnects after a disconnect */
1467 if (player->isInUse()) {
1468 gi.BroadcastPrintf(PRINT_CONSOLE, "%s already in use.\n", player->pers.netname);
1469 G_ClientDisconnect(*player);
1470 }
1471
1472 /* reset persistent data */
1473 OBJZERO(player->pers);
1474 G_ClientUserinfoChanged(*player, userinfo);
1475
1476 gi.BroadcastPrintf(PRINT_CONSOLE, "%s is connecting...\n", player->pers.netname);
1477 return true;
1478 }
1479
1480 /**
1481 * @sa G_ClientConnect
1482 */
G_ClientDisconnect(Player & player)1483 void G_ClientDisconnect (Player &player)
1484 {
1485 #if 0
1486 Edict* ent = nullptr;
1487 #endif
1488
1489 /* only if the player already sent his began */
1490 if (player.began) {
1491 level.numplayers--;
1492 gi.ConfigString(CS_PLAYERCOUNT, "%i", level.numplayers);
1493
1494 if (level.activeTeam == player.getTeam())
1495 G_ClientEndRound(player);
1496
1497 /* if no more players are connected - stop the server */
1498 G_MatchEndCheck();
1499 }
1500
1501 #if 0
1502 /* now let's remove all the edicts that belongs to this player */
1503 while ((ent = G_EdictsGetNextLivingActor(ent))) {
1504 if (ent->pnum == player->num)
1505 G_ActorDie(ent, STATE_DEAD, nullptr);
1506 }
1507 G_MatchEndCheck();
1508 #endif
1509
1510 player.began = false;
1511 player.roundDone = false;
1512 player.setReady(false);
1513
1514 gi.BroadcastPrintf(PRINT_CONSOLE, "%s disconnected.\n", player.pers.netname);
1515 }
1516
1517 /**
1518 * @brief Called after every player has joined
1519 */
G_ResetClientData(void)1520 void G_ResetClientData (void)
1521 {
1522 scoreMissionNum = 0;
1523 OBJZERO(scoreMission);
1524 }
1525